@skillrecordings/cli 0.10.1 → 0.11.0
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 +0 -5
- package/bin/skill.ts +0 -1
- package/dist/{chunk-TFLLCHUX.js → chunk-54VQQSWT.js} +13 -2
- package/dist/chunk-5ACOI6Z3.js +198 -0
- package/dist/chunk-5ACOI6Z3.js.map +1 -0
- package/dist/{chunk-HCFBOGCL.js → chunk-EGBS3VJI.js} +4 -5
- package/dist/{config-523F4OBH.js → config-HLXR42M4.js} +3 -3
- package/dist/duckdb-source-T7EJUOC7.js +353 -0
- package/dist/duckdb-source-T7EJUOC7.js.map +1 -0
- package/dist/index.js +9334 -7543
- package/dist/index.js.map +1 -1
- package/dist/{pipeline-DRKFIH73.js → pipeline-H52QEIFE.js} +2 -2
- package/package.json +8 -6
- package/plugin/.claude-plugin/plugin.json +9 -0
- package/plugin/skills/front-inbox/SKILL.md +360 -0
- package/plugin/skills/front-inbox/examples/bulk-archive.md +16 -0
- package/plugin/skills/front-inbox/examples/triage-tt.md +16 -0
- package/.env.encrypted +0 -0
- package/dist/preload.js +0 -112
- package/dist/preload.js.map +0 -1
- /package/dist/{chunk-TFLLCHUX.js.map → chunk-54VQQSWT.js.map} +0 -0
- /package/dist/{chunk-HCFBOGCL.js.map → chunk-EGBS3VJI.js.map} +0 -0
- /package/dist/{config-523F4OBH.js.map → config-HLXR42M4.js.map} +0 -0
- /package/dist/{pipeline-DRKFIH73.js.map → pipeline-H52QEIFE.js.map} +0 -0
package/bin/skill.mjs
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
// Load secrets via 1Password/age encryption before importing CLI
|
|
4
|
-
// This sets DATABASE_URL and other env vars from encrypted .env.encrypted
|
|
5
|
-
await import('../dist/preload.js')
|
|
6
|
-
|
|
7
|
-
// Now import the main CLI with secrets available
|
|
8
3
|
await import('../dist/index.js')
|
package/bin/skill.ts
CHANGED
|
@@ -3560,7 +3560,7 @@ ${message.body}
|
|
|
3560
3560
|
---
|
|
3561
3561
|
Write your response:`;
|
|
3562
3562
|
if (useAgentMode && appId) {
|
|
3563
|
-
const { runSupportAgent } = await import("./config-
|
|
3563
|
+
const { runSupportAgent } = await import("./config-HLXR42M4.js");
|
|
3564
3564
|
await log("debug", "draft using agent mode", {
|
|
3565
3565
|
workflow: "pipeline",
|
|
3566
3566
|
step: "draft",
|
|
@@ -7028,6 +7028,17 @@ async function runThreadPipeline(input, options = {}) {
|
|
|
7028
7028
|
}
|
|
7029
7029
|
|
|
7030
7030
|
export {
|
|
7031
|
+
FrontApiError,
|
|
7032
|
+
createChannelsClient,
|
|
7033
|
+
createContactsClient,
|
|
7034
|
+
createConversationsClient,
|
|
7035
|
+
createDraftsClient,
|
|
7036
|
+
createInboxesClient,
|
|
7037
|
+
createMessagesClient,
|
|
7038
|
+
createTagsClient,
|
|
7039
|
+
createTeammatesClient,
|
|
7040
|
+
createTemplatesClient,
|
|
7041
|
+
createInstrumentedBaseClient,
|
|
7031
7042
|
createInstrumentedFrontClient,
|
|
7032
7043
|
computeMessageSignals,
|
|
7033
7044
|
computeThreadSignals,
|
|
@@ -7085,4 +7096,4 @@ export {
|
|
|
7085
7096
|
runPipeline,
|
|
7086
7097
|
runThreadPipeline
|
|
7087
7098
|
};
|
|
7088
|
-
//# sourceMappingURL=chunk-
|
|
7099
|
+
//# sourceMappingURL=chunk-54VQQSWT.js.map
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import {
|
|
2
|
+
init_esm_shims
|
|
3
|
+
} from "./chunk-WFANXVQG.js";
|
|
4
|
+
|
|
5
|
+
// ../core/src/faq/filters.ts
|
|
6
|
+
init_esm_shims();
|
|
7
|
+
var NOISE_SENDER_DOMAINS = [
|
|
8
|
+
// Transcription services
|
|
9
|
+
"castingwords.com",
|
|
10
|
+
// Email/Marketing services
|
|
11
|
+
"postmarkapp.com",
|
|
12
|
+
"convertkit.com",
|
|
13
|
+
"kit.com",
|
|
14
|
+
"kit-mail6.com",
|
|
15
|
+
"convertkit-mail4.com",
|
|
16
|
+
"mailgun.org",
|
|
17
|
+
"sendgrid.net",
|
|
18
|
+
// Cloud services
|
|
19
|
+
"algolia.com",
|
|
20
|
+
"cloudinary.com",
|
|
21
|
+
"mux.com",
|
|
22
|
+
"aws.amazon.com",
|
|
23
|
+
"amazonaws.com",
|
|
24
|
+
"vercel.com",
|
|
25
|
+
// Google services
|
|
26
|
+
"google.com",
|
|
27
|
+
"google.co.uk",
|
|
28
|
+
"googlemail.com",
|
|
29
|
+
// Payment/Billing
|
|
30
|
+
"stripe.com",
|
|
31
|
+
"paddle.com",
|
|
32
|
+
// Training/Enterprise platforms
|
|
33
|
+
"placedelaformation.com",
|
|
34
|
+
"allwyn-lotterysolutions.com",
|
|
35
|
+
"atlassian.net",
|
|
36
|
+
// Email routing/filtering
|
|
37
|
+
"mixmax.com",
|
|
38
|
+
// Security/DMARC
|
|
39
|
+
"dmarc.postmarkapp.com",
|
|
40
|
+
"postmarkdmarc.com",
|
|
41
|
+
"activecampaign.com"
|
|
42
|
+
];
|
|
43
|
+
var AUTO_REPLY_PATTERNS = [
|
|
44
|
+
// Chinese auto-replies
|
|
45
|
+
/这是.*自动回复/,
|
|
46
|
+
/[你您]好.*我已收到您的邮件/,
|
|
47
|
+
/我已收到您的邮件/,
|
|
48
|
+
/您的邮件我已经收到/,
|
|
49
|
+
/我最近正在休假中/,
|
|
50
|
+
// English auto-replies
|
|
51
|
+
/vacation.*auto.*reply/i,
|
|
52
|
+
/out of office/i,
|
|
53
|
+
/automatic reply/i,
|
|
54
|
+
/auto-reply/i,
|
|
55
|
+
// Calendar notifications
|
|
56
|
+
/has accepted this invitation/i,
|
|
57
|
+
/has declined this invitation/i,
|
|
58
|
+
/invitation from google calendar/i,
|
|
59
|
+
// Email routing tools
|
|
60
|
+
/uses Mixmax to route/i,
|
|
61
|
+
/keeping their inbox clear/i,
|
|
62
|
+
// Service desk auto-responses
|
|
63
|
+
/just confirming that we got your request/i,
|
|
64
|
+
/reply above this line/i,
|
|
65
|
+
// Unsubscribe confirmations
|
|
66
|
+
/you have been unsubscribed/i,
|
|
67
|
+
/successfully unsubscribed/i
|
|
68
|
+
];
|
|
69
|
+
var SPAM_PATTERNS = [
|
|
70
|
+
// Influencer outreach (Head AI)
|
|
71
|
+
/reaching out from Head/i,
|
|
72
|
+
/AI-powered influencer/i,
|
|
73
|
+
/Partnerships at Head Creator/i,
|
|
74
|
+
// Generic cold outreach - broad patterns
|
|
75
|
+
/collaboration/i,
|
|
76
|
+
// Catches: collab, collaboration, collaborations, etc.
|
|
77
|
+
/partnership/i,
|
|
78
|
+
// Catches: partner, partnership, partnerships, etc.
|
|
79
|
+
/sponsored/i,
|
|
80
|
+
// Sponsored content requests
|
|
81
|
+
/affiliate/i,
|
|
82
|
+
// Affiliate program requests
|
|
83
|
+
// Generic cold outreach - specific patterns
|
|
84
|
+
/I hope this email finds you well.*My name is/i,
|
|
85
|
+
/I'd like to discuss/i,
|
|
86
|
+
/brand collab/i,
|
|
87
|
+
/creator partnership/i,
|
|
88
|
+
// SEO spam
|
|
89
|
+
/increase your organic traffic/i,
|
|
90
|
+
/boost your SEO/i,
|
|
91
|
+
/backlink opportunity/i
|
|
92
|
+
];
|
|
93
|
+
var LESSON_COMMENT_PATTERN = /^.*writes:[\s\S]*---\s*lesson:/i;
|
|
94
|
+
var SERVICE_NOTIFICATION_PATTERNS = [
|
|
95
|
+
// Transcription services
|
|
96
|
+
/We've just finished your transcription/i,
|
|
97
|
+
/We just finished up transcribing order/i,
|
|
98
|
+
// DMARC/Email reports
|
|
99
|
+
/emails were sent using.*between/i,
|
|
100
|
+
/DMARC Compliance/i,
|
|
101
|
+
/DMARC aligned/i,
|
|
102
|
+
/dmarc.postmarkapp.com/i,
|
|
103
|
+
// Usage reports
|
|
104
|
+
/Weekly report.*Plan usage/i,
|
|
105
|
+
/Usage Report/i,
|
|
106
|
+
/monthly invoice is available/i,
|
|
107
|
+
// Payment notifications
|
|
108
|
+
/Your payment was successful/i,
|
|
109
|
+
/We received your payment/i,
|
|
110
|
+
// Security notifications
|
|
111
|
+
/new sign-in.*device/i,
|
|
112
|
+
/unrecognized device/i,
|
|
113
|
+
/We found some security gaps/i,
|
|
114
|
+
// Google Workspace
|
|
115
|
+
/Google Workspace.*invoice/i,
|
|
116
|
+
/Google Analytics.*tips/i,
|
|
117
|
+
/Dear Administrator/i
|
|
118
|
+
];
|
|
119
|
+
function isNoiseSenderDomain(email) {
|
|
120
|
+
const domain = email.split("@")[1]?.toLowerCase() ?? "";
|
|
121
|
+
return NOISE_SENDER_DOMAINS.some(
|
|
122
|
+
(noiseDomain) => domain === noiseDomain || domain.endsWith(`.${noiseDomain}`)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
function isAutoReply(text) {
|
|
126
|
+
return AUTO_REPLY_PATTERNS.some((pattern) => pattern.test(text));
|
|
127
|
+
}
|
|
128
|
+
function isSpam(text) {
|
|
129
|
+
return SPAM_PATTERNS.some((pattern) => pattern.test(text));
|
|
130
|
+
}
|
|
131
|
+
function isLessonComment(text) {
|
|
132
|
+
return LESSON_COMMENT_PATTERN.test(text);
|
|
133
|
+
}
|
|
134
|
+
function isServiceNotification(text) {
|
|
135
|
+
return SERVICE_NOTIFICATION_PATTERNS.some((pattern) => pattern.test(text));
|
|
136
|
+
}
|
|
137
|
+
function shouldFilter(text, senderEmail) {
|
|
138
|
+
if (senderEmail && isNoiseSenderDomain(senderEmail)) {
|
|
139
|
+
return { filtered: true, reason: "sender_domain" };
|
|
140
|
+
}
|
|
141
|
+
if (isLessonComment(text)) {
|
|
142
|
+
return { filtered: true, reason: "lesson_comment" };
|
|
143
|
+
}
|
|
144
|
+
if (isAutoReply(text)) {
|
|
145
|
+
return { filtered: true, reason: "auto_reply" };
|
|
146
|
+
}
|
|
147
|
+
if (isSpam(text)) {
|
|
148
|
+
return { filtered: true, reason: "spam" };
|
|
149
|
+
}
|
|
150
|
+
if (isServiceNotification(text)) {
|
|
151
|
+
return { filtered: true, reason: "service_notification" };
|
|
152
|
+
}
|
|
153
|
+
return { filtered: false };
|
|
154
|
+
}
|
|
155
|
+
function createFilterStats() {
|
|
156
|
+
return {
|
|
157
|
+
total: 0,
|
|
158
|
+
filtered: 0,
|
|
159
|
+
passed: 0,
|
|
160
|
+
byReason: {}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function updateFilterStats(stats, result) {
|
|
164
|
+
stats.total++;
|
|
165
|
+
if (result.filtered) {
|
|
166
|
+
stats.filtered++;
|
|
167
|
+
if (result.reason) {
|
|
168
|
+
stats.byReason[result.reason] = (stats.byReason[result.reason] ?? 0) + 1;
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
stats.passed++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function formatFilterStats(stats) {
|
|
175
|
+
const lines = [
|
|
176
|
+
`\u{1F4CA} Filter Statistics:`,
|
|
177
|
+
` Total processed: ${stats.total}`,
|
|
178
|
+
` Filtered out: ${stats.filtered} (${(stats.filtered / stats.total * 100).toFixed(1)}%)`,
|
|
179
|
+
` Passed: ${stats.passed} (${(stats.passed / stats.total * 100).toFixed(1)}%)`
|
|
180
|
+
];
|
|
181
|
+
if (Object.keys(stats.byReason).length > 0) {
|
|
182
|
+
lines.push(` By reason:`);
|
|
183
|
+
for (const [reason, count] of Object.entries(stats.byReason).sort(
|
|
184
|
+
(a, b) => b[1] - a[1]
|
|
185
|
+
)) {
|
|
186
|
+
lines.push(` - ${reason}: ${count}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return lines.join("\n");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export {
|
|
193
|
+
shouldFilter,
|
|
194
|
+
createFilterStats,
|
|
195
|
+
updateFilterStats,
|
|
196
|
+
formatFilterStats
|
|
197
|
+
};
|
|
198
|
+
//# sourceMappingURL=chunk-5ACOI6Z3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../core/src/faq/filters.ts"],"sourcesContent":["/**\n * FAQ Preprocessing Filters\n *\n * Filters to remove noise before clustering. These patterns were identified\n * by auditing Phase 0 cluster quality (see docs/cluster-quality-diagnosis.md).\n *\n * Issue: #110\n *\n * @module faq/filters\n */\n\n/**\n * Sender domains that should be filtered out before clustering.\n * These are service providers, not customer support requests.\n */\nexport const NOISE_SENDER_DOMAINS = [\n // Transcription services\n 'castingwords.com',\n\n // Email/Marketing services\n 'postmarkapp.com',\n 'convertkit.com',\n 'kit.com',\n 'kit-mail6.com',\n 'convertkit-mail4.com',\n 'mailgun.org',\n 'sendgrid.net',\n\n // Cloud services\n 'algolia.com',\n 'cloudinary.com',\n 'mux.com',\n 'aws.amazon.com',\n 'amazonaws.com',\n 'vercel.com',\n\n // Google services\n 'google.com',\n 'google.co.uk',\n 'googlemail.com',\n\n // Payment/Billing\n 'stripe.com',\n 'paddle.com',\n\n // Training/Enterprise platforms\n 'placedelaformation.com',\n 'allwyn-lotterysolutions.com',\n 'atlassian.net',\n\n // Email routing/filtering\n 'mixmax.com',\n\n // Security/DMARC\n 'dmarc.postmarkapp.com',\n 'postmarkdmarc.com',\n 'activecampaign.com',\n] as const\n\n/**\n * Auto-reply patterns that indicate noise, not real support requests.\n */\nexport const AUTO_REPLY_PATTERNS = [\n // Chinese auto-replies\n /这是.*自动回复/,\n /[你您]好.*我已收到您的邮件/,\n /我已收到您的邮件/,\n /您的邮件我已经收到/,\n /我最近正在休假中/,\n\n // English auto-replies\n /vacation.*auto.*reply/i,\n /out of office/i,\n /automatic reply/i,\n /auto-reply/i,\n\n // Calendar notifications\n /has accepted this invitation/i,\n /has declined this invitation/i,\n /invitation from google calendar/i,\n\n // Email routing tools\n /uses Mixmax to route/i,\n /keeping their inbox clear/i,\n\n // Service desk auto-responses\n /just confirming that we got your request/i,\n /reply above this line/i,\n\n // Unsubscribe confirmations\n /you have been unsubscribed/i,\n /successfully unsubscribed/i,\n] as const\n\n/**\n * Spam/outreach patterns - cold emails to support addresses\n */\nexport const SPAM_PATTERNS = [\n // Influencer outreach (Head AI)\n /reaching out from Head/i,\n /AI-powered influencer/i,\n /Partnerships at Head Creator/i,\n\n // Generic cold outreach - broad patterns\n /collaboration/i, // Catches: collab, collaboration, collaborations, etc.\n /partnership/i, // Catches: partner, partnership, partnerships, etc.\n /sponsored/i, // Sponsored content requests\n /affiliate/i, // Affiliate program requests\n\n // Generic cold outreach - specific patterns\n /I hope this email finds you well.*My name is/i,\n /I'd like to discuss/i,\n /brand collab/i,\n /creator partnership/i,\n\n // SEO spam\n /increase your organic traffic/i,\n /boost your SEO/i,\n /backlink opportunity/i,\n] as const\n\n/**\n * Lesson comment pattern - these are egghead lesson comments, not support\n */\nexport const LESSON_COMMENT_PATTERN = /^.*writes:[\\s\\S]*---\\s*lesson:/i\n\n/**\n * Service notification patterns - automated emails from service providers\n */\nexport const SERVICE_NOTIFICATION_PATTERNS = [\n // Transcription services\n /We've just finished your transcription/i,\n /We just finished up transcribing order/i,\n\n // DMARC/Email reports\n /emails were sent using.*between/i,\n /DMARC Compliance/i,\n /DMARC aligned/i,\n /dmarc.postmarkapp.com/i,\n\n // Usage reports\n /Weekly report.*Plan usage/i,\n /Usage Report/i,\n /monthly invoice is available/i,\n\n // Payment notifications\n /Your payment was successful/i,\n /We received your payment/i,\n\n // Security notifications\n /new sign-in.*device/i,\n /unrecognized device/i,\n /We found some security gaps/i,\n\n // Google Workspace\n /Google Workspace.*invoice/i,\n /Google Analytics.*tips/i,\n /Dear Administrator/i,\n] as const\n\n/**\n * Marketing reply indicators - replies to outbound marketing emails\n */\nexport const MARKETING_REPLY_PATTERNS = [\n // Price objection replies (from TotalTypeScript campaigns)\n /What's holding you back from buying/i,\n /Total TypeScript Core Volume Pre-Release/i,\n /workshop.*discount.*limited time/i,\n] as const\n\n/**\n * Check if a message should be filtered based on sender domain.\n */\nexport function isNoiseSenderDomain(email: string): boolean {\n const domain = email.split('@')[1]?.toLowerCase() ?? ''\n return NOISE_SENDER_DOMAINS.some(\n (noiseDomain) =>\n domain === noiseDomain || domain.endsWith(`.${noiseDomain}`)\n )\n}\n\n/**\n * Check if a message matches any auto-reply pattern.\n */\nexport function isAutoReply(text: string): boolean {\n return AUTO_REPLY_PATTERNS.some((pattern) => pattern.test(text))\n}\n\n/**\n * Check if a message matches any spam/outreach pattern.\n */\nexport function isSpam(text: string): boolean {\n return SPAM_PATTERNS.some((pattern) => pattern.test(text))\n}\n\n/**\n * Check if a message is an egghead lesson comment.\n */\nexport function isLessonComment(text: string): boolean {\n return LESSON_COMMENT_PATTERN.test(text)\n}\n\n/**\n * Check if a message is a service notification.\n */\nexport function isServiceNotification(text: string): boolean {\n return SERVICE_NOTIFICATION_PATTERNS.some((pattern) => pattern.test(text))\n}\n\n/**\n * Check if a message is a reply to a marketing email.\n * These are detected by checking if the quoted/forwarded content matches marketing patterns.\n */\nexport function isMarketingReply(text: string): boolean {\n return MARKETING_REPLY_PATTERNS.some((pattern) => pattern.test(text))\n}\n\n/**\n * Filter result with reason for transparency/debugging.\n */\nexport interface FilterResult {\n /** Whether the message should be filtered */\n filtered: boolean\n /** Reason for filtering (if filtered) */\n reason?:\n | 'sender_domain'\n | 'auto_reply'\n | 'spam'\n | 'lesson_comment'\n | 'service_notification'\n | 'marketing_reply'\n}\n\n/**\n * Check if a message should be filtered before clustering.\n *\n * @param text - Message text\n * @param senderEmail - Sender email address\n * @returns FilterResult with filtered status and reason\n */\nexport function shouldFilter(text: string, senderEmail?: string): FilterResult {\n // Check sender domain first (cheapest check)\n if (senderEmail && isNoiseSenderDomain(senderEmail)) {\n return { filtered: true, reason: 'sender_domain' }\n }\n\n // Check lesson comment pattern\n if (isLessonComment(text)) {\n return { filtered: true, reason: 'lesson_comment' }\n }\n\n // Check auto-reply patterns\n if (isAutoReply(text)) {\n return { filtered: true, reason: 'auto_reply' }\n }\n\n // Check spam patterns\n if (isSpam(text)) {\n return { filtered: true, reason: 'spam' }\n }\n\n // Check service notification patterns\n if (isServiceNotification(text)) {\n return { filtered: true, reason: 'service_notification' }\n }\n\n // Don't filter marketing replies by default - they might still be useful\n // for understanding customer objections, just not for FAQ extraction\n // Uncomment to enable:\n // if (isMarketingReply(text)) {\n // return { filtered: true, reason: 'marketing_reply' }\n // }\n\n return { filtered: false }\n}\n\n/**\n * Filter statistics for logging/monitoring.\n */\nexport interface FilterStats {\n total: number\n filtered: number\n passed: number\n byReason: Record<string, number>\n}\n\n/**\n * Create empty filter stats.\n */\nexport function createFilterStats(): FilterStats {\n return {\n total: 0,\n filtered: 0,\n passed: 0,\n byReason: {},\n }\n}\n\n/**\n * Update filter stats with a result.\n */\nexport function updateFilterStats(\n stats: FilterStats,\n result: FilterResult\n): void {\n stats.total++\n if (result.filtered) {\n stats.filtered++\n if (result.reason) {\n stats.byReason[result.reason] = (stats.byReason[result.reason] ?? 0) + 1\n }\n } else {\n stats.passed++\n }\n}\n\n/**\n * Format filter stats for display.\n */\nexport function formatFilterStats(stats: FilterStats): string {\n const lines = [\n `📊 Filter Statistics:`,\n ` Total processed: ${stats.total}`,\n ` Filtered out: ${stats.filtered} (${((stats.filtered / stats.total) * 100).toFixed(1)}%)`,\n ` Passed: ${stats.passed} (${((stats.passed / stats.total) * 100).toFixed(1)}%)`,\n ]\n\n if (Object.keys(stats.byReason).length > 0) {\n lines.push(` By reason:`)\n for (const [reason, count] of Object.entries(stats.byReason).sort(\n (a, b) => b[1] - a[1]\n )) {\n lines.push(` - ${reason}: ${count}`)\n }\n }\n\n return lines.join('\\n')\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createTool
|
|
3
|
-
} from "./chunk-F4EM72IH.js";
|
|
4
1
|
import {
|
|
5
2
|
Redis2,
|
|
6
3
|
traceMemoryCite,
|
|
@@ -21,6 +18,9 @@ import {
|
|
|
21
18
|
import {
|
|
22
19
|
queryVectors
|
|
23
20
|
} from "./chunk-H3D6VCME.js";
|
|
21
|
+
import {
|
|
22
|
+
createTool
|
|
23
|
+
} from "./chunk-F4EM72IH.js";
|
|
24
24
|
import {
|
|
25
25
|
__export,
|
|
26
26
|
init_esm_shims
|
|
@@ -22336,7 +22336,6 @@ export {
|
|
|
22336
22336
|
getDb,
|
|
22337
22337
|
closeDb,
|
|
22338
22338
|
database,
|
|
22339
|
-
IntegrationClient,
|
|
22340
22339
|
getApp,
|
|
22341
22340
|
getRedis,
|
|
22342
22341
|
getOutcomeHistory,
|
|
@@ -22345,4 +22344,4 @@ export {
|
|
|
22345
22344
|
DEFAULT_AGENT_MODEL,
|
|
22346
22345
|
runSupportAgent
|
|
22347
22346
|
};
|
|
22348
|
-
//# sourceMappingURL=chunk-
|
|
22347
|
+
//# sourceMappingURL=chunk-EGBS3VJI.js.map
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
SUPPORT_AGENT_PROMPT,
|
|
4
4
|
agentTools,
|
|
5
5
|
runSupportAgent
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-F4EM72IH.js";
|
|
6
|
+
} from "./chunk-EGBS3VJI.js";
|
|
8
7
|
import "./chunk-KEV3QKXP.js";
|
|
9
8
|
import "./chunk-MLNDSBZ4.js";
|
|
10
9
|
import "./chunk-ZNF7XD2S.js";
|
|
11
10
|
import "./chunk-H3D6VCME.js";
|
|
12
11
|
import "./chunk-MG37YDAK.js";
|
|
12
|
+
import "./chunk-F4EM72IH.js";
|
|
13
13
|
import "./chunk-WFANXVQG.js";
|
|
14
14
|
export {
|
|
15
15
|
DEFAULT_AGENT_MODEL,
|
|
@@ -17,4 +17,4 @@ export {
|
|
|
17
17
|
agentTools,
|
|
18
18
|
runSupportAgent
|
|
19
19
|
};
|
|
20
|
-
//# sourceMappingURL=config-
|
|
20
|
+
//# sourceMappingURL=config-HLXR42M4.js.map
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import {
|
|
2
|
+
shouldFilter
|
|
3
|
+
} from "./chunk-5ACOI6Z3.js";
|
|
4
|
+
import {
|
|
5
|
+
init_esm_shims
|
|
6
|
+
} from "./chunk-WFANXVQG.js";
|
|
7
|
+
|
|
8
|
+
// ../core/src/faq/duckdb-source.ts
|
|
9
|
+
init_esm_shims();
|
|
10
|
+
var INBOX_TO_APP = {
|
|
11
|
+
inb_1bwzr: "epic-react",
|
|
12
|
+
// KCD Support (Epic React is the main product)
|
|
13
|
+
inb_3srbb: "total-typescript",
|
|
14
|
+
inb_1c77r: "egghead",
|
|
15
|
+
inb_jqs11: "epic-ai",
|
|
16
|
+
inb_3pqh3: "pro-tailwind",
|
|
17
|
+
inb_2odqf: "just-javascript",
|
|
18
|
+
inb_4bj7r: "ai-hero",
|
|
19
|
+
inb_3bkef: "testing-accessibility",
|
|
20
|
+
inb_jqs2t: "epic-web",
|
|
21
|
+
inb_1zh3b: "egghead",
|
|
22
|
+
inb_43olj: "pro-nextjs"
|
|
23
|
+
};
|
|
24
|
+
var APP_TO_INBOX = Object.entries(
|
|
25
|
+
INBOX_TO_APP
|
|
26
|
+
).reduce(
|
|
27
|
+
(acc, [inboxId, appSlug]) => {
|
|
28
|
+
if (!acc[appSlug]) {
|
|
29
|
+
acc[appSlug] = [];
|
|
30
|
+
}
|
|
31
|
+
acc[appSlug].push(inboxId);
|
|
32
|
+
return acc;
|
|
33
|
+
},
|
|
34
|
+
{}
|
|
35
|
+
);
|
|
36
|
+
function toFrontMessage(row) {
|
|
37
|
+
const createdAt = toJSDate(row.created_at);
|
|
38
|
+
const createdAtTimestamp = createdAt ? Math.floor(createdAt.getTime() / 1e3) : 0;
|
|
39
|
+
return {
|
|
40
|
+
id: row.id,
|
|
41
|
+
type: "email",
|
|
42
|
+
is_inbound: row.is_inbound,
|
|
43
|
+
is_draft: false,
|
|
44
|
+
error_type: null,
|
|
45
|
+
version: null,
|
|
46
|
+
created_at: createdAtTimestamp,
|
|
47
|
+
subject: "",
|
|
48
|
+
blurb: row.body_text?.slice(0, 100) ?? "",
|
|
49
|
+
body: row.body_html ?? row.body_text ?? "",
|
|
50
|
+
text: row.body_text ?? "",
|
|
51
|
+
author: row.author_email ? {
|
|
52
|
+
id: row.author_email,
|
|
53
|
+
email: row.author_email,
|
|
54
|
+
first_name: row.author_name?.split(" ")[0] ?? "",
|
|
55
|
+
last_name: row.author_name?.split(" ").slice(1).join(" ") ?? "",
|
|
56
|
+
is_admin: !row.is_inbound,
|
|
57
|
+
is_available: true,
|
|
58
|
+
is_blocked: false
|
|
59
|
+
} : null,
|
|
60
|
+
recipients: [],
|
|
61
|
+
attachments: [],
|
|
62
|
+
metadata: {},
|
|
63
|
+
_links: {
|
|
64
|
+
self: `https://api.frontapp.com/messages/${row.id}`,
|
|
65
|
+
related: {
|
|
66
|
+
conversation: `https://api.frontapp.com/conversations/${row.conversation_id}`
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function toFrontConversation(row) {
|
|
72
|
+
const createdAt = toJSDate(row.created_at);
|
|
73
|
+
const createdAtTimestamp = createdAt ? Math.floor(createdAt.getTime() / 1e3) : 0;
|
|
74
|
+
return {
|
|
75
|
+
id: row.id,
|
|
76
|
+
subject: row.subject ?? "",
|
|
77
|
+
status: row.status === "archived" ? "archived" : row.status,
|
|
78
|
+
assignee: row.assignee_email ? {
|
|
79
|
+
id: row.assignee_email,
|
|
80
|
+
email: row.assignee_email,
|
|
81
|
+
first_name: "",
|
|
82
|
+
last_name: ""
|
|
83
|
+
} : null,
|
|
84
|
+
recipient: row.customer_email ? {
|
|
85
|
+
handle: row.customer_email,
|
|
86
|
+
role: "from"
|
|
87
|
+
} : null,
|
|
88
|
+
tags: (row.tags ?? []).map((tag) => ({
|
|
89
|
+
id: tag,
|
|
90
|
+
name: tag
|
|
91
|
+
})),
|
|
92
|
+
links: [],
|
|
93
|
+
created_at: createdAtTimestamp,
|
|
94
|
+
is_private: false,
|
|
95
|
+
scheduled_reminders: [],
|
|
96
|
+
metadata: {},
|
|
97
|
+
_links: {
|
|
98
|
+
self: `https://api.frontapp.com/conversations/${row.id}`,
|
|
99
|
+
related: {
|
|
100
|
+
events: "",
|
|
101
|
+
followers: "",
|
|
102
|
+
messages: "",
|
|
103
|
+
comments: "",
|
|
104
|
+
inboxes: ""
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function extractQuestion(messages) {
|
|
110
|
+
const sorted = [...messages].sort((a, b) => a.created_at - b.created_at);
|
|
111
|
+
const firstInbound = sorted.find((m) => m.is_inbound);
|
|
112
|
+
if (!firstInbound) {
|
|
113
|
+
return "";
|
|
114
|
+
}
|
|
115
|
+
const text = firstInbound.text ?? firstInbound.body?.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim() ?? "";
|
|
116
|
+
return text;
|
|
117
|
+
}
|
|
118
|
+
function extractAnswer(messages) {
|
|
119
|
+
const sorted = [...messages].sort((a, b) => b.created_at - a.created_at);
|
|
120
|
+
const lastOutbound = sorted.find((m) => !m.is_inbound);
|
|
121
|
+
if (!lastOutbound) {
|
|
122
|
+
return "";
|
|
123
|
+
}
|
|
124
|
+
return lastOutbound.text ?? lastOutbound.body?.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim() ?? "";
|
|
125
|
+
}
|
|
126
|
+
function isSpamOrNoise(text, senderEmail) {
|
|
127
|
+
if (text.trim().length < 20) return true;
|
|
128
|
+
const result = shouldFilter(text, senderEmail);
|
|
129
|
+
return result.filtered;
|
|
130
|
+
}
|
|
131
|
+
function toJSDate(value) {
|
|
132
|
+
if (value === null || value === void 0) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
if (value instanceof Date) {
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
if (typeof value === "object" && "micros" in value) {
|
|
139
|
+
const ms = Number(value.micros) / 1e3;
|
|
140
|
+
return new Date(ms);
|
|
141
|
+
}
|
|
142
|
+
if (typeof value === "number") {
|
|
143
|
+
if (value < 1e9) {
|
|
144
|
+
return new Date(value * 1e3);
|
|
145
|
+
}
|
|
146
|
+
return new Date(value);
|
|
147
|
+
}
|
|
148
|
+
if (typeof value === "string") {
|
|
149
|
+
return new Date(value);
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
async function createDuckDBSource(config) {
|
|
154
|
+
const { DuckDBInstance } = await import("./lib-R6DEEJCP.js");
|
|
155
|
+
const instance = await DuckDBInstance.create(
|
|
156
|
+
config.dbPath
|
|
157
|
+
);
|
|
158
|
+
const connection = await instance.connect();
|
|
159
|
+
const statusFilter = config.statusFilter ?? ["archived"];
|
|
160
|
+
return {
|
|
161
|
+
name: "duckdb-cache",
|
|
162
|
+
async getConversations(options) {
|
|
163
|
+
const { appId, since, limit = 500 } = options;
|
|
164
|
+
let inboxIds = config.inboxIds;
|
|
165
|
+
if (!inboxIds && appId) {
|
|
166
|
+
inboxIds = APP_TO_INBOX[appId];
|
|
167
|
+
if (!inboxIds || inboxIds.length === 0) {
|
|
168
|
+
console.warn(`No inbox mapping found for app: ${appId}`);
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const whereClauses = [];
|
|
173
|
+
const params = {};
|
|
174
|
+
if (statusFilter.length > 0) {
|
|
175
|
+
whereClauses.push(
|
|
176
|
+
`status IN (${statusFilter.map((_, i) => `$status_${i}`).join(", ")})`
|
|
177
|
+
);
|
|
178
|
+
statusFilter.forEach((s, i) => {
|
|
179
|
+
params[`status_${i}`] = s;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (inboxIds && inboxIds.length > 0) {
|
|
183
|
+
whereClauses.push(
|
|
184
|
+
`inbox_id IN (${inboxIds.map((_, i) => `$inbox_${i}`).join(", ")})`
|
|
185
|
+
);
|
|
186
|
+
inboxIds.forEach((id, i) => {
|
|
187
|
+
params[`inbox_${i}`] = id;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (since) {
|
|
191
|
+
whereClauses.push(`last_message_at >= $since::TIMESTAMP`);
|
|
192
|
+
params.since = since.toISOString();
|
|
193
|
+
}
|
|
194
|
+
if (config.minThreadLength !== void 0) {
|
|
195
|
+
whereClauses.push(`thread_depth >= $minThreadLength`);
|
|
196
|
+
params.minThreadLength = config.minThreadLength;
|
|
197
|
+
}
|
|
198
|
+
if (config.maxThreadLength !== void 0) {
|
|
199
|
+
whereClauses.push(`thread_depth <= $maxThreadLength`);
|
|
200
|
+
params.maxThreadLength = config.maxThreadLength;
|
|
201
|
+
}
|
|
202
|
+
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
203
|
+
const query = `
|
|
204
|
+
SELECT *
|
|
205
|
+
FROM conversations
|
|
206
|
+
${whereClause}
|
|
207
|
+
ORDER BY last_message_at DESC
|
|
208
|
+
LIMIT $limit
|
|
209
|
+
`;
|
|
210
|
+
params.limit = limit;
|
|
211
|
+
const reader = await connection.runAndReadAll(query, params);
|
|
212
|
+
const rows = reader.getRowObjectsJS();
|
|
213
|
+
console.log(`DuckDB: Found ${rows.length} conversations matching filters`);
|
|
214
|
+
const results = [];
|
|
215
|
+
let processed = 0;
|
|
216
|
+
for (const row of rows) {
|
|
217
|
+
processed++;
|
|
218
|
+
if (processed % 100 === 0) {
|
|
219
|
+
console.log(`Processing ${processed}/${rows.length}...`);
|
|
220
|
+
}
|
|
221
|
+
const messagesReader = await connection.runAndReadAll(
|
|
222
|
+
`SELECT * FROM messages WHERE conversation_id = $convId ORDER BY created_at ASC`,
|
|
223
|
+
{ convId: row.id }
|
|
224
|
+
);
|
|
225
|
+
const messageRows = messagesReader.getRowObjectsJS();
|
|
226
|
+
if (messageRows.length === 0) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const messages = messageRows.map(toFrontMessage);
|
|
230
|
+
const conversation = toFrontConversation(row);
|
|
231
|
+
const question = extractQuestion(messages);
|
|
232
|
+
const answer = extractAnswer(messages);
|
|
233
|
+
if (!question || !answer) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const firstInbound = messages.find((m) => m.is_inbound);
|
|
237
|
+
const senderEmail = firstInbound?.author?.email;
|
|
238
|
+
if (isSpamOrNoise(question, senderEmail)) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const answerFilter = shouldFilter(answer);
|
|
242
|
+
if (answerFilter.filtered && answerFilter.reason === "auto_reply") {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const tagNames = (row.tags ?? []).map((t) => t.toLowerCase());
|
|
246
|
+
if (tagNames.includes("spam") || tagNames.includes("collaboration")) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const resolvedAppId = appId ?? INBOX_TO_APP[row.inbox_id] ?? "unknown";
|
|
250
|
+
results.push({
|
|
251
|
+
conversationId: row.id,
|
|
252
|
+
question,
|
|
253
|
+
answer,
|
|
254
|
+
subject: row.subject ?? "",
|
|
255
|
+
resolvedAt: toJSDate(row.last_message_at) ?? /* @__PURE__ */ new Date(),
|
|
256
|
+
appId: resolvedAppId,
|
|
257
|
+
wasUnchanged: false,
|
|
258
|
+
// Unknown from cache
|
|
259
|
+
draftSimilarity: void 0,
|
|
260
|
+
tags: row.tags ?? [],
|
|
261
|
+
_raw: {
|
|
262
|
+
conversation,
|
|
263
|
+
messages
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
console.log(
|
|
268
|
+
`DuckDB: Extracted ${results.length} conversations with Q&A pairs`
|
|
269
|
+
);
|
|
270
|
+
return results;
|
|
271
|
+
},
|
|
272
|
+
async getMessages(conversationId) {
|
|
273
|
+
const reader = await connection.runAndReadAll(
|
|
274
|
+
`SELECT * FROM messages WHERE conversation_id = $convId ORDER BY created_at ASC`,
|
|
275
|
+
{ convId: conversationId }
|
|
276
|
+
);
|
|
277
|
+
const rows = reader.getRowObjectsJS();
|
|
278
|
+
return rows.map(toFrontMessage);
|
|
279
|
+
},
|
|
280
|
+
async getStats() {
|
|
281
|
+
const totalReader = await connection.runAndReadAll(
|
|
282
|
+
`SELECT COUNT(*) as count FROM conversations`
|
|
283
|
+
);
|
|
284
|
+
const totalRows = totalReader.getRowObjectsJS();
|
|
285
|
+
const totalConversations = Number(totalRows[0]?.count ?? 0);
|
|
286
|
+
const filterClauses = [];
|
|
287
|
+
const filterParams = {};
|
|
288
|
+
if (statusFilter.length > 0) {
|
|
289
|
+
filterClauses.push(
|
|
290
|
+
`status IN (${statusFilter.map((_, i) => `$status_${i}`).join(", ")})`
|
|
291
|
+
);
|
|
292
|
+
statusFilter.forEach((s, i) => {
|
|
293
|
+
filterParams[`status_${i}`] = s;
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
if (config.inboxIds && config.inboxIds.length > 0) {
|
|
297
|
+
filterClauses.push(
|
|
298
|
+
`inbox_id IN (${config.inboxIds.map((_, i) => `$inbox_${i}`).join(", ")})`
|
|
299
|
+
);
|
|
300
|
+
config.inboxIds.forEach((id, i) => {
|
|
301
|
+
filterParams[`inbox_${i}`] = id;
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
const filterWhere = filterClauses.length > 0 ? `WHERE ${filterClauses.join(" AND ")}` : "";
|
|
305
|
+
const filteredReader = await connection.runAndReadAll(
|
|
306
|
+
`SELECT COUNT(*) as count FROM conversations ${filterWhere}`,
|
|
307
|
+
filterParams
|
|
308
|
+
);
|
|
309
|
+
const filteredRows = filteredReader.getRowObjectsJS();
|
|
310
|
+
const filteredConversations = Number(filteredRows[0]?.count ?? 0);
|
|
311
|
+
const messagesReader = await connection.runAndReadAll(
|
|
312
|
+
`SELECT COUNT(*) as count FROM messages`
|
|
313
|
+
);
|
|
314
|
+
const messagesRows = messagesReader.getRowObjectsJS();
|
|
315
|
+
const totalMessages = Number(messagesRows[0]?.count ?? 0);
|
|
316
|
+
const inboxReader = await connection.runAndReadAll(
|
|
317
|
+
`SELECT COUNT(DISTINCT inbox_id) as count FROM conversations`
|
|
318
|
+
);
|
|
319
|
+
const inboxRows = inboxReader.getRowObjectsJS();
|
|
320
|
+
const inboxCount = Number(inboxRows[0]?.count ?? 0);
|
|
321
|
+
const dateReader = await connection.runAndReadAll(
|
|
322
|
+
`SELECT MIN(last_message_at) as oldest, MAX(last_message_at) as newest FROM conversations`
|
|
323
|
+
);
|
|
324
|
+
const dateRows = dateReader.getRowObjectsJS();
|
|
325
|
+
const dateRange = {
|
|
326
|
+
oldest: toJSDate(dateRows[0]?.oldest),
|
|
327
|
+
newest: toJSDate(dateRows[0]?.newest)
|
|
328
|
+
};
|
|
329
|
+
return {
|
|
330
|
+
totalConversations,
|
|
331
|
+
filteredConversations,
|
|
332
|
+
totalMessages,
|
|
333
|
+
inboxCount,
|
|
334
|
+
dateRange
|
|
335
|
+
};
|
|
336
|
+
},
|
|
337
|
+
async close() {
|
|
338
|
+
connection.closeSync();
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function getInboxIdsForApp(appSlug) {
|
|
343
|
+
return APP_TO_INBOX[appSlug];
|
|
344
|
+
}
|
|
345
|
+
function getAppForInbox(inboxId) {
|
|
346
|
+
return INBOX_TO_APP[inboxId];
|
|
347
|
+
}
|
|
348
|
+
export {
|
|
349
|
+
createDuckDBSource,
|
|
350
|
+
getAppForInbox,
|
|
351
|
+
getInboxIdsForApp
|
|
352
|
+
};
|
|
353
|
+
//# sourceMappingURL=duckdb-source-T7EJUOC7.js.map
|