@skillrecordings/cli 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/skill.mjs +21 -0
- package/dist/chunk-2NCCVTEE.js +22342 -0
- package/dist/chunk-2NCCVTEE.js.map +1 -0
- package/dist/chunk-3E3GYSZR.js +7071 -0
- package/dist/chunk-3E3GYSZR.js.map +1 -0
- package/dist/chunk-F4EM72IH.js +86 -0
- package/dist/chunk-F4EM72IH.js.map +1 -0
- package/dist/chunk-FGP7KUQW.js +432 -0
- package/dist/chunk-FGP7KUQW.js.map +1 -0
- package/dist/chunk-H3D6VCME.js +55 -0
- package/dist/chunk-H3D6VCME.js.map +1 -0
- package/dist/chunk-HK3PEWFD.js +208 -0
- package/dist/chunk-HK3PEWFD.js.map +1 -0
- package/dist/chunk-KEV3QKXP.js +4495 -0
- package/dist/chunk-KEV3QKXP.js.map +1 -0
- package/dist/chunk-MG37YDAK.js +882 -0
- package/dist/chunk-MG37YDAK.js.map +1 -0
- package/dist/chunk-MLNDSBZ4.js +482 -0
- package/dist/chunk-MLNDSBZ4.js.map +1 -0
- package/dist/chunk-N2WIV2JV.js +22 -0
- package/dist/chunk-N2WIV2JV.js.map +1 -0
- package/dist/chunk-PWWRCN5W.js +2067 -0
- package/dist/chunk-PWWRCN5W.js.map +1 -0
- package/dist/chunk-SKHBM3XP.js +7746 -0
- package/dist/chunk-SKHBM3XP.js.map +1 -0
- package/dist/chunk-WFANXVQG.js +64 -0
- package/dist/chunk-WFANXVQG.js.map +1 -0
- package/dist/chunk-WYKL32C3.js +275 -0
- package/dist/chunk-WYKL32C3.js.map +1 -0
- package/dist/chunk-ZNF7XD2S.js +134 -0
- package/dist/chunk-ZNF7XD2S.js.map +1 -0
- package/dist/config-AUAIYDSI.js +20 -0
- package/dist/config-AUAIYDSI.js.map +1 -0
- package/dist/fileFromPath-XN7LXIBI.js +134 -0
- package/dist/fileFromPath-XN7LXIBI.js.map +1 -0
- package/dist/getMachineId-bsd-KW2E7VK3.js +42 -0
- package/dist/getMachineId-bsd-KW2E7VK3.js.map +1 -0
- package/dist/getMachineId-darwin-ROXJUJX5.js +42 -0
- package/dist/getMachineId-darwin-ROXJUJX5.js.map +1 -0
- package/dist/getMachineId-linux-KVZEHQSU.js +34 -0
- package/dist/getMachineId-linux-KVZEHQSU.js.map +1 -0
- package/dist/getMachineId-unsupported-PPRILPPA.js +25 -0
- package/dist/getMachineId-unsupported-PPRILPPA.js.map +1 -0
- package/dist/getMachineId-win-IIF36LEJ.js +44 -0
- package/dist/getMachineId-win-IIF36LEJ.js.map +1 -0
- package/dist/index.js +112703 -0
- package/dist/index.js.map +1 -0
- package/dist/lib-R6DEEJCP.js +7623 -0
- package/dist/lib-R6DEEJCP.js.map +1 -0
- package/dist/pipeline-IAVVAKTU.js +120 -0
- package/dist/pipeline-IAVVAKTU.js.map +1 -0
- package/dist/query-NTP5NVXN.js +25 -0
- package/dist/query-NTP5NVXN.js.map +1 -0
- package/dist/routing-BAEPFB7V.js +390 -0
- package/dist/routing-BAEPFB7V.js.map +1 -0
- package/dist/stripe-lookup-charge-EPRUMZDL.js +56 -0
- package/dist/stripe-lookup-charge-EPRUMZDL.js.map +1 -0
- package/dist/stripe-payment-history-SJPKA63N.js +67 -0
- package/dist/stripe-payment-history-SJPKA63N.js.map +1 -0
- package/dist/stripe-subscription-status-L4Z65GB3.js +58 -0
- package/dist/stripe-subscription-status-L4Z65GB3.js.map +1 -0
- package/dist/stripe-verify-refund-FZDKCIUQ.js +54 -0
- package/dist/stripe-verify-refund-FZDKCIUQ.js.map +1 -0
- package/dist/support-memory-WSG7SDKG.js +10 -0
- package/dist/support-memory-WSG7SDKG.js.map +1 -0
- package/package.json +10 -7
- package/.env.encrypted +0 -0
- package/CHANGELOG.md +0 -35
- package/data/tt-archive-dataset.json +0 -1
- package/data/validate-test-dataset.json +0 -97
- package/docs/CLI-AUTH.md +0 -504
- package/preload.ts +0 -18
- package/src/__tests__/init.test.ts +0 -74
- package/src/alignment-test.ts +0 -64
- package/src/check-apps.ts +0 -16
- package/src/commands/auth/decrypt.ts +0 -123
- package/src/commands/auth/encrypt.ts +0 -81
- package/src/commands/auth/index.ts +0 -50
- package/src/commands/auth/keygen.ts +0 -41
- package/src/commands/auth/status.ts +0 -164
- package/src/commands/axiom/forensic.ts +0 -868
- package/src/commands/axiom/index.ts +0 -697
- package/src/commands/build-dataset.ts +0 -311
- package/src/commands/db-status.ts +0 -47
- package/src/commands/deploys.ts +0 -219
- package/src/commands/eval-local/compare.ts +0 -171
- package/src/commands/eval-local/health.ts +0 -212
- package/src/commands/eval-local/index.ts +0 -76
- package/src/commands/eval-local/real-tools.ts +0 -416
- package/src/commands/eval-local/run.ts +0 -1168
- package/src/commands/eval-local/score-production.ts +0 -256
- package/src/commands/eval-local/seed.ts +0 -276
- package/src/commands/eval-pipeline/index.ts +0 -53
- package/src/commands/eval-pipeline/real-tools.ts +0 -492
- package/src/commands/eval-pipeline/run.ts +0 -1316
- package/src/commands/eval-pipeline/seed.ts +0 -395
- package/src/commands/eval-prompt.ts +0 -496
- package/src/commands/eval.test.ts +0 -253
- package/src/commands/eval.ts +0 -108
- package/src/commands/faq-classify.ts +0 -460
- package/src/commands/faq-cluster.ts +0 -135
- package/src/commands/faq-extract.ts +0 -249
- package/src/commands/faq-mine.ts +0 -432
- package/src/commands/faq-review.ts +0 -426
- package/src/commands/front/index.ts +0 -351
- package/src/commands/front/pull-conversations.ts +0 -275
- package/src/commands/front/tags.ts +0 -825
- package/src/commands/front-cache.ts +0 -1277
- package/src/commands/front-stats.ts +0 -75
- package/src/commands/health.test.ts +0 -82
- package/src/commands/health.ts +0 -362
- package/src/commands/init.test.ts +0 -89
- package/src/commands/init.ts +0 -106
- package/src/commands/inngest/client.ts +0 -294
- package/src/commands/inngest/events.ts +0 -296
- package/src/commands/inngest/investigate.ts +0 -382
- package/src/commands/inngest/runs.ts +0 -149
- package/src/commands/inngest/signal.ts +0 -143
- package/src/commands/kb-sync.ts +0 -498
- package/src/commands/memory/find.ts +0 -135
- package/src/commands/memory/get.ts +0 -87
- package/src/commands/memory/index.ts +0 -97
- package/src/commands/memory/stats.ts +0 -163
- package/src/commands/memory/store.ts +0 -49
- package/src/commands/memory/vote.ts +0 -159
- package/src/commands/pipeline.ts +0 -127
- package/src/commands/responses.ts +0 -856
- package/src/commands/tools.ts +0 -293
- package/src/commands/wizard.ts +0 -319
- package/src/index.ts +0 -172
- package/src/lib/crypto.ts +0 -56
- package/src/lib/env-loader.ts +0 -206
- package/src/lib/onepassword.ts +0 -137
- package/src/test-agent-local.ts +0 -115
- package/tsconfig.json +0 -11
- package/vitest.config.ts +0 -10
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../core/src/pipeline/index.ts","../../core/src/pipeline/steps/classify.ts","../../core/src/pipeline/steps/thread-signals.ts","../../core/src/pipeline/steps/comment.ts","../../core/src/front/instrumented-client.ts","../../front-sdk/src/schemas/common.ts","../../front-sdk/src/schemas/conversation.ts","../../front-sdk/src/schemas/message.ts","../../front-sdk/src/schemas/draft.ts","../../front-sdk/src/schemas/template.ts","../../front-sdk/src/schemas/tag.ts","../../front-sdk/src/schemas/inbox.ts","../../front-sdk/src/schemas/channel.ts","../../front-sdk/src/schemas/contact.ts","../../front-sdk/src/schemas/teammate.ts","../../front-sdk/src/client/base.ts","../../front-sdk/src/client/channels.ts","../../front-sdk/src/client/contacts.ts","../../front-sdk/src/client/conversations.ts","../../front-sdk/src/client/drafts.ts","../../front-sdk/src/client/inboxes.ts","../../front-sdk/src/client/messages.ts","../../front-sdk/src/client/tags.ts","../../front-sdk/src/client/teammates.ts","../../front-sdk/src/client/templates.ts","../../front-sdk/src/index.ts","../../core/src/pipeline/steps/draft.ts","../../core/src/templates/match.ts","../../core/src/pipeline/steps/draft-prompts.ts","../../core/src/pipeline/steps/gather.ts","../../core/src/pipeline/steps/route.ts","../../core/src/pipeline/steps/validate.ts","../../core/src/skill-retrieval.ts","../../core/src/pipeline/thresholds.ts","../../core/src/pipeline/types.ts","../../core/src/pipeline/steps/tag.ts","../../core/src/tags/registry.ts","../../core/src/pipeline/steps/archive.ts"],"sourcesContent":["/**\n * Pipeline orchestrator\n *\n * Runs the full classify → route → gather → draft → validate → send pipeline.\n */\n\nimport type {\n AppConfig,\n ClassifyOutput,\n CommentOutput,\n DraftOutput,\n GatherOutput,\n PipelineInput,\n PipelineOutput,\n PipelineStepResult,\n RouteOutput,\n ThreadClassifyInput,\n ThreadClassifyOutput,\n} from './types'\n\nimport { classify, classifyThread } from './steps/classify'\nimport { addSupportComment } from './steps/comment'\nimport { draft } from './steps/draft'\nimport { formatContextForPrompt, gather } from './steps/gather'\nimport { route, routeThread, shouldRespond, shouldSilence } from './steps/route'\nimport { type ValidateResult, validate } from './steps/validate'\n\n// Re-export types and steps\nexport * from './types'\nexport {\n classify,\n extractSignals,\n fastClassify,\n // Thread-aware (v3)\n classifyThread,\n fastClassifyThread,\n llmClassifyThread,\n} from './steps/classify'\nexport {\n route,\n shouldRespond,\n shouldSilence,\n shouldEscalate,\n getRoutingRules,\n // Thread-aware (v3)\n routeThread,\n getThreadRoutingRules,\n // Memory-aware (v4)\n routeWithMemory,\n routeThreadWithMemory,\n recordRoutingOutcome,\n recordEscalationConfirmed,\n recordShouldHaveEscalated,\n recordUnnecessaryEscalation,\n type RouteWithMemoryInput,\n type ThreadRouteWithMemoryInput,\n type RouteWithMemoryOutput,\n type RecordRoutingOutcomeInput,\n} from './steps/route'\nexport { gather, formatContextForPrompt, extractEmail } from './steps/gather'\nexport {\n draft,\n getPromptForCategory,\n storeDraftCorrection,\n storeDraftSuccess,\n type DraftResult,\n type StoreDraftCorrectionInput,\n} from './steps/draft'\nexport { buildCategoryPrompt, BASE_DRAFT_PROMPT } from './steps/draft-prompts'\nexport {\n validate,\n validateSync,\n formatIssues,\n hasIssueType,\n type ValidateOptions,\n type ValidateResult,\n} from './steps/validate'\n// Thread signals (v3)\nexport {\n computeThreadSignals,\n computeMessageSignals,\n isThreadResolved,\n shouldSupportTeammate,\n} from './steps/thread-signals'\n// Comment step (v3 - support_teammate action)\nexport {\n addSupportComment,\n createCommentStep,\n formatSupportComment,\n formatMinimalComment,\n // Decision comment (v4 - agent reasoning)\n addDecisionComment,\n formatDecisionComment,\n type DecisionCommentContext,\n type AddDecisionCommentOptions,\n // Escalation/approval/audit formatters\n formatEscalationComment,\n formatApprovalComment,\n formatAuditComment,\n} from './steps/comment'\n// Tag step (v4 - category tagging)\nexport {\n applyTag,\n createTagStep,\n getTagsForCategory,\n applyMultipleTags,\n type TagStepOptions,\n} from './steps/tag'\n// Archive step (v4 - conversation archiving)\nexport {\n archiveConversation,\n createArchiveStep,\n shouldArchive,\n type ArchiveStepOptions,\n} from './steps/archive'\n\n// ============================================================================\n// Pipeline configuration\n// ============================================================================\n\nexport interface PipelineOptions {\n classifyModel?: string\n draftModel?: string\n maxRetries?: number\n validateStrictMode?: boolean\n // Inject tools for gather step\n gatherTools?: import('./steps/gather').GatherTools\n // Inject custom implementations for testing\n gatherFn?: (input: any) => Promise<GatherOutput>\n draftFn?: (input: any) => Promise<DraftOutput>\n sendFn?: (input: any) => Promise<{ sent: boolean; messageId?: string }>\n}\n\n// ============================================================================\n// Main pipeline\n// ============================================================================\n\nexport async function runPipeline(\n input: PipelineInput,\n options: PipelineOptions = {}\n): Promise<PipelineOutput> {\n const startTime = Date.now()\n const steps: PipelineStepResult[] = []\n\n let classification: ClassifyOutput | null = null\n let routing: RouteOutput | null = null\n let context: GatherOutput | null = null\n let draftResult: DraftOutput | null = null\n let validation: ValidateResult | null = null\n\n // -------------------------------------------------------------------------\n // Step 1: Classify\n // -------------------------------------------------------------------------\n const classifyStart = Date.now()\n try {\n classification = await classify(input.message, {\n model: options.classifyModel,\n })\n steps.push({\n step: 'classify',\n durationMs: Date.now() - classifyStart,\n success: true,\n output: classification,\n })\n } catch (error) {\n steps.push({\n step: 'classify',\n durationMs: Date.now() - classifyStart,\n success: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n return {\n action: 'escalate_human',\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // -------------------------------------------------------------------------\n // Step 2: Route\n // -------------------------------------------------------------------------\n const routeStart = Date.now()\n try {\n routing = route({\n message: input.message,\n classification,\n appConfig: input.appConfig,\n })\n steps.push({\n step: 'route',\n durationMs: Date.now() - routeStart,\n success: true,\n output: routing,\n })\n } catch (error) {\n steps.push({\n step: 'route',\n durationMs: Date.now() - routeStart,\n success: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n return {\n action: 'escalate_human',\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // If not responding, stop here\n if (!shouldRespond(routing.action)) {\n return {\n action: routing.action,\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // -------------------------------------------------------------------------\n // Step 3: Gather\n // -------------------------------------------------------------------------\n const gatherStart = Date.now()\n try {\n if (options.gatherFn) {\n context = await options.gatherFn({\n message: input.message,\n classification,\n appId: input.appConfig.appId,\n })\n } else {\n // Use real gather with provided tools\n context = await gather(\n {\n message: input.message,\n classification,\n appId: input.appConfig.appId,\n },\n { tools: options.gatherTools }\n )\n }\n steps.push({\n step: 'gather',\n durationMs: Date.now() - gatherStart,\n success: true,\n output: {\n hasUser: !!context.user,\n purchaseCount: context.purchases.length,\n knowledgeCount: context.knowledge.length,\n errors: context.gatherErrors.length,\n },\n })\n } catch (error) {\n steps.push({\n step: 'gather',\n durationMs: Date.now() - gatherStart,\n success: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n // Gather failure = escalate, don't expose error\n return {\n action: 'escalate_human',\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // -------------------------------------------------------------------------\n // Step 4: Draft\n // -------------------------------------------------------------------------\n const draftStart = Date.now()\n try {\n if (options.draftFn) {\n draftResult = await options.draftFn({\n message: input.message,\n classification,\n context,\n })\n } else {\n // Use real draft with LLM\n draftResult = await draft(\n {\n message: input.message,\n classification,\n context,\n },\n { model: options.draftModel }\n )\n }\n steps.push({\n step: 'draft',\n durationMs: Date.now() - draftStart,\n success: true,\n output: {\n draftLength: draftResult.draft.length,\n toolsUsed: draftResult.toolsUsed,\n },\n })\n } catch (error) {\n steps.push({\n step: 'draft',\n durationMs: Date.now() - draftStart,\n success: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n return {\n action: 'escalate_human',\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // -------------------------------------------------------------------------\n // Step 5: Validate (with memory check for repeated mistakes)\n // -------------------------------------------------------------------------\n const validateStart = Date.now()\n try {\n validation = await validate(\n {\n draft: draftResult.draft,\n context,\n strictMode: options.validateStrictMode,\n customerMessage: {\n subject: input.message.subject,\n body: input.message.body,\n },\n },\n {\n appId: input.appConfig.appId,\n category: classification?.category,\n }\n )\n steps.push({\n step: 'validate',\n durationMs: Date.now() - validateStart,\n success: true,\n output: {\n valid: validation.valid,\n issueCount: validation.issues.length,\n issues: validation.issues.map((i) => ({\n type: i.type,\n severity: i.severity,\n })),\n relevanceCheckPerformed: validation.relevanceCheckPerformed,\n relevanceScore: validation.relevance,\n },\n })\n } catch (error) {\n steps.push({\n step: 'validate',\n durationMs: Date.now() - validateStart,\n success: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n return {\n action: 'escalate_human',\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // If validation failed, don't send\n if (!validation.valid) {\n return {\n action: 'escalate_human',\n response: draftResult.draft,\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // -------------------------------------------------------------------------\n // Step 6: Send (if not dry run)\n // -------------------------------------------------------------------------\n if (input.dryRun) {\n return {\n action: 'respond',\n response: draftResult.draft,\n sent: false,\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n const sendStart = Date.now()\n try {\n if (options.sendFn) {\n const sendResult = await options.sendFn({\n conversationId: input.message.conversationId,\n draft: draftResult.draft,\n appId: input.appConfig.appId,\n })\n steps.push({\n step: 'send',\n durationMs: Date.now() - sendStart,\n success: sendResult.sent,\n output: { messageId: sendResult.messageId },\n })\n return {\n action: 'respond',\n response: draftResult.draft,\n sent: sendResult.sent,\n messageId: sendResult.messageId,\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n } else {\n // No send function = dry run\n steps.push({\n step: 'send',\n durationMs: 0,\n success: true,\n output: { skipped: true },\n })\n return {\n action: 'respond',\n response: draftResult.draft,\n sent: false,\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n } catch (error) {\n steps.push({\n step: 'send',\n durationMs: Date.now() - sendStart,\n success: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n return {\n action: 'escalate_human',\n response: draftResult.draft,\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n}\n\n// ============================================================================\n// Thread-aware pipeline (v3)\n// ============================================================================\n\nexport interface ThreadPipelineInput {\n thread: ThreadClassifyInput\n appConfig: AppConfig\n dryRun?: boolean\n}\n\nexport interface ThreadPipelineOptions extends PipelineOptions {\n /** Front API token for comment step */\n frontApiToken?: string\n /** Comment author teammate ID */\n commentAuthorId?: string\n}\n\n/**\n * Run the thread-aware pipeline.\n *\n * Handles new actions like support_teammate (adds comment instead of draft).\n */\nexport async function runThreadPipeline(\n input: ThreadPipelineInput,\n options: ThreadPipelineOptions = {}\n): Promise<PipelineOutput> {\n const startTime = Date.now()\n const steps: PipelineStepResult[] = []\n\n let classification: ThreadClassifyOutput | null = null\n let routing: RouteOutput | null = null\n let context: GatherOutput | null = null\n let draftResult: DraftOutput | null = null\n let validation: ValidateResult | null = null\n let commentResult: CommentOutput | null = null\n\n // -------------------------------------------------------------------------\n // Step 1: Classify (thread-aware)\n // -------------------------------------------------------------------------\n const classifyStart = Date.now()\n try {\n classification = await classifyThread(input.thread, {\n model: options.classifyModel,\n })\n steps.push({\n step: 'classify',\n durationMs: Date.now() - classifyStart,\n success: true,\n output: {\n category: classification.category,\n confidence: classification.confidence,\n threadLength: classification.signals.threadLength,\n hasTeammate: classification.signals.hasTeammateMessage,\n },\n })\n } catch (error) {\n steps.push({\n step: 'classify',\n durationMs: Date.now() - classifyStart,\n success: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n return {\n action: 'escalate_human',\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // -------------------------------------------------------------------------\n // Step 2: Route (thread-aware)\n // -------------------------------------------------------------------------\n const routeStart = Date.now()\n try {\n routing = routeThread({\n classification,\n appConfig: input.appConfig,\n })\n steps.push({\n step: 'route',\n durationMs: Date.now() - routeStart,\n success: true,\n output: routing,\n })\n } catch (error) {\n steps.push({\n step: 'route',\n durationMs: Date.now() - routeStart,\n success: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n return {\n action: 'escalate_human',\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // If not responding AND not support_teammate, stop here\n if (!shouldRespond(routing.action) && routing.action !== 'support_teammate') {\n return {\n action: routing.action,\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // -------------------------------------------------------------------------\n // Step 3: Gather (needed for both respond and support_teammate)\n // -------------------------------------------------------------------------\n const gatherStart = Date.now()\n try {\n // Convert thread trigger to single message format for gather\n const triggerAsMessage = {\n subject: input.thread.triggerMessage.subject || '',\n body: input.thread.triggerMessage.body,\n from: input.thread.triggerMessage.author?.email,\n conversationId: input.thread.conversationId,\n appId: input.thread.appId,\n }\n\n if (options.gatherFn) {\n context = await options.gatherFn({\n message: triggerAsMessage,\n classification: {\n category: classification.category,\n confidence: classification.confidence,\n signals: classification.signals, // ThreadSignals extends MessageSignals\n reasoning: classification.reasoning,\n },\n appId: input.appConfig.appId,\n })\n } else {\n context = await gather(\n {\n message: triggerAsMessage,\n classification: {\n category: classification.category,\n confidence: classification.confidence,\n signals: classification.signals,\n reasoning: classification.reasoning,\n },\n appId: input.appConfig.appId,\n },\n { tools: options.gatherTools }\n )\n }\n steps.push({\n step: 'gather',\n durationMs: Date.now() - gatherStart,\n success: true,\n output: {\n hasUser: !!context.user,\n purchaseCount: context.purchases.length,\n knowledgeCount: context.knowledge.length,\n errors: context.gatherErrors.length,\n },\n })\n } catch (error) {\n steps.push({\n step: 'gather',\n durationMs: Date.now() - gatherStart,\n success: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n return {\n action: 'escalate_human',\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // -------------------------------------------------------------------------\n // Branch: support_teammate → add comment\n // -------------------------------------------------------------------------\n if (routing.action === 'support_teammate') {\n if (!input.dryRun && options.frontApiToken) {\n const commentStart = Date.now()\n try {\n commentResult = await addSupportComment(\n {\n conversationId: input.thread.conversationId,\n context,\n appId: input.appConfig.appId,\n },\n {\n frontApiToken: options.frontApiToken,\n authorId: options.commentAuthorId,\n }\n )\n steps.push({\n step: 'send', // Reuse send step name for simplicity\n durationMs: Date.now() - commentStart,\n success: commentResult.added,\n output: { type: 'comment', added: commentResult.added },\n })\n } catch (error) {\n steps.push({\n step: 'send',\n durationMs: Date.now() - commentStart,\n success: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n }\n\n return {\n action: 'support_teammate',\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // -------------------------------------------------------------------------\n // Step 4: Draft (only for respond action)\n // -------------------------------------------------------------------------\n const draftStart = Date.now()\n try {\n const triggerAsMessage = {\n subject: input.thread.triggerMessage.subject || '',\n body: input.thread.triggerMessage.body,\n from: input.thread.triggerMessage.author?.email,\n conversationId: input.thread.conversationId,\n appId: input.thread.appId,\n }\n\n if (options.draftFn) {\n draftResult = await options.draftFn({\n message: triggerAsMessage,\n classification,\n context,\n })\n } else {\n draftResult = await draft(\n {\n message: triggerAsMessage,\n classification: {\n category: classification.category,\n confidence: classification.confidence,\n signals: classification.signals,\n reasoning: classification.reasoning,\n },\n context,\n },\n { model: options.draftModel }\n )\n }\n steps.push({\n step: 'draft',\n durationMs: Date.now() - draftStart,\n success: true,\n output: {\n draftLength: draftResult.draft.length,\n toolsUsed: draftResult.toolsUsed,\n },\n })\n } catch (error) {\n steps.push({\n step: 'draft',\n durationMs: Date.now() - draftStart,\n success: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n return {\n action: 'escalate_human',\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // -------------------------------------------------------------------------\n // Step 5: Validate (with memory check for repeated mistakes)\n // -------------------------------------------------------------------------\n const validateStart = Date.now()\n try {\n // Extract customer message from thread's trigger message for relevance check\n const triggerMsg = input.thread.triggerMessage\n validation = await validate(\n {\n draft: draftResult.draft,\n context,\n strictMode: options.validateStrictMode,\n customerMessage: {\n subject: triggerMsg.subject ?? '',\n body: triggerMsg.body,\n },\n },\n {\n appId: input.appConfig.appId,\n category: classification?.category,\n }\n )\n steps.push({\n step: 'validate',\n durationMs: Date.now() - validateStart,\n success: true,\n output: {\n valid: validation.valid,\n issueCount: validation.issues.length,\n issues: validation.issues.map((i) => ({\n type: i.type,\n severity: i.severity,\n })),\n relevanceCheckPerformed: validation.relevanceCheckPerformed,\n relevanceScore: validation.relevance,\n },\n })\n } catch (error) {\n steps.push({\n step: 'validate',\n durationMs: Date.now() - validateStart,\n success: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n return {\n action: 'escalate_human',\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n if (!validation.valid) {\n return {\n action: 'escalate_human',\n response: draftResult.draft,\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n // -------------------------------------------------------------------------\n // Step 6: Send (if not dry run)\n // -------------------------------------------------------------------------\n if (input.dryRun) {\n return {\n action: 'respond',\n response: draftResult.draft,\n sent: false,\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n\n const sendStart = Date.now()\n try {\n if (options.sendFn) {\n const sendResult = await options.sendFn({\n conversationId: input.thread.conversationId,\n draft: draftResult.draft,\n appId: input.appConfig.appId,\n })\n steps.push({\n step: 'send',\n durationMs: Date.now() - sendStart,\n success: sendResult.sent,\n output: { messageId: sendResult.messageId },\n })\n return {\n action: 'respond',\n response: draftResult.draft,\n sent: sendResult.sent,\n messageId: sendResult.messageId,\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n } else {\n steps.push({\n step: 'send',\n durationMs: 0,\n success: true,\n output: { skipped: true },\n })\n return {\n action: 'respond',\n response: draftResult.draft,\n sent: false,\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n } catch (error) {\n steps.push({\n step: 'send',\n durationMs: Date.now() - sendStart,\n success: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n return {\n action: 'escalate_human',\n response: draftResult.draft,\n steps,\n totalDurationMs: Date.now() - startTime,\n }\n }\n}\n","/**\n * Step 1: CLASSIFY\n *\n * Categorizes incoming messages before any other processing.\n * Uses a combination of rules and LLM for nuanced classification.\n *\n * Memory Integration:\n * Before LLM classification, queries memory for similar past tickets\n * and their outcomes to improve classification accuracy.\n */\n\nimport { SupportMemoryService } from '@skillrecordings/memory/support-memory'\nimport { generateObject } from 'ai'\nimport { z } from 'zod'\nimport {\n type RelevantMemory,\n citeMemories,\n formatMemoriesCompact,\n queryMemoriesForStage,\n} from '../../memory/query'\nimport { log, traceClassification } from '../../observability/axiom'\nimport type {\n ClassifyInput,\n ClassifyOutput,\n MessageCategory,\n MessageSignals,\n ThreadClassifyInput,\n ThreadClassifyOutput,\n ThreadSignals,\n} from '../types'\nimport {\n computeThreadSignals,\n isThreadResolved,\n shouldSupportTeammate,\n} from './thread-signals'\n\n// ============================================================================\n// Signal extraction (deterministic, no LLM)\n// ============================================================================\n\nconst EMAIL_REGEX = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g\nconst DATE_PATTERNS = [\n /(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\\s+\\d{1,2}/i,\n /\\d{1,2}\\/\\d{1,2}\\/\\d{2,4}/,\n /\\d{4}-\\d{2}-\\d{2}/,\n /(?:yesterday|last\\s+(?:week|month))/i,\n]\nconst ERROR_PATTERNS = [\n /error\\s*:?\\s*\\d+/i,\n /exception/i,\n /stack\\s*trace/i,\n /failed\\s+to/i,\n /cannot\\s+(?:read|access|find)/i,\n]\nconst INSTRUCTOR_NAMES = [\n 'matt',\n 'pocock',\n 'kent',\n 'dodds',\n 'wesbos',\n 'wes bos',\n]\nconst ANGRY_PATTERNS = [\n /(?:wtf|what the (?:fuck|hell))/i,\n /this is (?:ridiculous|unacceptable|bullshit)/i,\n /i(?:'m| am) (?:furious|pissed|angry)/i,\n /worst\\s+(?:experience|service)/i,\n /(?:refund|money back)\\s+(?:now|immediately)/i,\n]\nconst AUTOMATED_PATTERNS = [\n /auto-?reply/i,\n /out of (?:the )?office/i,\n /automatic\\s+response/i,\n /do\\s+not\\s+reply/i,\n /noreply@/i,\n /mailer-daemon/i,\n /postmaster@/i,\n]\nconst VENDOR_PATTERNS = [\n /partnership\\s+opportunity/i,\n /collaboration\\s+proposal/i,\n /sponsored?\\s+(?:post|content)/i,\n /backlink/i,\n /guest\\s+(?:post|article)/i,\n /seo\\s+(?:services?|optimization)/i,\n /influencer\\s+(?:campaign|manager|marketing)/i,\n // Business/partnership pitches - be specific\n /sponsoring\\s+your\\s+(?:channel|content|newsletter)/i,\n /campaign\\s+budget/i,\n /creator\\s+fee/i,\n /\\d+\\s*(?:USD|EUR|GBP)\\s+(?:creator|fee|budget)/i,\n /partnerships?\\s+@\\s+\\w+/i, // \"Partnerships @ [Company]\"\n /(?:vp|director)\\s+of\\s+(?:gtm|growth|marketing|partnerships)/i,\n // Additional sponsor/collab patterns\n /\\bpaid\\s+collab\\b/i, // \"paid collab\"\n /\\bsponsor\\b.{0,30}\\b(?:content|channel|video|newsletter)\\b/i, // \"sponsor...content\" with up to 30 chars between\n /\\b(?:we'?d?\\s+)?love\\s+to\\s+sponsor\\b/i, // \"we'd love to sponsor\", \"love to sponsor\"\n /\\bcollab\\s+(?:proposal|opportunity)\\b/i, // \"collab proposal\"\n // Affiliate/referral outreach\n /\\baffiliate\\s+(?:opportunity|commission|income|revenue|program)/i, // \"affiliate Opportunity\", \"affiliate commission\"\n /\\bearn\\s+(?:a\\s+)?commission\\b/i, // \"earn a commission\", \"earn commission\"\n /\\breferral\\s+(?:commission|income|fee|payout)/i, // \"referral commission\"\n // Unsolicited product/beta promotion\n /\\bthought\\s+of\\s+you\\s+for\\s+(?:this|our|the)\\b/i, // \"thought of you for this release\"\n /\\bproduction[- ]ready\\s+(?:saas|app|template|boilerplate|platform|starter)/i, // cold SaaS pitch\n]\n\n// Legal threat patterns - escalate_urgent ONLY\nconst LEGAL_THREAT_PATTERNS = [\n /\\b(?:my\\s+)?lawyer/i,\n /\\blegal\\s+action/i,\n /\\bsue\\s+(?:you|your|the)/i,\n /\\bsuing\\b/i,\n /\\blawsuit/i,\n /\\battorney/i,\n /\\bcourt\\b.*\\b(?:action|case|filing)/i,\n /\\blegal\\s+(?:team|counsel|representation)/i,\n /\\breport(?:ing)?\\s+(?:to|this\\s+to)\\s+(?:the\\s+)?(?:ftc|bbb|attorney\\s+general|consumer\\s+protection)/i,\n]\n\n// Outside policy timeframe patterns (30+ days ago)\nconst OUTSIDE_POLICY_PATTERNS = [\n /\\b(?:6|7|8|9|\\d{2,})\\s+weeks?\\s+ago/i, // \"6 weeks ago\"\n /\\b(?:45|5\\d|6\\d|7\\d|8\\d|9\\d|\\d{3,})\\s+days?\\s+ago/i, // \"45 days ago\"\n /\\b(?:2|3|4|5|6|7|8|9|1\\d)\\s+months?\\s+ago/i, // \"2+ months ago\"\n /\\bover\\s+(?:a\\s+)?month\\s+ago/i, // \"over a month ago\"\n /\\bmore\\s+than\\s+(?:a\\s+)?month/i, // \"more than a month\"\n /\\b(?:last|this)\\s+(?:year|winter|spring|summer|fall|autumn)/i, // seasonal references\n /\\bbought\\s+(?:it\\s+)?(?:back\\s+in|last)\\s+(?:january|february|march|april|may|june|july|august|september|october|november|december)/i,\n]\n\n// Personal/casual message patterns (for instructor routing)\nconst PERSONAL_MESSAGE_PATTERNS = [\n /^(?:hey|hi|hello|yo|sup)[\\s,!]*(?:matt|man|dude)?[!\\s]*$/i, // Just a greeting\n /\\blol\\b/i,\n /\\bhaha\\b/i,\n /^(?:this\\s+is\\s+)?(?:crazy|wild|insane|amazing|awesome)[!\\s]*$/i,\n /^(?:love\\s+(?:it|this|your))/i,\n /\\b(?:big\\s+fan|huge\\s+fan)\\b/i,\n]\n\n// Presales FAQ patterns - answerable with KB (pricing, curriculum, requirements, discounts)\nconst PRESALES_FAQ_PATTERNS = [\n /\\bhow\\s+much\\s+(?:does|is|do|would)\\b/i, // \"how much does it cost\"\n /\\b(?:what(?:'s| is| are)?)\\s+(?:the\\s+)?(?:price|cost|pricing)\\b/i, // \"what's the price\"\n /\\bwhat(?:'s| is)?\\s+included\\b/i, // \"what's included\"\n /\\bwhat\\s+(?:do\\s+)?(?:i|you)\\s+(?:get|learn|cover)\\b/i, // \"what do I get\"\n /\\bcurriculum\\b/i,\n /\\bmodules?\\b.*\\b(?:cover|include|contain)\\b/i, // \"what modules does it cover\"\n /\\brequirements?\\b/i, // tech requirements\n /\\bprerequisites?\\b/i,\n /\\bppp\\b/i, // purchasing power parity\n /\\bstudent\\s+discount\\b/i,\n /\\bregional\\s+(?:pricing|discount)\\b/i,\n /\\bcoupon\\b/i,\n /\\bdiscount\\s+(?:code|available|offer)\\b/i,\n /\\bis\\s+(?:this|it)\\s+right\\s+for\\s+me\\b/i, // \"is this right for me\"\n /\\bsuitable\\s+for\\s+(?:beginners?|intermediate|advanced)\\b/i,\n /\\bdo\\s+(?:i|you)\\s+need\\s+to\\s+know\\b/i, // \"do I need to know X\"\n /\\bhow\\s+long\\s+(?:is|does|will)\\b/i, // \"how long is the course\"\n /\\blifetime\\s+access\\b/i,\n /\\bupdates?\\s+(?:included|free)\\b/i,\n]\n\n// Presales Team patterns - enterprise/team sales inquiries (escalate_human)\nconst PRESALES_TEAM_PATTERNS = [\n /\\bteam\\s+of\\s+\\d+\\b/i, // \"team of 5 developers\"\n /\\bcompany\\s+license\\b/i,\n /\\bsite\\s+license\\b/i,\n /\\benterprise\\b/i,\n /\\bcorporate\\s+(?:license|purchase|training)\\b/i,\n /\\bprocurement\\b/i,\n /\\bpurchase\\s+order\\b/i,\n /\\b(?:p\\.?o\\.?|po)\\s*(?:#|number|:)/i, // \"PO number\", \"P.O. #\"\n /\\binvoice\\s+(?:for\\s+)?(?:our\\s+)?company\\b/i, // company invoice context\n /\\bl&d\\s+(?:budget|team|department)\\b/i, // L&D budget\n /\\blearning\\s+(?:and\\s+)?development\\b/i,\n /\\btraining\\s+budget\\b/i,\n /\\bvolume\\s+discount\\b/i,\n /\\bbulk\\s+(?:pricing|purchase|licenses?)\\b/i,\n /\\bmultiple\\s+(?:seats|licenses|users)\\b/i,\n /\\b(?:for|across)\\s+(?:my|our)\\s+(?:team|department|org|organization)\\b/i,\n]\n\n// Patterns that indicate genuine appreciation (not sign-offs like \"Thanks,\\nJohn\" or business \"we'd love to\")\n// Must be in context of appreciation to the instructor, not polite closings\nconst GENUINE_APPRECIATION_PATTERNS = [\n /\\bthank(?:s|ing)?\\s+(?:you\\s+)?(?:so\\s+much|for\\s+(?:your|the|everything|this))/i, // \"thanks so much\", \"thank you for\"\n /\\b(?:love|loving)\\s+(?:your|the|what\\s+you)/i, // \"love your work\", \"loving the content\"\n /\\b(?:amazing|awesome|incredible)\\s+(?:work|content|course|stuff)/i, // \"amazing work\"\n /\\bchanged\\s+(?:my|how\\s+I)/i, // \"changed my life\"\n /\\b(?:big|huge)\\s+fan\\b/i, // \"big fan\"\n /\\breally\\s+appreciate/i, // \"really appreciate\"\n /\\bkeep\\s+(?:it\\s+)?up\\b/i, // \"keep it up\", \"keep up\"\n /\\b(?:love|loving)\\s+what\\s+you(?:'re|\\s+are)\\s+doing/i, // \"love what you're doing\"\n]\n\nexport function extractSignals(input: ClassifyInput): MessageSignals {\n const text = `${input.subject} ${input.body}`.toLowerCase()\n const fullText = `${input.subject} ${input.body}` // preserve case for some patterns\n\n // Check for genuine appreciation (not sign-offs like \"Thanks,\\nJohn\")\n const hasGenuineAppreciation = GENUINE_APPRECIATION_PATTERNS.some((p) =>\n p.test(fullText)\n )\n\n return {\n hasEmailInBody: EMAIL_REGEX.test(input.body),\n hasPurchaseDate: DATE_PATTERNS.some((p) => p.test(text)),\n hasErrorMessage: ERROR_PATTERNS.some((p) => p.test(text)),\n isReply: input.subject.toLowerCase().startsWith('re:'),\n mentionsInstructor: INSTRUCTOR_NAMES.some((n) => text.includes(n)),\n hasAngrySentiment: ANGRY_PATTERNS.some((p) => p.test(text)),\n isAutomated:\n AUTOMATED_PATTERNS.some((p) => p.test(text)) ||\n (input.from ? /noreply|no-reply|mailer-daemon/i.test(input.from) : false),\n isVendorOutreach: VENDOR_PATTERNS.some((p) => p.test(text)),\n // Escalation signals\n hasLegalThreat: LEGAL_THREAT_PATTERNS.some((p) => p.test(fullText)),\n hasOutsidePolicyTimeframe: OUTSIDE_POLICY_PATTERNS.some((p) =>\n p.test(fullText)\n ),\n // Personal message to instructor requires EITHER:\n // 1. Obvious personal patterns (greeting only, lol, haha, etc.)\n // 2. Mentions instructor AND has genuine appreciation (not just \"Thanks,\" sign-off)\n isPersonalToInstructor:\n PERSONAL_MESSAGE_PATTERNS.some((p) => p.test(fullText)) ||\n (INSTRUCTOR_NAMES.some((n) => text.includes(n)) &&\n hasGenuineAppreciation),\n // Presales signals\n isPresalesFaq: PRESALES_FAQ_PATTERNS.some((p) => p.test(fullText)),\n isPresalesTeam: PRESALES_TEAM_PATTERNS.some((p) => p.test(fullText)),\n }\n}\n\n// ============================================================================\n// Fast-path classification (no LLM needed)\n// ============================================================================\n\nexport interface FastClassifyResult extends ClassifyOutput {\n /** Internal tracking of which fast-path rule matched (for logging only) */\n _fastPathRule?: string\n}\n\nexport function fastClassify(\n input: ClassifyInput,\n signals: MessageSignals\n): FastClassifyResult | null {\n const text = `${input.subject} ${input.body}`.toLowerCase()\n\n // Automated system messages - high confidence\n if (signals.isAutomated) {\n return {\n category: 'system',\n confidence: 0.95,\n signals,\n reasoning: 'Detected automated/system message patterns',\n _fastPathRule: 'automated_system',\n }\n }\n\n // Vendor outreach - high confidence\n if (signals.isVendorOutreach && !signals.hasEmailInBody) {\n return {\n category: 'spam',\n confidence: 0.9,\n signals,\n reasoning: 'Detected vendor/marketing outreach patterns',\n _fastPathRule: 'vendor_outreach',\n }\n }\n\n // Explicit refund request\n if (/\\b(?:refund|money\\s+back|cancel.*purchase)\\b/i.test(text)) {\n return {\n category: 'support_refund',\n confidence: 0.85,\n signals,\n reasoning: 'Explicit refund request detected',\n _fastPathRule: 'explicit_refund',\n }\n }\n\n // Access issues - common patterns\n if (\n /(?:can'?t|cannot|unable to)\\s+(?:access|log\\s*in|sign\\s*in)/i.test(text) ||\n /(?:lost|no)\\s+access/i.test(text) ||\n /restore.*access/i.test(text)\n ) {\n return {\n category: 'support_access',\n confidence: 0.85,\n signals,\n reasoning: 'Access issue patterns detected',\n _fastPathRule: 'access_issue',\n }\n }\n\n // Transfer request\n if (\n /(?:transfer|move).*(?:purchase|license|account)/i.test(text) ||\n /(?:different|wrong|change).*email/i.test(text)\n ) {\n return {\n category: 'support_transfer',\n confidence: 0.8,\n signals,\n reasoning: 'Transfer request patterns detected',\n _fastPathRule: 'transfer_request',\n }\n }\n\n // Invoice/billing\n if (/\\b(?:invoice|receipt|tax\\s+document|billing)\\b/i.test(text)) {\n return {\n category: 'support_billing',\n confidence: 0.85,\n signals,\n reasoning: 'Billing/invoice request detected',\n _fastPathRule: 'invoice_billing',\n }\n }\n\n // Fan mail - REMOVED from fast-path.\n // Fan mail vs presales is too nuanced for regex. Messages like \"I'd love to\n // learn about X\" or responses to instructor outreach were over-classified as\n // fan_mail when they should be presales_consult or voc_response.\n // All potential fan_mail now goes through LLM for proper disambiguation.\n\n return null // Need LLM for nuanced classification\n}\n\n// ============================================================================\n// LLM classification (for complex cases)\n// ============================================================================\n\nconst classifySchema = z.object({\n category: z.enum([\n 'support_access',\n 'support_refund',\n 'support_transfer',\n 'support_technical',\n 'support_billing',\n 'fan_mail',\n 'spam',\n 'system',\n 'unknown',\n 'presales_faq',\n 'presales_consult',\n 'presales_team',\n ]),\n confidence: z.number().min(0).max(1),\n reasoning: z.string(),\n})\n\nconst CLASSIFY_PROMPT = `You are a support message classifier. Categorize the incoming message.\n\nCategories:\n- support_access: Login issues, can't access purchased content, account problems\n- support_refund: Wants money back, cancel purchase, refund request\n- support_transfer: Move purchase to different email, license transfer\n- support_technical: Questions about course content, code help, how to use product\n- support_billing: Invoice request, receipt needed, tax documents, payment questions\n- presales_faq: Pre-purchase questions answerable with KB (pricing, what's included, curriculum, tech requirements, discounts, PPP)\n- presales_consult: Pre-purchase questions needing instructor judgment (\"should I buy X or Y?\", career advice tied to product, expressing interest in learning a topic)\n- presales_team: Enterprise/team inquiries (team of X, company license, procurement, PO, L&D budget, volume/bulk pricing)\n- fan_mail: UNSOLICITED, PURE appreciation with NO questions, NO asks, NO purchase intent. Only genuine \"thank you, you changed my life\" messages with zero requests.\n- spam: Vendor outreach, partnership proposals, marketing, SEO services, affiliate programs/commissions, product promotions, beta program invites from external companies, unsolicited business proposals. Key signals: company representative pitching a product/service, affiliate commission language, \"thought of you for [product]\", beta invitations, SaaS/tool promotions directed at the instructor.\n- system: Automated replies, bounces, out-of-office, system notifications\n- unknown: Can't confidently categorize\n\nRules:\n- If it's clearly a customer needing help with their purchase → support_*\n- If asking about product BEFORE purchasing → presales_*\n - Simple factual questions (price, curriculum, requirements) → presales_faq\n - \"Which course should I buy?\" or career advice → presales_consult\n - Expressing interest in a topic or responding to outreach → presales_consult\n - Team/enterprise/bulk inquiries → presales_team\n- fan_mail is ONLY for unsolicited, pure appreciation with NOTHING else:\n - Must have NO questions (no \"?\", no \"how do I\", no \"can you\")\n - Must have NO purchase/learning intent (no \"I want to learn\", \"interested in buying\")\n - Must have NO response to outreach (no quoted emails, no \"you asked me about\")\n - Must be PURELY \"thank you / you changed my life / amazing work\" and nothing more\n- If it's someone trying to sell/partner → spam\n- If it's an affiliate opportunity, product promotion, or beta invite from an external company → spam (NOT support_billing)\n- If it's automated → system\n- Only use unknown if genuinely ambiguous\n\nVendor outreach / spam signals (these are NOT billing):\n- \"affiliate opportunity\" or \"affiliate commission\" → spam (recruiting as affiliate)\n- \"[Product] Beta Program by [Company]\" → spam (unsolicited product pitch)\n- \"Production-ready SaaS [anything]\" with promotion → spam (cold product pitch)\n- \"Thought of you for this release\" → spam (targeted product promotion)\n- Company rep promoting their product TO the instructor → spam, not support_billing\n- Unsolicited business proposals with no existing customer relationship → spam\n\nKey disambiguation — fan_mail is RARE. When in doubt, choose presales_consult:\n- \"I love your content\" + ANY question about learning → presales_consult, NOT fan_mail\n- Response to instructor outreach email → presales_consult, NOT fan_mail\n- Appreciation + \"how do I buy/access/start\" → presales_faq, NOT fan_mail\n- \"I'd love to learn about X\" → presales_consult, NOT fan_mail\n- Sharing what interests them about a topic → presales_consult, NOT fan_mail\n- ONLY pure \"thank you, you changed my life\" with ZERO asks = fan_mail\n\nVendor outreach examples (all → spam, NOT support_billing):\n- \"Production-ready SaaS boilerplate — affiliate Opportunity\" → spam (affiliate pitch)\n- \"MuleRun AI Agent Builder Beta Program by Alibaba\" → spam (product promotion)\n- \"Paid collab idea: thought of you for this release\" → spam (cold outreach)\n- \"Earn 30% commission per sale\" → spam (affiliate recruitment)\n\nOutput your classification with confidence (0-1) and brief reasoning.`\n\nexport async function llmClassify(\n input: ClassifyInput,\n signals: MessageSignals,\n model: string = 'anthropic/claude-haiku-4-5',\n options: { appId?: string; runId?: string } = {}\n): Promise<ClassifyOutput & { citedMemoryIds?: string[] }> {\n const message = `Subject: ${input.subject}\\n\\nBody:\\n${input.body}`\n\n // Query memories for similar past classifications\n let memoryContext = ''\n let citedMemoryIds: string[] = []\n\n if (options.appId) {\n try {\n const memories = await queryMemoriesForStage({\n appId: options.appId,\n stage: 'classify',\n situation: message,\n limit: 5,\n threshold: 0.6,\n })\n\n if (memories.length > 0) {\n memoryContext = formatMemoriesCompact(memories)\n citedMemoryIds = memories.map((m) => m.id)\n\n await log('debug', 'classify memory query results', {\n workflow: 'pipeline',\n step: 'classify',\n appId: options.appId,\n memoriesFound: memories.length,\n topScore: memories[0]?.score ?? 0,\n memoryIds: citedMemoryIds,\n })\n\n // Record citation if we have a run ID\n if (options.runId && citedMemoryIds.length > 0) {\n await citeMemories(citedMemoryIds, options.runId, options.appId)\n }\n }\n } catch (error) {\n // Log but don't fail classification if memory query fails\n await log('warn', 'classify memory query failed', {\n workflow: 'pipeline',\n step: 'classify',\n appId: options.appId,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n // Build prompt with memory context if available\n const systemPrompt = memoryContext\n ? `${memoryContext}\\n\\n${CLASSIFY_PROMPT}`\n : CLASSIFY_PROMPT\n\n const { object } = await generateObject({\n model,\n schema: classifySchema,\n system: systemPrompt,\n prompt: message,\n })\n\n return {\n category: object.category as MessageCategory,\n confidence: object.confidence,\n signals,\n reasoning: object.reasoning,\n citedMemoryIds: citedMemoryIds.length > 0 ? citedMemoryIds : undefined,\n }\n}\n\n// ============================================================================\n// Main classify function\n// ============================================================================\n\nexport interface ClassifyOptions {\n model?: string\n forceLLM?: boolean // Skip fast-path, always use LLM\n appId?: string // App ID for memory queries\n conversationId?: string // For memory citation tracking\n runId?: string // Pipeline run ID for citation tracking\n}\n\nexport async function classify(\n input: ClassifyInput,\n options: ClassifyOptions = {}\n): Promise<ClassifyOutput & { citedMemoryIds?: string[] }> {\n const {\n model = 'anthropic/claude-haiku-4-5',\n forceLLM = false,\n appId,\n runId,\n conversationId,\n } = options\n\n const startTime = Date.now()\n\n await log('debug', 'classify started', {\n workflow: 'pipeline',\n step: 'classify',\n appId,\n conversationId,\n messageLength: input.body.length,\n forceLLM,\n })\n\n // Extract signals first (always)\n const signals = extractSignals(input)\n\n // Try fast-path classification\n if (!forceLLM) {\n const fastResult = fastClassify(input, signals)\n if (fastResult) {\n const durationMs = Date.now() - startTime\n\n // High-cardinality decision-point logging\n await log('info', 'classify:decision', {\n workflow: 'pipeline',\n step: 'classify',\n appId,\n conversationId,\n // Decision outcome\n category: fastResult.category,\n confidence: fastResult.confidence,\n reasoning: fastResult.reasoning,\n // Decision path\n decisionPath: 'fast-path',\n fastPathRule: fastResult._fastPathRule,\n // Input signals that drove the decision\n signalsActive: Object.entries(signals)\n .filter(([_, v]) => v === true)\n .map(([k]) => k),\n signalsInactive: Object.entries(signals)\n .filter(([_, v]) => v === false)\n .map(([k]) => k),\n // Raw signals for debugging\n hasEmailInBody: signals.hasEmailInBody,\n hasPurchaseDate: signals.hasPurchaseDate,\n hasErrorMessage: signals.hasErrorMessage,\n isReply: signals.isReply,\n mentionsInstructor: signals.mentionsInstructor,\n hasAngrySentiment: signals.hasAngrySentiment,\n isAutomated: signals.isAutomated,\n isVendorOutreach: signals.isVendorOutreach,\n hasLegalThreat: signals.hasLegalThreat,\n hasOutsidePolicyTimeframe: signals.hasOutsidePolicyTimeframe,\n isPersonalToInstructor: signals.isPersonalToInstructor,\n isPresalesFaq: signals.isPresalesFaq,\n isPresalesTeam: signals.isPresalesTeam,\n // Context\n usedLLM: false,\n durationMs,\n })\n\n // Return without internal tracking property\n const { _fastPathRule: _, ...result } = fastResult\n return result\n }\n }\n\n // Fall back to LLM (with memory integration)\n const result = await llmClassify(input, signals, model, { appId, runId })\n\n const durationMs = Date.now() - startTime\n\n // High-cardinality decision-point logging\n await log('info', 'classify:decision', {\n workflow: 'pipeline',\n step: 'classify',\n appId,\n conversationId,\n // Decision outcome\n category: result.category,\n confidence: result.confidence,\n reasoning: result.reasoning,\n // Decision path\n decisionPath: 'llm',\n fastPathRule: null,\n // Why LLM was needed (fast-path didn't match)\n llmReason: forceLLM ? 'forceLLM=true' : 'no_fast_path_match',\n // Input signals that drove the decision\n signalsActive: Object.entries(signals)\n .filter(([_, v]) => v === true)\n .map(([k]) => k),\n signalsInactive: Object.entries(signals)\n .filter(([_, v]) => v === false)\n .map(([k]) => k),\n // Raw signals for debugging\n hasEmailInBody: signals.hasEmailInBody,\n hasPurchaseDate: signals.hasPurchaseDate,\n hasErrorMessage: signals.hasErrorMessage,\n isReply: signals.isReply,\n mentionsInstructor: signals.mentionsInstructor,\n hasAngrySentiment: signals.hasAngrySentiment,\n isAutomated: signals.isAutomated,\n isVendorOutreach: signals.isVendorOutreach,\n hasLegalThreat: signals.hasLegalThreat,\n hasOutsidePolicyTimeframe: signals.hasOutsidePolicyTimeframe,\n isPersonalToInstructor: signals.isPersonalToInstructor,\n isPresalesFaq: signals.isPresalesFaq,\n isPresalesTeam: signals.isPresalesTeam,\n // Memory context\n usedLLM: true,\n model,\n memoriesCited: result.citedMemoryIds?.length ?? 0,\n citedMemoryIds: result.citedMemoryIds,\n durationMs,\n })\n\n return result\n}\n\n// ============================================================================\n// Thread-aware classification (v3)\n// ============================================================================\n\nconst threadClassifySchema = z.object({\n category: z.enum([\n 'support_access',\n 'support_refund',\n 'support_transfer',\n 'support_technical',\n 'support_billing',\n 'fan_mail',\n 'spam',\n 'system',\n 'unknown',\n 'instructor_strategy',\n 'resolved',\n 'awaiting_customer',\n 'voc_response',\n 'presales_faq',\n 'presales_consult',\n 'presales_team',\n ]),\n confidence: z.number().min(0).max(1),\n reasoning: z.string(),\n})\n\nconst THREAD_CLASSIFY_PROMPT = `You are classifying a support THREAD. Analyze the full conversation to determine the category.\n\n## Categories\n\n**Support Categories (customer needs help with existing purchase):**\n- support_access: Can't access purchased content, login issues, license problems\n- support_refund: Wants money back. Look for: \"refund\", \"cancel\", \"money back\", dissatisfaction with purchase\n- support_transfer: Move purchase to different email, license transfer\n- support_technical: Questions about course content, code help, how-to questions\n- support_billing: Invoice/receipt requests, pricing questions, discount inquiries, tax documents\n\n**Presales Categories (BEFORE purchase - no evidence of existing purchase):**\n- presales_faq: Simple questions answerable with KB. Look for:\n - Pricing questions (\"how much\", \"what's the cost\")\n - \"What's included?\" / curriculum / modules\n - Tech requirements / prerequisites\n - PPP / student / regional discounts\n - \"Is this right for me if I know X?\"\n - Lifetime access, updates included\n- presales_consult: Needs instructor judgment. Look for:\n - \"Should I buy X or Y?\"\n - \"Which course is right for me?\"\n - Career advice tied to product choice\n - Complex \"is this suitable for my situation\" questions\n- presales_team: Enterprise/team sales inquiries. Look for:\n - \"team of X developers/engineers\"\n - \"company license\" / \"site license\"\n - \"procurement\" / \"purchase order\" / \"PO\"\n - \"L&D budget\" / \"training budget\"\n - \"volume discount\" / \"bulk pricing\"\n - Company domain email (not gmail/yahoo/personal)\n - Multiple seats/licenses\n\n**Non-Support Categories:**\n- voc_response: Reply to OUR automated email sequence (check-ins, surveys). Look for quoted text from our outreach.\n- fan_mail: UNSOLICITED, PURE appreciation with ZERO questions, ZERO asks, ZERO purchase/learning intent. Customer reached out entirely on their own ONLY to say thanks or praise — nothing more. This is RARE.\n- spam: Vendor/partnership pitch, affiliate programs, product promotions, beta invites from external companies, unsolicited business proposals. Someone representing a COMPANY trying to sell TO us, partner, or recruit as affiliate. Key signals: affiliate opportunity/commission language, \"thought of you for [product]\", beta program invitations, SaaS promotions.\n- system: Automated notifications, bounces, password resets. No human intent.\n- instructor_strategy: Instructor discussing business/content strategy (internal)\n- resolved: Issue was FULLY HANDLED + customer EXPLICITLY confirmed resolution\n- awaiting_customer: Our last message asked a question, waiting for reply\n- unknown: Genuinely cannot categorize\n\n## Key Distinctions (READ CAREFULLY)\n\n### support_refund vs support_billing\n- \"I want a refund\" → support_refund (wants money BACK)\n- \"Can I get an invoice?\" → support_billing (wants a DOCUMENT)\n- \"What's your refund policy?\" + unhappy context → support_refund\n- \"Do you have any discounts?\" → support_billing (pricing question)\n- Mentions \"invoice\" in passing during refund request → still support_refund (primary intent)\n\n### resolved vs active support\n- RESOLVED requires: (1) we helped, (2) customer CONFIRMS with phrases like \"that worked\", \"all set\", \"got it\"\n- Polite \"thanks\" on a NEW request is NOT resolved\n- Customer saying \"thanks in advance\" is NOT resolved\n- 3+ message thread ending with customer confirmation → probably resolved\n\n### voc_response vs fan_mail\n- Look for QUOTED TEXT (lines with \">\") from our automated emails\n- Quoted automation (\"What interests you about AI?\") → voc_response\n- NO quoted automation + genuine unsolicited praise → fan_mail\n- Survey/sequence replies are voc_response even if positive\n\n### spam vs fan_mail\n- spam: \"I'm [Name] from [Company]\", \"partnership opportunity\", trying to SELL something\n- spam: \"affiliate opportunity\", \"affiliate commission\", beta program invite from external company\n- spam: Product promotion (\"Production-ready SaaS boilerplate\"), \"thought of you for this release\"\n- fan_mail: Individual (not company), expressing ONLY pure gratitude — no asks, no questions\n- \"Big fan\" + business pitch → spam (the \"fan\" is a hook)\n- \"Big fan\" + pure personal thanks, no asks → fan_mail\n\n### fan_mail vs presales_consult (CRITICAL — fan_mail is over-classified)\nfan_mail is RARE. Default to presales_consult when appreciation is mixed with ANY of these:\n- ANY question (\"?\", \"how do I\", \"can you\", \"where do I\")\n- Interest in learning (\"I'd love to learn about X\", \"interested in AI/TypeScript/etc.\")\n- Response to instructor outreach (quoted email, \"you asked about\", \"your email\")\n- Career/learning journey sharing (\"I've been exploring\", \"I want to get into\")\n- Purchase intent (\"how do I buy\", \"how to access\", \"sign up\", \"enroll\")\n- Topic engagement (\"what interests me is...\", \"I'm excited about...\")\n\nONLY classify as fan_mail when the message is:\n1. UNSOLICITED (not replying to our outreach/survey)\n2. PURE appreciation (\"thank you\", \"you changed my life\", \"amazing work\")\n3. Contains ZERO questions or requests\n4. Contains ZERO purchase/learning intent\n5. The person wants NOTHING — they just want to say thanks\n\nExamples of what is NOT fan_mail:\n- \"Love your content! I'd love to learn more about TypeScript\" → presales_consult\n- \"Thanks for the email! I'm really interested in AI agents\" → presales_consult (responding to outreach + topic interest)\n- \"Big fan of your work. How do I get started?\" → presales_faq\n- \"Your course changed my life! Do you have anything on React?\" → presales_consult\n- Replying to Matt's \"What interests you about AI?\" email → presales_consult (response to outreach)\n\n### fan_mail vs voc_response (outreach replies)\n- If the message is replying to an automated outreach/survey email → voc_response\n- If the thread contains quoted text from our email sequences → voc_response\n- Even if the reply is positive/appreciative, it's voc_response if it's replying to us\n- fan_mail is ONLY unsolicited — the customer initiated contact purely to give thanks\n\n### presales_* vs support_billing\n- Key question: Does the person ALREADY OWN the product?\n- \"How much does it cost?\" with NO purchase evidence → presales_faq\n- \"Can I get an invoice?\" from an existing customer → support_billing\n- \"Do you have student discounts?\" BEFORE buying → presales_faq\n- \"Can I get a receipt for my purchase last month?\" → support_billing\n- Pricing questions from non-customers → presales_faq\n\n### presales_faq vs presales_consult\n- presales_faq: Factual, answerable from documentation (price, curriculum, requirements)\n- presales_consult: Subjective, needs instructor judgment (\"which course is right for me?\")\n- \"What does the course cover?\" → presales_faq (factual)\n- \"I know X, Y, Z - is this course right for me?\" → could be presales_faq if simple\n- \"Should I buy A or B given my career goals?\" → presales_consult (subjective advice)\n\n## Few-Shot Examples\n\n### Example 1: support_refund (NOT billing)\nMessage: \"Hi, I purchased Total TypeScript last month but haven't had time to use it. I'd like to request a refund please. Thanks!\"\n→ support_refund (wants money back, \"thanks\" is politeness not resolution)\n\n### Example 2: support_billing\nMessage: \"Hi, can I get an invoice for my purchase? I need it for my company's records.\"\n→ support_billing (wants document, not money back)\n\n### Example 3: resolved\nThread: [Customer: \"Can't access my course\"] → [Us: \"Reset your access\"] → [Customer: \"That worked, thanks!\"]\n→ resolved (explicit \"worked\" confirmation after our help)\n\n### Example 4: voc_response\nMessage: \"> What interests you about AI?\\n\\nI've been exploring AI tools for my workflow and really excited about the possibilities...\"\n→ voc_response (replying to our survey/check-in email)\n\n### Example 5: fan_mail (PURE appreciation, no asks)\nMessage: \"Just wanted to say your TypeScript course completely changed how I think about types. Thank you so much!\"\n→ fan_mail (unsolicited, PURE appreciation, no questions, no asks, no purchase intent)\n\n### Example 5b: presales_consult (NOT fan_mail — has topic interest)\nMessage: \"Love your work Matt! I've been getting into AI lately and would love to learn more about building agents. What interests me most is the practical side.\"\n→ presales_consult (appreciation is present BUT combined with topic interest and learning intent — this is a potential customer, not just a thank-you)\n\n### Example 5c: presales_consult (NOT fan_mail — response to outreach)\nMessage: \"> What interests you about AI?\\n\\nHey Matt, great question! I've been exploring AI tools for my dev workflow. Really excited about agentic coding and how it changes everything.\"\n→ presales_consult (replying to instructor outreach with topic interest — NOT fan_mail even though positive. Could also be voc_response if the outreach was automated.)\n\n### Example 5d: presales_consult (NOT fan_mail — appreciation + learning interest)\nMessage: \"Hi Matt, I'm a big fan of your TypeScript content. I'd love to learn about AI development. After a few AI coding projects, I'm realizing the field is changing fast.\"\n→ presales_consult (starts with appreciation but the substance is about learning interest — this person is a potential customer)\n\n### Example 5e: presales_faq (NOT fan_mail — appreciation + purchase question)\nMessage: \"Love what you're doing with Total TypeScript! How do I get access to the pro bundle? Is there a student discount?\"\n→ presales_faq (appreciation + explicit purchase question = presales, not fan mail)\n\n### Example 6: spam (partnership pitch)\nMessage: \"Hi, I'm Sarah from ContentBoost Agency. I'd love to discuss a partnership opportunity to help grow your newsletter...\"\n→ spam (company representative, commercial intent)\n\n### Example 6b: spam (affiliate outreach — NOT support_billing)\nMessage: \"Production-ready SaaS boilerplate — affiliate Opportunity. Earn 30% commission per sale...\"\n→ spam (affiliate recruitment pitch, NOT a billing question despite \"opportunity\" language)\n\n### Example 6c: spam (product promotion — NOT support_billing)\nMessage: \"MuleRun AI Agent Builder Beta Program by Alibaba. We'd love to invite you to try our new platform...\"\n→ spam (unsolicited product pitch / beta invite from external company)\n\n### Example 6d: spam (targeted product promotion)\nMessage: \"Paid collab idea: thought of you for this release. We're launching a new dev tool and would love your involvement...\"\n→ spam (cold outreach using \"thought of you\" hook — commercial intent)\n\n### Example 7: NOT resolved\nThread: [Customer: \"I need a refund because...\"] → [Us: \"Sure, processing now\"]\n→ support_refund (we're processing, but customer hasn't confirmed receipt)\n\n### Example 8: presales_faq\nMessage: \"Hi, I'm considering Total TypeScript. What's included in the Pro bundle? Do you offer PPP pricing for Brazil?\"\n→ presales_faq (factual pricing/content questions, answerable from KB)\n\n### Example 9: presales_consult\nMessage: \"I'm a mid-level dev trying to level up. Should I start with Total TypeScript or go straight to the advanced course? I already know some generics.\"\n→ presales_consult (needs instructor judgment, \"which course is right for me\")\n\n### Example 10: presales_team\nMessage: \"Hi, I'm the L&D manager at Acme Corp. We have a team of 12 developers and want to purchase licenses. Do you offer volume discounts or can we pay by invoice/PO?\"\n→ presales_team (enterprise inquiry: team size, L&D, volume discount, PO)\n\n### Example 11: presales_faq (NOT support_billing)\nMessage: \"How much does the course cost? Do you have any student discounts?\"\n→ presales_faq (asking about pricing BEFORE purchase, not an existing customer billing issue)\n\nOutput your classification with confidence (0.0-1.0) and brief reasoning.`\n\n// ============================================================================\n// Spam pre-filtering patterns (rule-based, no LLM needed)\n// ============================================================================\n\nconst SPAM_PATTERNS = [\n // Partnership/collaboration pitches (flexible matching)\n /\\bpartnership\\s*(?:opportunity|proposal|idea)?\\b/i,\n /\\bpaid\\s+partnership\\b/i,\n /\\b(?:make|create|explore)\\s+a\\s+(?:paid\\s+)?partnership\\b/i,\n /\\bcollaboration?\\s+(?:opportunity|proposal|idea)\\b/i,\n /\\bwould\\s+(?:you\\s+)?(?:be\\s+)?interested\\s+in\\s+(?:a\\s+)?(?:partnership|collaboration|working\\s+together)\\b/i,\n /\\breaching\\s+out.*?(?:partnership|opportunity|collaboration)\\b/i,\n /\\b(?:collaborate|partner)\\s+with\\s+(?:you|your)/i,\n /\\bexcited\\s+to\\s+(?:collaborate|partner|work\\s+with)\\b/i,\n\n // Affiliate/sponsorship offers\n /\\baffiliate\\s+(?:program|partnership|link|structure)\\b/i,\n /\\bsponsored?\\s+(?:content|post|video|opportunity)\\b/i,\n /\\bearn\\s+\\d+%\\s+(?:of\\s+)?(?:what|every|each|for)\\b/i,\n /\\bcreator\\s+fee\\b/i,\n /\\bcampaign\\s+budget\\b/i,\n /\\bworked\\s+with\\s+(?:a\\s+)?(?:bunch|lot|many)\\s+of\\s+creators\\b/i,\n // Additional sponsor/collab patterns\n /\\bpaid\\s+collab\\b/i, // \"paid collab\"\n /\\bsponsor\\b.{0,30}\\b(?:content|channel|video|newsletter)\\b/i, // \"sponsor...content\" with up to 30 chars between\n /\\b(?:we'?d?\\s+)?love\\s+to\\s+sponsor\\b/i, // \"we'd love to sponsor\", \"love to sponsor\"\n /\\bcollab\\s+(?:proposal|opportunity)\\b/i, // \"collab proposal\"\n\n // SEO/backlink spam\n /\\bguest\\s+post\\b/i,\n /\\bbacklink(?:s|ing)?\\b/i,\n /\\blink\\s+building\\b/i,\n /\\bseo\\s+(?:services?|optimization|agency)\\b/i,\n\n // Influencer/marketing outreach\n /\\binfluencer\\s+(?:marketing|campaign|outreach|manager)\\b/i,\n /\\b(?:vp|director)\\s+of\\s+(?:gtm|growth|marketing|partnerships)\\b/i,\n /\\bpartnerships?\\s+@\\s+\\w+/i, // \"Partnerships @ [Company]\"\n\n // Cold outreach patterns (follow-up spam)\n /\\bi'?ve?\\s+(?:been\\s+)?(?:emailed?|reached\\s+out|contacted).*?(?:few|couple|several)\\s+times\\b/i,\n /\\bthis\\s+will\\s+be\\s+my\\s+last\\s+follow[\\s-]?up\\b/i,\n /\\bwanted\\s+to\\s+(?:follow\\s+up|check\\s+in)\\s+once\\s+more\\b/i,\n\n // Product launches/promotions TO us (not FROM us)\n /\\bwe(?:'re|\\s+are)\\s+launching\\s+\\d+\\+?\\s+(?:ai\\s+)?products?\\b/i,\n /\\bkeep\\s+you\\s+on\\s+(?:the\\s+)?(?:priority|prio)\\s+list\\b/i,\n /\\bfuture\\s+campaigns?\\b/i,\n\n // Affiliate/referral outreach\n /\\baffiliate\\s+(?:opportunity|commission|income|revenue|program|partnership|link)\\b/i,\n /\\bearn\\s+(?:a\\s+)?(?:commission|income|revenue)\\s+(?:by|from|for|with|per)\\b/i,\n /\\breferral\\s+(?:commission|income|fee|payout)\\b/i,\n\n // Product/beta pitches from external companies\n /\\bbeta\\s+(?:program|invite|invitation)\\b/i,\n /\\bthought\\s+of\\s+you\\s+for\\b/i,\n /\\binvite\\s+you\\s+to\\s+(?:try|join|test|check\\s+out|explore|use)\\b/i,\n\n // Cold product promotion\n /\\bproduction[- ]ready\\s+(?:saas|app|template|boilerplate|platform|starter)/i,\n]\n\n/**\n * Fast-path spam detection.\n * Catches obvious spam patterns before LLM classification.\n *\n * Strategy: Check the FIRST INBOUND message for spam patterns.\n * This catches partnership pitches even when auto-replies have been sent.\n *\n * We're conservative: only flag as spam if the INITIAL request was spam.\n * Multi-message threads where customer engaged genuinely go to LLM.\n */\nfunction fastDetectSpam(input: ThreadClassifyInput): boolean {\n const { messages } = input\n\n // Find the first inbound message (the original customer/spam request)\n const firstInbound = messages.find((m) => m.direction === 'in')\n if (!firstInbound) return false\n\n const body = firstInbound.body\n\n // Check all spam patterns against the first inbound message\n if (SPAM_PATTERNS.some((p) => p.test(body))) {\n // Additional check: if thread has genuine back-and-forth engagement\n // (customer replied with substance after our response), be conservative\n const inboundCount = messages.filter((m) => m.direction === 'in').length\n const outboundCount = messages.filter((m) => m.direction === 'out').length\n\n // If there's real engagement (customer replied multiple times substantively)\n // and we've been responding, let LLM decide\n if (inboundCount >= 3 && outboundCount >= 2) {\n return false\n }\n\n return true\n }\n\n return false\n}\n\nexport interface FastClassifyThreadResult extends ThreadClassifyOutput {\n /** Internal tracking of which fast-path rule matched (for logging only) */\n _fastPathRule?: string\n}\n\n/**\n * Thread-aware fast-path classification.\n *\n * Classification hierarchy:\n * 1. System messages (automated/no-reply senders)\n * 2. Spam (obvious partnership/affiliate/SEO patterns)\n * 3. Internal thread (instructor strategy)\n * 4. Resolved (customer confirmed resolution)\n * 5. Awaiting customer (human teammate asked question, waiting for reply)\n * 6. LLM classification (support_*, fan_mail, voc_response, nuanced spam)\n */\nexport function fastClassifyThread(\n input: ThreadClassifyInput,\n signals: ThreadSignals\n): FastClassifyThreadResult | null {\n // 1. System messages - automated/no-reply senders (highest priority)\n if (signals.isAutomated && signals.threadLength === 1) {\n return {\n category: 'system',\n confidence: 0.95,\n signals,\n reasoning: 'Automated system message',\n _fastPathRule: 'system_automated',\n }\n }\n\n // 1.5. AD tag - Front tag indicating spam/ads (no LLM needed)\n if (input.tags?.includes('AD')) {\n return {\n category: 'spam',\n confidence: 0.95,\n signals,\n reasoning: 'Tagged as AD in Front',\n _fastPathRule: 'ad_tag',\n }\n }\n\n // 2. Spam - obvious patterns (before wasting LLM on it)\n // Only for initial messages without meaningful engagement\n if (fastDetectSpam(input)) {\n return {\n category: 'spam',\n confidence: 0.9,\n signals,\n reasoning: 'Detected spam patterns (partnership/affiliate/SEO outreach)',\n _fastPathRule: 'spam_patterns',\n }\n }\n\n // Also catch vendor outreach from signals (legacy pattern detection)\n if (signals.isVendorOutreach && signals.threadLength <= 2) {\n return {\n category: 'spam',\n confidence: 0.85,\n signals,\n reasoning: 'Detected vendor/marketing outreach patterns',\n _fastPathRule: 'vendor_outreach',\n }\n }\n\n // 3. Internal thread (instructor strategy) - based on author, not content\n if (signals.isInternalThread || signals.instructorIsAuthor) {\n return {\n category: 'instructor_strategy',\n confidence: 0.9,\n signals,\n reasoning: 'Thread is internal/instructor-initiated',\n _fastPathRule: 'internal_thread',\n }\n }\n\n // 4. Resolved thread - based on thread structure + explicit resolution phrases\n if (isThreadResolved(signals)) {\n return {\n category: 'resolved',\n confidence: 0.85,\n signals,\n reasoning: 'Customer indicated resolution (thanks + confirmation)',\n _fastPathRule: 'resolved_thread',\n }\n }\n\n // 5. Thread state: awaiting_customer\n // This is state-based, not content-based - if last message was outbound, we're waiting for customer\n const lastMessage = input.messages[input.messages.length - 1]\n if (lastMessage?.direction === 'out') {\n return {\n category: 'awaiting_customer',\n confidence: 0.9,\n signals,\n reasoning: 'Last message was outbound, waiting for customer response',\n _fastPathRule: 'awaiting_customer',\n }\n }\n\n // 6. Everything else goes to LLM for nuanced classification\n // This includes: support_*, fan_mail, spam (ambiguous), voc_response\n return null\n}\n\n/**\n * Thread-aware LLM classification with memory integration.\n */\nexport async function llmClassifyThread(\n input: ThreadClassifyInput,\n signals: ThreadSignals,\n model: string = 'anthropic/claude-haiku-4-5',\n options: { runId?: string } = {}\n): Promise<ThreadClassifyOutput & { citedMemoryIds?: string[] }> {\n // Build thread context for prompt\n const threadContext = `\nThread with ${signals.threadLength} messages over ${signals.threadDurationHours.toFixed(1)} hours.\nPattern: ${signals.threadPattern}\nCustomer messages: ${signals.customerMessageCount}\nTeammate engaged: ${signals.hasTeammateMessage ? 'Yes' : 'No'}\nInstructor involved: ${signals.hasInstructorMessage ? 'Yes' : 'No'}\nLast responder: ${signals.lastResponderType}\n`.trim()\n\n // Build message history\n const messageHistory = input.messages\n .map((m, i) => {\n const author =\n m.author?.type || (m.direction === 'in' ? 'customer' : 'agent')\n return `[${i + 1}] ${author.toUpperCase()}: ${m.body.slice(0, 500)}${m.body.length > 500 ? '...' : ''}`\n })\n .join('\\n\\n')\n\n // Query memories for similar past thread classifications\n let memoryContext = ''\n let citedMemoryIds: string[] = []\n\n try {\n // Use the trigger message for semantic search\n const situation = `Subject: ${input.triggerMessage.subject || ''}\\n\\n${input.triggerMessage.body}`\n\n const memories = await queryMemoriesForStage({\n appId: input.appId,\n stage: 'classify',\n situation,\n limit: 5,\n threshold: 0.6,\n })\n\n if (memories.length > 0) {\n memoryContext = formatMemoriesCompact(memories)\n citedMemoryIds = memories.map((m) => m.id)\n\n await log('debug', 'classifyThread memory query results', {\n workflow: 'pipeline',\n step: 'classifyThread',\n appId: input.appId,\n memoriesFound: memories.length,\n topScore: memories[0]?.score ?? 0,\n memoryIds: citedMemoryIds,\n })\n\n // Record citation if we have a run ID\n if (options.runId && citedMemoryIds.length > 0) {\n await citeMemories(citedMemoryIds, options.runId, input.appId)\n }\n }\n } catch (error) {\n // Log but don't fail classification if memory query fails\n await log('warn', 'classifyThread memory query failed', {\n workflow: 'pipeline',\n step: 'classifyThread',\n appId: input.appId,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n // Build prompt with memory context if available\n const systemPrompt = memoryContext\n ? `${memoryContext}\\n\\n${THREAD_CLASSIFY_PROMPT}`\n : THREAD_CLASSIFY_PROMPT\n\n const prompt = `${threadContext}\\n\\n---\\n\\nMessages:\\n${messageHistory}`\n\n const { object } = await generateObject({\n model,\n schema: threadClassifySchema,\n system: systemPrompt,\n prompt,\n })\n\n return {\n category: object.category as MessageCategory,\n confidence: object.confidence,\n signals,\n reasoning: object.reasoning,\n citedMemoryIds: citedMemoryIds.length > 0 ? citedMemoryIds : undefined,\n }\n}\n\n/**\n * Main thread classification function.\n * Uses fast-path when possible, falls back to LLM with memory integration.\n */\nexport async function classifyThread(\n input: ThreadClassifyInput,\n options: ClassifyOptions = {}\n): Promise<ThreadClassifyOutput & { citedMemoryIds?: string[] }> {\n const {\n model = 'anthropic/claude-haiku-4-5',\n forceLLM = false,\n runId,\n } = options\n\n const startTime = Date.now()\n\n await log('debug', 'classifyThread started', {\n workflow: 'pipeline',\n step: 'classifyThread',\n appId: input.appId,\n threadLength: input.messages.length,\n forceLLM,\n })\n\n // Compute thread signals\n const signals = computeThreadSignals(input)\n\n // Try fast-path\n if (!forceLLM) {\n const fastResult = fastClassifyThread(input, signals)\n if (fastResult) {\n const durationMs = Date.now() - startTime\n\n // High-cardinality decision-point logging\n await log('info', 'classifyThread:decision', {\n workflow: 'pipeline',\n step: 'classifyThread',\n appId: input.appId,\n // Decision outcome\n category: fastResult.category,\n confidence: fastResult.confidence,\n reasoning: fastResult.reasoning,\n // Decision path\n decisionPath: 'fast-path',\n fastPathRule: fastResult._fastPathRule,\n // Thread context\n threadLength: signals.threadLength,\n threadDurationHours: signals.threadDurationHours,\n threadPattern: signals.threadPattern,\n customerMessageCount: signals.customerMessageCount,\n // Thread signals that drove the decision\n hasTeammateMessage: signals.hasTeammateMessage,\n hasInstructorMessage: signals.hasInstructorMessage,\n instructorIsAuthor: signals.instructorIsAuthor,\n isInternalThread: signals.isInternalThread,\n awaitingCustomerReply: signals.awaitingCustomerReply,\n hasResolutionPhrase: signals.hasResolutionPhrase,\n lastResponderType: signals.lastResponderType,\n // Input signals\n hasAngrySentiment: signals.hasAngrySentiment,\n hasLegalThreat: signals.hasLegalThreat,\n isAutomated: signals.isAutomated,\n isVendorOutreach: signals.isVendorOutreach,\n isPersonalToInstructor: signals.isPersonalToInstructor,\n // Context\n hasTags: !!input.tags?.length,\n tagCount: input.tags?.length ?? 0,\n usedLLM: false,\n durationMs,\n })\n\n // Return without internal tracking property\n const { _fastPathRule: _, ...threadResult } = fastResult\n return threadResult\n }\n }\n\n // Fall back to LLM with memory integration\n const result = await llmClassifyThread(input, signals, model, { runId })\n\n const durationMs = Date.now() - startTime\n\n // High-cardinality decision-point logging\n await log('info', 'classifyThread:decision', {\n workflow: 'pipeline',\n step: 'classifyThread',\n appId: input.appId,\n // Decision outcome\n category: result.category,\n confidence: result.confidence,\n reasoning: result.reasoning,\n // Decision path\n decisionPath: 'llm',\n fastPathRule: null,\n llmReason: forceLLM ? 'forceLLM=true' : 'no_fast_path_match',\n // Thread context\n threadLength: signals.threadLength,\n threadDurationHours: signals.threadDurationHours,\n threadPattern: signals.threadPattern,\n customerMessageCount: signals.customerMessageCount,\n // Thread signals that drove the decision\n hasTeammateMessage: signals.hasTeammateMessage,\n hasInstructorMessage: signals.hasInstructorMessage,\n instructorIsAuthor: signals.instructorIsAuthor,\n isInternalThread: signals.isInternalThread,\n awaitingCustomerReply: signals.awaitingCustomerReply,\n hasResolutionPhrase: signals.hasResolutionPhrase,\n lastResponderType: signals.lastResponderType,\n // Input signals\n hasAngrySentiment: signals.hasAngrySentiment,\n hasLegalThreat: signals.hasLegalThreat,\n isAutomated: signals.isAutomated,\n isVendorOutreach: signals.isVendorOutreach,\n isPersonalToInstructor: signals.isPersonalToInstructor,\n // Memory context\n usedLLM: true,\n model,\n memoriesCited: result.citedMemoryIds?.length ?? 0,\n citedMemoryIds: result.citedMemoryIds,\n durationMs,\n })\n\n return result\n}\n\n// ============================================================================\n// Misclassification Learning\n// ============================================================================\n\nexport interface RecordMisclassificationInput {\n /** App identifier */\n appId: string\n /** Original message subject */\n subject: string\n /** Original message body */\n body: string\n /** What the agent classified it as */\n originalCategory: MessageCategory\n /** What the human corrected it to */\n correctedCategory: MessageCategory\n /** Conversation ID for tracking */\n conversationId: string\n /** Optional: The run ID that produced the misclassification */\n runId?: string\n /** Optional: Memory IDs that were cited in the original classification */\n citedMemoryIds?: string[]\n}\n\n/**\n * Record a classification correction to memory.\n *\n * Call this when a human corrects an agent's classification.\n * Stores the mistake so future similar tickets can learn from it.\n *\n * @example\n * ```typescript\n * await recordMisclassification({\n * appId: 'total-typescript',\n * subject: 'Invoice needed',\n * body: 'I need an invoice for my company...',\n * originalCategory: 'support_access',\n * correctedCategory: 'support_billing',\n * conversationId: 'cnv_abc123'\n * })\n * ```\n */\nexport async function recordMisclassification(\n input: RecordMisclassificationInput\n): Promise<void> {\n const {\n appId,\n subject,\n body,\n originalCategory,\n correctedCategory,\n conversationId,\n runId,\n citedMemoryIds,\n } = input\n\n const situation = `Subject: ${subject}\\n\\n${body}`\n\n // Store the misclassification as a memory for future learning\n await SupportMemoryService.store({\n app_slug: appId,\n situation,\n decision: `Classified as: ${originalCategory}`,\n stage: 'classify',\n outcome: 'corrected',\n correction: `Should have been: ${correctedCategory}`,\n category: correctedCategory,\n conversation_id: conversationId,\n tags: ['misclassification', originalCategory, correctedCategory],\n })\n\n // If we know which memories were cited in the failed classification,\n // record the failure outcome for those memories\n if (runId && citedMemoryIds && citedMemoryIds.length > 0) {\n try {\n await SupportMemoryService.recordCitationOutcome(\n citedMemoryIds,\n runId,\n 'failure',\n appId\n )\n } catch (error) {\n console.warn(\n '[recordMisclassification] Failed to record citation outcome:',\n error\n )\n }\n }\n}\n","/**\n * Thread Signal Computation\n *\n * Computes thread-level signals from a list of messages.\n * Used by the classifier to understand thread context.\n */\n\nimport type {\n MessageAuthorType,\n MessageSignals,\n ThreadClassifyInput,\n ThreadMessage,\n ThreadSignals,\n} from '../types'\n\n// ============================================================================\n// Resolution patterns\n// ============================================================================\n\nconst THANK_YOU_PATTERNS = [\n /\\bthank(s| you)\\b/i,\n /\\bappreciate\\b/i,\n /\\bcheers\\b/i,\n /\\bperfect\\b/i,\n /\\bawesome\\b/i,\n /\\bgreat\\b/i,\n]\n\nconst RESOLUTION_PATTERNS = [\n /\\b(that |it |this )?(work(s|ed)|fixed|solved)\\b(?!@)/i, // \"that worked\" not \"work@email\"\n /\\ball (good|set|sorted|done)\\b/i,\n /\\bgot it(,? thanks)?\\b/i,\n /\\bmakes sense\\b/i,\n /\\bno (more |further )?(questions?|issues?|problems?)\\b/i,\n /\\bsuccessfully\\b/i,\n /\\bproblem('s| is)? solved\\b/i,\n /\\bissue('s| is)? (fixed|resolved)\\b/i,\n /\\bi('m| am) (all )?set\\b/i,\n /\\bthis (helps|resolved|fixed)\\b/i,\n /\\bexactly what i needed\\b/i,\n]\n\n// ============================================================================\n// Single-message signal patterns (from v2)\n// ============================================================================\n\nconst EMAIL_IN_BODY_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/\nconst PURCHASE_DATE_PATTERN =\n /\\b(bought|purchased|ordered|paid)\\b.*\\b(on|in|at|last|this)\\b/i\nconst ERROR_MESSAGE_PATTERN = /\\b(error|exception|failed|crash|bug|broken)\\b/i\nconst INSTRUCTOR_MENTION_PATTERN =\n /\\b(matt|pocock|kent|dodds|josh|comeau|instructor|creator|author)\\b/i\nconst ANGRY_SENTIMENT_PATTERN =\n /\\b(angry|frustrated|annoyed|furious|terrible|awful|worst|hate|scam|rip.?off|unacceptable|ridiculous)\\b/i\nconst AUTOMATED_PATTERN =\n /\\b(auto.?reply|out of office|automatic|do not reply|noreply|mailer.?daemon|undeliverable)\\b/i\nconst VENDOR_OUTREACH_PATTERN =\n /\\b(partnership|sponsor(?:ing)?|collaborate|backlink|seo|guest post|link building|promotional|advertise|creator fee|campaign budget|influencer campaign|vp of (?:gtm|growth|partnerships))\\b/i\n\n// Legal threat patterns - escalate_urgent ONLY\nconst LEGAL_THREAT_PATTERN =\n /\\b(?:(?:my\\s+)?lawyer|legal\\s+action|sue\\s+(?:you|your)|suing|lawsuit|attorney|court\\b.*?(?:action|case|filing)|legal\\s+(?:team|counsel)|report(?:ing)?\\s+(?:to|this\\s+to)\\s+(?:the\\s+)?(?:ftc|bbb|attorney\\s+general))\\b/i\n\n// Outside policy timeframe patterns\nconst OUTSIDE_POLICY_PATTERN =\n /\\b(?:(?:6|7|8|9|\\d{2,})\\s+weeks?\\s+ago|(?:45|5\\d|6\\d|7\\d|8\\d|9\\d|\\d{3,})\\s+days?\\s+ago|(?:2|3|4|5|6|7|8|9|1\\d)\\s+months?\\s+ago|over\\s+(?:a\\s+)?month\\s+ago|more\\s+than\\s+(?:a\\s+)?month)\\b/i\n\n// Personal/casual message patterns\nconst PERSONAL_MESSAGE_PATTERN =\n /\\b(?:lol|haha|crazy|wild|insane|big\\s+fan|huge\\s+fan)\\b/i\n\n// ============================================================================\n// Signal computation\n// ============================================================================\n\n/**\n * Compute single-message signals (same as v2, for backwards compat)\n */\nexport function computeMessageSignals(\n body: string,\n subject: string = ''\n): MessageSignals {\n const text = `${subject} ${body}`\n\n return {\n hasEmailInBody: EMAIL_IN_BODY_PATTERN.test(body),\n hasPurchaseDate: PURCHASE_DATE_PATTERN.test(text),\n hasErrorMessage: ERROR_MESSAGE_PATTERN.test(text),\n isReply: subject.toLowerCase().startsWith('re:'),\n mentionsInstructor: INSTRUCTOR_MENTION_PATTERN.test(text),\n hasAngrySentiment: ANGRY_SENTIMENT_PATTERN.test(text),\n isAutomated: AUTOMATED_PATTERN.test(text),\n isVendorOutreach: VENDOR_OUTREACH_PATTERN.test(text),\n // Escalation signals\n hasLegalThreat: LEGAL_THREAT_PATTERN.test(text),\n hasOutsidePolicyTimeframe: OUTSIDE_POLICY_PATTERN.test(text),\n isPersonalToInstructor:\n PERSONAL_MESSAGE_PATTERN.test(text) ||\n (INSTRUCTOR_MENTION_PATTERN.test(text) &&\n /(?:thank|love|amazing|big fan)/i.test(text)),\n // Presales signals (set by LLM classifier, defaulted here)\n isPresalesFaq: false,\n isPresalesTeam: false,\n }\n}\n\n/**\n * Compute thread-level signals from a full conversation thread.\n */\nexport function computeThreadSignals(\n input: ThreadClassifyInput\n): ThreadSignals {\n const { messages, triggerMessage, instructorTeammateId } = input\n\n if (messages.length === 0) {\n throw new Error('Thread must have at least one message')\n }\n\n // Sort messages by timestamp (should already be sorted, but ensure)\n const sorted = [...messages].sort((a, b) => a.timestamp - b.timestamp)\n const firstMsg = sorted[0]!\n const lastMsg = sorted[sorted.length - 1]!\n\n // Compute base message signals from trigger message\n const baseSignals = computeMessageSignals(\n triggerMessage.body,\n triggerMessage.subject\n )\n\n // Also check signals across entire thread\n const allText = sorted.map((m) => `${m.subject || ''} ${m.body}`).join(' ')\n const threadWideSignals = computeMessageSignals(allText)\n\n // Thread structure\n const threadDurationMs = lastMsg.timestamp - firstMsg.timestamp\n const threadDurationHours = threadDurationMs / (1000 * 60 * 60)\n\n // Count by author type\n let customerMessageCount = 0\n let teammateMessageCount = 0\n let agentMessageCount = 0\n let hasInstructorMessage = false\n let instructorIsAuthor = false\n\n for (const msg of sorted) {\n const authorType =\n msg.author?.type || (msg.direction === 'in' ? 'customer' : 'agent')\n\n switch (authorType) {\n case 'customer':\n customerMessageCount++\n break\n case 'teammate':\n teammateMessageCount++\n break\n case 'instructor':\n teammateMessageCount++ // Instructors count as teammates for counts\n hasInstructorMessage = true\n if (msg === firstMsg) instructorIsAuthor = true\n break\n case 'agent':\n agentMessageCount++\n break\n }\n\n // Also check by teammate ID (only if both are defined)\n if (\n instructorTeammateId &&\n msg.author?.teammateId &&\n msg.author.teammateId === instructorTeammateId\n ) {\n hasInstructorMessage = true\n if (msg === firstMsg) instructorIsAuthor = true\n }\n }\n\n // Build thread pattern (e.g., \"in-out-in-out\")\n const threadPattern = sorted.map((m) => m.direction).join('-')\n\n // Resolution signals - check customer messages only\n const customerMessages = sorted.filter(\n (m) => m.direction === 'in' && (m.author?.type === 'customer' || !m.author)\n )\n const lastCustomerMsg = customerMessages[customerMessages.length - 1]\n\n const hasThankYou = lastCustomerMsg\n ? THANK_YOU_PATTERNS.some((p) => p.test(lastCustomerMsg.body))\n : false\n\n const hasResolutionPhrase = lastCustomerMsg\n ? RESOLUTION_PATTERNS.some((p) => p.test(lastCustomerMsg.body))\n : false\n\n // Awaiting customer reply: HUMAN teammate sent the last message\n // NOT just any outbound (auto-replies don't count)\n // This means we asked the customer a question and are waiting for their answer\n //\n // Requirements:\n // 1. Last message is outbound\n // 2. A human teammate has participated (not just agent auto-replies)\n // 3. Thread has back-and-forth (customer initiated)\n const hasHumanOutbound = teammateMessageCount > 0\n const awaitingCustomerReply =\n lastMsg.direction === 'out' && hasHumanOutbound && customerMessageCount > 0\n\n // Teammate signals\n const teammateMessages = sorted.filter(\n (m) => m.author?.type === 'teammate' || m.author?.type === 'instructor'\n )\n const hasTeammateMessage = teammateMessages.length > 0\n\n // Recent teammate response: did a teammate respond after the last customer message?\n const lastCustomerTimestamp = lastCustomerMsg?.timestamp || 0\n const hasRecentTeammateResponse = teammateMessages.some(\n (m) => m.timestamp > lastCustomerTimestamp\n )\n\n // Internal thread: no customer messages\n const isInternalThread = customerMessageCount === 0\n\n // Last responder type\n const lastResponderType: MessageAuthorType =\n lastMsg.author?.type || (lastMsg.direction === 'in' ? 'customer' : 'agent')\n\n return {\n // Base signals (merged - thread-wide takes precedence for detection)\n hasEmailInBody:\n baseSignals.hasEmailInBody || threadWideSignals.hasEmailInBody,\n hasPurchaseDate:\n baseSignals.hasPurchaseDate || threadWideSignals.hasPurchaseDate,\n hasErrorMessage:\n baseSignals.hasErrorMessage || threadWideSignals.hasErrorMessage,\n isReply: sorted.length > 1 || baseSignals.isReply,\n mentionsInstructor:\n baseSignals.mentionsInstructor || threadWideSignals.mentionsInstructor,\n hasAngrySentiment:\n baseSignals.hasAngrySentiment || threadWideSignals.hasAngrySentiment,\n isAutomated: baseSignals.isAutomated,\n isVendorOutreach: baseSignals.isVendorOutreach && sorted.length === 1, // Only single-message spam\n // Escalation signals (merged - thread-wide takes precedence)\n hasLegalThreat:\n baseSignals.hasLegalThreat || threadWideSignals.hasLegalThreat,\n hasOutsidePolicyTimeframe:\n baseSignals.hasOutsidePolicyTimeframe ||\n threadWideSignals.hasOutsidePolicyTimeframe,\n isPersonalToInstructor:\n baseSignals.isPersonalToInstructor ||\n threadWideSignals.isPersonalToInstructor,\n // Presales signals (merged from base)\n isPresalesFaq: baseSignals.isPresalesFaq || threadWideSignals.isPresalesFaq,\n isPresalesTeam:\n baseSignals.isPresalesTeam || threadWideSignals.isPresalesTeam,\n\n // Thread structure\n threadLength: sorted.length,\n threadDurationHours,\n customerMessageCount,\n teammateMessageCount,\n agentMessageCount,\n lastMessageDirection: lastMsg.direction,\n threadPattern,\n\n // Resolution signals\n hasThankYou,\n hasResolutionPhrase,\n awaitingCustomerReply,\n\n // Teammate/author signals\n hasTeammateMessage,\n hasRecentTeammateResponse,\n hasInstructorMessage,\n instructorIsAuthor,\n isInternalThread,\n lastResponderType,\n }\n}\n\n/**\n * Check if thread appears resolved based on signals.\n *\n * Resolution requires:\n * 1. We already responded (there's an outbound message)\n * 2. Customer EXPLICITLY confirms resolution (not just \"thanks\")\n * 3. The resolution phrase indicates the ISSUE is solved, not just politeness\n *\n * IMPORTANT: A polite \"thanks\" on a new request is NOT resolution.\n * We need explicit phrases like \"that worked\", \"all set\", \"got it thanks\".\n */\nexport function isThreadResolved(signals: ThreadSignals): boolean {\n // Must have had a back-and-forth (we responded at some point)\n const weResponded =\n signals.agentMessageCount > 0 || signals.teammateMessageCount > 0\n if (!weResponded) {\n return false\n }\n\n // Thread must have at least 2 messages (request + response at minimum)\n // Actually, should be at least 3: request -> our response -> their confirmation\n if (signals.threadLength < 2) {\n return false\n }\n\n // Last message must be from customer (they're confirming resolution)\n if (signals.lastMessageDirection !== 'in') {\n return false\n }\n\n // STRICT: Require explicit resolution phrase\n // \"Thanks\" alone is NOT enough - they could be thanking us in advance for a request\n // Resolution phrase indicates the issue was actually handled\n if (signals.hasResolutionPhrase) {\n return true\n }\n\n return false\n}\n\n/**\n * Check if we should support a teammate instead of responding.\n */\nexport function shouldSupportTeammate(signals: ThreadSignals): boolean {\n // Teammate engaged AND customer replied after AND last message is from customer\n return (\n signals.hasTeammateMessage &&\n signals.lastResponderType === 'customer' &&\n !signals.awaitingCustomerReply\n )\n}\n","/**\n * Step 5b: COMMENT\n *\n * Adds a support comment to a conversation when a teammate is handling.\n * Used for the support_teammate action - provides research context without\n * drafting a customer-facing response.\n *\n * Also includes formatters for:\n * - Escalation comments (full context for human handoff)\n * - Approval comments (draft review with confidence)\n * - Audit comments (lightweight action trail)\n */\n\nimport { createInstrumentedFrontClient } from '../../front/instrumented-client'\nimport type { CommentInput, CommentOutput, GatherOutput } from '../types'\n\n// ============================================================================\n// Escalation & Handoff Types\n// ============================================================================\n\nexport type EscalationType =\n | 'urgent'\n | 'normal'\n | 'instructor'\n | 'teammate_support'\n | 'voc'\n\nexport interface CustomerInfo {\n email: string\n name?: string\n id?: string\n}\n\nexport interface PurchaseInfo {\n productName: string\n productId?: string\n purchasedAt: string\n status: string\n amount?: number\n}\n\nexport interface EscalationLinks {\n /** Admin profile URL */\n admin?: string\n /** Magic login link */\n magicLogin?: string\n /** Front conversation link */\n frontConversation?: string\n}\n\nexport interface EscalationContext {\n /** Escalation priority/type */\n type: EscalationType\n /** Why this was escalated */\n reason: string\n /** Customer information */\n customer: CustomerInfo\n /** Customer purchases */\n purchases?: PurchaseInfo[]\n /** Classification details */\n classification?: {\n category: string\n confidence: number\n reasoning?: string\n }\n /** What the agent found/tried */\n agentFindings?: string[]\n /** Quick links for the human agent */\n links?: EscalationLinks\n}\n\nexport interface ApprovalContext {\n /** Draft being reviewed */\n draft: string\n /** Why it needs review */\n reviewReason: string\n /** Agent confidence score (0-1) */\n confidence: number\n /** Classification category */\n category?: string\n /** Customer email for context */\n customerEmail?: string\n /** Optional action links (approve/edit) */\n actionLinks?: {\n approve?: string\n edit?: string\n }\n}\n\nexport interface AuditContext {\n /** Action that was taken */\n action: 'auto_sent' | 'draft_created' | 'silenced' | 'escalated' | string\n /** Category of the message */\n category: string\n /** Confidence score (0-1) */\n confidence: number\n /** Timestamp of the action */\n timestamp?: Date\n /** Optional message ID for reference */\n messageId?: string\n}\n\n// ============================================================================\n// Comment formatting\n// ============================================================================\n\n/**\n * Format gathered context into a useful support comment.\n */\nexport function formatSupportComment(context: GatherOutput): string {\n const parts: string[] = ['🤖 **Agent Research Context**\\n']\n\n // Customer info\n if (context.user) {\n parts.push(`**Customer:** ${context.user.email}`)\n if (context.user.name) {\n parts.push(`**Name:** ${context.user.name}`)\n }\n }\n\n // Purchases\n if (context.purchases.length > 0) {\n parts.push('\\n**Purchases:**')\n for (const p of context.purchases) {\n const status = p.status !== 'active' ? ` (${p.status})` : ''\n const amount = p.amount ? ` - $${(p.amount / 100).toFixed(2)}` : ''\n parts.push(`- ${p.productName}${amount}${status}`)\n }\n } else if (context.user) {\n parts.push('\\n**Purchases:** None found for this email')\n }\n\n // Knowledge base hits\n if (context.knowledge.length > 0) {\n parts.push('\\n**Relevant KB:**')\n for (const k of context.knowledge.slice(0, 3)) {\n const source = k.source ? ` [${k.source}]` : ''\n parts.push(`- ${k.content.slice(0, 100)}...${source}`)\n }\n }\n\n // Similar resolved tickets\n const similarTickets = context.knowledge.filter(\n (k) => k.type === 'similar_ticket'\n )\n if (similarTickets.length > 0) {\n parts.push('\\n**Similar tickets:**')\n for (const t of similarTickets.slice(0, 2)) {\n parts.push(`- ${t.content.slice(0, 150)}...`)\n }\n }\n\n // Prior memory\n if (context.priorMemory.length > 0) {\n parts.push('\\n**Agent memory:**')\n for (const m of context.priorMemory.slice(0, 2)) {\n parts.push(`- ${m.content.slice(0, 100)}...`)\n }\n }\n\n // Gather errors (internal note)\n if (context.gatherErrors.length > 0) {\n parts.push('\\n⚠️ _Some data unavailable:_')\n for (const e of context.gatherErrors) {\n parts.push(`- ${e.step}: ${e.error}`)\n }\n }\n\n return parts.join('\\n')\n}\n\n/**\n * Format a minimal comment when we have limited context.\n */\nexport function formatMinimalComment(context: GatherOutput): string {\n const parts: string[] = ['🤖 **Agent Context**\\n']\n\n if (context.user) {\n parts.push(`Customer: ${context.user.email}`)\n parts.push(\n `Purchases: ${context.purchases.length > 0 ? context.purchases.map((p) => p.productName).join(', ') : 'None found'}`\n )\n } else {\n parts.push('_Could not look up customer info_')\n }\n\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Escalation & Handoff Formatters\n// ============================================================================\n\n/**\n * Get emoji for escalation type.\n */\nfunction getEscalationEmoji(type: EscalationType): string {\n switch (type) {\n case 'urgent':\n return '🚨'\n case 'instructor':\n return '👨🏫'\n case 'teammate_support':\n return '🤝'\n case 'voc':\n return '📣'\n default:\n return '⚠️'\n }\n}\n\n/**\n * Format escalation type for display.\n */\nfunction formatEscalationType(type: EscalationType): string {\n switch (type) {\n case 'urgent':\n return 'URGENT'\n case 'instructor':\n return 'Instructor'\n case 'teammate_support':\n return 'Teammate Support'\n case 'voc':\n return 'Voice of Customer'\n default:\n return 'Escalated'\n }\n}\n\n/**\n * Format a purchase for display with optional admin link.\n */\nfunction formatPurchase(purchase: PurchaseInfo, adminBaseUrl?: string): string {\n const status = purchase.status !== 'active' ? ` (${purchase.status})` : ''\n const amount = purchase.amount\n ? ` - $${(purchase.amount / 100).toFixed(2)}`\n : ''\n\n // Add admin link if available\n if (adminBaseUrl && purchase.productId) {\n const adminLink = `${adminBaseUrl}/purchases/${purchase.productId}`\n return `- [${purchase.productName}](${adminLink})${amount}${status}`\n }\n\n return `- ${purchase.productName}${amount}${status}`\n}\n\n/**\n * Format an escalation comment for Front conversation.\n * Provides full context for human agents handling escalations.\n *\n * @example\n * ```ts\n * const comment = formatEscalationComment({\n * type: 'urgent',\n * reason: 'Legal threat detected',\n * customer: { email: '[EMAIL]', name: 'John Doe' },\n * purchases: [{ productName: 'Course', purchasedAt: '2024-01-01', status: 'active' }],\n * links: { admin: 'https://admin.example.com/users/123', magicLogin: 'https://...' }\n * })\n * ```\n */\nexport function formatEscalationComment(context: EscalationContext): string {\n const parts: string[] = []\n\n // Header with type + emoji\n const emoji = getEscalationEmoji(context.type)\n const typeLabel = formatEscalationType(context.type)\n parts.push(`${emoji} **Agent Escalation (${typeLabel})**\\n`)\n\n // Escalation reason\n parts.push(`**Reason:** ${context.reason}`)\n\n // Classification details if available\n if (context.classification) {\n parts.push(`**Category:** ${context.classification.category}`)\n parts.push(\n `**Confidence:** ${Math.round(context.classification.confidence * 100)}%`\n )\n if (context.classification.reasoning) {\n parts.push(`**Agent reasoning:** ${context.classification.reasoning}`)\n }\n }\n\n // Customer info section\n parts.push('\\n---')\n parts.push('**Customer Info**')\n parts.push(`- **Email:** ${context.customer.email}`)\n if (context.customer.name) {\n parts.push(`- **Name:** ${context.customer.name}`)\n }\n if (context.customer.id) {\n parts.push(`- **ID:** ${context.customer.id}`)\n }\n\n // Purchases section\n parts.push('\\n**Purchases:**')\n if (context.purchases && context.purchases.length > 0) {\n const adminBaseUrl = context.links?.admin?.replace(/\\/users\\/.*$/, '')\n for (const p of context.purchases) {\n parts.push(formatPurchase(p, adminBaseUrl))\n }\n } else {\n parts.push('_No purchases found for this email_')\n }\n\n // Agent findings (what was tried/found)\n if (context.agentFindings && context.agentFindings.length > 0) {\n parts.push('\\n**What Agent Found/Tried:**')\n for (const finding of context.agentFindings) {\n parts.push(`- ${finding}`)\n }\n }\n\n // Quick links section\n if (context.links) {\n const hasLinks =\n context.links.admin ||\n context.links.magicLogin ||\n context.links.frontConversation\n if (hasLinks) {\n parts.push('\\n---')\n parts.push('**Quick Links**')\n if (context.links.admin) {\n parts.push(`- [Admin Profile](${context.links.admin})`)\n }\n if (context.links.magicLogin) {\n parts.push(`- [Magic Login](${context.links.magicLogin})`)\n }\n if (context.links.frontConversation) {\n parts.push(`- [Front Conversation](${context.links.frontConversation})`)\n }\n }\n }\n\n return parts.join('\\n')\n}\n\n/**\n * Format an approval comment for draft review.\n * Shows the draft preview with confidence and review context.\n *\n * @example\n * ```ts\n * const comment = formatApprovalComment({\n * draft: 'Hi! Here is your magic link...',\n * reviewReason: 'Low confidence response',\n * confidence: 0.65,\n * category: 'support_access',\n * customerEmail: '[EMAIL]'\n * })\n * ```\n */\nexport function formatApprovalComment(context: ApprovalContext): string {\n const parts: string[] = []\n\n // Header\n parts.push('🔍 **Draft Pending Review**\\n')\n\n // Why it needs review\n parts.push(`**Review Reason:** ${context.reviewReason}`)\n\n // Confidence with visual indicator\n const confidencePercent = Math.round(context.confidence * 100)\n const confidenceEmoji =\n confidencePercent >= 80 ? '🟢' : confidencePercent >= 60 ? '🟡' : '🔴'\n parts.push(`**Confidence:** ${confidenceEmoji} ${confidencePercent}%`)\n\n // Category if available\n if (context.category) {\n parts.push(`**Category:** ${context.category}`)\n }\n\n // Customer email for quick context\n if (context.customerEmail) {\n parts.push(`**Customer:** ${context.customerEmail}`)\n }\n\n // Draft preview\n parts.push('\\n---')\n parts.push('**Draft Preview:**')\n parts.push('')\n // Indent the draft for visual separation\n const draftLines = context.draft.split('\\n')\n for (const line of draftLines) {\n parts.push(`> ${line}`)\n }\n\n // Action links if available\n if (context.actionLinks) {\n parts.push('\\n---')\n parts.push('**Actions:**')\n if (context.actionLinks.approve) {\n parts.push(`- [✅ Approve & Send](${context.actionLinks.approve})`)\n }\n if (context.actionLinks.edit) {\n parts.push(`- [✏️ Edit Draft](${context.actionLinks.edit})`)\n }\n }\n\n return parts.join('\\n')\n}\n\n/**\n * Format a lightweight audit comment for action tracking.\n * Records what action was taken for the audit trail.\n *\n * @example\n * ```ts\n * const comment = formatAuditComment({\n * action: 'auto_sent',\n * category: 'support_access',\n * confidence: 0.92,\n * timestamp: new Date()\n * })\n * ```\n */\nexport function formatAuditComment(context: AuditContext): string {\n const parts: string[] = []\n\n // Determine emoji based on action\n const actionEmoji =\n context.action === 'auto_sent'\n ? '✅'\n : context.action === 'draft_created'\n ? '📝'\n : context.action === 'silenced'\n ? '🔇'\n : context.action === 'escalated'\n ? '⚠️'\n : '🤖'\n\n // Compact header\n parts.push(\n `${actionEmoji} **Agent Action: ${formatActionLabel(context.action)}**`\n )\n\n // Category and confidence on same line\n const confidencePercent = Math.round(context.confidence * 100)\n parts.push(\n `Category: ${context.category} | Confidence: ${confidencePercent}%`\n )\n\n // Timestamp\n const timestamp = context.timestamp ?? new Date()\n parts.push(`Timestamp: ${timestamp.toISOString()}`)\n\n // Message ID if available\n if (context.messageId) {\n parts.push(`Message ID: ${context.messageId}`)\n }\n\n return parts.join('\\n')\n}\n\n/**\n * Format action type for display.\n */\nfunction formatActionLabel(action: string): string {\n switch (action) {\n case 'auto_sent':\n return 'Auto-sent'\n case 'draft_created':\n return 'Draft Created'\n case 'silenced':\n return 'Silenced'\n case 'escalated':\n return 'Escalated'\n default:\n return action.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase())\n }\n}\n\n// ============================================================================\n// Comment step\n// ============================================================================\n\nexport interface AddCommentOptions {\n /** Front API token */\n frontApiToken: string\n /** Teammate ID to attribute the comment to (optional) */\n authorId?: string\n /** Use minimal format instead of full context */\n minimal?: boolean\n}\n\n/**\n * Add a support comment to a Front conversation.\n *\n * @param input - Conversation ID and gathered context\n * @param options - Front API token and formatting options\n * @returns Result with success status\n */\nexport async function addSupportComment(\n input: CommentInput,\n options: AddCommentOptions\n): Promise<CommentOutput> {\n const { conversationId, context } = input\n const { frontApiToken, authorId, minimal } = options\n\n try {\n const front = createInstrumentedFrontClient({ apiToken: frontApiToken })\n\n // Format the comment\n const body = minimal\n ? formatMinimalComment(context)\n : formatSupportComment(context)\n\n // Add comment to conversation\n await front.conversations.addComment(conversationId, body, authorId)\n\n return {\n added: true,\n // Front doesn't return comment ID from this endpoint\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return {\n added: false,\n error: message,\n }\n }\n}\n\n// ============================================================================\n// Standalone function for direct use\n// ============================================================================\n\n/**\n * Create a comment step function with pre-configured options.\n */\nexport function createCommentStep(options: AddCommentOptions) {\n return (input: CommentInput) => addSupportComment(input, options)\n}\n\n// ============================================================================\n// Agent Decision Comment\n// ============================================================================\n\n/**\n * Context for formatting an agent decision comment.\n */\nexport interface DecisionCommentContext {\n /** Message category from classification */\n category: string\n /** Classification confidence (0-1) */\n confidence: number\n /** Agent's reasoning for the classification */\n reasoning?: string\n /** Route action taken */\n action: string\n /** Reason for the routing decision */\n actionReason?: string\n /** Customer email if found */\n customerEmail?: string\n /** Customer name if found */\n customerName?: string\n /** Number of purchases found */\n purchaseCount?: number\n /** Purchase product names */\n purchaseNames?: string[]\n}\n\n/**\n * Format an agent decision comment.\n * Explains the classification and routing decision with context.\n *\n * @example For spam:\n * ```\n * 🤖 Agent Decision: Silenced\n * Category: spam (98%)\n * Reasoning: Vendor outreach/partnership proposal\n * Action: No response needed.\n * ```\n *\n * @example For support:\n * ```\n * 🤖 Agent Decision: Responding\n * Category: support_access (98%)\n * Context: User [EMAIL] found with 3 purchases\n * Action: Draft auto-approved and sent.\n * ```\n */\nexport function formatDecisionComment(context: DecisionCommentContext): string {\n const parts: string[] = []\n\n // Determine emoji and action label\n const actionEmoji = getActionEmoji(context.action)\n const actionLabel = getActionLabel(context.action)\n\n // Header\n parts.push(`${actionEmoji} **Agent Decision: ${actionLabel}**\\n`)\n\n // Classification details\n const confidencePercent = Math.round(context.confidence * 100)\n parts.push(`**Category:** ${context.category} (${confidencePercent}%)`)\n\n if (context.reasoning) {\n parts.push(`**Reasoning:** ${context.reasoning}`)\n }\n\n // Customer context (if available)\n if (context.customerEmail) {\n const customerLine = context.customerName\n ? `${context.customerName} (${context.customerEmail})`\n : context.customerEmail\n parts.push(`**Customer:** ${customerLine}`)\n\n if (context.purchaseCount !== undefined) {\n if (context.purchaseCount > 0 && context.purchaseNames) {\n parts.push(\n `**Purchases:** ${context.purchaseCount} (${context.purchaseNames.join(', ')})`\n )\n } else {\n parts.push(`**Purchases:** None found`)\n }\n }\n }\n\n // Action explanation\n parts.push('')\n if (context.actionReason) {\n parts.push(`**Action:** ${context.actionReason}`)\n }\n\n return parts.join('\\n')\n}\n\n/**\n * Get emoji for route action.\n */\nfunction getActionEmoji(action: string): string {\n switch (action) {\n case 'respond':\n return '💬'\n case 'silence':\n return '🔇'\n case 'escalate_human':\n return '⚠️'\n case 'escalate_instructor':\n return '👨🏫'\n case 'escalate_urgent':\n return '🚨'\n case 'support_teammate':\n return '🤝'\n case 'catalog_voc':\n return '📣'\n default:\n return '🤖'\n }\n}\n\n/**\n * Get human-readable label for route action.\n */\nfunction getActionLabel(action: string): string {\n switch (action) {\n case 'respond':\n return 'Responding'\n case 'silence':\n return 'Silenced'\n case 'escalate_human':\n return 'Escalated to Human'\n case 'escalate_instructor':\n return 'Escalated to Instructor'\n case 'escalate_urgent':\n return 'Urgent Escalation'\n case 'support_teammate':\n return 'Supporting Teammate'\n case 'catalog_voc':\n return 'Cataloging VOC'\n default:\n return action.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase())\n }\n}\n\n/**\n * Options for adding a decision comment.\n */\nexport interface AddDecisionCommentOptions {\n /** Front API token */\n frontApiToken: string\n /** Teammate ID to attribute the comment to (optional) */\n authorId?: string\n}\n\n/**\n * Add an agent decision comment to a Front conversation.\n * Explains what the agent decided and why.\n *\n * @param conversationId - Front conversation ID\n * @param context - Decision context for formatting\n * @param options - Front API configuration\n * @returns Result with success status\n */\nexport async function addDecisionComment(\n conversationId: string,\n context: DecisionCommentContext,\n options: AddDecisionCommentOptions\n): Promise<{ added: boolean; error?: string; durationMs: number }> {\n const startTime = Date.now()\n\n try {\n const front = createInstrumentedFrontClient({\n apiToken: options.frontApiToken,\n })\n const body = formatDecisionComment(context)\n\n await front.conversations.addComment(conversationId, body, options.authorId)\n\n return {\n added: true,\n durationMs: Date.now() - startTime,\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n console.error(\n `[CommentStep] Failed to add decision comment to ${conversationId}:`,\n message\n )\n\n return {\n added: false,\n error: message,\n durationMs: Date.now() - startTime,\n }\n }\n}\n","import {\n ErrorResponseSchema,\n FRONT_API_BASE,\n FrontApiError,\n type FrontClientConfig,\n createChannelsClient,\n createContactsClient,\n createConversationsClient,\n createDraftsClient,\n createInboxesClient,\n createMessagesClient,\n createTagsClient,\n createTeammatesClient,\n createTemplatesClient,\n} from '@skillrecordings/front-sdk'\nimport { z } from 'zod'\nimport { log } from '../observability/axiom'\n\nconst REQUEST_ID_HEADERS = [\n 'x-request-id',\n 'x-front-request-id',\n 'x-amzn-requestid',\n 'x-amz-request-id',\n]\n\nfunction extractRequestId(headers: Headers): string | undefined {\n for (const header of REQUEST_ID_HEADERS) {\n const value = headers.get(header)\n if (value) return value\n }\n return undefined\n}\n\nfunction detectFieldPresence(data: unknown): {\n hasLinksRelatedChildren: boolean\n hasHighlights: boolean\n} {\n let hasLinksRelatedChildren = false\n let hasHighlights = false\n\n const checkObject = (value: unknown) => {\n if (!value || typeof value !== 'object') return\n const record = value as Record<string, unknown>\n if (\n !hasHighlights &&\n Object.prototype.hasOwnProperty.call(record, 'highlights')\n ) {\n hasHighlights = true\n }\n\n if (!hasLinksRelatedChildren) {\n const links = record._links\n if (links && typeof links === 'object') {\n const related = (links as Record<string, unknown>).related\n if (related && typeof related === 'object') {\n if (Object.prototype.hasOwnProperty.call(related, 'children')) {\n hasLinksRelatedChildren = true\n }\n }\n }\n }\n }\n\n checkObject(data)\n\n if (data && typeof data === 'object') {\n const results = (data as Record<string, unknown>)._results\n if (Array.isArray(results)) {\n for (const item of results) {\n checkObject(item)\n if (hasLinksRelatedChildren && hasHighlights) break\n }\n }\n }\n\n return { hasLinksRelatedChildren, hasHighlights }\n}\n\nasync function logFrontRequest(options: {\n level: 'info' | 'warn' | 'error'\n method: string\n endpoint: string\n statusCode: number\n durationMs: number\n requestId?: string\n hasLinksRelatedChildren?: boolean\n hasHighlights?: boolean\n errorMessage?: string\n}) {\n await log(options.level, 'Front API request', {\n endpoint: options.endpoint,\n method: options.method,\n httpStatus: options.statusCode,\n durationMs: options.durationMs,\n requestId: options.requestId,\n hasLinksRelatedChildren: options.hasLinksRelatedChildren,\n hasHighlights: options.hasHighlights,\n errorMessage: options.errorMessage,\n })\n}\n\nasync function logFrontSdkFallback(options: {\n method: string\n endpoint: string\n requestId?: string\n errorMessage: string\n}) {\n await log('warn', 'Front SDK validation failed, SDK fallback used', {\n endpoint: options.endpoint,\n method: options.method,\n requestId: options.requestId,\n errorMessage: options.errorMessage,\n sdkValidationFailed: true,\n sdkFallbackUsed: true,\n fallbackOccurred: true,\n eventType: 'front_sdk_fallback',\n })\n}\n\nasync function handleRateLimit(\n response: Response,\n attempt: number\n): Promise<number> {\n const retryAfter = response.headers.get('Retry-After')\n return retryAfter\n ? parseInt(retryAfter, 10) * 1000\n : Math.min(1000 * Math.pow(2, attempt), 30000)\n}\n\nexport function createInstrumentedBaseClient(config: FrontClientConfig) {\n const baseUrl = config.baseUrl ?? FRONT_API_BASE\n const headers = {\n Authorization: `Bearer ${config.apiToken}`,\n 'Content-Type': 'application/json',\n }\n\n async function request<T>(\n method: string,\n path: string,\n schema?: z.ZodType<T>,\n body?: unknown,\n maxRetries = 3\n ): Promise<T> {\n const endpoint = path.startsWith('http') ? path : `${baseUrl}${path}`\n const startTime = Date.now()\n let lastRequestId: string | undefined\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n let response: Response\n try {\n response = await fetch(endpoint, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n })\n } catch (error) {\n const durationMs = Date.now() - startTime\n await logFrontRequest({\n level: 'error',\n method,\n endpoint: path,\n statusCode: 0,\n durationMs,\n errorMessage: error instanceof Error ? error.message : String(error),\n })\n throw error\n }\n\n lastRequestId = extractRequestId(response.headers)\n\n if (response.status === 429) {\n const delay = await handleRateLimit(response, attempt)\n await new Promise((resolve) => setTimeout(resolve, delay))\n continue\n }\n\n const durationMs = Date.now() - startTime\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}))\n const parsed = ErrorResponseSchema.safeParse(errorData)\n const errorMessage = parsed.success\n ? parsed.data._error.message\n : response.statusText\n\n await logFrontRequest({\n level: 'error',\n method,\n endpoint: path,\n statusCode: response.status,\n durationMs,\n requestId: lastRequestId,\n errorMessage,\n })\n\n if (parsed.success) {\n const { status, title, message, details } = parsed.data._error\n throw new FrontApiError(status, title, message, details)\n }\n throw new FrontApiError(\n response.status,\n 'Unknown Error',\n response.statusText\n )\n }\n\n if (response.status === 204) {\n await logFrontRequest({\n level: 'info',\n method,\n endpoint: path,\n statusCode: response.status,\n durationMs,\n requestId: lastRequestId,\n hasLinksRelatedChildren: false,\n hasHighlights: false,\n })\n return undefined as T\n }\n\n const data = await response.json()\n const fieldPresence = detectFieldPresence(data)\n\n await logFrontRequest({\n level: 'info',\n method,\n endpoint: path,\n statusCode: response.status,\n durationMs,\n requestId: lastRequestId,\n hasLinksRelatedChildren: fieldPresence.hasLinksRelatedChildren,\n hasHighlights: fieldPresence.hasHighlights,\n })\n\n if (schema) {\n try {\n return schema.parse(data)\n } catch (error) {\n await logFrontSdkFallback({\n method,\n endpoint: path,\n requestId: lastRequestId,\n errorMessage:\n error instanceof Error ? error.message : String(error),\n })\n return data as T\n }\n }\n return data as T\n }\n\n const durationMs = Date.now() - startTime\n await logFrontRequest({\n level: 'error',\n method,\n endpoint: path,\n statusCode: 429,\n durationMs,\n requestId: lastRequestId,\n errorMessage: 'Max retries exceeded',\n })\n\n throw new FrontApiError(429, 'Rate Limited', 'Max retries exceeded')\n }\n\n return {\n get: <T>(path: string, schema?: z.ZodType<T>) =>\n request<T>('GET', path, schema),\n post: <T>(path: string, body: unknown, schema?: z.ZodType<T>) =>\n request<T>('POST', path, schema, body),\n patch: <T>(path: string, body: unknown, schema?: z.ZodType<T>) =>\n request<T>('PATCH', path, schema, body),\n put: <T>(path: string, body: unknown, schema?: z.ZodType<T>) =>\n request<T>('PUT', path, schema, body),\n delete: <T>(path: string, schema?: z.ZodType<T>) =>\n request<T>('DELETE', path, schema),\n }\n}\n\nexport function createInstrumentedFrontClient(config: FrontClientConfig) {\n const baseClient = createInstrumentedBaseClient(config)\n\n return {\n raw: baseClient,\n conversations: createConversationsClient(baseClient),\n messages: createMessagesClient(baseClient),\n drafts: createDraftsClient(baseClient),\n templates: createTemplatesClient(baseClient),\n tags: createTagsClient(baseClient),\n inboxes: createInboxesClient(baseClient),\n channels: createChannelsClient(baseClient),\n contacts: createContactsClient(baseClient),\n teammates: createTeammatesClient(baseClient),\n }\n}\n\nexport type InstrumentedFrontClient = ReturnType<\n typeof createInstrumentedFrontClient\n>\n","import { z } from 'zod'\n\n/**\n * Links schema for Front API responses\n * Contains hypermedia links for resource navigation\n */\nexport const LinksSchema = z.object({\n self: z.string().url(),\n})\n\nexport type Links = z.infer<typeof LinksSchema>\n\n/**\n * Pagination schema for Front API responses\n * Contains optional next page URL for cursor-based pagination\n * Note: Front returns null (not undefined) when there's no next page\n */\nexport const PaginationSchema = z.object({\n next: z.string().url().nullish(),\n})\n\nexport type Pagination = z.infer<typeof PaginationSchema>\n\n/**\n * Generic paginated response schema\n * All Front API list endpoints follow this structure\n */\nexport function PaginatedResponseSchema<T extends z.ZodTypeAny>(\n resultSchema: T\n) {\n return z.object({\n _pagination: PaginationSchema.optional(),\n _links: LinksSchema,\n _results: z.array(resultSchema),\n })\n}\n\nexport type PaginatedResponse<T> = {\n _pagination?: Pagination\n _links: Links\n _results: T[]\n}\n\n/**\n * Error response schema for Front API errors\n * Returned when API requests fail (4xx/5xx status codes)\n */\nexport const ErrorResponseSchema = z.object({\n _error: z.object({\n status: z.number().int(),\n title: z.string(),\n message: z.string(),\n details: z.array(z.string()).optional(),\n }),\n})\n\nexport type ErrorResponse = z.infer<typeof ErrorResponseSchema>\n","import { z } from 'zod'\nimport { PaginatedResponseSchema } from './common'\n\n/**\n * Recipient schema for conversation participants\n * Represents an email address or contact handle with role (from/to/cc/bcc)\n */\nexport const RecipientSchema = z.object({\n _links: z\n .object({\n related: z\n .object({\n contact: z.string().optional(),\n })\n .optional(),\n })\n .optional(),\n handle: z.string(),\n role: z.enum(['from', 'to', 'cc', 'bcc']),\n name: z.string().nullable().optional(),\n})\n\nexport type Recipient = z.infer<typeof RecipientSchema>\n\n/**\n * Conversation status enum\n * Represents the current state of a conversation in Front\n */\nexport const ConversationStatusSchema = z.enum([\n 'archived',\n 'unassigned',\n 'assigned',\n 'deleted',\n 'snoozed',\n 'invisible',\n])\n\nexport type ConversationStatus = z.infer<typeof ConversationStatusSchema>\n\n/**\n * Tag schema for conversation tags\n */\nexport const TagSchema = z.object({\n id: z.string(),\n name: z.string(),\n})\n\nexport type Tag = z.infer<typeof TagSchema>\n\n/**\n * Link schema for conversation links\n */\nexport const LinkSchema = z.object({\n id: z.string(),\n name: z.string().nullable(),\n external_url: z.string(),\n})\n\nexport type Link = z.infer<typeof LinkSchema>\n\n/**\n * Assignee (teammate) schema\n */\nexport const AssigneeSchema = z.object({\n id: z.string(),\n email: z.string(),\n first_name: z.string().optional(),\n last_name: z.string().optional(),\n})\n\nexport type Assignee = z.infer<typeof AssigneeSchema>\n\n/**\n * Reminder schema for scheduled reminders\n */\nexport const ReminderSchema = z.object({\n scheduled_at: z.number(),\n})\n\nexport type Reminder = z.infer<typeof ReminderSchema>\n\n/**\n * Conversation metadata schema\n */\nexport const ConversationMetadataSchema = z.object({\n external_conversation_ids: z.array(z.string()).optional(),\n})\n\nexport type ConversationMetadata = z.infer<typeof ConversationMetadataSchema>\n\n/**\n * Conversation schema (cnv_xxx)\n * Represents a complete conversation thread in Front\n */\nexport const ConversationSchema = z.object({\n _links: z.object({\n self: z.string(),\n related: z.object({\n events: z.string(),\n followers: z.string(),\n messages: z.string(),\n comments: z.string(),\n inboxes: z.string(),\n last_message: z.string().optional(),\n }),\n }),\n id: z.string(),\n subject: z.string(),\n status: ConversationStatusSchema,\n assignee: AssigneeSchema.nullable(),\n recipient: RecipientSchema.nullable(),\n tags: z.array(TagSchema),\n links: z.array(LinkSchema),\n custom_fields: z.record(z.string(), z.unknown()).optional(),\n created_at: z.number(),\n waiting_since: z.number().optional(),\n is_private: z.boolean(),\n scheduled_reminders: z.array(ReminderSchema),\n metadata: ConversationMetadataSchema.optional(),\n})\n\nexport type Conversation = z.infer<typeof ConversationSchema>\n\n/**\n * Paginated conversation list schema\n */\nexport const ConversationListSchema =\n PaginatedResponseSchema(ConversationSchema)\n\nexport type ConversationList = z.infer<typeof ConversationListSchema>\n\n/**\n * Schema for updating a conversation\n * Only includes fields that can be modified via PATCH\n */\nexport const UpdateConversationSchema = z.object({\n assignee_id: z.string().optional(),\n inbox_id: z.string().optional(),\n status: ConversationStatusSchema.optional(),\n tags: z.array(z.string()).optional(),\n})\n\nexport type UpdateConversation = z.infer<typeof UpdateConversationSchema>\n","import { z } from 'zod'\nimport { PaginatedResponseSchema } from './common'\n\n/**\n * Recipient schema for message recipients\n * Represents an email/message recipient with their role\n */\nexport const RecipientSchema = z.object({\n _links: z\n .object({\n related: z\n .object({\n contact: z.string().nullable(),\n })\n .optional(),\n })\n .optional(),\n handle: z.string(),\n role: z.enum(['from', 'to', 'cc', 'bcc', 'reply-to']),\n name: z.string().nullable().optional(),\n})\n\nexport type Recipient = z.infer<typeof RecipientSchema>\n\n/**\n * Author schema for message authors (teammates)\n * Represents a teammate who authored a message\n */\nexport const AuthorSchema = z.object({\n _links: z\n .object({\n self: z.string(),\n related: z.object({\n inboxes: z.string(),\n conversations: z.string(),\n }),\n })\n .optional(),\n id: z.string(),\n email: z.string(),\n username: z.string().optional(),\n first_name: z.string().optional(),\n last_name: z.string().optional(),\n is_admin: z.boolean().optional(),\n is_available: z.boolean().optional(),\n is_blocked: z.boolean().optional(),\n})\n\nexport type Author = z.infer<typeof AuthorSchema>\n\n/**\n * Attachment schema for Front API messages and templates\n * Represents files attached to messages or message templates\n */\nexport const AttachmentSchema = z.object({\n id: z.string(),\n filename: z.string(),\n url: z.string(),\n content_type: z.string(),\n size: z.number(),\n metadata: z.object({\n is_inline: z.boolean(),\n cid: z.string().optional(),\n }),\n})\n\nexport type Attachment = z.infer<typeof AttachmentSchema>\n\n/**\n * Signature schema for message signatures\n */\nexport const SignatureSchema = z.object({\n id: z.string(),\n name: z.string(),\n body: z.string(),\n is_default: z.boolean().optional(),\n})\n\nexport type Signature = z.infer<typeof SignatureSchema>\n\n/**\n * Message schema for Front API messages\n * Represents a message in a conversation\n */\nexport const MessageSchema = z.object({\n _links: z.object({\n self: z.string(),\n related: z.object({\n conversation: z.string(),\n message_replied_to: z.string().optional(),\n }),\n }),\n id: z.string(),\n type: z.enum([\n 'email',\n 'tweet',\n 'sms',\n 'smooch',\n 'facebook',\n 'intercom',\n 'call',\n 'custom',\n ]),\n is_inbound: z.boolean(),\n is_draft: z.boolean(),\n error_type: z.string().nullable(),\n version: z.string().nullable(),\n created_at: z.number(),\n subject: z.string().nullable(),\n blurb: z.string(),\n body: z.string(),\n text: z.string().nullable(),\n author: AuthorSchema.nullable(),\n recipients: z.array(RecipientSchema),\n attachments: z.array(AttachmentSchema),\n signature: SignatureSchema.nullable().optional(),\n metadata: z\n .object({\n headers: z.record(z.string(), z.string()).optional(),\n thread_ref: z.string().optional(),\n is_forward: z.boolean().optional(),\n })\n .optional(),\n})\n\nexport type Message = z.infer<typeof MessageSchema>\n\n/**\n * Paginated message list schema\n */\nexport const MessageListSchema = PaginatedResponseSchema(MessageSchema)\n\nexport type MessageList = z.infer<typeof MessageListSchema>\n\n/**\n * Create message request schema\n * Used when creating a new message via a channel\n */\nexport const CreateMessageSchema = z.object({\n to: z.array(z.string()),\n cc: z.array(z.string()).optional(),\n bcc: z.array(z.string()).optional(),\n subject: z.string().optional(),\n body: z.string(),\n author_id: z.string().optional(),\n attachments: z.array(z.string()).optional(), // Attachment IDs\n})\n\nexport type CreateMessage = z.infer<typeof CreateMessageSchema>\n","import { z } from 'zod'\nimport { PaginatedResponseSchema } from './common'\nimport { AttachmentSchema, AuthorSchema, RecipientSchema } from './message'\n\n/**\n * Draft schema for Front API drafts\n * Drafts are messages that haven't been sent yet\n */\nexport const DraftSchema = z.object({\n _links: z.object({\n self: z.string(),\n related: z.object({\n conversation: z.string(),\n message_replied_to: z.string().optional(),\n }),\n }),\n id: z.string(),\n version: z.string(),\n author: AuthorSchema,\n recipients: z.array(RecipientSchema),\n body: z.string(),\n subject: z.string().nullable(),\n attachments: z.array(AttachmentSchema),\n created_at: z.number(),\n channel_id: z.string().optional(),\n})\n\nexport type Draft = z.infer<typeof DraftSchema>\n\n/**\n * Paginated draft list schema\n */\nexport const DraftListSchema = PaginatedResponseSchema(DraftSchema)\n\nexport type DraftList = z.infer<typeof DraftListSchema>\n\n/**\n * Create draft request schema\n * Used for creating new drafts on a channel or conversation\n */\nexport const CreateDraftSchema = z.object({\n body: z.string(),\n channel_id: z.string(),\n author_id: z.string().optional(),\n to: z.array(z.string()).optional(),\n cc: z.array(z.string()).optional(),\n bcc: z.array(z.string()).optional(),\n subject: z.string().optional(),\n signature_id: z.string().optional(),\n mode: z.enum(['private', 'shared']).optional(),\n})\n\nexport type CreateDraft = z.infer<typeof CreateDraftSchema>\n\n/**\n * Edit draft request schema\n * Requires version for optimistic locking\n */\nexport const EditDraftSchema = z.object({\n body: z.string().optional(),\n to: z.array(z.string()).optional(),\n cc: z.array(z.string()).optional(),\n bcc: z.array(z.string()).optional(),\n subject: z.string().optional(),\n version: z.string(), // Required for optimistic locking\n})\n\nexport type EditDraft = z.infer<typeof EditDraftSchema>\n","import { z } from 'zod'\nimport { PaginatedResponseSchema } from './common'\nimport { AttachmentSchema } from './message'\n\n/**\n * Message template folder schema\n * Folders are used to organize message templates\n */\nexport const MessageTemplateFolderSchema = z.object({\n _links: z.object({\n self: z.string(),\n }),\n id: z.string(),\n name: z.string(),\n})\n\nexport type MessageTemplateFolder = z.infer<typeof MessageTemplateFolderSchema>\n\n/**\n * Message template schema\n * Templates are reusable message drafts with subject, body, and optional attachments\n */\nexport const MessageTemplateSchema = z.object({\n _links: z.object({\n self: z.string(),\n related: z.object({\n owner: z.string(),\n folder: z.string().optional(),\n }),\n }),\n id: z.string(),\n name: z.string(),\n subject: z.string().nullable(),\n body: z.string(),\n is_available_for_all_inboxes: z.boolean(),\n attachments: z.array(AttachmentSchema).optional(),\n folder: z.string().nullable().optional(),\n})\n\nexport type MessageTemplate = z.infer<typeof MessageTemplateSchema>\n\n/**\n * Paginated message template list\n */\nexport const MessageTemplateListSchema = PaginatedResponseSchema(\n MessageTemplateSchema\n)\n\nexport type MessageTemplateList = z.infer<typeof MessageTemplateListSchema>\n\n/**\n * Paginated message template folder list\n */\nexport const MessageTemplateFolderListSchema = PaginatedResponseSchema(\n MessageTemplateFolderSchema\n)\n\nexport type MessageTemplateFolderList = z.infer<\n typeof MessageTemplateFolderListSchema\n>\n\n/**\n * Schema for creating a new message template\n */\nexport const CreateMessageTemplateSchema = z.object({\n name: z.string(),\n subject: z.string(),\n body: z.string(),\n folder_id: z.string().optional(),\n inbox_ids: z.array(z.string()).optional(),\n})\n\nexport type CreateMessageTemplate = z.infer<typeof CreateMessageTemplateSchema>\n\n/**\n * Schema for updating an existing message template\n */\nexport const UpdateMessageTemplateSchema = z.object({\n name: z.string().optional(),\n subject: z.string().optional(),\n body: z.string().optional(),\n folder_id: z.string().nullable().optional(),\n inbox_ids: z.array(z.string()).optional(),\n})\n\nexport type UpdateMessageTemplate = z.infer<typeof UpdateMessageTemplateSchema>\n","import { z } from 'zod'\nimport { PaginatedResponseSchema } from './common'\n\n/**\n * Tag highlight color options\n * Known colors in Front UI (not exhaustive - Front may add more)\n */\nexport const KnownTagHighlights = [\n 'black',\n 'grey',\n 'pink',\n 'red',\n 'orange',\n 'yellow',\n 'green',\n 'teal',\n 'blue',\n 'purple',\n] as const\n\n/**\n * Tag highlight schema - accepts any string since Front may add new colors\n */\nexport const TagHighlightSchema = z.string()\n\n/**\n * Tag schema for Front API\n * Tags categorize conversations for organization and filtering\n */\nexport const TagSchema = z.object({\n _links: z.object({\n self: z.string(),\n related: z.object({\n conversations: z.string(),\n owner: z.string(),\n children: z.string().nullish(),\n }),\n }),\n id: z.string(),\n name: z.string(),\n description: z.string().nullable().optional(),\n highlight: TagHighlightSchema.nullable().optional(),\n is_private: z.boolean(),\n is_visible_in_conversation_lists: z.boolean().optional(),\n created_at: z.number().optional(),\n updated_at: z.number().optional(),\n})\n\nexport const TagListSchema = PaginatedResponseSchema(TagSchema)\n\nexport const CreateTagSchema = z.object({\n name: z.string(),\n description: z.string().optional(),\n highlight: TagHighlightSchema.optional(),\n})\n\nexport const UpdateTagSchema = z.object({\n name: z.string().optional(),\n description: z.string().nullable().optional(),\n highlight: TagHighlightSchema.nullable().optional(),\n})\n\nexport type Tag = z.infer<typeof TagSchema>\nexport type TagList = z.infer<typeof TagListSchema>\n","import { z } from 'zod'\nimport { PaginatedResponseSchema } from './common'\n\n/**\n * Inbox schema for Front API responses\n * Represents a Front inbox with teammates, conversations, and channels\n */\nexport const InboxSchema = z.object({\n _links: z.object({\n self: z.string().url(),\n related: z.object({\n teammates: z.string().url(),\n conversations: z.string().url(),\n channels: z.string().url(),\n owner: z.string().url(),\n }),\n }),\n id: z.string(),\n name: z.string(),\n is_private: z.boolean(),\n is_public: z.boolean().optional(),\n address: z.string().optional(),\n send_as: z.string().optional(),\n})\n\n/**\n * Paginated inbox list schema\n */\nexport const InboxListSchema = PaginatedResponseSchema(InboxSchema)\n\n/**\n * Schema for creating a new inbox\n */\nexport const CreateInboxSchema = z.object({\n name: z.string(),\n teammate_ids: z.array(z.string()).optional(),\n})\n\nexport type Inbox = z.infer<typeof InboxSchema>\nexport type InboxList = z.infer<typeof InboxListSchema>\nexport type CreateInbox = z.infer<typeof CreateInboxSchema>\n","import { z } from 'zod'\nimport { PaginatedResponseSchema } from './common'\n\n/**\n * Channel types supported by Front\n */\nexport const ChannelTypeSchema = z.enum([\n 'smtp',\n 'imap',\n 'twilio',\n 'twitter',\n 'facebook',\n 'intercom',\n 'truly',\n 'custom',\n])\n\nexport type ChannelType = z.infer<typeof ChannelTypeSchema>\n\n/**\n * Channel schema\n * Represents a communication channel in Front (email, SMS, social, etc.)\n */\nexport const ChannelSchema = z.object({\n _links: z.object({\n self: z.string().url(),\n related: z.object({\n inbox: z.string().url(),\n owner: z.string().url().optional(),\n }),\n }),\n id: z.string(),\n type: ChannelTypeSchema,\n address: z.string(),\n send_as: z.string().optional(),\n name: z.string().optional(),\n is_private: z.boolean().optional(),\n is_valid: z.boolean().optional(),\n})\n\nexport type Channel = z.infer<typeof ChannelSchema>\n\n/**\n * Paginated list of channels\n */\nexport const ChannelListSchema = PaginatedResponseSchema(ChannelSchema)\n\nexport type ChannelList = z.infer<typeof ChannelListSchema>\n\n/**\n * Schema for creating a new channel\n */\nexport const CreateChannelSchema = z.object({\n type: ChannelTypeSchema,\n settings: z.record(z.string(), z.unknown()),\n})\n\nexport type CreateChannel = z.infer<typeof CreateChannelSchema>\n\n/**\n * Schema for updating a channel\n */\nexport const UpdateChannelSchema = z.object({\n name: z.string().optional(),\n settings: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport type UpdateChannel = z.infer<typeof UpdateChannelSchema>\n","import { z } from 'zod'\nimport { PaginatedResponseSchema } from './common'\n\nexport const ContactHandleSourceSchema = z.enum([\n 'email',\n 'phone',\n 'twitter',\n 'facebook',\n 'intercom',\n 'front_chat',\n 'custom',\n])\n\nexport const ContactHandleSchema = z.object({\n handle: z.string(),\n source: ContactHandleSourceSchema,\n})\n\nexport const ContactGroupSchema = z.object({\n _links: z.object({ self: z.string() }),\n id: z.string(),\n name: z.string(),\n})\n\nexport const ContactSchema = z.object({\n _links: z.object({\n self: z.string(),\n related: z.object({\n notes: z.string(),\n conversations: z.string(),\n owner: z.string().optional(),\n }),\n }),\n id: z.string(),\n name: z.string().nullable(),\n description: z.string().nullable(),\n avatar_url: z.string().nullable(),\n is_spammer: z.boolean(),\n links: z.array(z.string()),\n groups: z.array(ContactGroupSchema),\n handles: z.array(ContactHandleSchema),\n custom_fields: z.record(z.string(), z.unknown()),\n is_private: z.boolean().optional(),\n})\n\nexport const ContactListSchema = PaginatedResponseSchema(ContactSchema)\n\nexport const CreateContactSchema = z.object({\n name: z.string().optional(),\n description: z.string().optional(),\n handles: z.array(ContactHandleSchema),\n group_names: z.array(z.string()).optional(),\n custom_fields: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport const UpdateContactSchema = z.object({\n name: z.string().optional(),\n description: z.string().optional(),\n avatar: z.string().optional(), // base64 or URL\n is_spammer: z.boolean().optional(),\n custom_fields: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport type Contact = z.infer<typeof ContactSchema>\nexport type ContactList = z.infer<typeof ContactListSchema>\nexport type ContactHandle = z.infer<typeof ContactHandleSchema>\nexport type ContactHandleSource = z.infer<typeof ContactHandleSourceSchema>\nexport type ContactGroup = z.infer<typeof ContactGroupSchema>\nexport type CreateContact = z.infer<typeof CreateContactSchema>\nexport type UpdateContact = z.infer<typeof UpdateContactSchema>\n","import { z } from 'zod'\nimport { PaginatedResponseSchema } from './common'\n\n/**\n * Teammate schema for Front API\n * Represents a teammate/user in the Front workspace\n */\nexport const TeammateSchema = z.object({\n _links: z.object({\n self: z.string(),\n related: z.object({\n inboxes: z.string(),\n conversations: z.string(),\n }),\n }),\n id: z.string(),\n email: z.string(),\n username: z.string(),\n first_name: z.string(),\n last_name: z.string(),\n is_admin: z.boolean(),\n is_available: z.boolean(),\n is_blocked: z.boolean(),\n custom_fields: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport type Teammate = z.infer<typeof TeammateSchema>\n\n/**\n * Paginated list of teammates\n */\nexport const TeammateListSchema = PaginatedResponseSchema(TeammateSchema)\n\nexport type TeammateList = z.infer<typeof TeammateListSchema>\n\n/**\n * Schema for updating a teammate\n */\nexport const UpdateTeammateSchema = z.object({\n username: z.string().optional(),\n first_name: z.string().optional(),\n last_name: z.string().optional(),\n is_available: z.boolean().optional(),\n custom_fields: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport type UpdateTeammate = z.infer<typeof UpdateTeammateSchema>\n","import { z } from 'zod'\nimport { ErrorResponseSchema } from '../schemas/common'\n\nexport const FRONT_API_BASE = 'https://api2.frontapp.com'\n\n/**\n * Configuration for the Front API client\n */\nexport interface FrontClientConfig {\n /** API token for authentication (required) */\n apiToken: string\n /** Optional base URL override (defaults to https://api2.frontapp.com) */\n baseUrl?: string\n}\n\n/**\n * Custom error class for Front API errors\n * Provides structured error information from the API\n */\nexport class FrontApiError extends Error {\n constructor(\n public readonly status: number,\n public readonly title: string,\n message: string,\n public readonly details?: string[]\n ) {\n super(message)\n this.name = 'FrontApiError'\n }\n}\n\n/**\n * Handle rate limiting with exponential backoff\n * Uses Retry-After header if present, otherwise exponential backoff\n */\nasync function handleRateLimit(\n response: Response,\n attempt: number\n): Promise<number> {\n const retryAfter = response.headers.get('Retry-After')\n const delay = retryAfter\n ? parseInt(retryAfter, 10) * 1000\n : Math.min(1000 * Math.pow(2, attempt), 30000)\n return delay\n}\n\n/**\n * Create a base HTTP client with authentication and error handling\n * Includes automatic retry logic for 429 rate limit responses\n */\nexport function createBaseClient(config: FrontClientConfig) {\n const baseUrl = config.baseUrl ?? FRONT_API_BASE\n const headers = {\n Authorization: `Bearer ${config.apiToken}`,\n 'Content-Type': 'application/json',\n }\n\n /**\n * Generic request handler with retry logic and error handling\n */\n async function request<T>(\n method: string,\n path: string,\n schema?: z.ZodType<T>,\n body?: unknown,\n maxRetries = 3\n ): Promise<T> {\n const url = path.startsWith('http') ? path : `${baseUrl}${path}`\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n })\n\n // Handle rate limiting (429) with exponential backoff\n if (response.status === 429) {\n const delay = await handleRateLimit(response, attempt)\n await new Promise((r) => setTimeout(r, delay))\n continue\n }\n\n // Handle error responses\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}))\n const parsed = ErrorResponseSchema.safeParse(errorData)\n if (parsed.success) {\n const { status, title, message, details } = parsed.data._error\n throw new FrontApiError(status, title, message, details)\n }\n throw new FrontApiError(\n response.status,\n 'Unknown Error',\n response.statusText\n )\n }\n\n // Handle empty responses (204 No Content)\n if (response.status === 204) {\n return undefined as T\n }\n\n const data = await response.json()\n\n // Validate response with schema if provided\n if (schema) {\n return schema.parse(data)\n }\n return data as T\n }\n\n throw new FrontApiError(429, 'Rate Limited', 'Max retries exceeded')\n }\n\n return {\n /**\n * Send a GET request\n */\n get: <T>(path: string, schema?: z.ZodType<T>) =>\n request<T>('GET', path, schema),\n\n /**\n * Send a POST request\n */\n post: <T>(path: string, body: unknown, schema?: z.ZodType<T>) =>\n request<T>('POST', path, schema, body),\n\n /**\n * Send a PATCH request\n */\n patch: <T>(path: string, body: unknown, schema?: z.ZodType<T>) =>\n request<T>('PATCH', path, schema, body),\n\n /**\n * Send a PUT request\n */\n put: <T>(path: string, body: unknown, schema?: z.ZodType<T>) =>\n request<T>('PUT', path, schema, body),\n\n /**\n * Send a DELETE request\n */\n delete: <T>(path: string, schema?: z.ZodType<T>) =>\n request<T>('DELETE', path, schema),\n }\n}\n\n/**\n * Type for the base client instance\n */\nexport type BaseClient = ReturnType<typeof createBaseClient>\n","import {\n type Channel,\n type ChannelList,\n ChannelListSchema,\n ChannelSchema,\n type CreateChannel,\n type UpdateChannel,\n} from '../schemas/channel'\nimport type { BaseClient } from './base'\n\n/**\n * Channels client for Front API\n * Manages communication channels (email, SMS, social media, etc.)\n */\nexport function createChannelsClient(client: BaseClient) {\n return {\n /**\n * List all channels\n * @returns Paginated list of channels\n */\n list: () => client.get<ChannelList>('/channels', ChannelListSchema),\n\n /**\n * Get a specific channel by ID\n * @param id - Channel ID\n * @returns Channel details\n */\n get: (id: string) => client.get<Channel>(`/channels/${id}`, ChannelSchema),\n\n /**\n * Update a channel\n * @param id - Channel ID\n * @param data - Update payload (name and/or settings)\n * @returns Updated channel\n */\n update: (id: string, data: UpdateChannel) =>\n client.patch<Channel>(`/channels/${id}`, data, ChannelSchema),\n\n /**\n * Validate a channel configuration\n * Tests channel connectivity and authentication\n * @param id - Channel ID\n */\n validate: (id: string) => client.post<void>(`/channels/${id}/validate`, {}),\n\n /**\n * Create a new channel\n * Channels are created within an inbox\n * @param inboxId - Inbox ID where channel will be created\n * @param data - Channel type and settings\n * @returns Created channel\n */\n create: (inboxId: string, data: CreateChannel) =>\n client.post<Channel>(`/inboxes/${inboxId}/channels`, data, ChannelSchema),\n }\n}\n","import { ContactListSchema, ContactSchema } from '../schemas/contact'\nimport type { Contact, ContactHandle, ContactList } from '../schemas/contact'\nimport type { BaseClient } from './base'\n\nexport function createContactsClient(client: BaseClient) {\n return {\n list: () => client.get<ContactList>('/contacts', ContactListSchema),\n get: (id: string) => client.get<Contact>(`/contacts/${id}`, ContactSchema),\n create: (data: {\n handles: ContactHandle[]\n name?: string\n description?: string\n }) => client.post<Contact>('/contacts', data, ContactSchema),\n update: (\n id: string,\n data: { name?: string; description?: string; is_spammer?: boolean }\n ) => client.patch<Contact>(`/contacts/${id}`, data, ContactSchema),\n delete: (id: string) => client.delete<void>(`/contacts/${id}`),\n merge: (targetId: string, sourceIds: string[]) =>\n client.post<Contact>(\n '/contacts/merge',\n { target_contact_id: targetId, contact_ids: sourceIds },\n ContactSchema\n ),\n listConversations: (id: string) =>\n client.get(`/contacts/${id}/conversations`),\n addHandle: (id: string, handle: ContactHandle) =>\n client.post<void>(`/contacts/${id}/handles`, handle),\n deleteHandle: (id: string, handle: string, source: string) =>\n client.delete<void>(\n `/contacts/${id}/handles?handle=${encodeURIComponent(handle)}&source=${encodeURIComponent(source)}`\n ),\n listNotes: (id: string) => client.get(`/contacts/${id}/notes`),\n addNote: (id: string, body: string, authorId?: string) =>\n client.post(`/contacts/${id}/notes`, { body, author_id: authorId }),\n }\n}\n","import {\n type Conversation,\n type ConversationList,\n ConversationListSchema,\n ConversationSchema,\n type UpdateConversation,\n UpdateConversationSchema,\n} from '../schemas/conversation'\nimport type { BaseClient } from './base'\n\n/**\n * Create a conversations client for managing Front conversations\n * Provides methods for fetching, updating, and managing conversation resources\n */\nexport function createConversationsClient(client: BaseClient) {\n return {\n /**\n * Get a single conversation by ID\n * @param id - Conversation ID (cnv_xxx)\n */\n get: (id: string) =>\n client.get<Conversation>(`/conversations/${id}`, ConversationSchema),\n\n /**\n * List all conversations\n * Returns paginated results\n */\n list: () =>\n client.get<ConversationList>('/conversations', ConversationListSchema),\n\n /**\n * Search conversations by query\n * @param query - Search query string\n */\n search: (query: string) =>\n client.get<ConversationList>(\n `/conversations/search/${encodeURIComponent(query)}`,\n ConversationListSchema\n ),\n\n /**\n * Update a conversation\n * @param id - Conversation ID (cnv_xxx)\n * @param data - Update payload (assignee, status, tags, etc.)\n */\n update: (id: string, data: UpdateConversation) =>\n client.patch<Conversation>(\n `/conversations/${id}`,\n data,\n ConversationSchema\n ),\n\n /**\n * Update conversation assignee\n * @param id - Conversation ID (cnv_xxx)\n * @param assigneeId - Teammate ID (tea_xxx)\n * @see https://dev.frontapp.com/reference/update-conversation-assignee\n */\n updateAssignee: (id: string, assigneeId: string) =>\n client.put<void>(`/conversations/${id}/assignee`, {\n assignee_id: assigneeId,\n }),\n\n /**\n * List messages in a conversation\n * @param id - Conversation ID (cnv_xxx)\n */\n listMessages: (id: string) => client.get(`/conversations/${id}/messages`),\n\n /**\n * List comments in a conversation\n * @param id - Conversation ID (cnv_xxx)\n */\n listComments: (id: string) => client.get(`/conversations/${id}/comments`),\n\n /**\n * Add a comment to a conversation\n * @param id - Conversation ID (cnv_xxx)\n * @param body - Comment body (markdown supported)\n * @param authorId - Optional author teammate ID\n */\n addComment: (id: string, body: string, authorId?: string) =>\n client.post(`/conversations/${id}/comments`, {\n body,\n author_id: authorId,\n }),\n\n /**\n * Add a tag to a conversation\n * @param id - Conversation ID (cnv_xxx)\n * @param tagId - Tag ID (tag_xxx)\n */\n addTag: (id: string, tagId: string) =>\n client.post<void>(`/conversations/${id}/tags`, {\n tag_ids: [tagId],\n }),\n\n /**\n * Remove a tag from a conversation\n * @param id - Conversation ID (cnv_xxx)\n * @param tagId - Tag ID (tag_xxx)\n */\n removeTag: (id: string, tagId: string) =>\n client.delete<void>(`/conversations/${id}/tags/${tagId}`),\n }\n}\n","import {\n CreateDraftSchema,\n DraftListSchema,\n DraftSchema,\n EditDraftSchema,\n} from '../schemas/draft'\nimport type { CreateDraft, Draft, DraftList, EditDraft } from '../schemas/draft'\nimport type { BaseClient } from './base'\n\n/**\n * Drafts API client\n * Provides methods for creating and managing message drafts\n */\nexport function createDraftsClient(client: BaseClient) {\n return {\n /**\n * Create draft on channel\n * @param channelId - Channel ID to create draft on\n * @param data - Draft creation data\n */\n create: (channelId: string, data: CreateDraft) =>\n client.post<Draft>(`/channels/${channelId}/drafts`, data, DraftSchema),\n\n /**\n * Create draft reply on conversation\n * @param conversationId - Conversation ID to reply to\n * @param data - Draft creation data\n */\n createReply: (conversationId: string, data: CreateDraft) =>\n client.post<Draft>(\n `/conversations/${conversationId}/drafts`,\n data,\n DraftSchema\n ),\n\n /**\n * List drafts on conversation\n * @param conversationId - Conversation ID\n */\n list: (conversationId: string) =>\n client.get<DraftList>(\n `/conversations/${conversationId}/drafts`,\n DraftListSchema\n ),\n\n /**\n * Edit draft (requires version for optimistic locking)\n * @param draftId - Draft ID\n * @param data - Draft edit data (must include version)\n */\n edit: (draftId: string, data: EditDraft) =>\n client.patch<Draft>(`/drafts/${draftId}`, data, DraftSchema),\n\n /**\n * Delete draft\n * @param draftId - Draft ID\n */\n delete: (draftId: string) => client.delete<void>(`/drafts/${draftId}`),\n }\n}\n","import { InboxListSchema, InboxSchema } from '../schemas/inbox'\nimport type { CreateInbox, Inbox, InboxList } from '../schemas/inbox'\nimport type { BaseClient } from './base'\n\n/**\n * Client for Front Inboxes API\n * @see https://dev.frontapp.com/reference/inboxes\n */\nexport function createInboxesClient(client: BaseClient) {\n return {\n /**\n * List all inboxes\n * @see https://dev.frontapp.com/reference/list-inboxes\n */\n list: () => client.get<InboxList>('/inboxes', InboxListSchema),\n\n /**\n * Get inbox by ID\n * @see https://dev.frontapp.com/reference/get-inbox\n */\n get: (id: string) => client.get<Inbox>(`/inboxes/${id}`, InboxSchema),\n\n /**\n * Create a new inbox\n * @see https://dev.frontapp.com/reference/create-inbox\n */\n create: (data: CreateInbox) =>\n client.post<Inbox>('/inboxes', data, InboxSchema),\n\n /**\n * List channels for an inbox\n * @see https://dev.frontapp.com/reference/list-inbox-channels\n */\n listChannels: (id: string) => client.get(`/inboxes/${id}/channels`),\n\n /**\n * List conversations for an inbox\n * @see https://dev.frontapp.com/reference/list-inbox-conversations\n */\n listConversations: (\n id: string,\n params?: { q?: string; limit?: number }\n ) => {\n const searchParams = new URLSearchParams()\n if (params?.q) searchParams.set('q', params.q)\n if (params?.limit) searchParams.set('limit', String(params.limit))\n const query = searchParams.toString()\n return client.get(\n `/inboxes/${id}/conversations${query ? `?${query}` : ''}`\n )\n },\n\n /**\n * List teammates for an inbox\n * @see https://dev.frontapp.com/reference/list-inbox-teammates\n */\n listTeammates: (id: string) => client.get(`/inboxes/${id}/teammates`),\n\n /**\n * Add teammates to an inbox\n * @see https://dev.frontapp.com/reference/add-inbox-teammates\n */\n addTeammate: (id: string, teammateId: string) =>\n client.post<void>(`/inboxes/${id}/teammates`, {\n teammate_ids: [teammateId],\n }),\n\n /**\n * Remove teammate from an inbox\n * @see https://dev.frontapp.com/reference/remove-inbox-teammates\n */\n removeTeammate: (id: string, teammateId: string) =>\n client.delete<void>(`/inboxes/${id}/teammates/${teammateId}`),\n }\n}\n\nexport type InboxesClient = ReturnType<typeof createInboxesClient>\n","import { MessageListSchema, MessageSchema } from '../schemas/message'\nimport type { CreateMessage, Message, MessageList } from '../schemas/message'\nimport type { BaseClient } from './base'\n\n/**\n * Create a messages client for Front API operations\n * Provides methods for fetching, creating, and managing messages\n */\nexport function createMessagesClient(client: BaseClient) {\n return {\n /**\n * Get a single message by ID\n * @param id - Message ID (msg_xxx)\n */\n get: (id: string) => client.get<Message>(`/messages/${id}`, MessageSchema),\n\n /**\n * Mark a message as seen\n * @param id - Message ID (msg_xxx)\n */\n markSeen: (id: string) => client.post<void>(`/messages/${id}/seen`, {}),\n\n /**\n * Get the seen status of a message\n * @param id - Message ID (msg_xxx)\n */\n getSeenStatus: (id: string) => client.get(`/messages/${id}/seen`),\n\n /**\n * Create a new message via a channel\n * @param channelId - Channel ID (cha_xxx)\n * @param data - Message data (to, body, subject, etc.)\n */\n create: (channelId: string, data: CreateMessage) =>\n client.post<Message>(\n `/channels/${channelId}/messages`,\n data,\n MessageSchema\n ),\n }\n}\n\n/**\n * Type for the messages client instance\n */\nexport type MessagesClient = ReturnType<typeof createMessagesClient>\n","import { TagListSchema, TagSchema } from '../schemas/tag'\nimport type { Tag, TagList } from '../schemas/tag'\nimport type { BaseClient } from './base'\n\n/**\n * Tags client for the Front API\n * Provides methods for managing tags\n *\n * Tags are used to categorize and organize conversations.\n * They can be private or public, and have optional highlighting.\n *\n * @see https://dev.frontapp.com/reference/tags\n */\nexport function createTagsClient(client: BaseClient) {\n return {\n /**\n * List all tags\n * @returns Paginated list of tags\n */\n list: () => client.get<TagList>('/tags', TagListSchema),\n\n /**\n * Get a specific tag by ID\n * @param id - Tag ID (e.g., \"tag_123\")\n * @returns Tag details\n */\n get: (id: string) => client.get<Tag>(`/tags/${id}`, TagSchema),\n\n /**\n * Create a new tag\n * @param data - Tag creation payload\n * @returns Created tag\n */\n create: (data: {\n name: string\n description?: string\n highlight?: string\n }) => client.post<Tag>('/tags', data, TagSchema),\n\n /**\n * Update an existing tag\n * @param id - Tag ID to update\n * @param data - Tag update payload\n * @returns Updated tag\n */\n update: (\n id: string,\n data: {\n name?: string\n description?: string | null\n highlight?: string | null\n }\n ) => client.patch<Tag>(`/tags/${id}`, data, TagSchema),\n\n /**\n * Delete a tag\n * @param id - Tag ID to delete\n * @returns void on success\n */\n delete: (id: string) => client.delete<void>(`/tags/${id}`),\n\n /**\n * List child tags of a parent tag\n * @param id - Parent tag ID\n * @returns Paginated list of child tags\n */\n listChildren: (id: string) =>\n client.get<TagList>(`/tags/${id}/children`, TagListSchema),\n\n /**\n * List conversations with this tag\n * @param id - Tag ID\n * @returns Conversations endpoint (raw response)\n */\n listConversations: (id: string) => client.get(`/tags/${id}/conversations`),\n }\n}\n","import {\n type Teammate,\n type TeammateList,\n TeammateListSchema,\n TeammateSchema,\n type UpdateTeammate,\n} from '../schemas/teammate'\nimport type { BaseClient } from './base'\n\n/**\n * Create a teammates client for Front API\n * Provides methods to manage teammates (users) in the workspace\n */\nexport function createTeammatesClient(client: BaseClient) {\n return {\n /**\n * List all teammates\n * @returns Paginated list of teammates\n */\n list: () => client.get<TeammateList>('/teammates', TeammateListSchema),\n\n /**\n * Get a teammate by ID\n * @param id - Teammate ID (e.g., \"tea_abc123\")\n * @returns Teammate details\n */\n get: (id: string) =>\n client.get<Teammate>(`/teammates/${id}`, TeammateSchema),\n\n /**\n * Update a teammate\n * @param id - Teammate ID\n * @param data - Partial teammate data to update\n * @returns Updated teammate\n */\n update: (id: string, data: UpdateTeammate) =>\n client.patch<Teammate>(`/teammates/${id}`, data, TeammateSchema),\n\n /**\n * List conversations assigned to a teammate\n * @param id - Teammate ID\n * @returns Paginated list of conversations (untyped - use conversations client for typed response)\n */\n listConversations: (id: string) =>\n client.get(`/teammates/${id}/conversations`),\n\n /**\n * List inboxes accessible to a teammate\n * @param id - Teammate ID\n * @returns Paginated list of inboxes (untyped - use inboxes client for typed response)\n */\n listInboxes: (id: string) => client.get(`/teammates/${id}/inboxes`),\n }\n}\n","import {\n MessageTemplateFolderListSchema,\n MessageTemplateFolderSchema,\n MessageTemplateListSchema,\n MessageTemplateSchema,\n} from '../schemas/template'\nimport type {\n CreateMessageTemplate,\n MessageTemplate,\n MessageTemplateFolder,\n MessageTemplateFolderList,\n MessageTemplateList,\n UpdateMessageTemplate,\n} from '../schemas/template'\nimport type { BaseClient } from './base'\n\n/**\n * Client for Front message templates API\n * Provides methods for managing message templates and folders\n */\nexport function createTemplatesClient(client: BaseClient) {\n return {\n /**\n * List all message templates\n * @returns Paginated list of message templates\n */\n list: () =>\n client.get<MessageTemplateList>(\n '/message_templates',\n MessageTemplateListSchema\n ),\n\n /**\n * Get a specific message template by ID\n * @param id - Template ID (rsp_xxx)\n * @returns Message template\n */\n get: (id: string) =>\n client.get<MessageTemplate>(\n `/message_templates/${id}`,\n MessageTemplateSchema\n ),\n\n /**\n * Create a new message template\n * @param data - Template data (name, subject, body, folder_id, inbox_ids)\n * @returns Created message template\n */\n create: (data: CreateMessageTemplate) =>\n client.post<MessageTemplate>(\n '/message_templates',\n data,\n MessageTemplateSchema\n ),\n\n /**\n * Update an existing message template\n * @param id - Template ID (rsp_xxx)\n * @param data - Fields to update\n * @returns Updated message template\n */\n update: (id: string, data: UpdateMessageTemplate) =>\n client.patch<MessageTemplate>(\n `/message_templates/${id}`,\n data,\n MessageTemplateSchema\n ),\n\n /**\n * Delete a message template\n * @param id - Template ID (rsp_xxx)\n */\n delete: (id: string) => client.delete<void>(`/message_templates/${id}`),\n\n // Folder operations\n\n /**\n * List all message template folders\n * @returns Paginated list of folders\n */\n listFolders: () =>\n client.get<MessageTemplateFolderList>(\n '/message_template_folders',\n MessageTemplateFolderListSchema\n ),\n\n /**\n * Get a specific folder by ID\n * @param id - Folder ID (fld_xxx)\n * @returns Message template folder\n */\n getFolder: (id: string) =>\n client.get<MessageTemplateFolder>(\n `/message_template_folders/${id}`,\n MessageTemplateFolderSchema\n ),\n\n /**\n * Create a new template folder\n * @param name - Folder name\n * @returns Created folder\n */\n createFolder: (name: string) =>\n client.post<MessageTemplateFolder>(\n '/message_template_folders',\n { name },\n MessageTemplateFolderSchema\n ),\n\n /**\n * Delete a template folder\n * @param id - Folder ID (fld_xxx)\n */\n deleteFolder: (id: string) =>\n client.delete<void>(`/message_template_folders/${id}`),\n }\n}\n","/**\n * @skillrecordings/front-sdk\n * Typed Front API SDK with Zod validation\n */\n\n// Common schemas\nexport {\n LinksSchema,\n PaginationSchema,\n PaginatedResponseSchema,\n ErrorResponseSchema,\n type Links,\n type Pagination,\n type PaginatedResponse,\n type ErrorResponse,\n} from './schemas/common.js'\n\n// Conversation schemas\nexport {\n RecipientSchema,\n ConversationStatusSchema,\n TagSchema as ConversationTagSchema,\n LinkSchema,\n AssigneeSchema,\n ReminderSchema,\n ConversationMetadataSchema,\n ConversationSchema,\n ConversationListSchema,\n UpdateConversationSchema,\n type Recipient,\n type ConversationStatus,\n type Tag as ConversationTag,\n type Link,\n type Assignee,\n type Reminder,\n type ConversationMetadata,\n type Conversation,\n type ConversationList,\n type UpdateConversation,\n} from './schemas/conversation.js'\n\n// Message schemas\nexport {\n AuthorSchema,\n AttachmentSchema,\n SignatureSchema,\n MessageSchema,\n MessageListSchema,\n CreateMessageSchema,\n type Author,\n type Attachment,\n type Signature,\n type Message,\n type MessageList,\n type CreateMessage,\n} from './schemas/message.js'\n\n// Draft schemas\nexport {\n DraftSchema,\n DraftListSchema,\n CreateDraftSchema,\n EditDraftSchema,\n type Draft,\n type DraftList,\n type CreateDraft,\n type EditDraft,\n} from './schemas/draft.js'\n\n// Template schemas\nexport {\n MessageTemplateSchema,\n MessageTemplateListSchema,\n MessageTemplateFolderSchema,\n MessageTemplateFolderListSchema,\n CreateMessageTemplateSchema,\n UpdateMessageTemplateSchema,\n type MessageTemplate,\n type MessageTemplateList,\n type MessageTemplateFolder,\n type MessageTemplateFolderList,\n type CreateMessageTemplate,\n type UpdateMessageTemplate,\n} from './schemas/template.js'\n\n// Tag schemas\nexport {\n TagHighlightSchema,\n TagSchema,\n TagListSchema,\n CreateTagSchema,\n UpdateTagSchema,\n type Tag,\n type TagList,\n} from './schemas/tag.js'\n\n// Inbox schemas\nexport {\n InboxSchema,\n InboxListSchema,\n CreateInboxSchema,\n type Inbox,\n type InboxList,\n type CreateInbox,\n} from './schemas/inbox.js'\n\n// Channel schemas\nexport {\n ChannelTypeSchema,\n ChannelSchema,\n ChannelListSchema,\n CreateChannelSchema,\n UpdateChannelSchema,\n type Channel,\n type ChannelList,\n} from './schemas/channel.js'\n\n// Contact schemas\nexport {\n ContactHandleSourceSchema,\n ContactHandleSchema,\n ContactGroupSchema,\n ContactSchema,\n ContactListSchema,\n CreateContactSchema,\n UpdateContactSchema,\n type ContactHandle,\n type Contact,\n type ContactList,\n} from './schemas/contact.js'\n\n// Teammate schemas\nexport {\n TeammateSchema,\n TeammateListSchema,\n UpdateTeammateSchema,\n type Teammate,\n type TeammateList,\n} from './schemas/teammate.js'\n\n// Export base client utilities\nexport {\n FRONT_API_BASE,\n FrontApiError,\n createBaseClient,\n type FrontClientConfig,\n type BaseClient,\n} from './client/base.js'\n\n// Export individual client factories (for instrumented clients)\nexport { createChannelsClient } from './client/channels.js'\nexport { createContactsClient } from './client/contacts.js'\nexport { createConversationsClient } from './client/conversations.js'\nexport { createDraftsClient } from './client/drafts.js'\nexport { createInboxesClient } from './client/inboxes.js'\nexport { createMessagesClient } from './client/messages.js'\nexport { createTagsClient } from './client/tags.js'\nexport { createTeammatesClient } from './client/teammates.js'\nexport { createTemplatesClient } from './client/templates.js'\n\n// Import client factories\nimport { type FrontClientConfig, createBaseClient } from './client/base.js'\nimport { createChannelsClient } from './client/channels.js'\nimport { createContactsClient } from './client/contacts.js'\nimport { createConversationsClient } from './client/conversations.js'\nimport { createDraftsClient } from './client/drafts.js'\nimport { createInboxesClient } from './client/inboxes.js'\nimport { createMessagesClient } from './client/messages.js'\nimport { createTagsClient } from './client/tags.js'\nimport { createTeammatesClient } from './client/teammates.js'\nimport { createTemplatesClient } from './client/templates.js'\n\n/**\n * Pagination helper that automatically follows _pagination.next links.\n * Collects all results across pages into a single array.\n *\n * @example\n * ```ts\n * const allConversations = await paginate(\n * () => front.conversations.list(),\n * (url) => front.conversations.listFromUrl(url)\n * )\n * ```\n */\nexport async function paginate<\n T extends { _results: unknown[]; _pagination?: { next?: string | null } },\n>(\n firstPage: () => Promise<T>,\n getPage: (url: string) => Promise<T>\n): Promise<T['_results']> {\n const results: T['_results'] = []\n let page = await firstPage()\n results.push(...page._results)\n\n while (page._pagination?.next) {\n page = await getPage(page._pagination.next)\n results.push(...page._results)\n }\n\n return results\n}\n\n/**\n * Create a fully typed Front API client.\n *\n * @example\n * ```ts\n * const front = createFrontClient({ apiToken: 'xxx' })\n *\n * // Get a conversation\n * const conv = await front.conversations.get('cnv_xxx')\n *\n * // List messages\n * const messages = await front.conversations.listMessages('cnv_xxx')\n *\n * // Create a draft\n * const draft = await front.drafts.createReply('cnv_xxx', {\n * body: 'Hello!',\n * channel_id: 'cha_xxx',\n * })\n * ```\n */\nexport function createFrontClient(config: FrontClientConfig) {\n const baseClient = createBaseClient(config)\n\n return {\n /** Raw HTTP methods for custom requests */\n raw: baseClient,\n\n /** Conversation operations */\n conversations: createConversationsClient(baseClient),\n\n /** Message operations */\n messages: createMessagesClient(baseClient),\n\n /** Draft operations */\n drafts: createDraftsClient(baseClient),\n\n /** Message template operations */\n templates: createTemplatesClient(baseClient),\n\n /** Tag operations */\n tags: createTagsClient(baseClient),\n\n /** Inbox operations */\n inboxes: createInboxesClient(baseClient),\n\n /** Channel operations */\n channels: createChannelsClient(baseClient),\n\n /** Contact operations */\n contacts: createContactsClient(baseClient),\n\n /** Teammate operations */\n teammates: createTeammatesClient(baseClient),\n }\n}\n\n/** Type for the full Front client instance */\nexport type FrontClient = ReturnType<typeof createFrontClient>\n","/**\n * Step 4: DRAFT\n *\n * Generates response using gathered context.\n * Before LLM generation, checks for high-confidence template matches.\n *\n * Flow:\n * 1. Check for template match (>threshold confidence)\n * 2. If match: Use template with variable interpolation\n * 3. If no match: Query memory for relevant past decisions, then generate with LLM\n */\n\nimport { generateText } from 'ai'\nimport {\n type RelevantMemory,\n citeMemories,\n formatMemoriesForPrompt,\n queryMemoriesForStage,\n} from '../../memory/query'\nimport { log, traceDraftCreation } from '../../observability/axiom'\nimport {\n type TemplateMatch,\n buildTemplateVariables,\n createTemplateUsageLog,\n interpolateTemplate,\n logTemplateUsage,\n matchTemplate,\n} from '../../templates/match'\nimport type {\n DraftInput,\n DraftOutput,\n GatherOutput,\n MessageCategory,\n} from '../types'\nimport { buildCategoryPrompt } from './draft-prompts'\nimport { formatContextForPrompt } from './gather'\n\n// ============================================================================\n// Draft prompts\n// ============================================================================\n// Category-specific prompts are now in ./draft-prompts.ts which builds them\n// dynamically from gathered context (refund policy, invoice URLs, promotions,\n// license info, etc.). See buildCategoryPrompt() for the implementation.\n//\n// The old hardcoded prompts (with \"30 days\", \"totaltypescript.com/invoices\")\n// are replaced by dynamic versions that read from context with fallback defaults.\n//\n// The PROMPT_OVERRIDES map below allows runtime overrides via setPromptForCategory().\n// When set, the override takes precedence over the dynamic prompt.\n\n/** Runtime prompt overrides set via setPromptForCategory() */\nconst PROMPT_OVERRIDES: Partial<Record<MessageCategory, string>> = {}\n\n// ============================================================================\n// Main draft function\n// ============================================================================\n\nexport interface DraftOptions {\n model?: string\n promptOverride?: string\n /** App ID for template matching (required for template lookup) */\n appId?: string\n /** Confidence threshold for template matching (default: 0.9) */\n templateThreshold?: number\n /** Skip template matching and always use LLM */\n skipTemplateMatch?: boolean\n /** Skip memory query (for testing or when memory service unavailable) */\n skipMemoryQuery?: boolean\n /** Pipeline run ID for memory citation tracking */\n runId?: string\n /** Conversation ID for correction tracking */\n conversationId?: string\n /**\n * Use agent mode with tools (runSupportAgent) instead of raw generateText.\n * When true, the agent can use tools like processRefund, transferPurchase, etc.\n * Tool calls that require approval will be captured in the output.\n */\n useAgentMode?: boolean\n /** Customer email for agent context */\n customerEmail?: string\n /** Customer name for agent context */\n customerName?: string\n}\n\nexport interface DraftResult extends DraftOutput {\n /** Template used if matched, undefined if LLM generated */\n templateUsed?: TemplateMatch\n /** Memories that were cited in this draft */\n memoriesCited?: RelevantMemory[]\n}\n\nexport async function draft(\n input: DraftInput,\n options: DraftOptions = {}\n): Promise<DraftResult> {\n const {\n model = 'anthropic/claude-haiku-4-5',\n promptOverride,\n appId,\n templateThreshold = 0.9,\n skipTemplateMatch = false,\n skipMemoryQuery = false,\n runId,\n conversationId,\n } = options\n const { message, classification, context } = input\n\n const startTime = Date.now()\n\n await log('debug', 'draft started', {\n workflow: 'pipeline',\n step: 'draft',\n appId,\n conversationId,\n category: classification.category,\n messageLength: message.body.length,\n skipTemplateMatch,\n skipMemoryQuery,\n })\n\n // ─────────────────────────────────────────────────────────────────────────\n // Step 1: Try template matching first (if enabled)\n // ─────────────────────────────────────────────────────────────────────────\n if (!skipTemplateMatch && appId) {\n try {\n const query = `${message.subject} ${message.body}`.slice(0, 500)\n const matchResult = await matchTemplate({\n appId,\n category: classification.category,\n context,\n query,\n threshold: templateThreshold,\n })\n\n if (matchResult.match) {\n // Found high-confidence template match!\n const variables = buildTemplateVariables(context)\n const interpolatedContent = interpolateTemplate(\n matchResult.match.content,\n variables\n )\n\n // Log usage for analytics\n logTemplateUsage(\n createTemplateUsageLog(\n appId,\n classification.category,\n matchResult.match\n )\n )\n\n const durationMs = Date.now() - startTime\n\n // High-cardinality decision-point logging for template match\n await log('info', 'draft:decision', {\n workflow: 'pipeline',\n step: 'draft',\n appId,\n conversationId,\n // Decision outcome\n category: classification.category,\n decisionPath: 'template',\n // Template match details\n templateName: matchResult.match.name,\n templateConfidence: matchResult.match.confidence,\n templateThreshold,\n confidenceAboveThreshold:\n matchResult.match.confidence >= templateThreshold,\n // Draft result\n draftLength: interpolatedContent.length,\n variablesInterpolated: Object.keys(variables).length,\n // Decision inputs\n hasContext: !!context,\n hasUser: !!context.user,\n hasPurchases: context.purchases.length > 0,\n hasKnowledge: context.knowledge.length > 0,\n // Why this path\n usedTemplate: true,\n usedMemory: false,\n usedLLM: false,\n usedAgentMode: false,\n durationMs,\n })\n\n return {\n draft: interpolatedContent,\n reasoning: `Template match: \"${matchResult.match.name}\" (confidence: ${matchResult.match.confidence.toFixed(3)})`,\n toolsUsed: ['template_match'],\n durationMs,\n templateUsed: matchResult.match,\n }\n }\n\n // Log that we're falling back to LLM\n logTemplateUsage(\n createTemplateUsageLog(appId, classification.category, null)\n )\n } catch (error) {\n // Template matching failed, fall back to LLM silently\n await log('warn', 'draft template matching failed', {\n workflow: 'pipeline',\n step: 'draft',\n appId,\n conversationId,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Step 2: Query memory for relevant past decisions\n // ─────────────────────────────────────────────────────────────────────────\n let memories: RelevantMemory[] = []\n let memoryContext = ''\n\n if (!skipMemoryQuery && appId) {\n try {\n // Build situation context for memory query\n const situation = buildDraftSituation(\n classification.category,\n context,\n message\n )\n\n memories = await queryMemoriesForStage({\n appId,\n stage: 'draft',\n situation,\n category: classification.category,\n limit: 5,\n threshold: 0.6,\n })\n\n if (memories.length > 0) {\n memoryContext = formatMemoriesForPrompt(memories)\n\n await log('debug', 'draft memory query results', {\n workflow: 'pipeline',\n step: 'draft',\n appId,\n conversationId,\n memoriesFound: memories.length,\n topScore: memories[0]?.score ?? 0,\n memoryIds: memories.map((m) => m.id),\n })\n\n // Record citation for feedback tracking\n if (runId) {\n const memoryIds = memories.map((m) => m.id)\n await citeMemories(memoryIds, runId, appId).catch((err) => {\n log('warn', 'draft failed to record memory citations', {\n workflow: 'pipeline',\n step: 'draft',\n appId,\n error: err instanceof Error ? err.message : String(err),\n }).catch(() => {})\n })\n }\n }\n } catch (error) {\n // Memory query failed, continue without memory context\n await log('warn', 'draft memory query failed', {\n workflow: 'pipeline',\n step: 'draft',\n appId,\n conversationId,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Step 3: Generate with LLM or Agent (with memory context if available)\n // ─────────────────────────────────────────────────────────────────────────\n\n const { useAgentMode = false, customerEmail, customerName } = options\n\n // Build prompt — check runtime overrides first, then use dynamic\n // context-aware prompts that inject refund policy, invoice URLs,\n // promotions, license info, etc.\n const categoryPrompt =\n PROMPT_OVERRIDES[classification.category] ||\n buildCategoryPrompt(classification.category, context)\n const systemPrompt = promptOverride || categoryPrompt\n\n // Format context\n const contextSection = formatContextForPrompt(context)\n\n // Build user message with optional memory context\n const userMessage = `${contextSection}\n${memoryContext ? `\\n${memoryContext}\\n` : ''}\n## Customer Message\nSubject: ${message.subject}\n\n${message.body}\n\n---\nWrite your response:`\n\n // ─────────────────────────────────────────────────────────────────────────\n // Agent mode: Use runSupportAgent with tools (for HITL approval flow)\n // ─────────────────────────────────────────────────────────────────────────\n if (useAgentMode && appId) {\n const { runSupportAgent } = await import('../../agent/config')\n\n await log('debug', 'draft using agent mode', {\n workflow: 'pipeline',\n step: 'draft',\n appId,\n conversationId,\n category: classification.category,\n })\n\n // Build customer context for the agent\n const customerContext: {\n email?: string\n name?: string\n purchases?: Array<{ id: string; product: string; date: string }>\n } = {}\n\n if (customerEmail || context.user?.email) {\n customerContext.email = customerEmail ?? context.user?.email\n }\n if (customerName || context.user?.name) {\n customerContext.name = customerName ?? context.user?.name\n }\n if (context.purchases.length > 0) {\n customerContext.purchases = context.purchases.map((p) => ({\n id: p.id,\n product: p.productName,\n date: p.purchasedAt,\n }))\n }\n\n // Run the support agent with tools\n const agentResult = await runSupportAgent({\n message: userMessage,\n conversationHistory: [],\n customerContext,\n appId,\n model: model as\n | 'anthropic/claude-haiku-4-5'\n | 'anthropic/claude-sonnet-4-5'\n | 'anthropic/claude-opus-4-5',\n priorKnowledge: memoryContext || undefined,\n })\n\n const toolsUsed = memories.length > 0 ? ['memory_query'] : []\n // Add tool names from agent result\n if (agentResult.toolCalls.length > 0) {\n toolsUsed.push(...agentResult.toolCalls.map((tc) => tc.name))\n }\n\n const durationMs = Date.now() - startTime\n\n // High-cardinality decision-point logging for agent mode\n await log('info', 'draft:decision', {\n workflow: 'pipeline',\n step: 'draft',\n appId,\n conversationId,\n // Decision outcome\n category: classification.category,\n decisionPath: 'agent',\n // Draft result\n draftLength: agentResult.response.trim().length,\n // Agent mode details\n toolCallCount: agentResult.toolCalls.length,\n toolsInvoked: agentResult.toolCalls.map((tc) => tc.name),\n requiresApproval: agentResult.requiresApproval,\n autoSent: agentResult.autoSent,\n hasReasoning: !!agentResult.reasoning,\n // Memory context\n usedMemory: memories.length > 0,\n memoriesCited: memories.length,\n citedMemoryIds: memories.map((m) => m.id),\n topMemoryScore: memories[0]?.score,\n // Decision inputs\n hasContext: !!context,\n hasUser: !!context.user,\n hasPurchases: context.purchases.length > 0,\n hasKnowledge: context.knowledge.length > 0,\n hasCustomerEmail: !!customerEmail,\n hasCustomerName: !!customerName,\n // Why this path\n usedTemplate: false,\n usedLLM: true,\n usedAgentMode: true,\n model,\n durationMs,\n })\n\n // Extract draft: prefer response text, but fall back to draftResponse tool call result\n // This handles cases where the model uses the tool but doesn't produce additional text\n let draftContent = agentResult.response.trim()\n if (!draftContent && agentResult.toolCalls.length > 0) {\n const draftToolCall = agentResult.toolCalls.find(\n (tc) => tc.name === 'draftResponse'\n )\n const draftResult = draftToolCall?.result as\n | { body?: string; drafted?: boolean }\n | undefined\n if (draftResult?.body) {\n draftContent = draftResult.body\n await log('debug', 'draft extracted from draftResponse tool call', {\n workflow: 'pipeline',\n step: 'draft',\n appId,\n conversationId,\n draftLength: draftContent.length,\n })\n }\n }\n\n return {\n draft: draftContent,\n reasoning:\n agentResult.reasoning ??\n (memories.length > 0\n ? `Used ${memories.length} relevant memories for context`\n : undefined),\n toolsUsed,\n durationMs,\n templateUsed: undefined,\n memoriesCited: memories.length > 0 ? memories : undefined,\n // New fields for HITL approval\n toolCalls:\n agentResult.toolCalls.length > 0\n ? agentResult.toolCalls.map((tc) => ({\n name: tc.name,\n args: tc.args,\n result: tc.result,\n }))\n : undefined,\n requiresApproval: agentResult.requiresApproval,\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Standard mode: Raw LLM generation (no tools)\n // ─────────────────────────────────────────────────────────────────────────\n\n // Generate\n const result = await generateText({\n model,\n system: systemPrompt,\n messages: [{ role: 'user', content: userMessage }],\n })\n\n const toolsUsed = memories.length > 0 ? ['memory_query'] : []\n const durationMs = Date.now() - startTime\n\n // High-cardinality decision-point logging for LLM mode\n await log('info', 'draft:decision', {\n workflow: 'pipeline',\n step: 'draft',\n appId,\n conversationId,\n // Decision outcome\n category: classification.category,\n decisionPath: 'llm',\n // Draft result\n draftLength: result.text.trim().length,\n // Memory context\n usedMemory: memories.length > 0,\n memoriesCited: memories.length,\n citedMemoryIds: memories.map((m) => m.id),\n topMemoryScore: memories[0]?.score,\n // Decision inputs\n hasContext: !!context,\n hasUser: !!context.user,\n hasPurchases: context.purchases.length > 0,\n hasKnowledge: context.knowledge.length > 0,\n hasPromptOverride: !!promptOverride,\n // Why this path (not template, not agent)\n templateSkipped: skipTemplateMatch,\n templateNotMatched: !skipTemplateMatch,\n agentModeDisabled: !useAgentMode,\n usedTemplate: false,\n usedLLM: true,\n usedAgentMode: false,\n model,\n durationMs,\n })\n\n return {\n draft: result.text.trim(),\n reasoning:\n memories.length > 0\n ? `Used ${memories.length} relevant memories for context`\n : undefined,\n toolsUsed,\n durationMs,\n templateUsed: undefined,\n memoriesCited: memories.length > 0 ? memories : undefined,\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Get the prompt for a category. Returns any runtime override first,\n * then falls back to the dynamic context-aware prompt (with empty context\n * for the static fallback case).\n */\nexport function getPromptForCategory(category: MessageCategory): string {\n return (\n PROMPT_OVERRIDES[category] ||\n buildCategoryPrompt(category, {\n user: null,\n purchases: [],\n knowledge: [],\n history: [],\n priorMemory: [],\n priorConversations: [],\n gatherErrors: [],\n })\n )\n}\n\n/**\n * Override the prompt for a specific category at runtime.\n * Overrides take precedence over dynamic prompts.\n */\nexport function setPromptForCategory(\n category: MessageCategory,\n prompt: string\n): void {\n PROMPT_OVERRIDES[category] = prompt\n}\n\n/**\n * Build a situation string for memory query from draft context.\n * This captures the key elements that make situations similar.\n */\nfunction buildDraftSituation(\n category: MessageCategory,\n context: GatherOutput,\n message: { subject: string; body: string }\n): string {\n const parts: string[] = []\n\n // Category is primary signal\n parts.push(`Category: ${category}`)\n\n // Customer context if available\n if (context.user?.email) {\n parts.push(`Customer: ${context.user.email}`)\n }\n\n // Purchase context\n if (context.purchases.length > 0) {\n const purchaseInfo = context.purchases\n .slice(0, 2)\n .map((p) => p.productName)\n .join(', ')\n parts.push(`Products: ${purchaseInfo}`)\n }\n\n // Message summary (truncated)\n const messagePreview = `${message.subject} ${message.body}`.slice(0, 200)\n parts.push(`Issue: ${messagePreview}`)\n\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Draft Correction Storage\n// ============================================================================\n\nexport interface StoreDraftCorrectionInput {\n /** App identifier */\n appId: string\n /** Original draft generated by the agent */\n originalDraft: string\n /** Final draft after human editing */\n finalDraft: string\n /** Category of the support request */\n category: MessageCategory\n /** Conversation ID for tracking */\n conversationId: string\n /** Brief context summary (from gather step) */\n contextSummary: string\n /** Run ID for linking to cited memories */\n runId?: string\n /** Memory IDs that were cited in the original draft */\n citedMemoryIds?: string[]\n}\n\n/**\n * Store a draft correction when a human edits an agent-generated draft.\n *\n * This creates a memory for future reference:\n * - Stores what was drafted vs what was actually sent\n * - Marks outcome as 'corrected' so future drafts can learn\n * - Updates cited memories' success rate (if tracking enabled)\n *\n * Call this from webhook/event handlers when:\n * - Agent drafts a response\n * - Human edits it before sending\n * - The edited version is meaningfully different\n *\n * @example\n * ```typescript\n * // When human edits and sends\n * if (isDifferent(agentDraft, finalMessage)) {\n * await storeDraftCorrection({\n * appId: 'total-typescript',\n * originalDraft: agentDraft,\n * finalDraft: finalMessage,\n * category: 'support_refund',\n * conversationId: '123',\n * contextSummary: 'Customer requested refund after 60 days',\n * })\n * }\n * ```\n */\nexport async function storeDraftCorrection(\n input: StoreDraftCorrectionInput\n): Promise<void> {\n const { SupportMemoryService } = await import(\n '@skillrecordings/memory/support-memory'\n )\n\n const {\n appId,\n originalDraft,\n finalDraft,\n category,\n conversationId,\n contextSummary,\n runId,\n citedMemoryIds,\n } = input\n\n // Store the correction as a memory\n await SupportMemoryService.store({\n app_slug: appId,\n situation: `Category: ${category}\\nContext: ${contextSummary}`,\n decision: `Draft: ${originalDraft}`,\n stage: 'draft',\n outcome: 'corrected',\n correction: `Edited to: ${finalDraft}`,\n category,\n conversation_id: conversationId,\n })\n\n // If we have cited memory IDs, record that those memories led to a correction\n if (runId && citedMemoryIds && citedMemoryIds.length > 0) {\n try {\n const { recordCitationOutcome } = await import('../../memory/query')\n await recordCitationOutcome(citedMemoryIds, runId, 'failure', appId)\n } catch (error) {\n console.warn(\n '[storeDraftCorrection] Failed to record citation outcomes:',\n error\n )\n }\n }\n}\n\n/**\n * Store a successful draft (human approved without edits).\n *\n * Call this when a human sends an agent-drafted response without changes.\n * This reinforces the memory and updates cited memories' success rate.\n *\n * @example\n * ```typescript\n * // When human sends without edits\n * if (!isDifferent(agentDraft, finalMessage)) {\n * await storeDraftSuccess({\n * appId: 'total-typescript',\n * draft: agentDraft,\n * category: 'support_access',\n * conversationId: '123',\n * contextSummary: 'Customer couldn't log in',\n * })\n * }\n * ```\n */\nexport async function storeDraftSuccess(input: {\n appId: string\n draft: string\n category: MessageCategory\n conversationId: string\n contextSummary: string\n runId?: string\n citedMemoryIds?: string[]\n}): Promise<void> {\n const { SupportMemoryService } = await import(\n '@skillrecordings/memory/support-memory'\n )\n\n const {\n appId,\n draft,\n category,\n conversationId,\n contextSummary,\n runId,\n citedMemoryIds,\n } = input\n\n // Store the successful decision\n await SupportMemoryService.store({\n app_slug: appId,\n situation: `Category: ${category}\\nContext: ${contextSummary}`,\n decision: `Draft: ${draft}`,\n stage: 'draft',\n outcome: 'success',\n category,\n conversation_id: conversationId,\n })\n\n // Record success for cited memories\n if (runId && citedMemoryIds && citedMemoryIds.length > 0) {\n try {\n const { recordCitationOutcome } = await import('../../memory/query')\n await recordCitationOutcome(citedMemoryIds, runId, 'success', appId)\n } catch (error) {\n console.warn(\n '[storeDraftSuccess] Failed to record citation outcomes:',\n error\n )\n }\n }\n}\n","/**\n * Template matching module for finding and using canned responses.\n *\n * Before generating an LLM draft, checks if a high-confidence template\n * match exists in the vector store. If found with sufficient confidence,\n * uses the template instead of generating a fresh response.\n */\n\nimport type { GatherOutput } from '../pipeline/types'\nimport { queryVectors } from '../vector/client'\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface TemplateMatch {\n templateId: string\n content: string\n name: string\n confidence: number\n frontId: string // Original Front template ID\n}\n\nexport interface MatchTemplateOptions {\n /** App ID for scoping template search */\n appId: string\n /** Message category for relevance */\n category: string\n /** Gathered context including user/purchase info */\n context: GatherOutput\n /** Query text (usually message subject + body) */\n query: string\n /** Minimum confidence threshold (default 0.9) */\n threshold?: number\n /** Maximum templates to consider (default 5) */\n topK?: number\n}\n\nexport interface TemplateMatchResult {\n match: TemplateMatch | null\n /** All candidates considered (for debugging/analytics) */\n candidates: Array<{\n templateId: string\n name: string\n score: number\n }>\n /** Time taken to match in ms */\n durationMs: number\n}\n\nexport interface TemplateUsageLog {\n type: 'template_match' | 'llm_generation'\n templateId?: string\n templateName?: string\n confidence?: number\n appId: string\n category: string\n timestamp: number\n}\n\n// ============================================================================\n// Template Matching\n// ============================================================================\n\n/**\n * Attempt to find a high-confidence template match for the given query.\n *\n * Searches the vector store for templates (type='response', source='canned-response')\n * and returns the best match if it exceeds the confidence threshold.\n *\n * @param options - Match options including query, appId, and threshold\n * @returns TemplateMatch if found above threshold, null otherwise\n *\n * @example\n * ```ts\n * const result = await matchTemplate({\n * appId: 'total-typescript',\n * category: 'support_access',\n * context: gatherResult,\n * query: 'I cannot access my course',\n * threshold: 0.9\n * })\n *\n * if (result.match) {\n * // Use template instead of LLM\n * const response = interpolateTemplate(result.match.content, {\n * customer_name: context.user?.name || 'there'\n * })\n * }\n * ```\n */\nexport async function matchTemplate(\n options: MatchTemplateOptions\n): Promise<TemplateMatchResult> {\n const { appId, query, threshold = 0.9, topK = 5 } = options\n\n const startTime = Date.now()\n\n // Build filter for templates only\n // Filter: type='response' AND source='canned-response' AND appId matches\n const filter = `type = 'response' AND source = 'canned-response' AND appId = '${appId}'`\n\n const results = await queryVectors({\n data: query,\n topK,\n includeMetadata: true,\n includeData: true,\n filter,\n })\n\n const candidates = results.map((r) => ({\n templateId: r.id,\n name: r.metadata?.title || 'Unknown',\n score: r.score,\n }))\n\n // Find best match above threshold\n const bestMatch = results.find((r) => r.score >= threshold)\n\n const match: TemplateMatch | null = bestMatch\n ? {\n templateId: bestMatch.id,\n content: bestMatch.data || '',\n name: bestMatch.metadata?.title || 'Unknown',\n confidence: bestMatch.score,\n // Extract Front ID from the vector ID (format: front_template_{id})\n frontId: extractFrontId(bestMatch.id),\n }\n : null\n\n return {\n match,\n candidates,\n durationMs: Date.now() - startTime,\n }\n}\n\n/**\n * Extract the original Front template ID from the vector document ID.\n * Vector IDs are stored as `front_template_{frontId}`.\n */\nfunction extractFrontId(vectorId: string): string {\n const prefix = 'front_template_'\n if (vectorId.startsWith(prefix)) {\n return vectorId.slice(prefix.length)\n }\n return vectorId\n}\n\n// ============================================================================\n// Template Interpolation\n// ============================================================================\n\n/**\n * Standard template variables that can be interpolated.\n */\nexport interface TemplateVariables {\n customer_name?: string\n product_name?: string\n email?: string\n [key: string]: string | undefined\n}\n\n/**\n * Build template variables from gathered context.\n *\n * @param context - Gathered context from pipeline\n * @returns Variables ready for interpolation\n */\nexport function buildTemplateVariables(\n context: GatherOutput\n): TemplateVariables {\n return {\n customer_name: context.user?.name || undefined,\n email: context.user?.email || undefined,\n product_name: context.purchases[0]?.productName || undefined,\n }\n}\n\n/**\n * Interpolate template variables in content.\n *\n * Replaces `{{variable_name}}` patterns with corresponding values.\n * Unknown variables are left as-is to avoid breaking templates.\n *\n * @param content - Template content with {{variable}} placeholders\n * @param variables - Key-value pairs for substitution\n * @returns Interpolated content\n *\n * @example\n * ```ts\n * const result = interpolateTemplate(\n * 'Hi {{customer_name}}, your {{product_name}} purchase is confirmed.',\n * { customer_name: 'Joel', product_name: 'Total TypeScript' }\n * )\n * // => 'Hi Joel, your Total TypeScript purchase is confirmed.'\n * ```\n */\nexport function interpolateTemplate(\n content: string,\n variables: TemplateVariables\n): string {\n // Match {{variable_name}} patterns (with optional whitespace)\n return content.replace(/\\{\\{\\s*(\\w+)\\s*\\}\\}/g, (match, varName) => {\n const value = variables[varName]\n // Return the value if defined, otherwise keep the placeholder\n return value !== undefined ? value : match\n })\n}\n\n/**\n * Check if a template has unresolved variables after interpolation.\n *\n * @param content - Interpolated content\n * @returns Array of unresolved variable names\n */\nexport function findUnresolvedVariables(content: string): string[] {\n const matches = content.match(/\\{\\{\\s*(\\w+)\\s*\\}\\}/g) || []\n return matches.map((m) => m.replace(/\\{\\{\\s*|\\s*\\}\\}/g, ''))\n}\n\n// ============================================================================\n// Usage Tracking\n// ============================================================================\n\n/**\n * Log template usage for analytics.\n *\n * This function logs whether a template was used vs LLM generation.\n * In production, this could write to a database, send to analytics, etc.\n *\n * @param log - Usage log entry\n */\nexport function logTemplateUsage(log: TemplateUsageLog): void {\n // For now, just console log. In production, send to analytics service.\n if (log.type === 'template_match') {\n console.log(\n `[template-usage] TEMPLATE_MATCH appId=${log.appId} category=${log.category} ` +\n `templateId=${log.templateId} templateName=\"${log.templateName}\" ` +\n `confidence=${log.confidence?.toFixed(3)}`\n )\n } else {\n console.log(\n `[template-usage] LLM_GENERATION appId=${log.appId} category=${log.category}`\n )\n }\n}\n\n/**\n * Create a usage log entry for template match.\n */\nexport function createTemplateUsageLog(\n appId: string,\n category: string,\n match: TemplateMatch | null\n): TemplateUsageLog {\n if (match) {\n return {\n type: 'template_match',\n templateId: match.templateId,\n templateName: match.name,\n confidence: match.confidence,\n appId,\n category,\n timestamp: Date.now(),\n }\n }\n return {\n type: 'llm_generation',\n appId,\n category,\n timestamp: Date.now(),\n }\n}\n","/**\n * Dynamic draft prompts per category.\n *\n * Replaces hardcoded URLs and policy numbers with data gathered from\n * the app's SDK (refund policy, invoice URLs, promotions, license info).\n * Falls back to sensible defaults when SDK data isn't available.\n */\n\nimport type { GatherOutput, MessageCategory } from '../types'\n\n// ============================================================================\n// Base prompt (shared across all categories)\n// ============================================================================\n\nexport const BASE_DRAFT_PROMPT = `You are a support agent. Write a helpful response to the customer.\n\n## Style Guide\n- Be direct and concise\n- No corporate speak\n- No enthusiasm performance (\"Great!\", \"Happy to help!\")\n- Get to the point immediately\n- If you need info, just ask - no softening\n- 2-3 short paragraphs max\n\n## NEVER Use These Phrases\n- \"Great!\" or exclamatory openers\n- \"I'd recommend\" or \"I'd suggest\"\n- \"Let me know if you have any other questions\"\n- \"I hope this helps\"\n- \"Happy to help\"\n- \"I understand\" or \"I hear you\"\n- \"Thanks for reaching out\"\n- Em dashes (—)\n\n## If You Don't Have Info\nDon't make things up. If knowledge base has no answer:\n- Ask a clarifying question\n- Or say you'll look into it and follow up\n\nWrite your response now. Just the response text, nothing else.`\n\n// ============================================================================\n// Dynamic prompt builders (per category)\n// ============================================================================\n\n/**\n * Build the `support_refund` prompt with dynamic refund policy windows.\n *\n * Replaces hardcoded \"30 days\" / \"30-45 days\" / \"45 days\" with values\n * from the app's refund policy when available.\n */\nfunction buildRefundPrompt(context: GatherOutput): string {\n const policy = context.refundPolicy\n const autoWindow = policy?.autoApproveWindowDays ?? 30\n const manualWindow = policy?.manualApproveWindowDays ?? 45\n\n let policySection = `## Refund Requests\n- If within ${autoWindow} days: process it, say it's done\n- If ${autoWindow}-${manualWindow} days: say you'll submit for approval\n- If over ${manualWindow} days: explain policy but offer to escalate\n- Be matter-of-fact, not apologetic`\n\n // Append special conditions if the app has them\n if (policy?.specialConditions && policy.specialConditions.length > 0) {\n policySection += `\\n\\n### Special Conditions\\n${policy.specialConditions.map((c) => `- ${c}`).join('\\n')}`\n }\n\n // Link to policy page if available\n if (policy?.policyUrl) {\n policySection += `\\n\\nFull refund policy: ${policy.policyUrl}`\n }\n\n return `${BASE_DRAFT_PROMPT}\\n\\n${policySection}`\n}\n\n/**\n * Build the `support_billing` prompt with dynamic invoice URL.\n *\n * Replaces hardcoded `totaltypescript.com/invoices` with the app-specific\n * invoices URL when available.\n */\nfunction buildBillingPrompt(context: GatherOutput): string {\n const invoicesUrl =\n context.appInfo?.invoicesUrl ?? 'https://www.totaltypescript.com/invoices'\n\n return `${BASE_DRAFT_PROMPT}\n\n## Billing/Invoice\n- Point them to the invoices page: ${invoicesUrl}\n- Invoices are customizable - they can add company/tax info\n- PDFs are editable if they need adjustments`\n}\n\n/**\n * Build the `presales_faq` prompt with promotion/discount data.\n *\n * When the gather step fetched active promotions, injects them into\n * the prompt so the agent can answer pricing questions.\n */\nfunction buildPresalesFaqPrompt(context: GatherOutput): string {\n let promotionsSection = ''\n\n if (context.activePromotions && context.activePromotions.length > 0) {\n const promoLines = context.activePromotions.map((p) => {\n const discount =\n p.discountType === 'percent'\n ? `${p.discountAmount}% off`\n : `$${(p.discountAmount / 100).toFixed(2)} off`\n const validity = p.validUntil ? ` (until ${p.validUntil})` : ''\n const code = p.code ? ` — code: ${p.code}` : ''\n const conditions = p.conditions ? ` [${p.conditions}]` : ''\n return `- **${p.name}**: ${discount}${validity}${code}${conditions}`\n })\n\n promotionsSection = `\n\n## Current Promotions & Discounts\nThe following promotions are currently active:\n${promoLines.join('\\n')}\n\nUse this data to answer pricing, discount, and coupon questions. If a customer asks about a discount not listed here, let them know about what IS available or say you'll check with the team.`\n }\n\n return `${BASE_DRAFT_PROMPT}\n\n## Presales FAQ\n- Answer pricing, curriculum, requirements, and discount questions\n- Only reference information from the knowledge base or gathered context\n- Don't fabricate pricing or feature details\n- If unsure, offer to connect them with the team${promotionsSection}`\n}\n\n/**\n * Build the `presales_team` prompt with license/seat data.\n *\n * When the gather step fetched license info, injects seat counts\n * so the agent can answer team/enterprise questions.\n */\nfunction buildPresalesTeamPrompt(context: GatherOutput): string {\n let licenseSection = ''\n\n if (context.licenseInfo && context.licenseInfo.length > 0) {\n const licenseLines = context.licenseInfo.map((li) => {\n const parts = [\n `**${li.licenseType}** license (Purchase: ${li.purchaseId})`,\n `Seats: ${li.claimedSeats}/${li.totalSeats} claimed, ${li.availableSeats} available`,\n ]\n if (li.expiresAt) {\n parts.push(`Expires: ${li.expiresAt}`)\n }\n if (li.adminEmail) {\n parts.push(`Admin: ${li.adminEmail}`)\n }\n return parts.map((p) => ` - ${p}`).join('\\n')\n })\n\n licenseSection = `\n\n## License & Seat Data\n${licenseLines.join('\\n')}\n\nUse this data to answer team seat questions. If a customer needs more seats or wants to manage their team, provide the info above and offer to help with changes.`\n }\n\n return `${BASE_DRAFT_PROMPT}\n\n## Team/Enterprise Inquiries\n- Answer questions about team licensing, seat management, enterprise pricing\n- If license data is available above, reference it directly\n- For pricing questions beyond what's shown, offer to connect with the team\n- For enterprise custom deals, escalate to the team${licenseSection}`\n}\n\n// ============================================================================\n// Static prompts (categories without dynamic data needs)\n// ============================================================================\n\nconst STATIC_CATEGORY_PROMPTS: Partial<Record<MessageCategory, string>> = {\n support_access: `${BASE_DRAFT_PROMPT}\n\n## Access Issues\n- First check if we found their purchase\n- If no purchase found: ask which email they used to buy\n- If purchase found: offer magic link or check their login method\n- GitHub login issues: they may have multiple GitHub accounts`,\n\n support_transfer: `${BASE_DRAFT_PROMPT}\n\n## Transfer Requests\n- Need: current email, new email, reason\n- If we have all info: say you'll process it\n- If missing info: ask for what's missing`,\n\n support_technical: `${BASE_DRAFT_PROMPT}\n\n## Technical Questions\n- Only reference content from the knowledge base\n- Don't invent course modules or sections\n- If no knowledge found: ask what specific topic they need help with\n- Can point to Discord for code questions`,\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Build the system prompt for a given category, injecting dynamic\n * context data where available and falling back to defaults.\n *\n * This is the single entry point for prompt selection in the draft step.\n */\nexport function buildCategoryPrompt(\n category: MessageCategory,\n context: GatherOutput\n): string {\n switch (category) {\n case 'support_refund':\n return buildRefundPrompt(context)\n case 'support_billing':\n return buildBillingPrompt(context)\n case 'presales_faq':\n return buildPresalesFaqPrompt(context)\n case 'presales_team':\n return buildPresalesTeamPrompt(context)\n default:\n return STATIC_CATEGORY_PROMPTS[category] || BASE_DRAFT_PROMPT\n }\n}\n","/**\n * Step 3: GATHER\n *\n * Collects all context needed for drafting.\n * Only runs if route action = 'respond'.\n *\n * Memory Integration:\n * - Queries past memories to learn what info was missed before\n * - Prioritizes gathering based on past corrections\n * - Stores outcomes when important info is later found to be missing\n */\n\nimport { SupportMemoryService } from '@skillrecordings/memory/support-memory'\nimport type {\n ContentAccess,\n LicenseInfo,\n Promotion,\n RefundPolicy,\n UserActivity,\n} from '@skillrecordings/sdk/types'\nimport {\n type RelevantMemory,\n formatMemoriesCompact,\n queryMemoriesForStage,\n} from '../../memory/query'\nimport { log } from '../../observability/axiom'\nimport type {\n ConversationMessage,\n GatherError,\n GatherInput,\n GatherOutput,\n KnowledgeItem,\n MemoryItem,\n MessageCategory,\n PriorConversation,\n Purchase,\n User,\n} from '../types'\n\n// ============================================================================\n// Tool interfaces (to be wired to real implementations)\n// ============================================================================\n\nexport interface GatherTools {\n lookupUser?: (\n email: string,\n appId: string\n ) => Promise<{\n user: User | null\n purchases: Purchase[]\n }>\n searchKnowledge?: (query: string, appId: string) => Promise<KnowledgeItem[]>\n getHistory?: (conversationId: string) => Promise<ConversationMessage[]>\n searchMemory?: (query: string) => Promise<MemoryItem[]>\n getPriorConversations?: (\n email: string,\n options?: { currentConversationId?: string; days?: number; limit?: number }\n ) => Promise<PriorConversation[]>\n\n // Category-aware SDK tools (optional, provided when app integration exists)\n getRefundPolicy?: (appId: string) => Promise<RefundPolicy | null>\n getContentAccess?: (\n userId: string,\n appId: string\n ) => Promise<ContentAccess | null>\n getRecentActivity?: (\n userId: string,\n appId: string\n ) => Promise<UserActivity | null>\n getActivePromotions?: (appId: string) => Promise<Promotion[]>\n getLicenseInfo?: (\n purchaseId: string,\n appId: string\n ) => Promise<LicenseInfo | null>\n}\n\n// ============================================================================\n// Gather Priority Types (from memory corrections)\n// ============================================================================\n\n/**\n * Priority hints extracted from past memory corrections.\n * These tell us what info was missed in similar situations.\n */\nexport interface GatherPriorities {\n /** Data sources that should definitely be checked */\n mustGather: GatherPriorityItem[]\n /** Additional context that may be helpful */\n mayGather: GatherPriorityItem[]\n /** Relevant memories that informed these priorities */\n sourceMemories: RelevantMemory[]\n}\n\nexport interface GatherPriorityItem {\n /** What to gather (e.g., \"refund_history\", \"previous_purchases\") */\n dataType: string\n /** Why this is important (from correction) */\n reason: string\n /** Confidence based on memory score */\n confidence: number\n}\n\n/**\n * Input for storing a gather correction\n */\nexport interface StoreGatherCorrectionInput {\n /** App identifier */\n appId: string\n /** Category of the support request */\n category: MessageCategory\n /** Summary of the issue */\n summary: string\n /** What was actually gathered */\n gathered: string[]\n /** What should have also been gathered */\n missingInfo: string\n /** Optional conversation ID for tracing */\n conversationId?: string\n}\n\n// ============================================================================\n// Memory Query Functions\n// ============================================================================\n\n/**\n * Query memories relevant to the gather stage to learn what info\n * was missed in similar situations before.\n */\nexport async function queryGatherMemories(options: {\n appId: string\n category: string\n summary: string\n limit?: number\n}): Promise<RelevantMemory[]> {\n const { appId, category, summary, limit = 5 } = options\n\n try {\n const memories = await queryMemoriesForStage({\n appId,\n stage: 'gather',\n situation: `Category: ${category}\\nInitial issue: ${summary}`,\n category,\n limit,\n })\n return memories\n } catch (error) {\n // Don't fail gather if memory query fails\n console.warn('[gather] Memory query failed:', error)\n return []\n }\n}\n\n/**\n * Extract gather priorities from past corrections.\n *\n * Analyzes memory corrections to determine what data sources\n * should be prioritized when gathering context for similar issues.\n *\n * @example\n * // If past correction says \"Should have also gathered: refund history\"\n * // This will return { mustGather: [{ dataType: 'refund_history', ... }] }\n */\nexport function extractGatherPriorities(\n memories: RelevantMemory[]\n): GatherPriorities {\n const mustGather: GatherPriorityItem[] = []\n const mayGather: GatherPriorityItem[] = []\n\n // Known data types that can be extracted from corrections\n const dataTypePatterns: Record<string, RegExp[]> = {\n refund_history: [/refund\\s*history/i, /previous\\s*refunds?/i],\n purchase_history: [\n /purchase\\s*history/i,\n /previous\\s*purchases?/i,\n /all\\s*purchases/i,\n ],\n payment_method: [/payment\\s*method/i, /card\\s*details/i, /billing\\s*info/i],\n conversation_history: [/conversation\\s*history/i, /previous\\s*tickets?/i],\n account_status: [/account\\s*status/i, /subscription\\s*status/i],\n product_access: [/product\\s*access/i, /course\\s*access/i, /license/i],\n user_preferences: [/user\\s*preferences?/i, /settings/i],\n support_history: [\n /support\\s*history/i,\n /previous\\s*issues?/i,\n /past\\s*tickets?/i,\n ],\n team_membership: [/team\\s*membership/i, /organization/i, /team\\s*license/i],\n }\n\n for (const memory of memories) {\n // Only learn from corrected memories\n if (memory.outcome !== 'corrected' || !memory.correction) {\n continue\n }\n\n const correction = memory.correction.toLowerCase()\n\n // Try to extract specific data types mentioned in correction\n for (const [dataType, patterns] of Object.entries(dataTypePatterns)) {\n for (const pattern of patterns) {\n if (pattern.test(correction)) {\n const item: GatherPriorityItem = {\n dataType,\n reason: memory.correction,\n confidence: memory.score,\n }\n\n // High-confidence corrections are must-gather\n if (memory.score >= 0.7) {\n // Avoid duplicates\n if (!mustGather.some((p) => p.dataType === dataType)) {\n mustGather.push(item)\n }\n } else {\n if (!mayGather.some((p) => p.dataType === dataType)) {\n mayGather.push(item)\n }\n }\n break\n }\n }\n }\n\n // If no specific pattern matched but it's a high-confidence correction,\n // add a generic priority item\n if (memory.score >= 0.7 && memory.correction) {\n const genericMatch = correction.match(\n /should have (?:also )?gathered[:\\s]+(.+?)(?:\\.|$)/i\n )\n if (genericMatch && genericMatch[1]) {\n const dataType = genericMatch[1]\n .trim()\n .toLowerCase()\n .replace(/\\s+/g, '_')\n if (!mustGather.some((p) => p.dataType === dataType)) {\n mustGather.push({\n dataType,\n reason: memory.correction,\n confidence: memory.score,\n })\n }\n }\n }\n }\n\n return {\n mustGather,\n mayGather,\n sourceMemories: memories.filter((m) => m.outcome === 'corrected'),\n }\n}\n\n/**\n * Store a correction when the gather step missed important info\n * that a human later needed.\n */\nexport async function storeGatherCorrection(\n input: StoreGatherCorrectionInput\n): Promise<void> {\n const { appId, category, summary, gathered, missingInfo, conversationId } =\n input\n\n try {\n await SupportMemoryService.store({\n app_slug: appId,\n situation: `Category: ${category}\\nIssue: ${summary}`,\n decision: `Gathered: ${gathered.join(', ')}`,\n stage: 'gather',\n outcome: 'corrected',\n correction: `Should have also gathered: ${missingInfo}`,\n category,\n conversation_id: conversationId,\n tags: ['correction', 'gather_miss', category],\n })\n } catch (error) {\n console.error('[gather] Failed to store correction:', error)\n }\n}\n\n/**\n * Store success outcome when gather provided all needed context\n */\nexport async function storeGatherSuccess(input: {\n appId: string\n category: MessageCategory\n summary: string\n gathered: string[]\n conversationId?: string\n}): Promise<void> {\n const { appId, category, summary, gathered, conversationId } = input\n\n try {\n await SupportMemoryService.store({\n app_slug: appId,\n situation: `Category: ${category}\\nIssue: ${summary}`,\n decision: `Gathered: ${gathered.join(', ')}`,\n stage: 'gather',\n outcome: 'success',\n category,\n conversation_id: conversationId,\n tags: ['success', 'gather', category],\n })\n } catch (error) {\n console.warn('[gather] Failed to store success:', error)\n }\n}\n\n// ============================================================================\n// Email extraction\n// ============================================================================\n\nconst EMAIL_REGEX = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g\n\nexport function extractEmail(text: string): string | null {\n const matches = text.match(EMAIL_REGEX)\n if (!matches) return null\n\n // Filter out common non-customer emails\n const filtered = matches.filter((email) => {\n const lower = email.toLowerCase()\n return (\n !lower.includes('noreply') &&\n !lower.includes('no-reply') &&\n !lower.includes('mailer-daemon') &&\n !lower.includes('postmaster') &&\n !lower.includes('@totaltypescript.com') &&\n !lower.includes('@aihero.dev') &&\n !lower.includes('@egghead.io')\n )\n })\n\n return filtered[0] || null\n}\n\n/**\n * Determine customer email using senderEmail as PRIMARY source,\n * with body text extraction as FALLBACK.\n *\n * Returns the email to use and the source it came from.\n */\nexport function determineCustomerEmail(\n senderEmail: string | undefined,\n bodyText: string\n): { email: string | null; source: 'sender' | 'body' | 'none' } {\n // PRIMARY: Use sender email if available and valid\n if (senderEmail && senderEmail.trim()) {\n return { email: senderEmail.trim(), source: 'sender' }\n }\n\n // FALLBACK: Extract from body text (customer might mention a different email)\n const bodyEmail = extractEmail(bodyText)\n if (bodyEmail) {\n return { email: bodyEmail, source: 'body' }\n }\n\n return { email: null, source: 'none' }\n}\n\n// ============================================================================\n// Main gather function\n// ============================================================================\n\nexport interface GatherOptions {\n tools?: GatherTools\n timeout?: number\n /** Skip memory lookup (for testing or when memory service unavailable) */\n skipMemory?: boolean\n}\n\nexport interface EmailResolution {\n email: string | null\n source: 'sender' | 'body' | 'none'\n senderEmail?: string\n bodyExtractedEmail?: string | null\n}\n\n/** Extended gather output with memory context */\nexport interface GatherResultWithMemory extends GatherOutput {\n emailResolution?: EmailResolution\n /** Priorities extracted from past corrections */\n gatherPriorities?: GatherPriorities\n /** Memory context formatted for prompt injection */\n memoryContext?: string\n /** Which data sources were actually gathered */\n gatheredSources: string[]\n}\n\nexport async function gather(\n input: GatherInput,\n options: GatherOptions = {}\n): Promise<GatherResultWithMemory> {\n const { tools = {}, timeout = 5000, skipMemory = false } = options\n const { message, classification, appId } = input\n\n const startTime = Date.now()\n\n await log('debug', 'gather started', {\n workflow: 'pipeline',\n step: 'gather',\n appId,\n category: classification.category,\n messageLength: message.body.length,\n skipMemory,\n })\n\n const result: GatherResultWithMemory = {\n user: null,\n purchases: [],\n knowledge: [],\n history: [],\n priorMemory: [],\n priorConversations: [],\n gatherErrors: [],\n gatheredSources: [],\n }\n\n // ============================================================================\n // Step 1: Query memories for past corrections (learn what was missed before)\n // ============================================================================\n\n let gatherPriorities: GatherPriorities | undefined\n\n if (!skipMemory) {\n try {\n const summary = `${message.subject} ${message.body}`.slice(0, 300)\n const memories = await queryGatherMemories({\n appId,\n category: classification.category,\n summary,\n limit: 5,\n })\n\n if (memories.length > 0) {\n gatherPriorities = extractGatherPriorities(memories)\n result.gatherPriorities = gatherPriorities\n result.memoryContext = formatMemoriesCompact(memories)\n\n // Log if we have must-gather priorities from corrections\n if (gatherPriorities.mustGather.length > 0) {\n await log('debug', 'gather memory priorities found', {\n workflow: 'pipeline',\n step: 'gather',\n appId,\n mustGather: gatherPriorities.mustGather.map((p) => p.dataType),\n mayGather: gatherPriorities.mayGather.map((p) => p.dataType),\n memoriesFound: memories.length,\n })\n }\n }\n } catch (error) {\n // Memory lookup failure shouldn't block gathering\n await log('warn', 'gather memory lookup failed', {\n workflow: 'pipeline',\n step: 'gather',\n appId,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n // ============================================================================\n // Step 2: Determine customer email and prepare for data gathering\n // ============================================================================\n\n // Determine customer email - prioritize senderEmail, fallback to body extraction\n const bodyText = `${message.subject} ${message.body}`\n const bodyExtractedEmail = extractEmail(bodyText)\n const { email: customerEmail, source: emailSource } = determineCustomerEmail(\n message.from,\n bodyText\n )\n\n // Store resolution details for logging/debugging\n result.emailResolution = {\n email: customerEmail,\n source: emailSource,\n senderEmail: message.from,\n bodyExtractedEmail,\n }\n\n // High-cardinality decision-point logging for email resolution\n await log('info', 'gather:email-resolution', {\n workflow: 'pipeline',\n step: 'gather',\n appId,\n category: classification.category,\n // Decision outcome\n emailFound: !!customerEmail,\n emailSource,\n // Decision inputs\n hasSenderEmail: !!message.from,\n hasBodyExtractedEmail: !!bodyExtractedEmail,\n senderEmailDomain: message.from?.split('@')[1],\n // Decision explanation\n usedSender: emailSource === 'sender',\n usedBodyFallback: emailSource === 'body',\n emailMismatch:\n message.from && bodyExtractedEmail && message.from !== bodyExtractedEmail,\n })\n\n // ============================================================================\n // Step 3: Run all gather operations in parallel with timeout\n // ============================================================================\n\n // Run all gather operations in parallel with timeout\n const gatherPromises: Promise<void>[] = []\n\n // Lookup user\n if (tools.lookupUser && customerEmail) {\n gatherPromises.push(\n withTimeout(\n (async () => {\n try {\n const userResult = await tools.lookupUser!(customerEmail, appId)\n result.user = userResult.user\n result.purchases = userResult.purchases\n } catch (error) {\n result.gatherErrors.push({\n step: 'user',\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n })(),\n timeout,\n 'user lookup'\n ).catch((err) => {\n result.gatherErrors.push({ step: 'user', error: err.message })\n })\n )\n }\n\n // Search knowledge\n if (tools.searchKnowledge) {\n const query = `${message.subject} ${message.body}`.slice(0, 500)\n gatherPromises.push(\n withTimeout(\n (async () => {\n try {\n result.knowledge = await tools.searchKnowledge!(query, appId)\n } catch (error) {\n result.gatherErrors.push({\n step: 'knowledge',\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n })(),\n timeout,\n 'knowledge search'\n ).catch((err) => {\n result.gatherErrors.push({ step: 'knowledge', error: err.message })\n })\n )\n }\n\n // Get conversation history\n if (tools.getHistory && message.conversationId) {\n gatherPromises.push(\n withTimeout(\n (async () => {\n try {\n result.history = await tools.getHistory!(message.conversationId!)\n } catch (error) {\n result.gatherErrors.push({\n step: 'history',\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n })(),\n timeout,\n 'history fetch'\n ).catch((err) => {\n result.gatherErrors.push({ step: 'history', error: err.message })\n })\n )\n }\n\n // Search memory\n if (tools.searchMemory) {\n const query = `${classification.category} ${message.subject}`.slice(0, 200)\n gatherPromises.push(\n withTimeout(\n (async () => {\n try {\n result.priorMemory = await tools.searchMemory!(query)\n } catch (error) {\n result.gatherErrors.push({\n step: 'memory',\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n })(),\n timeout,\n 'memory search'\n ).catch((err) => {\n result.gatherErrors.push({ step: 'memory', error: err.message })\n })\n )\n }\n\n // Lookup prior conversations by same customer\n if (tools.getPriorConversations && customerEmail) {\n gatherPromises.push(\n withTimeout(\n (async () => {\n try {\n result.priorConversations = await tools.getPriorConversations!(\n customerEmail,\n {\n currentConversationId: message.conversationId,\n days: 90,\n limit: 10,\n }\n )\n } catch (error) {\n result.gatherErrors.push({\n step: 'priorConversations',\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n })(),\n timeout,\n 'prior conversations lookup'\n ).catch((err) => {\n result.gatherErrors.push({\n step: 'priorConversations',\n error: err.message,\n })\n })\n )\n }\n\n // Wait for all to complete\n await Promise.all(gatherPromises)\n\n // ============================================================================\n // Step 4: Track what was actually gathered (for outcome storage)\n // ============================================================================\n\n // Track successfully gathered data sources\n if (result.user) result.gatheredSources.push('user')\n if (result.purchases.length > 0) result.gatheredSources.push('purchases')\n if (result.knowledge.length > 0) result.gatheredSources.push('knowledge')\n if (result.history.length > 0) result.gatheredSources.push('history')\n if (result.priorMemory.length > 0) result.gatheredSources.push('memory')\n if (result.priorConversations.length > 0)\n result.gatheredSources.push('priorConversations')\n\n const durationMs = Date.now() - startTime\n\n // High-cardinality decision-point logging for gather completion\n await log('info', 'gather:decision', {\n workflow: 'pipeline',\n step: 'gather',\n appId,\n category: classification.category,\n // Context assembly outcome\n gatheredSources: result.gatheredSources,\n gatheredSourceCount: result.gatheredSources.length,\n failedSources: result.gatherErrors.map((e) => e.step),\n failedSourceCount: result.gatherErrors.length,\n // Individual source results\n hasUser: !!result.user,\n hasUserEmail: !!result.user?.email,\n hasUserName: !!result.user?.name,\n purchaseCount: result.purchases.length,\n purchaseProducts: result.purchases.map((p) => p.productName),\n purchaseStatuses: result.purchases.map((p) => p.status),\n knowledgeCount: result.knowledge.length,\n knowledgeTypes: result.knowledge.map((k) => k.type),\n historyCount: result.history.length,\n memoryCount: result.priorMemory.length,\n priorConversationCount: result.priorConversations.length,\n // Memory priority guidance\n hasMustGatherPriorities:\n (result.gatherPriorities?.mustGather.length ?? 0) > 0,\n mustGatherTypes: result.gatherPriorities?.mustGather.map((p) => p.dataType),\n mayGatherTypes: result.gatherPriorities?.mayGather.map((p) => p.dataType),\n memorySourceCount: result.gatherPriorities?.sourceMemories.length ?? 0,\n // Email resolution\n emailSource: result.emailResolution?.source,\n customerEmailFound: !!result.emailResolution?.email,\n // Errors (without exposing internal details)\n errorSteps: result.gatherErrors.map((e) => e.step),\n durationMs,\n })\n\n return result\n}\n\n/**\n * Record gather outcome after the full pipeline completes.\n *\n * Call this when:\n * - Human review reveals missing info (store correction)\n * - Response is sent successfully without edits (store success)\n *\n * @example\n * ```typescript\n * // When human needed info that wasn't gathered:\n * await recordGatherOutcome({\n * appId,\n * category: classification.category,\n * summary: `${message.subject} ${message.body}`.slice(0, 300),\n * gathered: gatherResult.gatheredSources,\n * outcome: 'corrected',\n * missingInfo: 'refund history - customer had 3 previous refunds',\n * conversationId: message.conversationId,\n * })\n * ```\n */\nexport async function recordGatherOutcome(input: {\n appId: string\n category: MessageCategory\n summary: string\n gathered: string[]\n outcome: 'success' | 'corrected'\n missingInfo?: string\n conversationId?: string\n}): Promise<void> {\n const {\n appId,\n category,\n summary,\n gathered,\n outcome,\n missingInfo,\n conversationId,\n } = input\n\n if (outcome === 'corrected' && missingInfo) {\n await storeGatherCorrection({\n appId,\n category,\n summary,\n gathered,\n missingInfo,\n conversationId,\n })\n } else if (outcome === 'success') {\n await storeGatherSuccess({\n appId,\n category,\n summary,\n gathered,\n conversationId,\n })\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nasync function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n name: string\n): Promise<T> {\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error(`${name} timed out after ${ms}ms`)), ms)\n })\n return Promise.race([promise, timeoutPromise])\n}\n\n/**\n * Format gather output for injection into draft prompt.\n * Sanitizes any errors - they become \"not found\" not \"API error\".\n *\n * If gather priorities are present (from memory corrections),\n * adds a section highlighting what past corrections indicated.\n */\nexport function formatContextForPrompt(\n context: GatherOutput | GatherResultWithMemory\n): string {\n const sections: string[] = []\n\n // User info\n if (context.user) {\n sections.push(`## Customer\n- Email: ${context.user.email}\n- Name: ${context.user.name || 'Unknown'}`)\n } else {\n sections.push(`## Customer\n- No account found for this email`)\n }\n\n // Purchases\n if (context.purchases.length > 0) {\n const purchaseList = context.purchases\n .map((p) => `- ${p.productName} (${p.purchasedAt}) - ${p.status}`)\n .join('\\n')\n sections.push(`## Purchases\n${purchaseList}`)\n } else {\n sections.push(`## Purchases\n- No purchases found`)\n }\n\n // Knowledge (if any)\n if (context.knowledge.length > 0) {\n const knowledgeList = context.knowledge\n .slice(0, 3) // Limit to top 3\n .map((k) => `- [${k.type}] ${k.content.slice(0, 200)}...`)\n .join('\\n')\n sections.push(`## Relevant Knowledge\n${knowledgeList}`)\n }\n\n // History (if any)\n if (context.history.length > 0) {\n const historyList = context.history\n .slice(-5) // Last 5 messages\n .map(\n (h) =>\n `- [${h.direction === 'in' ? 'Customer' : 'Support'}] ${h.body.slice(0, 100)}...`\n )\n .join('\\n')\n sections.push(`## Recent History\n${historyList}`)\n }\n\n // Prior conversations\n if (context.priorConversations && context.priorConversations.length > 0) {\n const convos = context.priorConversations\n const uniqueProducts = new Set<string>()\n for (const c of convos) {\n for (const tag of c.tags) {\n // Tags that look like product names (not system tags)\n if (!tag.startsWith('sr/') && !tag.startsWith('agent/')) {\n uniqueProducts.add(tag)\n }\n }\n }\n\n const isMultiProduct = uniqueProducts.size > 1\n const convoList = convos\n .map((c) => {\n const tagsStr = c.tags.length > 0 ? ` [${c.tags.join(', ')}]` : ''\n return `- \"${c.subject}\" (${c.status}, ${c.lastMessageAt})${tagsStr}`\n })\n .join('\\n')\n\n sections.push(`## Prior Conversations (${convos.length} in last 90 days)\n${isMultiProduct ? '⭐ **Multi-product customer (VIP)**\\n' : ''}${convoList}`)\n }\n\n // ── Category-aware SDK data (populated by gather step when available) ──\n\n // App info\n if (context.appInfo) {\n const ai = context.appInfo\n const urls: string[] = []\n if (ai.invoicesUrl) urls.push(`Invoices: ${ai.invoicesUrl}`)\n if (ai.discordUrl) urls.push(`Discord: ${ai.discordUrl}`)\n if (ai.refundPolicyUrl) urls.push(`Refund Policy: ${ai.refundPolicyUrl}`)\n sections.push(`## App Info\n- Product: ${ai.name}\n- Instructor: ${ai.instructorName}\n- Support Email: ${ai.supportEmail}${urls.length > 0 ? `\\n${urls.map((u) => `- ${u}`).join('\\n')}` : ''}`)\n }\n\n // Refund policy\n if (context.refundPolicy) {\n const rp = context.refundPolicy\n const conditions = rp.specialConditions?.length\n ? `\\n${rp.specialConditions.map((c) => `- ${c}`).join('\\n')}`\n : ''\n sections.push(`## Refund Policy\n- Auto-approve window: ${rp.autoApproveWindowDays} days\n- Manual approve window: ${rp.manualApproveWindowDays} days${rp.noRefundAfterDays ? `\\n- No refund after: ${rp.noRefundAfterDays} days` : ''}${rp.policyUrl ? `\\n- Policy URL: ${rp.policyUrl}` : ''}${conditions}`)\n }\n\n // Content access\n if (context.contentAccess) {\n const ca = context.contentAccess\n const productList = ca.products\n .map((p) => {\n const modules = p.modules\n ? ` (${p.modules.filter((m) => m.accessible).length}/${p.modules.length} modules accessible)`\n : ''\n const expires = p.expiresAt\n ? ` — expires ${p.expiresAt}`\n : ' — lifetime'\n return `- ${p.productName}: ${p.accessLevel}${modules}${expires}`\n })\n .join('\\n')\n const team = ca.teamMembership\n ? `\\n- Team: ${ca.teamMembership.teamName} (${ca.teamMembership.role})`\n : ''\n sections.push(`## Content Access\n${productList}${team}`)\n }\n\n // Recent activity\n if (context.recentActivity) {\n const ra = context.recentActivity\n const recentList = ra.recentItems\n .slice(0, 5)\n .map((item) => `- [${item.type}] ${item.title} (${item.timestamp})`)\n .join('\\n')\n sections.push(`## Recent Activity\n- Last login: ${ra.lastLoginAt ?? 'Unknown'}\n- Progress: ${ra.lessonsCompleted}/${ra.totalLessons} lessons (${ra.completionPercent}%)${recentList ? `\\n${recentList}` : ''}`)\n }\n\n // Active promotions\n if (context.activePromotions && context.activePromotions.length > 0) {\n const promoList = context.activePromotions\n .map((p) => {\n const discount =\n p.discountType === 'percent'\n ? `${p.discountAmount}% off`\n : `$${(p.discountAmount / 100).toFixed(2)} off`\n const code = p.code ? ` (code: ${p.code})` : ''\n const until = p.validUntil ? ` — until ${p.validUntil}` : ''\n const conditions = p.conditions ? ` [${p.conditions}]` : ''\n return `- ${p.name}: ${discount}${code}${until}${conditions}`\n })\n .join('\\n')\n sections.push(`## Active Promotions\n${promoList}`)\n }\n\n // Team license info\n if (context.licenseInfo && context.licenseInfo.length > 0) {\n const licenseList = context.licenseInfo\n .map((li) => {\n const claimed = li.claimedBy\n .slice(0, 5)\n .map((c) => ` - ${c.email} (claimed ${c.claimedAt})`)\n .join('\\n')\n const more =\n li.claimedBy.length > 5\n ? `\\n - ... and ${li.claimedBy.length - 5} more`\n : ''\n return `- License: ${li.licenseType} — ${li.claimedSeats}/${li.totalSeats} seats claimed (${li.availableSeats} available)${li.expiresAt ? ` — expires ${li.expiresAt}` : ''}${li.adminEmail ? `\\n - Admin: ${li.adminEmail}` : ''}${claimed ? `\\n${claimed}${more}` : ''}`\n })\n .join('\\n')\n sections.push(`## Team Licenses\n${licenseList}`)\n }\n\n // Note: gatherErrors are NEVER included - that's the whole point\n\n // Add memory priorities if present (from past corrections)\n const extendedContext = context as GatherResultWithMemory\n if (\n extendedContext.gatherPriorities &&\n extendedContext.gatherPriorities.mustGather.length > 0\n ) {\n const priorityNotes = extendedContext.gatherPriorities.mustGather\n .map((p) => `- **${p.dataType}**: ${p.reason}`)\n .join('\\n')\n sections.push(`## ⚠️ Past Corrections (check these)\n${priorityNotes}`)\n }\n\n // Add memory context if present\n if (extendedContext.memoryContext) {\n sections.push(extendedContext.memoryContext)\n }\n\n return sections.join('\\n\\n')\n}\n","/**\n * Step 2: ROUTE\n *\n * Decides what action to take based on classification.\n * Pure logic - no LLM, no external calls.\n *\n * v3 adds thread-aware routing with support_teammate action.\n * v4 adds memory integration for learning from past routing decisions.\n */\n\nimport { SupportMemoryService } from '@skillrecordings/memory/support-memory'\nimport {\n type RelevantMemory,\n citeMemories,\n formatMemoriesCompact,\n queryMemoriesForStage,\n} from '../../memory/query'\nimport { log, traceRouting } from '../../observability/axiom'\nimport type {\n AppConfig,\n MessageCategory,\n RouteAction,\n RouteInput,\n RouteOutput,\n ThreadClassifyOutput,\n ThreadSignals,\n} from '../types'\nimport { isThreadResolved, shouldSupportTeammate } from './thread-signals'\n\n// ============================================================================\n// Routing rules\n// ============================================================================\n\ninterface RoutingRule {\n name: string\n condition: (input: RouteInput) => boolean\n action: RouteAction\n reason: string\n}\n\nconst ROUTING_RULES: RoutingRule[] = [\n // System messages - always silent\n {\n name: 'system_silence',\n condition: ({ classification }) => classification.category === 'system',\n action: 'silence',\n reason: 'Automated/system message - no response needed',\n },\n\n // Spam - always silent\n {\n name: 'spam_silence',\n condition: ({ classification }) => classification.category === 'spam',\n action: 'silence',\n reason: 'Vendor/spam outreach - no response needed',\n },\n\n // Legal threats - URGENT escalation (lawyer, lawsuit, legal action)\n // This is the ONLY path to escalate_urgent\n {\n name: 'legal_threat_urgent',\n condition: ({ classification }) => classification.signals.hasLegalThreat,\n action: 'escalate_urgent',\n reason: 'Legal threat detected - urgent human review required',\n },\n\n // Fan mail - route to instructor (personal messages explicitly addressed to instructor)\n // NOTE: Must be BEFORE unknown_escalate so personal messages aren't misrouted\n {\n name: 'fan_mail_instructor',\n condition: ({ classification }) => classification.category === 'fan_mail',\n action: 'escalate_instructor',\n reason: 'Personal message for instructor',\n },\n\n // Personal/casual messages to instructor (even if not classified as fan_mail)\n // NOTE: Must be BEFORE unknown_escalate so isPersonalToInstructor signal is honored\n {\n name: 'personal_to_instructor',\n condition: ({ classification }) =>\n classification.signals.isPersonalToInstructor,\n action: 'escalate_instructor',\n reason: 'Personal message addressed to instructor',\n },\n\n // Unknown with low confidence - escalate\n {\n name: 'unknown_escalate',\n condition: ({ classification }) =>\n classification.category === 'unknown' || classification.confidence < 0.5,\n action: 'escalate_human',\n reason: 'Cannot confidently classify - needs human review',\n },\n\n // Refund outside policy window - needs human approval\n {\n name: 'refund_policy_violation',\n condition: ({ classification }) =>\n classification.category === 'support_refund' &&\n classification.signals.hasOutsidePolicyTimeframe,\n action: 'escalate_human',\n reason: 'Refund request outside policy window - needs human approval',\n },\n\n // Angry customer - human escalation (NOT urgent - that's for legal only)\n {\n name: 'angry_escalate',\n condition: ({ classification }) => classification.signals.hasAngrySentiment,\n action: 'escalate_human',\n reason: 'Frustrated customer - needs human attention and judgment',\n },\n\n // Support categories - respond\n {\n name: 'support_respond',\n condition: ({ classification }) =>\n classification.category.startsWith('support_'),\n action: 'respond',\n reason: 'Support request - agent should respond',\n },\n\n // Presales FAQ - agent can answer from knowledge base\n {\n name: 'presales_faq_respond',\n condition: ({ classification }) =>\n classification.category === 'presales_faq',\n action: 'respond',\n reason: 'Presales FAQ - can answer from knowledge base',\n },\n\n // Presales team/enterprise - needs sales process\n {\n name: 'presales_team_escalate',\n condition: ({ classification }) =>\n classification.category === 'presales_team',\n action: 'escalate_human',\n reason: 'Team/enterprise inquiry - needs sales process',\n },\n\n // Presales consult - needs instructor judgment, track for learning\n {\n name: 'presales_consult_escalate',\n condition: ({ classification }) =>\n classification.category === 'presales_consult',\n action: 'escalate_instructor',\n reason:\n 'Presales consultation - instructor can provide personalized guidance',\n },\n]\n\n// ============================================================================\n// Main route function\n// ============================================================================\n\nexport function route(input: RouteInput): RouteOutput {\n const startTime = Date.now()\n const { classification, appConfig } = input\n\n // Apply rules in order (first match wins)\n for (const rule of ROUTING_RULES) {\n if (rule.condition(input)) {\n const result = {\n action: rule.action,\n reason: rule.reason,\n }\n\n // High-cardinality decision-point logging\n log('info', 'route:decision', {\n workflow: 'pipeline',\n step: 'route',\n appId: appConfig?.appId,\n // Decision outcome\n action: result.action,\n reason: result.reason,\n ruleName: rule.name,\n // Classification inputs\n category: classification.category,\n confidence: classification.confidence,\n classificationReasoning: classification.reasoning,\n // Signal inputs that drove the decision\n hasAngrySentiment: classification.signals.hasAngrySentiment,\n hasLegalThreat: classification.signals.hasLegalThreat,\n hasOutsidePolicyTimeframe:\n classification.signals.hasOutsidePolicyTimeframe,\n isPersonalToInstructor: classification.signals.isPersonalToInstructor,\n isAutomated: classification.signals.isAutomated,\n isVendorOutreach: classification.signals.isVendorOutreach,\n isPresalesFaq: classification.signals.isPresalesFaq,\n isPresalesTeam: classification.signals.isPresalesTeam,\n // Decision explanation\n matchedRule: rule.name,\n rulesEvaluated:\n ROUTING_RULES.findIndex((r) => r.name === rule.name) + 1,\n totalRules: ROUTING_RULES.length,\n durationMs: Date.now() - startTime,\n }).catch(() => {})\n\n return result\n }\n }\n\n // Default: escalate if nothing matched\n const defaultResult = {\n action: 'escalate_human' as const,\n reason: 'No routing rule matched - needs human review',\n }\n\n // High-cardinality decision-point logging for default case\n log('info', 'route:decision', {\n workflow: 'pipeline',\n step: 'route',\n appId: appConfig?.appId,\n // Decision outcome\n action: defaultResult.action,\n reason: defaultResult.reason,\n ruleName: 'default_fallback',\n // Classification inputs\n category: classification.category,\n confidence: classification.confidence,\n classificationReasoning: classification.reasoning,\n // Signal inputs\n hasAngrySentiment: classification.signals.hasAngrySentiment,\n hasLegalThreat: classification.signals.hasLegalThreat,\n hasOutsidePolicyTimeframe: classification.signals.hasOutsidePolicyTimeframe,\n isPersonalToInstructor: classification.signals.isPersonalToInstructor,\n // Decision explanation\n matchedRule: null,\n rulesEvaluated: ROUTING_RULES.length,\n totalRules: ROUTING_RULES.length,\n noRuleMatched: true,\n durationMs: Date.now() - startTime,\n }).catch(() => {})\n\n return defaultResult\n}\n\n// ============================================================================\n// Rule customization\n// ============================================================================\n\nexport interface CustomRoutingRule {\n name: string\n category?: MessageCategory\n signals?: Partial<Record<string, boolean>>\n minConfidence?: number\n maxConfidence?: number\n action: RouteAction\n reason: string\n priority?: number // Lower = higher priority, default 100\n}\n\nexport function routeWithCustomRules(\n input: RouteInput,\n customRules: CustomRoutingRule[] = []\n): RouteOutput {\n // Convert custom rules to standard rules and sort by priority\n const allRules = [\n ...customRules.map((cr) => ({\n name: cr.name,\n condition: (ri: RouteInput) => {\n if (cr.category && ri.classification.category !== cr.category)\n return false\n if (cr.minConfidence && ri.classification.confidence < cr.minConfidence)\n return false\n if (cr.maxConfidence && ri.classification.confidence > cr.maxConfidence)\n return false\n if (cr.signals) {\n for (const [key, value] of Object.entries(cr.signals)) {\n if (\n (ri.classification.signals as unknown as Record<string, boolean>)[\n key\n ] !== value\n )\n return false\n }\n }\n return true\n },\n action: cr.action,\n reason: cr.reason,\n priority: cr.priority ?? 100,\n })),\n ...ROUTING_RULES.map((r, i) => ({ ...r, priority: 200 + i })),\n ].sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100))\n\n for (const rule of allRules) {\n if (rule.condition(input)) {\n return {\n action: rule.action,\n reason: rule.reason,\n }\n }\n }\n\n return {\n action: 'escalate_human',\n reason: 'No routing rule matched',\n }\n}\n\n// ============================================================================\n// Helpers for testing\n// ============================================================================\n\nexport function shouldRespond(action: RouteAction): boolean {\n return action === 'respond'\n}\n\nexport function shouldEscalate(action: RouteAction): boolean {\n return action.startsWith('escalate_')\n}\n\nexport function shouldSilence(action: RouteAction): boolean {\n return action === 'silence'\n}\n\nexport function getRoutingRules(): RoutingRule[] {\n return [...ROUTING_RULES]\n}\n\n// ============================================================================\n// Thread-aware routing (v3)\n// ============================================================================\n\nexport interface ThreadRouteInput {\n classification: ThreadClassifyOutput\n appConfig: AppConfig\n}\n\ninterface ThreadRoutingRule {\n name: string\n condition: (input: ThreadRouteInput) => boolean\n action: RouteAction\n reason: string\n}\n\nconst THREAD_ROUTING_RULES: ThreadRoutingRule[] = [\n // Resolved threads - silence\n {\n name: 'resolved_silence',\n condition: ({ classification }) =>\n classification.category === 'resolved' ||\n isThreadResolved(classification.signals),\n action: 'silence',\n reason: 'Thread already resolved - no response needed',\n },\n\n // Awaiting customer reply - silence\n {\n name: 'awaiting_customer_silence',\n condition: ({ classification }) =>\n classification.category === 'awaiting_customer' ||\n classification.signals.awaitingCustomerReply,\n action: 'silence',\n reason: 'Awaiting customer reply - no response needed yet',\n },\n\n // Teammate already engaged - support them\n {\n name: 'support_teammate',\n condition: ({ classification }) =>\n shouldSupportTeammate(classification.signals),\n action: 'support_teammate',\n reason: 'Human teammate is handling - adding context as comment',\n },\n\n // Instructor strategy - route to instructor\n {\n name: 'instructor_strategy',\n condition: ({ classification }) =>\n classification.category === 'instructor_strategy' ||\n classification.signals.instructorIsAuthor,\n action: 'escalate_instructor',\n reason: 'Instructor strategy discussion',\n },\n\n // Internal thread (no customers) - silence or escalate\n {\n name: 'internal_thread',\n condition: ({ classification }) => classification.signals.isInternalThread,\n action: 'silence',\n reason: 'Internal thread - no customer to respond to',\n },\n\n // System messages - always silent\n {\n name: 'system_silence',\n condition: ({ classification }) => classification.category === 'system',\n action: 'silence',\n reason: 'Automated/system message - no response needed',\n },\n\n // Spam - always silent (single-message vendor outreach)\n {\n name: 'spam_silence',\n condition: ({ classification }) => classification.category === 'spam',\n action: 'silence',\n reason: 'Vendor/spam outreach - no response needed',\n },\n\n // Legal threats - URGENT escalation (lawyer, lawsuit, legal action)\n // This is the ONLY path to escalate_urgent\n {\n name: 'legal_threat_urgent',\n condition: ({ classification }) => classification.signals.hasLegalThreat,\n action: 'escalate_urgent',\n reason: 'Legal threat detected - urgent human review required',\n },\n\n // VOC responses - catalog and analyze (valuable customer data)\n {\n name: 'voc_response_catalog',\n condition: ({ classification }) =>\n classification.category === 'voc_response',\n action: 'catalog_voc',\n reason:\n 'Voice of customer response - catalog, tag, notify Slack, maybe request testimonial expansion',\n },\n\n // Fan mail - route to instructor (personal messages)\n // NOTE: Must be BEFORE unknown_escalate so personal messages aren't misrouted\n {\n name: 'fan_mail_instructor',\n condition: ({ classification }) => classification.category === 'fan_mail',\n action: 'escalate_instructor',\n reason: 'Personal message for instructor',\n },\n\n // Personal/casual messages to instructor (even if not classified as fan_mail)\n // NOTE: Must be BEFORE unknown_escalate so isPersonalToInstructor signal is honored\n {\n name: 'personal_to_instructor',\n condition: ({ classification }) =>\n classification.signals.isPersonalToInstructor,\n action: 'escalate_instructor',\n reason: 'Personal message addressed to instructor',\n },\n\n // Unknown with low confidence - escalate\n {\n name: 'unknown_escalate',\n condition: ({ classification }) =>\n classification.category === 'unknown' || classification.confidence < 0.5,\n action: 'escalate_human',\n reason: 'Cannot confidently classify - needs human review',\n },\n\n // Refund outside policy window - needs human approval\n {\n name: 'refund_policy_violation',\n condition: ({ classification }) =>\n classification.category === 'support_refund' &&\n classification.signals.hasOutsidePolicyTimeframe,\n action: 'escalate_human',\n reason: 'Refund request outside policy window - needs human approval',\n },\n\n // Angry customer - human escalation (NOT urgent - that's for legal only)\n {\n name: 'angry_escalate',\n condition: ({ classification }) => classification.signals.hasAngrySentiment,\n action: 'escalate_human',\n reason: 'Frustrated customer - needs human attention and judgment',\n },\n\n // Support categories - respond\n {\n name: 'support_respond',\n condition: ({ classification }) =>\n classification.category.startsWith('support_'),\n action: 'respond',\n reason: 'Support request - agent should respond',\n },\n\n // Presales FAQ - agent can answer from knowledge base\n {\n name: 'presales_faq_respond',\n condition: ({ classification }) =>\n classification.category === 'presales_faq',\n action: 'respond',\n reason: 'Presales FAQ - can answer from knowledge base',\n },\n\n // Presales team/enterprise - needs sales process\n {\n name: 'presales_team_escalate',\n condition: ({ classification }) =>\n classification.category === 'presales_team',\n action: 'escalate_human',\n reason: 'Team/enterprise inquiry - needs sales process',\n },\n\n // Presales consult - needs instructor judgment, track for learning\n {\n name: 'presales_consult_escalate',\n condition: ({ classification }) =>\n classification.category === 'presales_consult',\n action: 'escalate_instructor',\n reason:\n 'Presales consultation - instructor can provide personalized guidance',\n },\n]\n\n/**\n * Route a thread-based classification to an action.\n * Includes thread-aware rules (resolved, awaiting, support_teammate).\n */\nexport function routeThread(input: ThreadRouteInput): RouteOutput {\n const startTime = Date.now()\n const { classification, appConfig } = input\n const signals = classification.signals\n\n for (const rule of THREAD_ROUTING_RULES) {\n if (rule.condition(input)) {\n const result = {\n action: rule.action,\n reason: rule.reason,\n }\n\n // High-cardinality decision-point logging\n log('info', 'routeThread:decision', {\n workflow: 'pipeline',\n step: 'routeThread',\n appId: appConfig?.appId,\n // Decision outcome\n action: result.action,\n reason: result.reason,\n ruleName: rule.name,\n // Classification inputs\n category: classification.category,\n confidence: classification.confidence,\n classificationReasoning: classification.reasoning,\n // Thread context\n threadLength: signals.threadLength,\n threadDurationHours: signals.threadDurationHours,\n threadPattern: signals.threadPattern,\n customerMessageCount: signals.customerMessageCount,\n lastResponderType: signals.lastResponderType,\n // Thread signals that drove the decision\n hasTeammateMessage: signals.hasTeammateMessage,\n hasInstructorMessage: signals.hasInstructorMessage,\n instructorIsAuthor: signals.instructorIsAuthor,\n isInternalThread: signals.isInternalThread,\n awaitingCustomerReply: signals.awaitingCustomerReply,\n hasResolutionPhrase: signals.hasResolutionPhrase,\n // Escalation signals\n hasAngrySentiment: signals.hasAngrySentiment,\n hasLegalThreat: signals.hasLegalThreat,\n hasOutsidePolicyTimeframe: signals.hasOutsidePolicyTimeframe,\n isPersonalToInstructor: signals.isPersonalToInstructor,\n // Decision explanation\n matchedRule: rule.name,\n rulesEvaluated:\n THREAD_ROUTING_RULES.findIndex((r) => r.name === rule.name) + 1,\n totalRules: THREAD_ROUTING_RULES.length,\n durationMs: Date.now() - startTime,\n }).catch(() => {})\n\n return result\n }\n }\n\n const defaultResult = {\n action: 'escalate_human' as const,\n reason: 'No routing rule matched - needs human review',\n }\n\n // High-cardinality decision-point logging for default case\n log('info', 'routeThread:decision', {\n workflow: 'pipeline',\n step: 'routeThread',\n appId: appConfig?.appId,\n // Decision outcome\n action: defaultResult.action,\n reason: defaultResult.reason,\n ruleName: 'default_fallback',\n // Classification inputs\n category: classification.category,\n confidence: classification.confidence,\n // Thread context\n threadLength: signals.threadLength,\n threadPattern: signals.threadPattern,\n customerMessageCount: signals.customerMessageCount,\n // Decision explanation\n matchedRule: null,\n rulesEvaluated: THREAD_ROUTING_RULES.length,\n totalRules: THREAD_ROUTING_RULES.length,\n noRuleMatched: true,\n durationMs: Date.now() - startTime,\n }).catch(() => {})\n\n return defaultResult\n}\n\n/**\n * Get all thread routing rules (for testing/inspection).\n */\nexport function getThreadRoutingRules(): ThreadRoutingRule[] {\n return [...THREAD_ROUTING_RULES]\n}\n\n// ============================================================================\n// Memory-aware routing (v4)\n// ============================================================================\n\nexport interface RouteWithMemoryInput extends RouteInput {\n conversationId?: string\n runId?: string\n}\n\nexport interface ThreadRouteWithMemoryInput extends ThreadRouteInput {\n conversationId: string\n runId?: string\n}\n\nexport interface RouteWithMemoryOutput extends RouteOutput {\n /** Memories that influenced this routing decision */\n citedMemoryIds?: string[]\n /** Memory context that was considered */\n memoryContext?: string\n /** Whether memory suggested a different route */\n memoryOverride?: {\n suggestedAction: RouteAction\n reason: string\n confidence: number\n }\n}\n\n/**\n * Route with memory integration.\n *\n * Queries past routing decisions to learn from similar situations:\n * - If similar tickets were escalated and confirmed correct, may suggest escalation\n * - If similar escalations were corrected (\"should have responded\"), avoids over-escalating\n *\n * @example\n * ```typescript\n * const result = await routeWithMemory({\n * message,\n * classification,\n * appConfig,\n * conversationId: 'cnv_abc123',\n * runId: 'run_xyz'\n * })\n *\n * // Check if memory suggested a different route\n * if (result.memoryOverride) {\n * console.log('Memory suggests:', result.memoryOverride.suggestedAction)\n * }\n * ```\n */\nexport async function routeWithMemory(\n input: RouteWithMemoryInput\n): Promise<RouteWithMemoryOutput> {\n const { message, classification, appConfig, conversationId, runId } = input\n\n const startTime = Date.now()\n\n await log('debug', 'routeWithMemory started', {\n workflow: 'pipeline',\n step: 'routeWithMemory',\n appId: appConfig.appId,\n conversationId,\n category: classification.category,\n })\n\n // Get base rule-based routing first\n const baseResult = route({ message, classification, appConfig })\n\n // Query memory for similar routing decisions\n let memories: RelevantMemory[] = []\n let memoryContext = ''\n let memoryOverride: RouteWithMemoryOutput['memoryOverride'] = undefined\n\n try {\n const situation = buildRoutingSituation(classification, message)\n\n memories = await queryMemoriesForStage({\n appId: appConfig.appId,\n stage: 'route',\n situation,\n category: classification.category,\n limit: 5,\n threshold: 0.6,\n })\n\n if (memories.length > 0) {\n memoryContext = formatMemoriesCompact(memories)\n\n // Analyze memories for routing guidance\n memoryOverride = analyzeMemoriesForRouting(memories, baseResult.action)\n\n await log('debug', 'routeWithMemory memory query results', {\n workflow: 'pipeline',\n step: 'routeWithMemory',\n appId: appConfig.appId,\n conversationId,\n memoriesFound: memories.length,\n topScore: memories[0]?.score ?? 0,\n hasOverride: !!memoryOverride,\n suggestedOverride: memoryOverride?.suggestedAction,\n })\n\n // Record citation if we have a run ID\n if (runId) {\n const citedIds = memories.map((m) => m.id)\n await citeMemories(citedIds, runId, appConfig.appId)\n }\n }\n } catch (error) {\n await log('warn', 'routeWithMemory memory query failed', {\n workflow: 'pipeline',\n step: 'routeWithMemory',\n appId: appConfig.appId,\n conversationId,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n const durationMs = Date.now() - startTime\n\n await log('info', 'routeWithMemory completed', {\n workflow: 'pipeline',\n step: 'routeWithMemory',\n appId: appConfig.appId,\n conversationId,\n action: baseResult.action,\n reason: baseResult.reason,\n memoriesCited: memories.length,\n hasOverride: !!memoryOverride,\n durationMs,\n })\n\n // If memory strongly suggests a different action, include the recommendation\n // but don't override automatically - let the caller decide\n return {\n action: baseResult.action,\n reason: memoryOverride\n ? `${baseResult.reason} [Memory suggests: ${memoryOverride.suggestedAction} - ${memoryOverride.reason}]`\n : baseResult.reason,\n citedMemoryIds: memories.length > 0 ? memories.map((m) => m.id) : undefined,\n memoryContext: memoryContext || undefined,\n memoryOverride,\n }\n}\n\n/**\n * Thread-aware routing with memory integration.\n *\n * @example\n * ```typescript\n * const result = await routeThreadWithMemory({\n * classification,\n * appConfig,\n * conversationId: 'cnv_abc123',\n * runId: 'run_xyz'\n * })\n * ```\n */\nexport async function routeThreadWithMemory(\n input: ThreadRouteWithMemoryInput\n): Promise<RouteWithMemoryOutput> {\n const { classification, appConfig, conversationId, runId } = input\n\n const startTime = Date.now()\n\n await log('debug', 'routeThreadWithMemory started', {\n workflow: 'pipeline',\n step: 'routeThreadWithMemory',\n appId: appConfig.appId,\n conversationId,\n category: classification.category,\n threadLength: classification.signals.threadLength,\n })\n\n // Get base rule-based routing first\n const baseResult = routeThread({ classification, appConfig })\n\n // Query memory for similar routing decisions\n let memories: RelevantMemory[] = []\n let memoryContext = ''\n let memoryOverride: RouteWithMemoryOutput['memoryOverride'] = undefined\n\n try {\n const situation = buildThreadRoutingSituation(classification)\n\n memories = await queryMemoriesForStage({\n appId: appConfig.appId,\n stage: 'route',\n situation,\n category: classification.category,\n limit: 5,\n threshold: 0.6,\n })\n\n if (memories.length > 0) {\n memoryContext = formatMemoriesCompact(memories)\n\n // Analyze memories for routing guidance\n memoryOverride = analyzeMemoriesForRouting(memories, baseResult.action)\n\n await log('debug', 'routeThreadWithMemory memory query results', {\n workflow: 'pipeline',\n step: 'routeThreadWithMemory',\n appId: appConfig.appId,\n conversationId,\n memoriesFound: memories.length,\n topScore: memories[0]?.score ?? 0,\n hasOverride: !!memoryOverride,\n suggestedOverride: memoryOverride?.suggestedAction,\n })\n\n // Record citation if we have a run ID\n if (runId) {\n const citedIds = memories.map((m) => m.id)\n await citeMemories(citedIds, runId, appConfig.appId)\n }\n }\n } catch (error) {\n await log('warn', 'routeThreadWithMemory memory query failed', {\n workflow: 'pipeline',\n step: 'routeThreadWithMemory',\n appId: appConfig.appId,\n conversationId,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n const durationMs = Date.now() - startTime\n\n await log('info', 'routeThreadWithMemory completed', {\n workflow: 'pipeline',\n step: 'routeThreadWithMemory',\n appId: appConfig.appId,\n conversationId,\n action: baseResult.action,\n reason: baseResult.reason,\n memoriesCited: memories.length,\n hasOverride: !!memoryOverride,\n durationMs,\n })\n\n return {\n action: baseResult.action,\n reason: memoryOverride\n ? `${baseResult.reason} [Memory suggests: ${memoryOverride.suggestedAction} - ${memoryOverride.reason}]`\n : baseResult.reason,\n citedMemoryIds: memories.length > 0 ? memories.map((m) => m.id) : undefined,\n memoryContext: memoryContext || undefined,\n memoryOverride,\n }\n}\n\n// ============================================================================\n// Memory Analysis Helpers\n// ============================================================================\n\n/**\n * Build situation string for routing memory query.\n */\nfunction buildRoutingSituation(\n classification: RouteInput['classification'],\n message: RouteInput['message']\n): string {\n const parts = [\n `Category: ${classification.category}`,\n `Confidence: ${(classification.confidence * 100).toFixed(0)}%`,\n ]\n\n // Add relevant signals\n const signals = classification.signals\n if (signals.hasAngrySentiment) parts.push('Sentiment: Angry')\n if (signals.hasLegalThreat) parts.push('Signal: Legal threat')\n if (signals.hasOutsidePolicyTimeframe)\n parts.push('Signal: Outside policy window')\n if (signals.isPersonalToInstructor)\n parts.push('Signal: Personal to instructor')\n\n // Add subject/body summary\n parts.push(`\\nIssue: ${message.subject}`)\n if (message.body.length > 200) {\n parts.push(message.body.slice(0, 200) + '...')\n } else {\n parts.push(message.body)\n }\n\n return parts.join('\\n')\n}\n\n/**\n * Build situation string for thread-based routing memory query.\n */\nfunction buildThreadRoutingSituation(\n classification: ThreadClassifyOutput\n): string {\n const parts = [\n `Category: ${classification.category}`,\n `Confidence: ${(classification.confidence * 100).toFixed(0)}%`,\n ]\n\n // Add thread signals\n const signals = classification.signals\n parts.push(`Thread length: ${signals.threadLength} messages`)\n if (signals.hasAngrySentiment) parts.push('Sentiment: Angry')\n if (signals.hasLegalThreat) parts.push('Signal: Legal threat')\n if (signals.hasTeammateMessage) parts.push('Signal: Teammate engaged')\n if (signals.hasInstructorMessage) parts.push('Signal: Instructor involved')\n\n // Add reasoning if available\n if (classification.reasoning) {\n parts.push(`\\nContext: ${classification.reasoning}`)\n }\n\n return parts.join('\\n')\n}\n\n/**\n * Analyze memories to determine if they suggest a different route.\n *\n * Logic:\n * - If similar situations were escalated and marked SUCCESS -> suggest escalating\n * - If similar situations were auto-responded and marked SUCCESS -> suggest responding\n * - If there are CORRECTED memories saying \"should have escalated\" -> suggest escalating\n * - If there are CORRECTED memories saying \"should have responded\" -> suggest responding\n */\nfunction analyzeMemoriesForRouting(\n memories: RelevantMemory[],\n currentAction: RouteAction\n): RouteWithMemoryOutput['memoryOverride'] {\n // Count outcomes by action suggested in memories\n const actionCounts: Record<\n string,\n { success: number; corrected: number; total: number }\n > = {}\n\n for (const memory of memories) {\n // Parse the decision to extract the route action\n const actionMatch = memory.decision.match(/Routed to:\\s*(\\w+)/i)\n const routedAction = actionMatch?.[1] || 'unknown'\n\n if (!actionCounts[routedAction]) {\n actionCounts[routedAction] = { success: 0, corrected: 0, total: 0 }\n }\n actionCounts[routedAction].total++\n\n if (memory.outcome === 'success') {\n actionCounts[routedAction].success++\n } else if (memory.outcome === 'corrected' && memory.correction) {\n actionCounts[routedAction].corrected++\n\n // Parse what the correction suggested\n const correctionMatch = memory.correction.match(/Should have:\\s*(\\w+)/i)\n const suggestedAction = correctionMatch?.[1]\n if (suggestedAction && suggestedAction !== routedAction) {\n if (!actionCounts[suggestedAction]) {\n actionCounts[suggestedAction] = { success: 0, corrected: 0, total: 0 }\n }\n // Count corrections as evidence FOR the suggested action\n actionCounts[suggestedAction].success++\n }\n }\n }\n\n // Determine if there's strong evidence for a different action\n let bestAction: string | null = null\n let bestScore = 0\n\n for (const [action, counts] of Object.entries(actionCounts)) {\n // Score = successes - (corrected * 2), weighted by recency (already in score)\n const score = counts.success - counts.corrected * 2\n if (score > bestScore && score >= 2) {\n // Need at least 2 positive signals\n bestAction = action\n bestScore = score\n }\n }\n\n // Only suggest override if best action differs from current\n if (\n bestAction &&\n bestAction !== currentAction &&\n isValidRouteAction(bestAction)\n ) {\n const counts = actionCounts[bestAction]\n const confidence = Math.min(0.9, 0.5 + bestScore * 0.1)\n\n return {\n suggestedAction: bestAction as RouteAction,\n reason: `${counts?.success ?? 0} similar tickets routed this way successfully`,\n confidence,\n }\n }\n\n // Check for corrections suggesting we should escalate when we're about to respond\n if (currentAction === 'respond') {\n for (const memory of memories) {\n if (\n memory.outcome === 'corrected' &&\n memory.correction &&\n memory.correction.includes('escalate') &&\n memory.score > 0.7 // High similarity\n ) {\n return {\n suggestedAction: 'escalate_human',\n reason: `Similar ticket was corrected: ${memory.correction}`,\n confidence: 0.7,\n }\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Type guard for valid route actions.\n */\nfunction isValidRouteAction(action: string): action is RouteAction {\n const validActions: RouteAction[] = [\n 'respond',\n 'silence',\n 'escalate_human',\n 'escalate_instructor',\n 'escalate_urgent',\n 'support_teammate',\n 'catalog_voc',\n ]\n return validActions.includes(action as RouteAction)\n}\n\n// ============================================================================\n// Routing Outcome Recording\n// ============================================================================\n\nexport interface RecordRoutingOutcomeInput {\n /** App identifier */\n appId: string\n /** Category of the ticket */\n category: MessageCategory\n /** Summary of the issue */\n issueSummary: string\n /** What action was taken */\n routedAction: RouteAction\n /** Was the routing correct? */\n wasCorrect: boolean\n /** If incorrect, what should have been done */\n correctAction?: RouteAction\n /** Conversation ID for tracking */\n conversationId: string\n /** Optional: Additional context about why the correction was needed */\n correctionReason?: string\n /** Optional: Memory IDs that influenced the original routing */\n citedMemoryIds?: string[]\n /** Optional: Run ID for citation tracking */\n runId?: string\n}\n\n/**\n * Record a routing outcome to memory.\n *\n * Call this when a human confirms or corrects a routing decision.\n * This enables the system to learn from routing mistakes.\n *\n * @example\n * ```typescript\n * // Routing was correct\n * await recordRoutingOutcome({\n * appId: 'total-typescript',\n * category: 'support_refund',\n * issueSummary: 'Customer requested refund after 2 months',\n * routedAction: 'escalate_human',\n * wasCorrect: true,\n * conversationId: 'cnv_abc123'\n * })\n *\n * // Routing was incorrect - should have escalated\n * await recordRoutingOutcome({\n * appId: 'total-typescript',\n * category: 'support_technical',\n * issueSummary: 'Complex technical question about edge case',\n * routedAction: 'respond',\n * wasCorrect: false,\n * correctAction: 'escalate_instructor',\n * correctionReason: 'Question required instructor expertise',\n * conversationId: 'cnv_def456'\n * })\n * ```\n */\nexport async function recordRoutingOutcome(\n input: RecordRoutingOutcomeInput\n): Promise<void> {\n const {\n appId,\n category,\n issueSummary,\n routedAction,\n wasCorrect,\n correctAction,\n conversationId,\n correctionReason,\n citedMemoryIds,\n runId,\n } = input\n\n const situation = `Category: ${category}\\nIssue: ${issueSummary}`\n const decision = `Routed to: ${routedAction}`\n\n const tags = ['routing', category, routedAction]\n if (!wasCorrect && correctAction) {\n tags.push('misroute', correctAction)\n }\n\n // Store the routing decision as a memory\n await SupportMemoryService.store({\n app_slug: appId,\n situation,\n decision,\n stage: 'route',\n outcome: wasCorrect ? 'success' : 'corrected',\n correction: wasCorrect\n ? undefined\n : `Should have: ${correctAction}${correctionReason ? ` - ${correctionReason}` : ''}`,\n category,\n conversation_id: conversationId,\n tags,\n })\n\n // If we know which memories were cited in the routing decision,\n // record the outcome for those memories\n if (runId && citedMemoryIds && citedMemoryIds.length > 0) {\n try {\n await SupportMemoryService.recordCitationOutcome(\n citedMemoryIds,\n runId,\n wasCorrect ? 'success' : 'failure',\n appId\n )\n } catch (error) {\n console.warn(\n '[recordRoutingOutcome] Failed to record citation outcome:',\n error\n )\n }\n }\n}\n\n/**\n * Record that routing to escalation was confirmed correct.\n * Convenience wrapper for common case.\n */\nexport async function recordEscalationConfirmed(\n appId: string,\n category: MessageCategory,\n issueSummary: string,\n escalationType: 'escalate_human' | 'escalate_instructor' | 'escalate_urgent',\n conversationId: string\n): Promise<void> {\n await recordRoutingOutcome({\n appId,\n category,\n issueSummary,\n routedAction: escalationType,\n wasCorrect: true,\n conversationId,\n })\n}\n\n/**\n * Record that a response should have been escalated instead.\n * Common case: agent responded but human had to intervene.\n */\nexport async function recordShouldHaveEscalated(\n appId: string,\n category: MessageCategory,\n issueSummary: string,\n correctEscalationType:\n | 'escalate_human'\n | 'escalate_instructor'\n | 'escalate_urgent',\n conversationId: string,\n reason?: string\n): Promise<void> {\n await recordRoutingOutcome({\n appId,\n category,\n issueSummary,\n routedAction: 'respond',\n wasCorrect: false,\n correctAction: correctEscalationType,\n correctionReason: reason,\n conversationId,\n })\n}\n\n/**\n * Record that an escalation was unnecessary - should have auto-responded.\n * Common case: escalated but could have been handled automatically.\n */\nexport async function recordUnnecessaryEscalation(\n appId: string,\n category: MessageCategory,\n issueSummary: string,\n escalationType: 'escalate_human' | 'escalate_instructor' | 'escalate_urgent',\n conversationId: string,\n reason?: string\n): Promise<void> {\n await recordRoutingOutcome({\n appId,\n category,\n issueSummary,\n routedAction: escalationType,\n wasCorrect: false,\n correctAction: 'respond',\n correctionReason: reason,\n conversationId,\n })\n}\n","/**\n * Step 5: VALIDATE\n *\n * Checks draft response for quality issues before sending.\n * Pattern checks are deterministic (no LLM) - fast and predictable.\n *\n * Memory Integration:\n * Before returning, queries memory for similar corrected drafts to catch\n * repeated mistakes. This is the \"does this draft repeat a known mistake?\" check.\n *\n * Relevance Check (LLM):\n * When the original customer message is available, uses a lightweight LLM call\n * (claude-haiku) to verify the draft actually addresses what the customer asked.\n * This prevents sending generic/off-topic responses to specific questions.\n */\n\nimport { generateObject } from 'ai'\nimport { z } from 'zod'\nimport { type RelevantMemory, queryCorrectedMemories } from '../../memory/query'\nimport { log } from '../../observability/axiom'\nimport { type RetrievedSkill, retrieveSkills } from '../../skill-retrieval'\nimport { getCategoryThreshold } from '../thresholds'\nimport type {\n GatherOutput,\n MessageCategory,\n ValidateInput,\n ValidateOutput,\n ValidationIssue,\n ValidationIssueType,\n ValidatorDecision,\n} from '../types'\n\n// ============================================================================\n// Validation patterns\n// ============================================================================\n\n// Internal state leaks - system info that should never reach customers\nconst INTERNAL_LEAK_PATTERNS = [\n /no instructor (?:routing |teammate )?(?:configured|set up|available)/i,\n /(?:can't|cannot|unable to) route/i,\n /should (?:be |go )routed/i,\n /routing (?:failed|error|not available)/i,\n /app not found/i,\n /configuration error/i,\n /api (?:error|failure)/i,\n /tool (?:failed|error)/i,\n /database error/i,\n /falls? outside (?:my |the )?scope/i,\n /outside the scope/i,\n /You'll want to reach out through/i,\n\n // ── Added from forensic audit (Epic 1A) ──────────────────────────────\n // Instructor/routing system disclosure\n /(?:don't|do not) have (?:a |an )?instructor/i,\n /no instructor (?:assignment|configuration)/i,\n /(?:can't|cannot) (?:forward|assign|transfer) (?:this |it )?(?:to |directly)/i,\n\n // System tool/capability disclosure\n /(?:can't|cannot|unable to) use \\w+(?:Tool|Instructor|Function)/i,\n /(?:in|within) (?:the|our|my) system/i,\n /internal (?:process|routing|configuration|pipeline|workflow|team|tool)/i,\n\n // System limitation disclosure\n /(?:not |un)?(?:configured|equipped|set up) to (?:forward|route|assign|escalate|transfer)/i,\n /(?:my|the|our) (?:tools?|capabilities|functions?|features?|system) (?:don't|do not|can't|cannot|won't|doesn't|does not)/i,\n\n // Business-team routing disclosure\n /(?:forwarded|routed|sent|assigned) to (?:\\w+(?:'s)? )?(?:business|development|sales|marketing)[\\w\\s]*(?:team|department|group)/i,\n /(?:business development|dev) team or equivalent/i,\n\n // Routing inability disclosure\n /(?:don't|do not) have (?:a |the )?(?:way|means|method|mechanism) to (?:route|forward|send|assign)/i,\n]\n\n// Meta-commentary - agent explaining itself instead of acting\nconst META_COMMENTARY_PATTERNS = [\n /^This is (?:a |an )/i, // \"This is a vendor email\"\n /I (?:won't|will not|cannot|can't) (?:respond|draft|reply)/i,\n /I(?:'m| am) going to (?:stop|not respond)/i,\n /No (?:response|action) needed/i,\n /Per my guidelines/i,\n /is (?:clearly |obviously )?(?:not )?(?:a )?support (?:request|ticket)/i,\n /is clearly meant for/i,\n /is clearly personal/i,\n /I should not draft/i,\n /This (?:should|needs to) be (?:handled|routed|forwarded|escalated|sent|assigned)/i,\n /I'll note that/i,\n\n // ── Added from forensic audit (Epic 1A) ──────────────────────────────\n // Agent explaining what it's going to do instead of doing it\n /I(?:'ll| will| am going to) (?:draft|compose|write|prepare|craft|put together) (?:a |an |the )?(?:response|reply|email|message)/i,\n /^(?:Here(?:'s| is)) (?:a |my |the )?(?:draft|proposed|suggested) (?:response|reply|email|message)/i,\n /^Let me (?:draft|compose|write|prepare|craft)/i,\n\n // Agent describing/categorizing the email instead of responding\n /^This (?:appears|seems|looks) to be (?:a |an )/i,\n /^This (?:email|message|inquiry|conversation|thread) (?:is|appears|seems|involves|contains|relates)/i,\n\n // Agent referencing its own decision-making about the conversation\n /I(?:'ve| have) (?:determined|assessed|classified|categorized|evaluated|identified) (?:this|that|the (?:conversation|email|message|request))/i,\n /(?:The |This )?conversation has been (?:classified|categorized|flagged|tagged|marked)/i,\n\n // Agent refusing to act (expanded — existing covers \"I won't respond\" but not \"doesn't need\")\n /(?:doesn't|does not|don't|do not) (?:need|require|warrant) (?:a |my |any )?(?:response|reply|action|draft)/i,\n /I (?:don't|do not) need to (?:respond|reply|act|draft)/i,\n /This (?:isn't|is not) (?:something|a (?:case|situation|matter)) I/i,\n\n // Agent talking about the customer in third person instead of TO them\n /^The (?:customer|user|sender|person|individual) (?:is |has |needs |wants |seems |appears |asked |wrote |sent |mentioned |requested )/i,\n /(?:the customer(?:'s)?|the user(?:'s)?|the sender(?:'s)?) (?:question|request|inquiry|issue|concern|email|message|problem) /i,\n\n // Agent narrating its routing/escalation process\n /^(?:I(?:'m| am) )?(?:Flagging|Routing|Escalating|Forwarding|Sending|Passing) (?:this|it) (?:to |for |along)/i,\n /I(?:'ll| will) (?:flag|route|escalate|forward|pass) (?:this|it) (?:to |for |along)/i,\n\n // Categorization language — labeling the email type (not customer-facing)\n /This is (?:a |an )?(?:business|vendor|partnership|sales|marketing|outreach|fan[\\s-]?mail|personal|promotional) (?:email|message|inquiry|pitch|request|outreach)/i,\n /not (?:a |an? )?(?:customer )?support (?:request|issue|ticket|question|inquiry)/i,\n]\n\n// Banned phrases - corporate speak the prompt explicitly forbids\nconst BANNED_PHRASES = [\n /^Great!/, // Exclamatory opener\n /I'd (?:recommend|suggest)/i, // Passive suggestions\n /I would (?:recommend|suggest)/i,\n /Is there a specific area you're curious about/i,\n /Would you like help with/i,\n /Let me know if you have any other questions/i,\n /I hope this helps/i,\n /Happy to help/i,\n /I understand/i, // Unless genuinely appropriate\n /I hear you/i,\n /I apologize for any inconvenience/i,\n /Thanks for reaching out/i,\n /Thanks for sharing/i,\n /\\u2014/, // Em dash\n /—/, // Em dash (alternate encoding)\n /I don't have the ability/i,\n /Please (?:feel free to )?reach out/i,\n /Don't hesitate to/i,\n]\n\nconst FABRICATION_PATTERNS = {\n price: /\\$[\\d,]+(?:\\.\\d{2})?/g,\n timeline: /within \\d+ (?:hours?|days?|weeks?)/gi,\n percentage: /\\d+%/g,\n guarantee: /guarantee|always|never|definitely|certainly/gi,\n} as const\n\n// Length thresholds\nconst MIN_RESPONSE_LENGTH = 10\nconst MAX_RESPONSE_LENGTH = 2000\n\n// ============================================================================\n// Individual validators\n// ============================================================================\n\nexport function calculateConfidenceScore(issues: ValidationIssue[]): number {\n let score = 1.0\n for (const issue of issues) {\n switch (issue.severity) {\n case 'error':\n score -= 0.3\n break\n case 'warning':\n score -= 0.1\n break\n case 'info':\n score -= 0.02\n break\n }\n }\n return Math.max(0, score)\n}\n\nfunction checkInternalLeaks(draft: string): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n\n for (const pattern of INTERNAL_LEAK_PATTERNS) {\n const match = draft.match(pattern)\n if (match) {\n issues.push({\n type: 'internal_leak',\n severity: 'error',\n message: 'Response exposes internal system state',\n match: match[0],\n position: match.index,\n })\n }\n }\n\n return issues\n}\n\nfunction checkMetaCommentary(draft: string): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n\n for (const pattern of META_COMMENTARY_PATTERNS) {\n const match = draft.match(pattern)\n if (match) {\n issues.push({\n type: 'meta_commentary',\n severity: 'error',\n message: 'Response contains meta-commentary about agent behavior',\n match: match[0],\n position: match.index,\n })\n }\n }\n\n return issues\n}\n\nfunction checkBannedPhrases(draft: string): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n\n for (const pattern of BANNED_PHRASES) {\n const match = draft.match(pattern)\n if (match) {\n issues.push({\n type: 'banned_phrase',\n severity: 'error',\n message: 'Response contains banned phrase',\n match: match[0],\n position: match.index,\n })\n }\n }\n\n return issues\n}\n\nfunction checkFabrication(\n draft: string,\n context: GatherOutput\n): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n\n // Check for fabricated course content when no knowledge was found\n // Guard against undefined context.knowledge from incomplete event data\n const hasKnowledge = (context.knowledge?.length ?? 0) > 0\n\n const fabricationPatterns = [\n /Start with the (?:fundamentals|basics) section/i,\n /covers? (?:core )?concepts like/i,\n /begin with (?:the )?(?:intro|introduction|basics)/i,\n /module \\d+/i, // Specific module references\n /lesson \\d+/i, // Specific lesson references\n /chapter \\d+/i, // Specific chapter references\n ]\n\n if (!hasKnowledge) {\n for (const pattern of fabricationPatterns) {\n const match = draft.match(pattern)\n if (match) {\n issues.push({\n type: 'fabrication',\n severity: 'error',\n message:\n 'Response references course content without knowledge base support',\n match: match[0],\n position: match.index,\n })\n }\n }\n }\n\n return issues\n}\n\ntype ClaimType = keyof typeof FABRICATION_PATTERNS\n\ninterface Claim {\n type: ClaimType\n value: string\n index?: number\n}\n\nfunction stripQuotedText(draft: string): string {\n const lines = draft.split(/\\r?\\n/)\n const keptLines: string[] = []\n\n for (const line of lines) {\n if (/^On .+ wrote:$/i.test(line.trim())) {\n break\n }\n\n if (/^\\s*>/.test(line)) {\n continue\n }\n\n keptLines.push(line)\n }\n\n const keptText = keptLines.join('\\n')\n\n return keptText.replace(\n /(customer|you)\\s+(?:said|wrote|mentioned|stated|shared|quoted)[:\\s]*\"[^\"]+\"/gi,\n (match) => match.replace(/\"[^\"]+\"/g, '')\n )\n}\n\nfunction extractClaims(draft: string): Claim[] {\n const claims: Claim[] = []\n for (const [type, pattern] of Object.entries(FABRICATION_PATTERNS)) {\n const matches = draft.matchAll(pattern)\n for (const match of matches) {\n claims.push({\n type: type as ClaimType,\n value: match[0],\n index: match.index,\n })\n }\n }\n return claims\n}\n\nfunction checkClaimAgainstSkills(\n claim: Claim,\n skills: RetrievedSkill[]\n): boolean {\n const claimValue = claim.value.toLowerCase()\n for (const skill of skills) {\n const description = skill.description?.toLowerCase() ?? ''\n const markdown = skill.markdown?.toLowerCase() ?? ''\n if (description.includes(claimValue) || markdown.includes(claimValue)) {\n return true\n }\n }\n return false\n}\n\nfunction checkFabricatedClaims(\n draft: string,\n skills: RetrievedSkill[]\n): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n const cleanedDraft = stripQuotedText(draft)\n const claims = extractClaims(cleanedDraft)\n\n for (const claim of claims) {\n const isSourced = checkClaimAgainstSkills(claim, skills)\n if (!isSourced) {\n issues.push({\n type: 'fabrication',\n severity: claim.type === 'price' ? 'error' : 'warning',\n message: `Unsourced ${claim.type} claim: ${claim.value}`,\n match: claim.value,\n position: claim.index,\n })\n }\n }\n\n return issues\n}\n\nfunction checkLength(draft: string): ValidationIssue[] {\n const issues: ValidationIssue[] = []\n\n if (draft.length < MIN_RESPONSE_LENGTH) {\n issues.push({\n type: 'too_short',\n severity: 'warning',\n message: `Response too short (${draft.length} chars, min ${MIN_RESPONSE_LENGTH})`,\n })\n }\n\n if (draft.length > MAX_RESPONSE_LENGTH) {\n issues.push({\n type: 'too_long',\n severity: 'warning',\n message: `Response too long (${draft.length} chars, max ${MAX_RESPONSE_LENGTH})`,\n })\n }\n\n return issues\n}\n\n// ============================================================================\n// Relevance check (LLM-based)\n// ============================================================================\n\nconst relevanceSchema = z.object({\n relevant: z\n .boolean()\n .describe('Whether the draft response addresses the customer message'),\n score: z.coerce\n .number()\n .finite()\n .min(0)\n .max(1)\n .describe(\n 'Relevance score from 0 (completely off-topic) to 1 (directly addresses the question)'\n ),\n reasoning: z\n .string()\n .describe('Brief explanation of the relevance assessment'),\n})\n\nconst RELEVANCE_CHECK_PROMPT = `You are a quality assurance checker for customer support responses.\n\nYour job: determine whether the draft response actually addresses what the customer asked.\n\nFlag as NOT relevant if the draft:\n- Is generic/templated when the customer asked a specific question\n- Is off-topic or clearly not responding to what was asked\n- Refers to missing or empty content (e.g., \"I notice your message came through without a subject line\" when the customer clearly wrote something)\n- Provides a canned greeting without addressing the actual question\n- Answers a completely different question than what was asked\n\nFlag as relevant if the draft:\n- Directly addresses the customer's question or concern\n- Acknowledges and responds to the specific topic raised\n- Even if imperfect, is clearly attempting to address the right topic\n\nBe practical — a response doesn't need to be perfect, just on-topic.`\n\n/**\n * Check if the draft response is relevant to the customer's message.\n * Uses a lightweight LLM call (claude-haiku) for semantic understanding.\n * Only runs when the customer message body is non-empty.\n */\nasync function checkRelevance(\n draft: string,\n customerMessage: { subject: string; body: string },\n model: string = 'anthropic/claude-haiku-4-5'\n): Promise<{ issues: ValidationIssue[]; score: number }> {\n const customerText = [\n customerMessage.subject ? `Subject: ${customerMessage.subject}` : '',\n customerMessage.body ? `Body: ${customerMessage.body}` : '',\n ]\n .filter(Boolean)\n .join('\\n')\n\n const prompt = `Customer message:\n${customerText}\n\nDraft response:\n${draft}\n\nIs this draft response relevant to what the customer asked?`\n\n const { object } = await generateObject({\n model,\n schema: relevanceSchema,\n system: RELEVANCE_CHECK_PROMPT,\n prompt,\n })\n\n const issues: ValidationIssue[] = []\n\n if (!object.relevant || object.score < 0.5) {\n issues.push({\n type: 'relevance',\n severity: 'error',\n message: `Draft does not address the customer's message: ${object.reasoning}`,\n })\n }\n\n return { issues, score: object.score }\n}\n\n// ============================================================================\n// Audience awareness check (LLM-based)\n// ============================================================================\n\nconst audienceAwarenessSchema = z.object({\n appropriate: z\n .boolean()\n .describe('Whether the draft uses customer-appropriate language'),\n issues: z\n .array(\n z.object({\n type: z\n .enum([\n 'technical_jargon',\n 'internal_reference',\n 'confusing_language',\n 'inappropriate_tone',\n ])\n .describe('Type of audience issue'),\n phrase: z.string().describe('The problematic phrase or term'),\n suggestion: z.string().optional().describe('Suggested alternative'),\n })\n )\n .describe('List of audience-inappropriate issues found'),\n reasoning: z\n .string()\n .describe('Brief explanation of the audience assessment'),\n})\n\nconst AUDIENCE_AWARENESS_PROMPT = `You are a quality assurance checker for customer support responses.\n\nYour job: determine whether the draft response uses appropriate language for customers.\n\nFlag as NOT appropriate if the draft:\n- Uses technical jargon without explanation (API, webhook, OAuth, endpoint, etc.)\n- References internal tools or processes (Stripe dashboard, Intercom, internal tickets, etc.)\n- Uses confusing or ambiguous language that a typical customer wouldn't understand\n- Has an inappropriate tone (condescending, overly casual, cold, etc.)\n\nFlag as appropriate if the draft:\n- Uses plain, everyday language\n- Explains technical concepts when necessary\n- Focuses on helping the customer, not internal processes\n- Maintains a warm, professional tone\n\nBe practical — some technical terms (like \"browser\" or \"login\") are fine. Flag only terms that typical customers wouldn't know.`\n\n/**\n * Check if the draft uses customer-appropriate language.\n * Uses an LLM call to detect jargon, internal references, and tone issues.\n */\nasync function checkAudienceAwareness(\n draft: string,\n model: string = 'anthropic/claude-haiku-4-5'\n): Promise<ValidationIssue[]> {\n const prompt = `Draft response to customer:\n${draft}\n\nIs this draft appropriate for a typical customer? Check for technical jargon, internal references, and tone.`\n\n const { object } = await generateObject({\n model,\n schema: audienceAwarenessSchema,\n system: AUDIENCE_AWARENESS_PROMPT,\n prompt,\n })\n\n const issues: ValidationIssue[] = []\n\n if (!object.appropriate && object.issues.length > 0) {\n for (const issue of object.issues) {\n issues.push({\n type: 'audience_inappropriate',\n severity: 'warning',\n message: `Draft contains ${issue.type.replace('_', ' ')}: \"${issue.phrase}\"${issue.suggestion ? ` (consider: \"${issue.suggestion}\")` : ''}`,\n match: issue.phrase,\n })\n }\n }\n\n return issues\n}\n\n// ============================================================================\n// Tool failure check\n// ============================================================================\n\n/** Critical tools that must succeed for certain categories */\nconst CRITICAL_TOOLS_BY_CATEGORY: Record<string, string[]> = {\n support_refund: ['user', 'purchases'],\n support_access: ['user'],\n support_billing: ['user', 'purchases'],\n support_transfer: ['user', 'purchases'],\n}\n\n/**\n * Check if critical gather tools failed for this category.\n * Returns an escalation decision if critical tools are unavailable.\n */\nfunction checkToolFailures(\n gatherErrors: Array<{ step: string; error: string }> | undefined,\n category: MessageCategory | undefined\n): { shouldEscalate: boolean; reason: string } {\n // Guard against undefined gatherErrors from incomplete event data\n if (!gatherErrors || gatherErrors.length === 0) {\n return { shouldEscalate: false, reason: '' }\n }\n\n const criticalTools = category\n ? (CRITICAL_TOOLS_BY_CATEGORY[category] ?? ['user'])\n : ['user']\n\n const failedCriticalTools = gatherErrors\n .filter((e) => criticalTools.includes(e.step))\n .map((e) => e.step)\n\n if (failedCriticalTools.length > 0) {\n return {\n shouldEscalate: true,\n reason: `System unable to verify customer - manual lookup needed (failed: ${failedCriticalTools.join(', ')})`,\n }\n }\n\n return { shouldEscalate: false, reason: '' }\n}\n\n// ============================================================================\n// Ground truth comparison (skill retrieval)\n// ============================================================================\n\nfunction extractRefundDays(text: string): number[] {\n if (!/refund/i.test(text)) return []\n\n const matches = text.matchAll(/(\\d{1,3})\\s*[- ]?\\s*day(?:s)?/gi)\n const days: number[] = []\n for (const match of matches) {\n const value = Number(match[1])\n if (!Number.isNaN(value)) {\n days.push(value)\n }\n }\n\n return days\n}\n\nfunction checkGroundTruth(\n draft: string,\n skills: RetrievedSkill[]\n): ValidationIssue[] {\n if (skills.length === 0) return []\n\n const draftDays = extractRefundDays(draft)\n if (draftDays.length === 0) return []\n\n const skillDays = skills.flatMap((skill) => {\n const skillText = [skill.name, skill.description, skill.markdown].filter(\n Boolean\n )\n return extractRefundDays(skillText.join('\\n'))\n })\n\n const uniqueSkillDays = Array.from(new Set(skillDays))\n if (uniqueSkillDays.length === 0) return []\n\n const uniqueDraftDays = Array.from(new Set(draftDays))\n const mismatchedDays = uniqueDraftDays.filter(\n (day) => !uniqueSkillDays.includes(day)\n )\n\n if (mismatchedDays.length === 0) return []\n\n return [\n {\n type: 'ground_truth_mismatch',\n severity: 'error',\n message: `Draft refund timeframe (${mismatchedDays.join(\n ', '\n )} days) conflicts with ground truth (${uniqueSkillDays.join(', ')} days)`,\n match: `draft:${mismatchedDays.join(', ')} skill:${uniqueSkillDays.join(\n ', '\n )}`,\n },\n ]\n}\n\n// ============================================================================\n// Main validate function\n// ============================================================================\n\n/**\n * Options for memory-enhanced validation\n */\nexport interface ValidateOptions {\n /** App ID for memory lookup */\n appId?: string\n /** Category of the support request (for more targeted memory queries) */\n category?: MessageCategory\n /** Skip memory query (for testing or when memory service unavailable) */\n skipMemoryQuery?: boolean\n /** Similarity threshold for matching corrections (default: 0.7) */\n correctionThreshold?: number\n /** Skip relevance check (for testing or when LLM unavailable) */\n skipRelevanceCheck?: boolean\n /** Model to use for relevance check (default: 'anthropic/claude-haiku-4-5') */\n relevanceModel?: string\n /** Check if draft uses customer-appropriate language (default: false) */\n checkAudienceAwareness?: boolean\n}\n\n/**\n * Extended validation result with memory context\n */\nexport interface ValidateResult extends ValidateOutput {\n /** Corrections that were checked against */\n correctionsChecked?: RelevantMemory[]\n /** Whether memory check was performed */\n memoryCheckPerformed: boolean\n /** Whether relevance check was performed */\n relevanceCheckPerformed: boolean\n}\n\nexport interface CategoryStats {\n sentUnchangedRate: number\n volume: number\n}\n\nexport async function getCategoryStats(\n _category?: MessageCategory\n): Promise<CategoryStats> {\n return {\n sentUnchangedRate: 0,\n volume: 0,\n }\n}\n\n/**\n * Synchronous validation - pattern checks only, no memory lookup.\n * Use this for fast, deterministic validation when memory isn't needed.\n */\nexport function validateSync(input: ValidateInput): ValidateOutput {\n const { draft, context, strictMode = false } = input\n\n const allIssues: ValidationIssue[] = [\n ...checkInternalLeaks(draft),\n ...checkMetaCommentary(draft),\n ...checkBannedPhrases(draft),\n ...checkFabrication(draft, context),\n ...checkLength(draft),\n ]\n\n // In strict mode, warnings are errors\n const issues = strictMode\n ? allIssues\n : allIssues.filter((i) => i.severity === 'error')\n\n const hasErrors = allIssues.some((i) => i.severity === 'error')\n\n return {\n valid: !hasErrors,\n issues: allIssues,\n suggestion: hasErrors\n ? 'Response has quality issues that would be visible to customers'\n : undefined,\n }\n}\n\n/**\n * Full validation with memory integration.\n * Queries memory for similar corrected drafts to catch repeated mistakes.\n *\n * @example\n * ```typescript\n * const result = await validate(\n * { draft, context },\n * { appId: 'total-typescript', category: 'support_refund' }\n * )\n *\n * if (!result.valid) {\n * console.log('Issues:', result.issues)\n * console.log('Corrections checked:', result.correctionsChecked?.length)\n * }\n * ```\n */\nexport async function validate(\n input: ValidateInput,\n options: ValidateOptions = {}\n): Promise<ValidateResult & ValidatorDecision> {\n const { draft, context, strictMode = false, customerMessage } = input\n const {\n appId,\n category,\n skipMemoryQuery = false,\n correctionThreshold = 0.7,\n skipRelevanceCheck = false,\n relevanceModel = 'anthropic/claude-haiku-4-5',\n checkAudienceAwareness: shouldCheckAudience = false,\n } = options\n\n const startTime = Date.now()\n\n await log('debug', 'validate started', {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n draftLength: draft.length,\n strictMode,\n skipMemoryQuery,\n })\n\n // Start with synchronous pattern checks\n const allIssues: ValidationIssue[] = [\n ...checkInternalLeaks(draft),\n ...checkMetaCommentary(draft),\n ...checkBannedPhrases(draft),\n ...checkFabrication(draft, context),\n ...checkLength(draft),\n ]\n\n const patternIssueCount = allIssues.length\n\n // ─────────────────────────────────────────────────────────────────────────\n // Ground Truth: Retrieve skills and compare draft against known info\n // ─────────────────────────────────────────────────────────────────────────\n const skillQuery =\n input.originalMessage ??\n customerMessage?.body ??\n customerMessage?.subject ??\n draft\n\n let relevantSkills: RetrievedSkill[] = []\n\n if (skillQuery.trim().length > 0) {\n try {\n relevantSkills = await retrieveSkills(skillQuery, { topK: 3 })\n allIssues.push(...checkGroundTruth(draft, relevantSkills))\n allIssues.push(...checkFabricatedClaims(draft, relevantSkills))\n } catch (error) {\n await log('warn', 'ground truth skill retrieval failed', {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n let correctionsChecked: RelevantMemory[] | undefined\n let memoryCheckPerformed = false\n\n // ─────────────────────────────────────────────────────────────────────────\n // Memory Check: Does this draft repeat a known mistake?\n // ─────────────────────────────────────────────────────────────────────────\n if (!skipMemoryQuery && appId) {\n try {\n // Build situation context for memory query\n const situation = buildValidationSituation(category, draft)\n\n // Query specifically for corrected memories (mistakes we've learned from)\n const corrections = await queryCorrectedMemories({\n appId,\n situation,\n stage: 'draft',\n limit: 5,\n })\n\n memoryCheckPerformed = true\n correctionsChecked = corrections\n\n if (corrections.length > 0) {\n await log('debug', 'validate memory corrections found', {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n correctionsFound: corrections.length,\n topScore: corrections[0]?.score ?? 0,\n })\n }\n\n // Check if current draft repeats any known mistakes\n if (corrections.length > 0) {\n const memoryIssues = await checkAgainstCorrections(\n draft,\n corrections,\n correctionThreshold\n )\n allIssues.push(...memoryIssues)\n }\n } catch (error) {\n // Memory query failed - log but don't fail validation\n await log('warn', 'validate memory query failed', {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n error: error instanceof Error ? error.message : String(error),\n })\n memoryCheckPerformed = false\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Relevance Check: Does this draft actually address the customer's message?\n // ─────────────────────────────────────────────────────────────────────────\n let relevanceScore: number | undefined\n let relevanceCheckPerformed = false\n\n const hasCustomerBody =\n customerMessage?.body && customerMessage.body.trim().length > 0\n\n if (skipRelevanceCheck) {\n await log('debug', 'relevance check skipped (skipRelevanceCheck=true)', {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n })\n } else if (!customerMessage) {\n await log(\n 'warn',\n 'relevance check skipped (customerMessage not provided)',\n {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n }\n )\n } else if (!hasCustomerBody) {\n await log('debug', 'relevance check skipped (empty customer body)', {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n customerSubjectLength: customerMessage.subject?.length ?? 0,\n customerBodyLength: customerMessage.body?.length ?? 0,\n })\n }\n\n if (!skipRelevanceCheck && hasCustomerBody && customerMessage) {\n try {\n await log('info', 'relevance check starting', {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n customerMessageLength:\n (customerMessage.subject?.length ?? 0) +\n (customerMessage.body?.length ?? 0),\n draftLength: draft.length,\n relevanceModel,\n })\n\n const relevanceResult = await checkRelevance(\n draft,\n customerMessage,\n relevanceModel\n )\n relevanceCheckPerformed = true\n relevanceScore = relevanceResult.score\n allIssues.push(...relevanceResult.issues)\n\n await log('info', 'relevance check completed', {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n relevanceScore: relevanceResult.score,\n relevanceIssues: relevanceResult.issues.length,\n relevant: relevanceResult.score >= 0.5,\n })\n } catch (error) {\n // Relevance check failed - log but don't fail validation\n await log('warn', 'relevance check failed', {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n error: error instanceof Error ? error.message : String(error),\n errorStack: error instanceof Error ? error.stack : undefined,\n relevanceModel,\n })\n relevanceCheckPerformed = false\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Audience Awareness Check: Is this draft customer-appropriate?\n // ─────────────────────────────────────────────────────────────────────────\n let audienceCheckPerformed = false\n\n if (shouldCheckAudience) {\n try {\n await log('info', 'audience awareness check starting', {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n draftLength: draft.length,\n })\n\n const audienceIssues = await checkAudienceAwareness(draft, relevanceModel)\n audienceCheckPerformed = true\n allIssues.push(...audienceIssues)\n\n await log('info', 'audience awareness check completed', {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n audienceIssues: audienceIssues.length,\n appropriate: audienceIssues.length === 0,\n })\n } catch (error) {\n await log('warn', 'audience awareness check failed', {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n error: error instanceof Error ? error.message : String(error),\n })\n audienceCheckPerformed = false\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Tool Failure Check: Did critical gather tools fail?\n // ─────────────────────────────────────────────────────────────────────────\n const toolFailureResult = checkToolFailures(context.gatherErrors, category)\n\n // In strict mode, warnings are errors\n const hasErrors = allIssues.some((i) => i.severity === 'error')\n const confidence = calculateConfidenceScore(allIssues)\n const threshold = getCategoryThreshold(category)\n\n const durationMs = Date.now() - startTime\n\n // Group issues by type for logging\n const issuesByType: Record<string, number> = {}\n for (const issue of allIssues) {\n issuesByType[issue.type] = (issuesByType[issue.type] ?? 0) + 1\n }\n\n const escalationKeyword = threshold.escalateOnKeywords?.find((keyword) => {\n const haystack = [\n input.originalMessage,\n customerMessage?.subject,\n customerMessage?.body,\n draft,\n ]\n .filter(Boolean)\n .join('\\n')\n .toLowerCase()\n return haystack.includes(keyword.toLowerCase())\n })\n\n let decision: ValidatorDecision = {\n action: 'draft',\n draft,\n issues: allIssues,\n }\n\n // Tool failure check takes priority — if critical tools failed, escalate\n if (toolFailureResult.shouldEscalate) {\n decision = {\n action: 'escalate',\n reason: toolFailureResult.reason,\n urgency: 'normal',\n }\n } else if (threshold.escalateAlways) {\n decision = {\n action: 'escalate',\n reason: 'Category requires human review',\n urgency: 'normal',\n }\n } else if (escalationKeyword) {\n decision = {\n action: 'escalate',\n reason: `Escalation keyword detected: ${escalationKeyword}`,\n urgency: 'high',\n }\n } else {\n const categoryStats = await getCategoryStats(category)\n const meetsAutoSend =\n categoryStats.sentUnchangedRate >= threshold.autoSendMinConfidence &&\n categoryStats.volume >= threshold.autoSendMinVolume &&\n confidence >= 0.95\n\n if (meetsAutoSend) {\n decision = {\n action: 'auto-send',\n draft,\n confidence,\n }\n } else if (!hasErrors && allIssues.length > 0) {\n decision = {\n action: 'needs-review',\n draft,\n concerns: allIssues.map((issue) => issue.message),\n }\n }\n }\n\n // High-cardinality decision-point logging\n await log('info', 'validate:decision', {\n workflow: 'pipeline',\n step: 'validate',\n appId,\n category,\n // Decision outcome\n valid: !hasErrors,\n action: decision.action,\n confidence,\n // Issue breakdown\n totalIssues: allIssues.length,\n errorCount: allIssues.filter((i) => i.severity === 'error').length,\n warningCount: allIssues.filter((i) => i.severity === 'warning').length,\n infoCount: allIssues.filter((i) => i.severity === 'info').length,\n // Issue types detected\n issuesByType,\n hasInternalLeak: (issuesByType['internal_leak'] ?? 0) > 0,\n hasMetaCommentary: (issuesByType['meta_commentary'] ?? 0) > 0,\n hasBannedPhrase: (issuesByType['banned_phrase'] ?? 0) > 0,\n hasFabrication: (issuesByType['fabrication'] ?? 0) > 0,\n hasGroundTruthMismatch: (issuesByType['ground_truth_mismatch'] ?? 0) > 0,\n hasRepeatedMistake: (issuesByType['repeated_mistake'] ?? 0) > 0,\n hasRelevanceIssue: (issuesByType['relevance'] ?? 0) > 0,\n hasAudienceIssue: (issuesByType['audience_inappropriate'] ?? 0) > 0,\n // Validation checks performed\n patternCheckCount: patternIssueCount,\n memoryCheckPerformed,\n relevanceCheckPerformed,\n audienceCheckPerformed,\n groundTruthCheckPerformed: relevantSkills.length > 0,\n // Tool failure check\n toolFailureEscalation: toolFailureResult.shouldEscalate,\n toolFailureReason: toolFailureResult.reason || null,\n gatherErrorCount: context.gatherErrors?.length ?? 0,\n gatherErrorSteps: context.gatherErrors?.map((e) => e.step) ?? [],\n // Relevance details\n relevanceScore,\n relevanceThreshold: 0.5,\n relevancePassed: relevanceScore === undefined || relevanceScore >= 0.5,\n // Memory correction check\n correctionsChecked: correctionsChecked?.length ?? 0,\n correctionThreshold,\n // Threshold configuration\n decisionThreshold: threshold,\n escalateAlways: threshold.escalateAlways,\n escalationKeywordMatched: escalationKeyword ?? null,\n autoSendEligible: confidence >= 0.95,\n // Ground truth\n skillsRetrieved: relevantSkills.length,\n // Decision explanation\n strictMode,\n durationMs,\n })\n\n return {\n ...decision,\n valid: !hasErrors,\n issues: allIssues,\n suggestion: hasErrors\n ? 'Response has quality issues that would be visible to customers'\n : undefined,\n relevance: relevanceScore,\n correctionsChecked,\n memoryCheckPerformed,\n relevanceCheckPerformed,\n }\n}\n\n/**\n * Build a situation string for memory query from validation context.\n */\nfunction buildValidationSituation(\n category: MessageCategory | undefined,\n draft: string\n): string {\n const parts: string[] = []\n\n if (category) {\n parts.push(`Category: ${category}`)\n }\n\n // Include draft content (truncated for query efficiency)\n const draftPreview = draft.slice(0, 300)\n parts.push(`Draft: ${draftPreview}`)\n\n return parts.join('\\n')\n}\n\n/**\n * Check if draft content matches any known corrections.\n * Uses text similarity to detect potential repeated mistakes.\n */\nasync function checkAgainstCorrections(\n draft: string,\n corrections: RelevantMemory[],\n threshold: number\n): Promise<ValidationIssue[]> {\n const issues: ValidationIssue[] = []\n\n for (const correction of corrections) {\n // Only flag if similarity is above threshold AND score is high\n // (high score means the situation is very similar)\n if (correction.score >= threshold) {\n // Check if draft contains similar problematic patterns\n const similarity = textSimilarity(draft, correction.decision)\n\n if (similarity >= 0.6) {\n // Draft is similar to a known bad decision\n issues.push({\n type: 'repeated_mistake',\n severity: 'error',\n message: `Draft may repeat a previously corrected mistake`,\n match: correction.correction\n ? `Previously corrected: ${truncate(correction.correction, 100)}`\n : `Similar to failed draft (${Math.round(correction.score * 100)}% match)`,\n })\n }\n }\n }\n\n return issues\n}\n\n/**\n * Simple text similarity using Jaccard coefficient on word sets.\n * Good enough for detecting if two texts cover similar content.\n */\nfunction textSimilarity(text1: string, text2: string): number {\n const normalize = (text: string): string[] => {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s]/g, ' ')\n .split(/\\s+/)\n .filter((word) => word.length > 2)\n }\n\n const words1 = normalize(text1)\n const words2 = normalize(text2)\n\n if (words1.length === 0 || words2.length === 0) return 0\n\n const set1 = new Set(words1)\n const set2 = new Set(words2)\n\n // Count intersection\n let intersectionCount = 0\n for (const word of words1) {\n if (set2.has(word)) {\n intersectionCount++\n set2.delete(word) // Avoid double counting\n }\n }\n\n // Union size = set1 size + remaining set2 size\n const unionSize = set1.size + set2.size\n\n return intersectionCount / unionSize\n}\n\nfunction truncate(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text\n return text.slice(0, maxLength - 3) + '...'\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nexport function getIssuesByType(\n issues: ValidationIssue[],\n type: ValidationIssueType\n): ValidationIssue[] {\n return issues.filter((i) => i.type === type)\n}\n\nexport function hasIssueType(\n issues: ValidationIssue[],\n type: ValidationIssueType\n): boolean {\n return issues.some((i) => i.type === type)\n}\n\nexport function formatIssues(issues: ValidationIssue[]): string {\n return issues\n .map(\n (i) =>\n `[${i.severity.toUpperCase()}] ${i.type}: ${i.message}${i.match ? ` (\"${i.match}\")` : ''}`\n )\n .join('\\n')\n}\n\n// ============================================================================\n// Pattern management (for customization)\n// ============================================================================\n\nexport function addBannedPhrase(pattern: RegExp): void {\n BANNED_PHRASES.push(pattern)\n}\n\nexport function addInternalLeakPattern(pattern: RegExp): void {\n INTERNAL_LEAK_PATTERNS.push(pattern)\n}\n\nexport function addMetaCommentaryPattern(pattern: RegExp): void {\n META_COMMENTARY_PATTERNS.push(pattern)\n}\n","import { Redis } from '@upstash/redis'\nimport { Index } from '@upstash/vector'\n\nexport interface SkillData {\n skill_id: string\n name: string\n description: string\n path: string\n sample_size?: number\n markdown?: string\n indexed_at: string\n}\n\nexport interface RetrievedSkill extends SkillData {\n score: number // Semantic similarity score\n}\n\nexport interface RetrievalOptions {\n topK?: number\n minScore?: number\n includeMarkdown?: boolean\n}\n\n/**\n * Retrieve skills relevant to a query.\n * Uses Vector for semantic search, Redis for full content.\n */\nexport async function retrieveSkills(\n query: string,\n options: RetrievalOptions = {}\n): Promise<RetrievedSkill[]> {\n const { topK = 3, minScore = 0.01, includeMarkdown = true } = options\n\n // Get clients from environment\n const vectorUrl = process.env.UPSTASH_VECTOR_REST_URL\n const vectorToken = process.env.UPSTASH_VECTOR_REST_TOKEN\n const redisUrl = process.env.UPSTASH_REDIS_REST_URL\n const redisToken = process.env.UPSTASH_REDIS_REST_TOKEN\n\n if (!vectorUrl || !vectorToken || !redisUrl || !redisToken) {\n throw new Error('Missing Upstash credentials in environment')\n }\n\n const vector = new Index({ url: vectorUrl, token: vectorToken })\n const redis = new Redis({ url: redisUrl, token: redisToken })\n\n // Step 1: Semantic search in Vector\n const skillsNs = vector.namespace('skills')\n const vectorResults = await skillsNs.query({\n data: query,\n topK,\n includeMetadata: true,\n })\n\n // Filter by minimum score\n const relevantResults = vectorResults.filter((r) => r.score >= minScore)\n\n if (relevantResults.length === 0) {\n return []\n }\n\n // Step 2: Fetch full content from Redis\n const skillIds = relevantResults.map((r) => r.id as string)\n const redisResults = await redis.mget<SkillData[]>(...skillIds)\n\n // Step 3: Combine Vector scores with Redis content\n const skills: RetrievedSkill[] = []\n\n for (let i = 0; i < relevantResults.length; i++) {\n const vectorResult = relevantResults[i]\n const redisData = redisResults[i] as SkillData | null\n\n if (!vectorResult) {\n continue\n }\n\n if (redisData) {\n // Redis client auto-parses JSON, so redisData is already an object\n const skillData: SkillData = redisData\n\n // Optionally strip markdown to reduce token usage\n if (!includeMarkdown && skillData.markdown) {\n delete skillData.markdown\n }\n\n skills.push({\n ...skillData,\n score: vectorResult.score,\n })\n }\n }\n\n return skills\n}\n\n/**\n * Format skills for inclusion in LLM context\n */\nexport function formatSkillsForContext(skills: RetrievedSkill[]): string {\n if (skills.length === 0) {\n return 'No relevant skills found.'\n }\n\n return skills\n .map((skill, i) => {\n const header = `## Skill ${i + 1}: ${skill.name} (relevance: ${(skill.score * 100).toFixed(1)}%)`\n const description = skill.description\n const markdown = skill.markdown\n ? `\\n\\n### Full Documentation:\\n${skill.markdown}`\n : ''\n\n return `${header}\\n${description}${markdown}`\n })\n .join('\\n\\n---\\n\\n')\n}\n","export interface CategoryThreshold {\n autoSendMinConfidence: number\n autoSendMinVolume: number\n escalateAlways?: boolean\n escalateOnKeywords?: string[]\n}\n\nexport const DEFAULT_CATEGORY_THRESHOLD: CategoryThreshold = {\n autoSendMinConfidence: 0.95,\n autoSendMinVolume: 50,\n}\n\nexport const CATEGORY_THRESHOLDS: Record<string, CategoryThreshold> = {\n 'support_team-license': {\n autoSendMinConfidence: 0.98,\n autoSendMinVolume: 50,\n escalateAlways: true,\n },\n 'support_bug-report': {\n autoSendMinConfidence: 0.95,\n autoSendMinVolume: 30,\n escalateOnKeywords: ['multiple users', 'widespread', 'everyone'],\n },\n support_refund: {\n autoSendMinConfidence: 0.95,\n autoSendMinVolume: 100,\n },\n default: DEFAULT_CATEGORY_THRESHOLD,\n}\n\nexport function getCategoryThreshold(category?: string): CategoryThreshold {\n if (!category) return DEFAULT_CATEGORY_THRESHOLD\n return CATEGORY_THRESHOLDS[category] ?? DEFAULT_CATEGORY_THRESHOLD\n}\n","/**\n * Pipeline type definitions\n *\n * Shared interfaces for all pipeline steps.\n * See ARCHITECTURE.md for design details.\n */\n\nimport type {\n AppInfo,\n ContentAccess,\n LicenseInfo,\n Promotion,\n RefundPolicy,\n UserActivity,\n} from '@skillrecordings/sdk/types'\n\n// ============================================================================\n// Categories\n// ============================================================================\n\nexport type MessageCategory =\n | 'support_access' // Login, purchase access issues\n | 'support_refund' // Refund requests\n | 'support_transfer' // License transfers\n | 'support_technical' // Product/code questions\n | 'support_billing' // Invoice, receipt, payment\n | 'technical_support' // General technical support issues\n | 'feedback' // Customer feedback (content, product, experience)\n | 'sales_pricing' // Sales and pricing inquiries\n | 'fan_mail' // Personal message to instructor\n | 'spam' // Vendor outreach, marketing\n | 'system' // Automated notifications, bounces\n | 'unknown' // Can't classify confidently\n // Thread-aware categories (v3)\n | 'instructor_strategy' // Instructor discussing business/content\n | 'resolved' // Thread already resolved\n | 'awaiting_customer' // Waiting for customer reply\n | 'voc_response' // Voice of customer: replies to our outreach/surveys\n // Presales categories (v4)\n | 'presales_faq' // Answerable with KB (pricing, curriculum, requirements)\n | 'presales_consult' // Needs instructor judgment (which course, career advice)\n | 'presales_team' // Enterprise/team sales inquiries\n\nexport type RouteAction =\n | 'respond' // Agent should draft a response\n | 'silence' // No response needed\n | 'escalate_human' // Flag for human review\n | 'escalate_instructor' // Route to instructor\n | 'escalate_urgent' // High priority human review\n | 'support_teammate' // Add context comment, teammate is handling\n | 'catalog_voc' // Catalog VOC response, notify Slack, maybe request expansion\n\n// ============================================================================\n// VOC (Voice of Customer) Types\n// ============================================================================\n\nexport type VocSentiment =\n | 'voc_positive' // Praise, success stories, \"loving it\"\n | 'voc_feedback' // Suggestions, critiques, feature requests\n | 'voc_blocker' // \"Too busy\", \"haven't started\", obstacles\n | 'voc_testimonial_candidate' // Compelling quotes worth expanding\n\nexport interface VocAnalysis {\n sentiment: VocSentiment\n confidence: number\n themes: string[] // e.g., [\"course_quality\", \"time_constraints\", \"ai_interest\"]\n quotableExcerpt?: string // Best quote for testimonial use\n shouldRequestExpansion: boolean\n expansionReason?: string // Why this is worth following up\n}\n\n// ============================================================================\n// Step 1: Classify\n// ============================================================================\n\n/** Single message input (v2 - backwards compatible) */\nexport interface ClassifyInput {\n subject: string\n body: string\n from?: string\n conversationId?: string\n appId?: string\n}\n\n// ============================================================================\n// Thread-based types (v3)\n// ============================================================================\n\n/** Author type for thread messages */\nexport type MessageAuthorType = 'customer' | 'teammate' | 'agent' | 'instructor'\n\n/** Author info attached to thread messages */\nexport interface ThreadMessageAuthor {\n type: MessageAuthorType\n email: string\n name?: string\n teammateId?: string // Front teammate ID if applicable\n}\n\n/** A single message within a thread */\nexport interface ThreadMessage {\n direction: 'in' | 'out'\n body: string\n timestamp: number\n subject?: string // Usually only on first message\n author?: ThreadMessageAuthor\n}\n\n/** Thread-based input for classification (v3) */\nexport interface ThreadClassifyInput {\n conversationId: string\n appId: string\n messages: ThreadMessage[] // Full thread, chronological\n triggerMessage: ThreadMessage // The message that triggered processing\n instructorTeammateId?: string // From app config, for detection\n tags?: string[] // Front tags for rule-based classification\n}\n\n/** Thread-level signals for classification */\nexport interface ThreadSignals extends MessageSignals {\n // Thread structure\n threadLength: number // Total messages\n threadDurationHours: number // First to last message\n customerMessageCount: number // Inbound from customers\n teammateMessageCount: number // Outbound from human teammates\n agentMessageCount: number // Outbound from agent/API\n lastMessageDirection: 'in' | 'out'\n threadPattern: string // e.g., \"in-out-in\" for back-and-forth\n\n // Resolution signals\n hasThankYou: boolean // Customer thanked us\n hasResolutionPhrase: boolean // \"that worked\", \"all set\", etc.\n awaitingCustomerReply: boolean // We asked a question, no reply yet\n\n // Teammate/author signals\n hasTeammateMessage: boolean // Human teammate responded (not agent)\n hasRecentTeammateResponse: boolean // Teammate responded after last customer msg\n hasInstructorMessage: boolean // Instructor participated\n instructorIsAuthor: boolean // Thread started BY instructor\n isInternalThread: boolean // Only teammates, no customers\n lastResponderType: MessageAuthorType\n}\n\n/** Output for thread classification */\nexport interface ThreadClassifyOutput {\n category: MessageCategory\n confidence: number\n signals: ThreadSignals\n reasoning?: string\n}\n\nexport interface ClassifyOutput {\n category: MessageCategory\n confidence: number // 0-1\n signals: MessageSignals\n reasoning?: string\n}\n\nexport interface MessageSignals {\n hasEmailInBody: boolean\n hasPurchaseDate: boolean\n hasErrorMessage: boolean\n isReply: boolean\n mentionsInstructor: boolean\n hasAngrySentiment: boolean\n isAutomated: boolean\n isVendorOutreach: boolean\n // Escalation signals\n hasLegalThreat: boolean // \"lawyer\", \"legal action\", \"sue\", etc.\n hasOutsidePolicyTimeframe: boolean // mentions purchasing > 30 days ago\n isPersonalToInstructor: boolean // casual/personal message to instructor\n // Presales signals\n isPresalesFaq: boolean // pricing, curriculum, requirements, discounts\n isPresalesTeam: boolean // enterprise/team sales inquiries\n}\n\n// ============================================================================\n// Step 2: Route\n// ============================================================================\n\nexport interface RouteInput {\n message: ClassifyInput\n classification: ClassifyOutput\n appConfig: AppConfig\n}\n\nexport interface RouteOutput {\n action: RouteAction\n reason: string\n}\n\nexport interface AppConfig {\n appId: string\n instructorTeammateId?: string // Front teammate ID for routing\n instructorConfigured: boolean\n autoSendEnabled: boolean\n escalationRules?: EscalationRule[]\n}\n\nexport interface EscalationRule {\n condition: string // e.g., \"category == 'support_refund' && amount > 500\"\n action: RouteAction\n reason: string\n}\n\n// ============================================================================\n// Step 3: Gather\n// ============================================================================\n\nexport interface GatherInput {\n message: ClassifyInput\n classification: ClassifyOutput\n appId: string\n}\n\nexport interface GatherOutput {\n user: User | null\n purchases: Purchase[]\n knowledge: KnowledgeItem[]\n history: ConversationMessage[]\n priorMemory: MemoryItem[]\n priorConversations: PriorConversation[]\n gatherErrors: GatherError[] // Track failures internally, never expose\n\n // ── Category-aware SDK data ──────────────────────────────────────────\n // These fields are populated conditionally based on classification category.\n // All are optional — null means \"not fetched\" or \"fetch failed gracefully\".\n\n /** App metadata — URLs, names, etc. (fetched once per conversation) */\n appInfo?: AppInfo | null\n /** Refund policy from app (fetched for support_refund) */\n refundPolicy?: RefundPolicy | null\n /** Content access details (fetched for support_access) */\n contentAccess?: ContentAccess | null\n /** Recent user activity (fetched for support_access) */\n recentActivity?: UserActivity | null\n /** Active promotions (fetched for presales_faq) */\n activePromotions?: Promotion[] | null\n /** License info per purchase (fetched for presales_team) */\n licenseInfo?: LicenseInfo[] | null\n}\n\n// ============================================================================\n// Prior Conversations (cross-conversation awareness)\n// ============================================================================\n\n/**\n * Summary of a prior conversation by the same customer.\n * Used to give the agent awareness of customer history.\n */\nexport interface PriorConversation {\n /** Front conversation ID (cnv_xxx) */\n conversationId: string\n /** Conversation subject line */\n subject: string\n /** Current status (archived, assigned, etc.) */\n status: string\n /** ISO timestamp of last message in conversation */\n lastMessageAt: string\n /** Total message count */\n messageCount: number\n /** Tags applied to this conversation */\n tags: string[]\n}\n\nexport interface User {\n id: string\n email: string\n name?: string\n createdAt?: string\n}\n\nexport interface Purchase {\n id: string\n productId: string\n productName: string\n purchasedAt: string\n amount?: number\n status: 'active' | 'refunded' | 'transferred'\n}\n\nexport interface KnowledgeItem {\n id: string\n type: 'faq' | 'article' | 'similar_ticket' | 'good_response'\n content: string\n relevance: number\n source?: string\n}\n\nexport interface ConversationMessage {\n direction: 'in' | 'out'\n body: string\n timestamp: number\n author?: string\n}\n\nexport interface MemoryItem {\n id: string\n content: string\n tags: string[]\n relevance: number\n}\n\nexport interface GatherError {\n step:\n | 'user'\n | 'purchases'\n | 'knowledge'\n | 'history'\n | 'memory'\n | 'priorConversations'\n | 'refundPolicy'\n | 'contentAccess'\n | 'recentActivity'\n | 'activePromotions'\n | 'licenseInfo'\n error: string\n // Never exposed to draft - just for debugging\n}\n\n// ============================================================================\n// Step 4: Draft\n// ============================================================================\n\nexport interface DraftInput {\n message: ClassifyInput\n classification: ClassifyOutput\n context: GatherOutput\n promptOverride?: string\n}\n\nexport interface DraftOutput {\n draft: string\n reasoning?: string\n toolsUsed: string[]\n durationMs: number\n /** Tool calls made by the agent (for HITL approval) */\n toolCalls?: Array<{\n name: string\n args: Record<string, unknown>\n result?: unknown\n }>\n /** Whether the draft requires human approval (e.g., refund, transfer) */\n requiresApproval?: boolean\n}\n\n// ============================================================================\n// Step 5: Validate\n// ============================================================================\n\nexport interface ValidateInput {\n draft: string\n context: GatherOutput\n strictMode?: boolean\n /** Original customer message (string) for skill-based ground truth checks */\n originalMessage?: string\n /** Original customer message for relevance checking */\n customerMessage?: {\n subject: string\n body: string\n }\n}\n\nexport interface ValidateOutput {\n valid: boolean\n issues: ValidationIssue[]\n suggestion?: string\n /** Relevance score from LLM check (0-1, only present when relevance check runs) */\n relevance?: number\n}\n\nexport type ValidatorDecision =\n | { action: 'auto-send'; draft: string; confidence: number }\n | { action: 'draft'; draft: string; issues?: ValidationIssue[] }\n | {\n action: 'escalate'\n reason: string\n urgency: 'normal' | 'high' | 'critical'\n }\n | { action: 'needs-review'; draft: string; concerns: string[] }\n\nexport interface ValidationIssue {\n type: ValidationIssueType\n severity: 'error' | 'warning' | 'info'\n message: string\n match?: string\n position?: number\n}\n\nexport type ValidationIssueType =\n | 'internal_leak'\n | 'meta_commentary'\n | 'banned_phrase'\n | 'fabrication'\n | 'too_short'\n | 'too_long'\n | 'bad_tone'\n | 'repeated_mistake' // Draft may repeat a known corrected mistake\n | 'relevance' // Draft doesn't address the customer's actual question\n | 'ground_truth_mismatch' // Draft contradicts retrieved skill content\n | 'audience_inappropriate' // Draft uses technical jargon or internal references\n | 'tool_failure' // Critical gather tool failed, cannot verify customer info\n\n// ============================================================================\n// Step 5b: Comment (for support_teammate action)\n// ============================================================================\n\nexport interface CommentInput {\n conversationId: string\n context: GatherOutput\n appId: string\n}\n\nexport interface CommentOutput {\n added: boolean\n commentId?: string\n error?: string\n}\n\n// ============================================================================\n// Step 6: Send\n// ============================================================================\n\nexport interface SendInput {\n conversationId: string\n draft: string\n appId: string\n}\n\nexport interface SendOutput {\n sent: boolean\n messageId?: string\n error?: string\n}\n\n// ============================================================================\n// Pipeline orchestration\n// ============================================================================\n\nexport interface PipelineInput {\n message: ClassifyInput\n appConfig: AppConfig\n dryRun?: boolean\n}\n\nexport interface PipelineOutput {\n action: RouteAction\n response?: string\n sent?: boolean\n messageId?: string\n steps: PipelineStepResult[]\n totalDurationMs: number\n}\n\nexport interface PipelineStepResult {\n step: 'classify' | 'route' | 'gather' | 'draft' | 'validate' | 'send'\n durationMs: number\n success: boolean\n output: unknown\n error?: string\n}\n\n// ============================================================================\n// Eval types\n// ============================================================================\n\nexport interface EvalScenario<TInput, TExpected> {\n id: string\n name: string\n input: TInput\n expected: TExpected\n tags?: string[]\n}\n\nexport interface EvalResult<TOutput> {\n scenarioId: string\n passed: boolean\n actual: TOutput\n durationMs: number\n errors: string[]\n}\n\nexport interface EvalSummary {\n total: number\n passed: number\n failed: number\n passRate: number\n durationMs: number\n byTag?: Record<string, { passed: number; failed: number }>\n}\n\n// ============================================================================\n// Tagging and Archiving\n// ============================================================================\n\n/**\n * Valid highlight colors for Front tags.\n * @see https://dev.frontapp.com/reference/tags\n */\nexport type TagHighlight =\n | 'red'\n | 'orange'\n | 'yellow'\n | 'green'\n | 'teal'\n | 'blue'\n | 'purple'\n | 'pink'\n | 'grey'\n | 'black'\n\n/**\n * Configuration for mapping a category to a Front tag.\n */\nexport interface CategoryTagConfig {\n /** Name of the tag in Front */\n tagName: string\n /** Highlight color for visual organization */\n highlight: TagHighlight\n /** Optional description for the tag */\n description?: string\n}\n\n/**\n * Maps message categories to Front tag configurations.\n */\nexport type CategoryTagMapping = Record<MessageCategory, CategoryTagConfig>\n\n/**\n * Skill name type - matches skills/index.json skill names.\n */\nexport type SkillName =\n | 'access-locked-out'\n | 'api-documentation-question'\n | 'app-crash-report'\n | 'broken-link-404-error'\n | 'certificate-request'\n | 'cohort-access-request'\n | 'cohort-schedule-inquiry'\n | 'content-feedback'\n | 'continuing-education-credits'\n | 'corporate-invoice'\n | 'course-content-locked'\n | 'course-difficulty-concern'\n | 'discount-code-request'\n | 'duplicate-purchase'\n | 'email-change'\n | 'email-delivery-failure'\n | 'event-sponsorship-request'\n | 'gift-purchase-option'\n | 'installment-payment-option'\n | 'invoice-billing-statement'\n | 'learning-path-guidance'\n | 'lesson-content-question'\n | 'login-link'\n | 'media-press-outreach'\n | 'nonprofit-government-discount'\n | 'outdated-course-content'\n | 'partnership-collaboration-inquiry'\n | 'password-reset-issue'\n | 'payment-method-issue'\n | 'ppp-pricing'\n | 'price-feedback'\n | 'pricing-inquiry'\n | 'refund-request'\n | 'scholarship-financial-aid'\n | 'security-vulnerability-report'\n | 'student-discount-request'\n | 'subscription-renewal-issue'\n | 'team-license-purchase'\n | 'technical-issue-course-content'\n | 'two-factor-auth-issue'\n | 'ui-ux-feedback'\n | 'website-bug-report'\n | 'workshop-attendance-confirmation'\n | 'workshop-cancellation-notice'\n | 'workshop-technical-setup'\n\n/**\n * Configuration for mapping a skill to a Front tag.\n */\nexport interface SkillTagConfig {\n /** Name of the tag in Front (prefixed with skill/) */\n tagName: string\n /** Highlight color for visual organization */\n highlight: TagHighlight\n /** Optional description for the tag */\n description?: string\n}\n\n/**\n * Maps skill names to Front tag configurations.\n */\nexport type SkillTagMapping = Record<SkillName, SkillTagConfig>\n\n/**\n * Input for the tag step.\n */\nexport interface TagInput {\n /** Front conversation ID (cnv_xxx) */\n conversationId: string\n /** Message category from classification */\n category: MessageCategory\n /** Skill name from classification (optional) */\n skill?: SkillName\n /** App configuration */\n appConfig: AppConfig\n}\n\n/**\n * Output from the tag step.\n */\nexport interface TagOutput {\n /** Whether the category tag was successfully applied */\n tagged: boolean\n /** Front tag ID for category if successful */\n tagId?: string\n /** Tag name that was applied for category */\n tagName?: string\n /** Whether the skill tag was successfully applied */\n skillTagged?: boolean\n /** Front tag ID for skill if successful */\n skillTagId?: string\n /** Skill tag name that was applied */\n skillTagName?: string\n /** Error message if failed */\n error?: string\n /** Duration of the operation in ms */\n durationMs?: number\n /** Whether the tag was recovered from archived state */\n recovered?: boolean\n}\n\n/**\n * Input for the archive step.\n */\nexport interface ArchiveInput {\n /** Front conversation ID (cnv_xxx) */\n conversationId: string\n /** Route action that was taken */\n action: RouteAction\n /** Reason for the action (from routing) */\n reason: string\n /** App configuration */\n appConfig: AppConfig\n}\n\n/**\n * Output from the archive step.\n */\nexport interface ArchiveOutput {\n /** Whether the conversation was archived */\n archived: boolean\n /** Error message if failed */\n error?: string\n /** Duration of the operation in ms */\n durationMs?: number\n}\n\n/**\n * Result of applying a decision comment to a conversation.\n */\nexport interface DecisionCommentOutput {\n /** Whether the comment was added */\n added: boolean\n /** Error message if failed */\n error?: string\n /** Duration of the operation in ms */\n durationMs?: number\n}\n","/**\n * Step: TAG\n *\n * Applies appropriate Front tags to conversations based on classification.\n * Tags help organize conversations and enable filtering/reporting in Front.\n *\n * This step runs after classification and is fire-and-forget (failures\n * are logged but don't block the pipeline).\n */\n\nimport { FrontApiError } from '@skillrecordings/front-sdk'\nimport { createInstrumentedFrontClient } from '../../front/instrumented-client'\nimport { log } from '../../observability/axiom'\nimport { type TagRegistry, createTagRegistry } from '../../tags/registry'\nimport type { MessageCategory, SkillName, TagInput, TagOutput } from '../types'\n\n// ============================================================================\n// Options\n// ============================================================================\n\nexport interface TagStepOptions {\n /** Front API token */\n frontApiToken: string\n /** Optional pre-initialized TagRegistry (for efficiency across calls) */\n tagRegistry?: TagRegistry\n /** Enable debug logging */\n debug?: boolean\n}\n\n// ============================================================================\n// Tag Step\n// ============================================================================\n\nasync function addTagToConversation(\n frontApiToken: string,\n conversationId: string,\n tagId: string\n): Promise<void> {\n const baseClient = createInstrumentedFrontClient({\n apiToken: frontApiToken,\n }).raw\n\n await baseClient.post(`/conversations/${conversationId}/tags`, {\n tag_ids: [tagId],\n })\n}\n\n/**\n * Apply a tag to a Front conversation based on category.\n * Optionally also applies a skill tag if skill is provided.\n *\n * @param input - Conversation ID, category, and optional skill\n * @param options - Front API token and optional registry\n * @returns Result with success status and tag info\n *\n * @example\n * ```ts\n * const result = await applyTag(\n * { conversationId: 'cnv_123', category: 'support_access', skill: 'login-link', appConfig },\n * { frontApiToken: 'xxx' }\n * )\n * // result: { tagged: true, tagId: 'tag_abc', tagName: 'access-issue', skillTagged: true, skillTagId: 'tag_xyz', skillTagName: 'skill/login-link' }\n * ```\n */\nexport async function applyTag(\n input: TagInput,\n options: TagStepOptions\n): Promise<TagOutput> {\n const startTime = Date.now()\n const { conversationId, category, skill } = input\n const { frontApiToken, debug } = options\n\n try {\n // Get or create tag registry\n let registry: TagRegistry\n try {\n registry =\n options.tagRegistry ?? createTagRegistry({ frontApiToken, debug })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n await log('error', 'tag registry creation failed', {\n step: 'apply-tag',\n conversationId,\n category,\n skill,\n error: message,\n errorType: error instanceof Error ? error.constructor.name : 'unknown',\n })\n return {\n tagged: false,\n error: `Registry creation failed: ${message}`,\n durationMs: Date.now() - startTime,\n }\n }\n\n // Get tag ID for this category\n let tagId: string | undefined\n try {\n tagId = await registry.getTagIdForCategory(category)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n const isFrontError = error instanceof FrontApiError\n await log('error', 'tag ID lookup failed', {\n step: 'apply-tag',\n conversationId,\n category,\n error: message,\n errorType: error instanceof Error ? error.constructor.name : 'unknown',\n frontApiStatus: isFrontError ? error.status : undefined,\n frontApiTitle: isFrontError ? error.title : undefined,\n })\n return {\n tagged: false,\n error: `Tag lookup failed: ${message}`,\n durationMs: Date.now() - startTime,\n }\n }\n\n const tagName = registry.getTagNameForCategory(category)\n\n if (!tagId) {\n await log('error', 'tag ID not found for category', {\n step: 'apply-tag',\n conversationId,\n category,\n tagName,\n error: `Could not get/create tag for category: ${category}`,\n })\n return {\n tagged: false,\n tagName,\n error: `Could not get/create tag for category: ${category}`,\n durationMs: Date.now() - startTime,\n }\n }\n\n // Apply category tag to conversation via Front API\n let categoryTagged = false\n let recovered = false\n try {\n await addTagToConversation(frontApiToken, conversationId, tagId)\n categoryTagged = true\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n const isFrontError = error instanceof FrontApiError\n\n // Check for archived tag error - attempt recovery by recreating the tag\n const isArchivedError =\n message.toLowerCase().includes('archived') ||\n (isFrontError && error.title?.toLowerCase().includes('archived'))\n\n if (isArchivedError) {\n await log('warn', 'Tag is archived, attempting recovery', {\n step: 'apply-tag',\n conversationId,\n category,\n tagId,\n tagName,\n originalError: message,\n })\n\n // Clear the stale tag from cache\n registry.clearCache()\n\n // Get the tag config to recreate it\n const config = registry.getTagConfigForCategory(category)\n\n try {\n const front = createInstrumentedFrontClient({\n apiToken: frontApiToken,\n })\n // Try to delete the archived tag first (if allowed)\n try {\n await front.tags.delete(tagId)\n await log('info', 'Deleted archived tag', {\n step: 'apply-tag',\n tagId,\n tagName,\n })\n } catch (deleteErr) {\n // Ignore delete errors - tag might not be deletable\n await log('debug', 'Could not delete archived tag (continuing)', {\n step: 'apply-tag',\n tagId,\n error:\n deleteErr instanceof Error\n ? deleteErr.message\n : String(deleteErr),\n })\n }\n\n // Create a fresh tag with the same name\n const newTag = await front.tags.create({\n name: config.tagName,\n description: config.description,\n highlight: config.highlight,\n })\n\n await log('info', 'Recreated tag after archive recovery', {\n step: 'apply-tag',\n conversationId,\n category,\n oldTagId: tagId,\n newTagId: newTag.id,\n tagName: config.tagName,\n })\n\n // Retry applying with the new tag ID\n await addTagToConversation(frontApiToken, conversationId, newTag.id)\n tagId = newTag.id\n categoryTagged = true\n recovered = true\n } catch (recoveryError) {\n const recoveryMsg =\n recoveryError instanceof Error\n ? recoveryError.message\n : String(recoveryError)\n await log('error', 'Tag recovery failed', {\n step: 'apply-tag',\n conversationId,\n category,\n tagId,\n tagName,\n originalError: message,\n recoveryError: recoveryMsg,\n })\n // Continue to try skill tag even if category tag failed\n }\n } else {\n // Non-archived error - log but continue to try skill tag\n await log('error', 'Front API addTag call failed', {\n step: 'apply-tag',\n conversationId,\n category,\n tagId,\n tagName,\n error: message,\n errorType:\n error instanceof Error ? error.constructor.name : 'unknown',\n frontApiStatus: isFrontError ? error.status : undefined,\n frontApiTitle: isFrontError ? error.title : undefined,\n })\n }\n }\n\n if (debug && categoryTagged) {\n console.log(\n `[TagStep] Applied category tag \"${tagName}\" (${tagId}) to ${conversationId}`\n )\n }\n\n // Apply skill tag if skill is provided\n let skillTagged = false\n let skillTagId: string | undefined\n let skillTagName: string | undefined\n\n if (skill) {\n try {\n skillTagId = await registry.getTagIdForSkill(skill)\n skillTagName = registry.getTagNameForSkill(skill)\n\n if (skillTagId) {\n await addTagToConversation(frontApiToken, conversationId, skillTagId)\n skillTagged = true\n\n if (debug) {\n console.log(\n `[TagStep] Applied skill tag \"${skillTagName}\" (${skillTagId}) to ${conversationId}`\n )\n }\n\n await log('info', 'skill tag applied successfully', {\n step: 'apply-tag',\n conversationId,\n skill,\n skillTagId,\n skillTagName,\n })\n } else {\n await log('warn', 'skill tag ID not found', {\n step: 'apply-tag',\n conversationId,\n skill,\n })\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n const isFrontError = error instanceof FrontApiError\n\n await log('error', 'skill tag application failed', {\n step: 'apply-tag',\n conversationId,\n skill,\n skillTagId,\n skillTagName,\n error: message,\n errorType:\n error instanceof Error ? error.constructor.name : 'unknown',\n frontApiStatus: isFrontError ? error.status : undefined,\n frontApiTitle: isFrontError ? error.title : undefined,\n })\n // Don't fail the entire operation if only skill tag failed\n }\n }\n\n // Return success if at least the category tag was applied\n if (categoryTagged) {\n return {\n tagged: true,\n tagId,\n tagName,\n skillTagged,\n skillTagId,\n skillTagName,\n recovered,\n durationMs: Date.now() - startTime,\n }\n }\n\n // Neither tag succeeded\n return {\n tagged: false,\n tagName,\n skillTagged,\n skillTagId,\n skillTagName,\n error: `Could not apply category tag for: ${category}`,\n durationMs: Date.now() - startTime,\n }\n } catch (error) {\n // Unexpected error — something outside the specific try/catches above\n const message = error instanceof Error ? error.message : String(error)\n await log('error', 'tag step unexpected error', {\n step: 'apply-tag',\n conversationId,\n category,\n skill,\n error: message,\n errorType: error instanceof Error ? error.constructor.name : 'unknown',\n stack: error instanceof Error ? error.stack : undefined,\n })\n\n return {\n tagged: false,\n error: `Unexpected: ${message}`,\n durationMs: Date.now() - startTime,\n }\n }\n}\n\n// ============================================================================\n// Factory function\n// ============================================================================\n\n/**\n * Create a tag step function with pre-configured options.\n *\n * @example\n * ```ts\n * const tagStep = createTagStep({ frontApiToken: 'xxx' })\n * await tagStep({ conversationId: 'cnv_123', category: 'spam', appConfig })\n * ```\n */\nexport function createTagStep(options: TagStepOptions) {\n return (input: TagInput) => applyTag(input, options)\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Get all tags that should be applied for a category.\n * Some categories may map to multiple tags.\n *\n * @param category - Message category\n * @param registry - TagRegistry instance\n * @returns Array of tag IDs\n */\nexport async function getTagsForCategory(\n category: MessageCategory,\n registry: TagRegistry\n): Promise<string[]> {\n const tagId = await registry.getTagIdForCategory(category)\n return tagId ? [tagId] : []\n}\n\n/**\n * Apply multiple tags to a conversation.\n * Useful when a conversation needs multiple category tags.\n *\n * @param conversationId - Front conversation ID\n * @param tagIds - Array of tag IDs to apply\n * @param frontApiToken - Front API token\n */\nexport async function applyMultipleTags(\n conversationId: string,\n tagIds: string[],\n frontApiToken: string\n): Promise<{ applied: string[]; failed: string[] }> {\n const applied: string[] = []\n const failed: string[] = []\n\n for (const tagId of tagIds) {\n try {\n await addTagToConversation(frontApiToken, conversationId, tagId)\n applied.push(tagId)\n } catch (error) {\n console.error(`[TagStep] Failed to apply tag ${tagId}:`, error)\n failed.push(tagId)\n }\n }\n\n return { applied, failed }\n}\n","/**\n * Tag Registry Service\n *\n * Maps message categories to Front tags with caching.\n * Auto-creates missing tags with appropriate highlight colors.\n *\n * @see https://dev.frontapp.com/reference/tags\n */\n\nimport { FrontApiError } from '@skillrecordings/front-sdk'\nimport { createInstrumentedFrontClient } from '../front/instrumented-client'\nimport { log } from '../observability/axiom'\nimport type {\n CategoryTagConfig,\n CategoryTagMapping,\n MessageCategory,\n SkillName,\n SkillTagConfig,\n SkillTagMapping,\n TagHighlight,\n} from '../pipeline/types'\n\n// ============================================================================\n// Skill Tag Configuration\n// ============================================================================\n\n/**\n * Prefix for skill-based tags.\n * Skills use pattern: skill/{skill-name} (e.g., skill/refund-request)\n */\nexport const SKILL_TAG_PREFIX = 'skill/'\n\n/**\n * Default mapping of skills to Front tags.\n * All 45 skills from skills/index.json mapped 1:1.\n */\nexport const DEFAULT_SKILL_TAG_MAPPING: SkillTagMapping = {\n // Access & Login skills (blue family)\n 'access-locked-out': {\n tagName: 'skill/access-locked-out',\n highlight: 'blue',\n description: 'Restore access for locked out customers',\n },\n 'login-link': {\n tagName: 'skill/login-link',\n highlight: 'blue',\n description: 'Login link requests',\n },\n 'password-reset-issue': {\n tagName: 'skill/password-reset-issue',\n highlight: 'blue',\n description: 'Password reset problems',\n },\n 'two-factor-auth-issue': {\n tagName: 'skill/two-factor-auth-issue',\n highlight: 'blue',\n description: '2FA authentication issues',\n },\n 'course-content-locked': {\n tagName: 'skill/course-content-locked',\n highlight: 'blue',\n description: 'Purchased content is locked',\n },\n 'cohort-access-request': {\n tagName: 'skill/cohort-access-request',\n highlight: 'blue',\n description: 'Cohort materials access',\n },\n\n // Technical issues (purple family)\n 'app-crash-report': {\n tagName: 'skill/app-crash-report',\n highlight: 'purple',\n description: 'Application crashes and bugs',\n },\n 'broken-link-404-error': {\n tagName: 'skill/broken-link-404-error',\n highlight: 'purple',\n description: 'Broken links and 404 errors',\n },\n 'technical-issue-course-content': {\n tagName: 'skill/technical-issue-course-content',\n highlight: 'purple',\n description: 'Technical issues with course content',\n },\n 'website-bug-report': {\n tagName: 'skill/website-bug-report',\n highlight: 'purple',\n description: 'Website bugs and errors',\n },\n 'email-delivery-failure': {\n tagName: 'skill/email-delivery-failure',\n highlight: 'purple',\n description: 'Email delivery failures',\n },\n 'api-documentation-question': {\n tagName: 'skill/api-documentation-question',\n highlight: 'purple',\n description: 'API and technical documentation',\n },\n\n // Billing & Payments (orange family)\n 'refund-request': {\n tagName: 'skill/refund-request',\n highlight: 'yellow',\n description: 'Refund requests',\n },\n 'duplicate-purchase': {\n tagName: 'skill/duplicate-purchase',\n highlight: 'orange',\n description: 'Duplicate purchase issues',\n },\n 'invoice-billing-statement': {\n tagName: 'skill/invoice-billing-statement',\n highlight: 'orange',\n description: 'Invoice and billing statements',\n },\n 'corporate-invoice': {\n tagName: 'skill/corporate-invoice',\n highlight: 'orange',\n description: 'Corporate invoice requests',\n },\n 'payment-method-issue': {\n tagName: 'skill/payment-method-issue',\n highlight: 'orange',\n description: 'Payment method problems',\n },\n 'subscription-renewal-issue': {\n tagName: 'skill/subscription-renewal-issue',\n highlight: 'orange',\n description: 'Subscription renewal issues',\n },\n 'installment-payment-option': {\n tagName: 'skill/installment-payment-option',\n highlight: 'orange',\n description: 'Payment plan inquiries',\n },\n\n // Pricing & Discounts (teal family)\n 'pricing-inquiry': {\n tagName: 'skill/pricing-inquiry',\n highlight: 'teal',\n description: 'Course pricing questions',\n },\n 'ppp-pricing': {\n tagName: 'skill/ppp-pricing',\n highlight: 'teal',\n description: 'PPP pricing requests',\n },\n 'discount-code-request': {\n tagName: 'skill/discount-code-request',\n highlight: 'teal',\n description: 'Discount code requests',\n },\n 'student-discount-request': {\n tagName: 'skill/student-discount-request',\n highlight: 'teal',\n description: 'Student discount inquiries',\n },\n 'nonprofit-government-discount': {\n tagName: 'skill/nonprofit-government-discount',\n highlight: 'teal',\n description: 'Nonprofit/government discounts',\n },\n 'scholarship-financial-aid': {\n tagName: 'skill/scholarship-financial-aid',\n highlight: 'teal',\n description: 'Scholarship and financial aid',\n },\n 'price-feedback': {\n tagName: 'skill/price-feedback',\n highlight: 'teal',\n description: 'Pricing feedback and concerns',\n },\n\n // Account & License (green family)\n 'email-change': {\n tagName: 'skill/email-change',\n highlight: 'green',\n description: 'Email/license transfers',\n },\n 'team-license-purchase': {\n tagName: 'skill/team-license-purchase',\n highlight: 'green',\n description: 'Team/bulk license purchases',\n },\n 'gift-purchase-option': {\n tagName: 'skill/gift-purchase-option',\n highlight: 'green',\n description: 'Gift purchase inquiries',\n },\n\n // Content & Learning (pink family)\n 'lesson-content-question': {\n tagName: 'skill/lesson-content-question',\n highlight: 'pink',\n description: 'Lesson content questions',\n },\n 'content-feedback': {\n tagName: 'skill/content-feedback',\n highlight: 'pink',\n description: 'Content feedback and suggestions',\n },\n 'outdated-course-content': {\n tagName: 'skill/outdated-course-content',\n highlight: 'pink',\n description: 'Outdated content reports',\n },\n 'course-difficulty-concern': {\n tagName: 'skill/course-difficulty-concern',\n highlight: 'pink',\n description: 'Course difficulty questions',\n },\n 'learning-path-guidance': {\n tagName: 'skill/learning-path-guidance',\n highlight: 'pink',\n description: 'Learning path recommendations',\n },\n 'certificate-request': {\n tagName: 'skill/certificate-request',\n highlight: 'pink',\n description: 'Certificate requests',\n },\n 'continuing-education-credits': {\n tagName: 'skill/continuing-education-credits',\n highlight: 'pink',\n description: 'CEU credit inquiries',\n },\n 'ui-ux-feedback': {\n tagName: 'skill/ui-ux-feedback',\n highlight: 'pink',\n description: 'UI/UX feedback',\n },\n\n // Workshops & Cohorts (purple-pink)\n 'cohort-schedule-inquiry': {\n tagName: 'skill/cohort-schedule-inquiry',\n highlight: 'purple',\n description: 'Cohort schedule questions',\n },\n 'workshop-attendance-confirmation': {\n tagName: 'skill/workshop-attendance-confirmation',\n highlight: 'purple',\n description: 'Workshop attendance confirmation',\n },\n 'workshop-cancellation-notice': {\n tagName: 'skill/workshop-cancellation-notice',\n highlight: 'red',\n description: 'Workshop cancellation notices',\n },\n 'workshop-technical-setup': {\n tagName: 'skill/workshop-technical-setup',\n highlight: 'purple',\n description: 'Workshop technical setup',\n },\n\n // Business & Partnerships (grey family)\n 'partnership-collaboration-inquiry': {\n tagName: 'skill/partnership-collaboration-inquiry',\n highlight: 'grey',\n description: 'Partnership inquiries',\n },\n 'event-sponsorship-request': {\n tagName: 'skill/event-sponsorship-request',\n highlight: 'grey',\n description: 'Event sponsorship requests',\n },\n 'media-press-outreach': {\n tagName: 'skill/media-press-outreach',\n highlight: 'grey',\n description: 'Media and press outreach',\n },\n 'security-vulnerability-report': {\n tagName: 'skill/security-vulnerability-report',\n highlight: 'red',\n description: 'Security vulnerability reports',\n },\n}\n\n// ============================================================================\n// Default Category → Tag Mapping\n// ============================================================================\n\n/**\n * Default mapping of message categories to Front tags.\n * Each category maps to a tag name and highlight color.\n */\nexport const DEFAULT_CATEGORY_TAG_MAPPING: CategoryTagMapping = {\n // Spam/System (red/grey - don't respond)\n spam: { tagName: 'spam', highlight: 'red', description: 'Spam or marketing' },\n system: {\n tagName: 'system',\n highlight: 'grey',\n description: 'Automated notifications',\n },\n\n // Support categories (blue family)\n support_access: {\n tagName: 'access-issue',\n highlight: 'blue',\n description: 'Login or access problems',\n },\n support_refund: {\n tagName: 'refund',\n highlight: 'yellow',\n description: 'Refund requests',\n },\n support_transfer: {\n tagName: 'transfer',\n highlight: 'green',\n description: 'License transfers',\n },\n support_technical: {\n tagName: 'technical',\n highlight: 'purple',\n description: 'Technical or product questions',\n },\n support_billing: {\n tagName: 'billing',\n highlight: 'orange',\n description: 'Invoice or payment issues',\n },\n technical_support: {\n tagName: 'technical-support',\n highlight: 'purple',\n description: 'General technical support issues',\n },\n feedback: {\n tagName: 'feedback',\n highlight: 'pink',\n description: 'Customer feedback',\n },\n sales_pricing: {\n tagName: 'sales-pricing',\n highlight: 'teal',\n description: 'Sales and pricing inquiries',\n },\n\n // Fan mail (pink - positive)\n fan_mail: {\n tagName: 'fan-mail',\n highlight: 'pink',\n description: 'Positive feedback to instructor',\n },\n\n // Presales (teal family)\n presales_faq: {\n tagName: 'presales',\n highlight: 'teal',\n description: 'Pre-purchase questions',\n },\n presales_consult: {\n tagName: 'presales',\n highlight: 'teal',\n description: 'Pre-purchase consultation',\n },\n presales_team: {\n tagName: 'presales-enterprise',\n highlight: 'teal',\n description: 'Enterprise/team inquiries',\n },\n\n // Voice of customer (green - valuable feedback)\n voc_response: {\n tagName: 'voc',\n highlight: 'green',\n description: 'Voice of customer responses',\n },\n\n // Thread-aware categories\n instructor_strategy: {\n tagName: 'instructor',\n highlight: 'purple',\n description: 'Instructor discussions',\n },\n resolved: {\n tagName: 'resolved',\n highlight: 'green',\n description: 'Thread already resolved',\n },\n awaiting_customer: {\n tagName: 'awaiting-reply',\n highlight: 'grey',\n description: 'Waiting for customer',\n },\n\n // Unknown (black - needs review)\n unknown: {\n tagName: 'needs-review',\n highlight: 'black',\n description: 'Could not classify',\n },\n}\n\n// ============================================================================\n// Tag Registry Class\n// ============================================================================\n\nexport interface TagRegistryOptions {\n /** Front API token */\n frontApiToken: string\n /** Override default category→tag mapping */\n categoryMapping?: Partial<CategoryTagMapping>\n /** Override default skill→tag mapping */\n skillMapping?: Partial<SkillTagMapping>\n /** Enable debug logging */\n debug?: boolean\n}\n\n/**\n * Registry for managing Front tags.\n *\n * Features:\n * - Maps categories to tag names with colors\n * - Caches tag IDs after first lookup\n * - Auto-creates missing tags\n *\n * @example\n * ```ts\n * const registry = new TagRegistry({ frontApiToken: 'xxx' })\n * const tagId = await registry.getTagIdForCategory('support_access')\n * // Returns 'tag_xxx' or creates if missing\n * ```\n */\nexport class TagRegistry {\n private front: ReturnType<typeof createInstrumentedFrontClient>\n private categoryMapping: CategoryTagMapping\n private skillMapping: SkillTagMapping\n private tagIdCache: Map<string, string> = new Map()\n private initialized = false\n private debug: boolean\n\n // Use raw API to avoid SDK schema parsing issues and to handle pagination.\n private async listTagsRaw(): Promise<Array<{ id: string; name: string }>> {\n type RawTagList = {\n _results: Array<{ id: string; name: string }>\n _pagination?: { next?: string | null }\n }\n\n const results: RawTagList['_results'] = []\n let page = await this.front.raw.get<RawTagList>('/tags')\n results.push(...page._results)\n\n while (page._pagination?.next) {\n page = await this.front.raw.get<RawTagList>(page._pagination.next)\n results.push(...page._results)\n }\n\n return results\n }\n\n constructor(options: TagRegistryOptions) {\n this.front = createInstrumentedFrontClient({\n apiToken: options.frontApiToken,\n })\n this.categoryMapping = {\n ...DEFAULT_CATEGORY_TAG_MAPPING,\n ...options.categoryMapping,\n }\n this.skillMapping = {\n ...DEFAULT_SKILL_TAG_MAPPING,\n ...options.skillMapping,\n }\n this.debug = options.debug ?? false\n }\n\n /**\n * Initialize the registry by fetching all existing tags.\n * Call this once before using getTagIdForCategory.\n */\n async initialize(): Promise<void> {\n if (this.initialized) return\n\n try {\n const tags = await this.listTagsRaw()\n for (const tag of tags) {\n this.tagIdCache.set(tag.name.toLowerCase(), tag.id)\n }\n this.initialized = true\n\n await log('debug', 'tag registry initialized', {\n component: 'TagRegistry',\n tagCount: tags.length,\n tagNames: tags.map((t) => t.name).slice(0, 20),\n })\n\n if (this.debug) {\n console.log(`[TagRegistry] Initialized with ${tags.length} tags`)\n }\n } catch (error) {\n const isFrontError = error instanceof FrontApiError\n const message = error instanceof Error ? error.message : String(error)\n\n await log('error', 'tag registry initialization failed', {\n component: 'TagRegistry',\n error: message,\n errorType: error instanceof Error ? error.constructor.name : 'unknown',\n frontApiStatus: isFrontError ? error.status : undefined,\n frontApiTitle: isFrontError ? error.title : undefined,\n })\n\n console.error('[TagRegistry] Failed to initialize:', error)\n throw error\n }\n }\n\n /**\n * Get the tag configuration for a category.\n */\n getTagConfigForCategory(category: MessageCategory): CategoryTagConfig {\n return this.categoryMapping[category] ?? this.categoryMapping.unknown\n }\n\n /**\n * Get the tag name for a category.\n */\n getTagNameForCategory(category: MessageCategory): string {\n return this.getTagConfigForCategory(category).tagName\n }\n\n /**\n * Get the highlight color for a category.\n */\n getHighlightForCategory(category: MessageCategory): TagHighlight {\n return this.getTagConfigForCategory(category).highlight\n }\n\n /**\n * Get or create a tag ID for a category.\n *\n * @param category - The message category\n * @returns Tag ID (tag_xxx) or undefined if failed\n */\n async getTagIdForCategory(\n category: MessageCategory\n ): Promise<string | undefined> {\n // Initialize if needed\n if (!this.initialized) {\n await this.initialize()\n }\n\n const config = this.getTagConfigForCategory(category)\n const tagName = config.tagName.toLowerCase()\n\n // Check cache first\n if (this.tagIdCache.has(tagName)) {\n return this.tagIdCache.get(tagName)\n }\n\n // Tag not in cache — try to create it\n await log('info', 'tag not in cache, attempting creation', {\n component: 'TagRegistry',\n category,\n tagName: config.tagName,\n cachedTagCount: this.tagIdCache.size,\n })\n\n try {\n if (this.debug) {\n console.log(`[TagRegistry] Creating tag: ${config.tagName}`)\n }\n const newTag = await this.front.tags.create({\n name: config.tagName,\n description: config.description,\n highlight: config.highlight,\n })\n this.tagIdCache.set(tagName, newTag.id)\n\n await log('info', 'tag created successfully', {\n component: 'TagRegistry',\n category,\n tagName: config.tagName,\n tagId: newTag.id,\n })\n\n return newTag.id\n } catch (error) {\n const isFrontError = error instanceof FrontApiError\n const message = error instanceof Error ? error.message : String(error)\n\n await log('warn', 'tag creation failed, attempting re-fetch', {\n component: 'TagRegistry',\n category,\n tagName: config.tagName,\n error: message,\n errorType: error instanceof Error ? error.constructor.name : 'unknown',\n frontApiStatus: isFrontError ? error.status : undefined,\n })\n\n // Tag might already exist (race condition) - try to find it\n if (this.debug) {\n console.log(`[TagRegistry] Create failed, re-fetching tags:`, error)\n }\n try {\n const tags = await this.listTagsRaw()\n const existing = tags.find((t) => t.name.toLowerCase() === tagName)\n if (existing) {\n this.tagIdCache.set(tagName, existing.id)\n\n await log('info', 'found existing tag on re-fetch', {\n component: 'TagRegistry',\n category,\n tagName: config.tagName,\n tagId: existing.id,\n })\n\n return existing.id\n }\n\n await log('error', 'tag not found on re-fetch either', {\n component: 'TagRegistry',\n category,\n tagName: config.tagName,\n availableTags: tags.map((t) => t.name).slice(0, 30),\n })\n } catch (refetchError) {\n const refetchMsg =\n refetchError instanceof Error\n ? refetchError.message\n : String(refetchError)\n await log('error', 'tag re-fetch also failed', {\n component: 'TagRegistry',\n category,\n tagName: config.tagName,\n originalError: message,\n refetchError: refetchMsg,\n })\n }\n\n console.error(\n `[TagRegistry] Failed to get/create tag for ${category}:`,\n error\n )\n return undefined\n }\n }\n\n /**\n * Get tag ID by tag name directly (for non-category tags).\n */\n async getTagIdByName(tagName: string): Promise<string | undefined> {\n if (!this.initialized) {\n await this.initialize()\n }\n\n const normalizedName = tagName.toLowerCase()\n if (this.tagIdCache.has(normalizedName)) {\n return this.tagIdCache.get(normalizedName)\n }\n\n // Not in cache, tag doesn't exist\n return undefined\n }\n\n // ============================================================================\n // Skill Tag Methods\n // ============================================================================\n\n /**\n * Get the tag configuration for a skill.\n */\n getTagConfigForSkill(skill: SkillName): SkillTagConfig | undefined {\n return this.skillMapping[skill]\n }\n\n /**\n * Get the tag name for a skill.\n */\n getTagNameForSkill(skill: SkillName): string | undefined {\n return this.getTagConfigForSkill(skill)?.tagName\n }\n\n /**\n * Get the highlight color for a skill.\n */\n getHighlightForSkill(skill: SkillName): TagHighlight | undefined {\n return this.getTagConfigForSkill(skill)?.highlight\n }\n\n /**\n * Get or create a tag ID for a skill.\n *\n * @param skill - The skill name\n * @returns Tag ID (tag_xxx) or undefined if failed\n */\n async getTagIdForSkill(skill: SkillName): Promise<string | undefined> {\n // Initialize if needed\n if (!this.initialized) {\n await this.initialize()\n }\n\n const config = this.getTagConfigForSkill(skill)\n if (!config) {\n await log('warn', 'unknown skill requested', {\n component: 'TagRegistry',\n skill,\n })\n return undefined\n }\n\n const tagName = config.tagName.toLowerCase()\n\n // Check cache first\n if (this.tagIdCache.has(tagName)) {\n return this.tagIdCache.get(tagName)\n }\n\n // Tag not in cache — try to create it\n await log('info', 'skill tag not in cache, attempting creation', {\n component: 'TagRegistry',\n skill,\n tagName: config.tagName,\n cachedTagCount: this.tagIdCache.size,\n })\n\n try {\n if (this.debug) {\n console.log(`[TagRegistry] Creating skill tag: ${config.tagName}`)\n }\n const newTag = await this.front.tags.create({\n name: config.tagName,\n description: config.description,\n highlight: config.highlight,\n })\n this.tagIdCache.set(tagName, newTag.id)\n\n await log('info', 'skill tag created successfully', {\n component: 'TagRegistry',\n skill,\n tagName: config.tagName,\n tagId: newTag.id,\n })\n\n return newTag.id\n } catch (error) {\n const isFrontError = error instanceof FrontApiError\n const message = error instanceof Error ? error.message : String(error)\n\n await log('warn', 'skill tag creation failed, attempting re-fetch', {\n component: 'TagRegistry',\n skill,\n tagName: config.tagName,\n error: message,\n errorType: error instanceof Error ? error.constructor.name : 'unknown',\n frontApiStatus: isFrontError ? error.status : undefined,\n })\n\n // Tag might already exist (race condition) - try to find it\n if (this.debug) {\n console.log(\n `[TagRegistry] Skill tag create failed, re-fetching tags:`,\n error\n )\n }\n try {\n const tags = await this.listTagsRaw()\n const existing = tags.find((t) => t.name.toLowerCase() === tagName)\n if (existing) {\n this.tagIdCache.set(tagName, existing.id)\n\n await log('info', 'found existing skill tag on re-fetch', {\n component: 'TagRegistry',\n skill,\n tagName: config.tagName,\n tagId: existing.id,\n })\n\n return existing.id\n }\n\n await log('error', 'skill tag not found on re-fetch either', {\n component: 'TagRegistry',\n skill,\n tagName: config.tagName,\n availableTags: tags.map((t) => t.name).slice(0, 30),\n })\n } catch (refetchError) {\n const refetchMsg =\n refetchError instanceof Error\n ? refetchError.message\n : String(refetchError)\n await log('error', 'skill tag re-fetch also failed', {\n component: 'TagRegistry',\n skill,\n tagName: config.tagName,\n originalError: message,\n refetchError: refetchMsg,\n })\n }\n\n console.error(\n `[TagRegistry] Failed to get/create tag for skill ${skill}:`,\n error\n )\n return undefined\n }\n }\n\n /**\n * Clear the tag ID cache. Useful if tags were modified externally.\n */\n clearCache(): void {\n this.tagIdCache.clear()\n this.initialized = false\n }\n}\n\n// ============================================================================\n// Factory function\n// ============================================================================\n\n/**\n * Create a TagRegistry instance.\n *\n * @example\n * ```ts\n * const registry = createTagRegistry({ frontApiToken: process.env.FRONT_TOKEN! })\n * const tagId = await registry.getTagIdForCategory('support_access')\n * ```\n */\nexport function createTagRegistry(options: TagRegistryOptions): TagRegistry {\n return new TagRegistry(options)\n}\n","/**\n * Step: ARCHIVE\n *\n * Archives Front conversations when the routing decision is \"silence\".\n * Archiving removes conversations from the inbox without deleting them.\n *\n * Archive conditions:\n * - Route action is 'silence'\n * - Categories: spam, system, resolved, awaiting_customer\n *\n * This step runs after routing and is fire-and-forget (failures\n * are logged but don't block the pipeline).\n */\n\nimport { createInstrumentedFrontClient } from '../../front/instrumented-client'\nimport type { ArchiveInput, ArchiveOutput, RouteAction } from '../types'\n\n// ============================================================================\n// Options\n// ============================================================================\n\nexport interface ArchiveStepOptions {\n /** Front API token */\n frontApiToken: string\n /** Enable debug logging */\n debug?: boolean\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * Route actions that should trigger archiving.\n */\nconst ARCHIVABLE_ACTIONS: RouteAction[] = ['silence']\n\n// ============================================================================\n// Archive Step\n// ============================================================================\n\n/**\n * Check if a conversation should be archived based on route action.\n */\nexport function shouldArchive(action: RouteAction): boolean {\n return ARCHIVABLE_ACTIONS.includes(action)\n}\n\n/**\n * Archive a Front conversation.\n *\n * Only archives if the route action is 'silence'. Other actions\n * (respond, escalate_*) keep the conversation in the inbox.\n *\n * @param input - Conversation ID, action, and reason\n * @param options - Front API token\n * @returns Result with success status\n *\n * @example\n * ```ts\n * const result = await archiveConversation(\n * { conversationId: 'cnv_123', action: 'silence', reason: 'spam', appConfig },\n * { frontApiToken: 'xxx' }\n * )\n * // result: { archived: true }\n * ```\n */\nexport async function archiveConversation(\n input: ArchiveInput,\n options: ArchiveStepOptions\n): Promise<ArchiveOutput> {\n const startTime = Date.now()\n const { conversationId, action, reason } = input\n const { frontApiToken, debug } = options\n\n // Only archive for silence actions\n if (!shouldArchive(action)) {\n if (debug) {\n console.log(\n `[ArchiveStep] Skipping archive for ${conversationId} - action is \"${action}\"`\n )\n }\n return {\n archived: false,\n durationMs: Date.now() - startTime,\n }\n }\n\n try {\n const front = createInstrumentedFrontClient({ apiToken: frontApiToken })\n\n // Archive the conversation by setting status to 'archived'\n await front.conversations.update(conversationId, {\n status: 'archived',\n })\n\n if (debug) {\n console.log(\n `[ArchiveStep] Archived ${conversationId} (reason: ${reason})`\n )\n }\n\n return {\n archived: true,\n durationMs: Date.now() - startTime,\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n console.error(`[ArchiveStep] Failed to archive ${conversationId}:`, message)\n\n return {\n archived: false,\n error: message,\n durationMs: Date.now() - startTime,\n }\n }\n}\n\n// ============================================================================\n// Factory function\n// ============================================================================\n\n/**\n * Create an archive step function with pre-configured options.\n *\n * @example\n * ```ts\n * const archiveStep = createArchiveStep({ frontApiToken: 'xxx' })\n * await archiveStep({ conversationId: 'cnv_123', action: 'silence', reason: 'spam', appConfig })\n * ```\n */\nexport function createArchiveStep(options: ArchiveStepOptions) {\n return (input: ArchiveInput) => archiveConversation(input, options)\n}\n"],"mappings":"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","names":["durationMs","_","result","z","RecipientSchema","TagSchema","TagSchema","durationMs","log","EMAIL_REGEX","durationMs","toolsUsed","SupportMemoryService","draft","generateObject","z","draft","z","generateObject"]}
|