@nebutra/support-deflector 0.1.1 → 0.1.2
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/.nebutra/debug/content-store.jsonl +3 -3
- package/.nebutra/debug/event-log.jsonl +1 -1
- package/.nebutra/debug/support-deflector.jsonl +1 -1
- package/.turbo/turbo-build.log +5 -5
- package/.turbo/turbo-test.log +5 -6
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +14 -0
- package/dist/{chunk-OEGPSIYO.js → chunk-EBB2GRYR.js} +10 -12
- package/dist/chunk-EBB2GRYR.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +7 -7
- package/src/index.ts +10 -12
- package/dist/chunk-OEGPSIYO.js.map +0 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
{"at":"2026-05-
|
|
2
|
-
{"at":"2026-05-
|
|
3
|
-
{"at":"2026-05-
|
|
1
|
+
{"at":"2026-05-19T08:15:24.961Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
2
|
+
{"at":"2026-05-19T08:15:25.012Z","type":"write","tenantId":"tenant_a","path":"support/tickets/ticket_1.json"}
|
|
3
|
+
{"at":"2026-05-19T08:15:25.135Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"traceId":"
|
|
1
|
+
{"at":"2026-05-19T08:15:25.029Z","traceId":"support_ticket_ticket_1_mpccyfyt","kind":"content_write","affected":["support/tickets/ticket_1.json"],"parent":null,"snapshot":{"support/tickets/ticket_1.json":"{\n \"ticket\": {\n \"id\": \"ticket_1\",\n \"tenantId\": \"tenant_a\",\n \"customer\": {\n \"id\": \"customer_1\",\n \"email\": \"a@example.com\",\n \"plan\": \"free\"\n },\n \"subject\": \"How do refunds work?\",\n \"body\": \"Can I get a refund if I cancel this week?\"\n },\n \"decision\": {\n \"play\": \"ticket_triage\",\n \"action\": \"auto-answer\",\n \"confidence\": 0.9,\n \"classification\": {\n \"category\": \"billing\",\n \"sentiment\": \"neutral\",\n \"highValue\": false,\n \"complaint\": false\n },\n \"reply\": {\n \"subject\": \"Re: How do refunds work?\",\n \"body\": \"Refunds are available within 14 days. If you want, reply here and we will help with the next step.\",\n \"citations\": [\n \"kb_refund\"\n ]\n }\n }\n}\n"},"id":"85337bfc5848725ef392012dc2a6e07585d8bcb6e352e97b5abbae05c60cdd71","tenantId":"tenant_a","summary":"content_write touched 1 path(s)","objectHashes":["e79f9ed0f7be6b3229b5eff19a9f5e242fb2575fe56c38dfdda06bf56d882706"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"at":"2026-05-
|
|
1
|
+
{"at":"2026-05-19T08:15:25.043Z","type":"ticket_decision","tenantId":"tenant_a","ticketId":"ticket_1","action":"auto-answer","eventId":"85337bfc5848725ef392012dc2a6e07585d8bcb6e352e97b5abbae05c60cdd71"}
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @nebutra/support-deflector@0.1.
|
|
2
|
+
> @nebutra/support-deflector@0.1.2 build /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/support-deflector
|
|
3
3
|
> tsup
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[32mESM[39m [1mdist/cli.js [22m[32m1.65 KB[39m
|
|
13
13
|
[32mESM[39m [1mdist/index.js [22m[32m248.00 B[39m
|
|
14
|
-
[32mESM[39m [1mdist/chunk-
|
|
14
|
+
[32mESM[39m [1mdist/chunk-EBB2GRYR.js [22m[32m5.97 KB[39m
|
|
15
15
|
[32mESM[39m [1mdist/cli.js.map [22m[32m2.80 KB[39m
|
|
16
16
|
[32mESM[39m [1mdist/index.js.map [22m[32m71.00 B[39m
|
|
17
|
-
[32mESM[39m [1mdist/chunk-
|
|
18
|
-
[32mESM[39m ⚡️ Build success in
|
|
17
|
+
[32mESM[39m [1mdist/chunk-EBB2GRYR.js.map [22m[32m13.22 KB[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 89ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 9998ms
|
|
21
21
|
[32mDTS[39m [1mdist/cli.d.ts [22m[32m13.00 B[39m
|
|
22
22
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m3.08 KB[39m
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
|
|
2
|
-
> @nebutra/support-deflector@0.1.
|
|
2
|
+
> @nebutra/support-deflector@0.1.2 test /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/support-deflector
|
|
3
3
|
> vitest run
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
[1m[30m[46m RUN [49m[39m[22m [36mv4.1.4 [39m[90m/home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/support-deflector[39m
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
[32m✓[39m src/index.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 287[2mms[22m[39m
|
|
8
|
+
[32m✓[39m src/index.test.ts [2m([22m[2m5 tests[22m[2m)[22m[33m 517[2mms[22m[39m
|
|
9
|
+
[33m[2m✓[22m[39m runs ticket_triage and records the decision [33m 318[2mms[22m[39m
|
|
11
10
|
|
|
12
11
|
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
13
12
|
[2m Tests [22m [1m[32m5 passed[39m[22m[90m (5)[39m
|
|
14
|
-
[2m Start at [22m
|
|
15
|
-
[2m Duration [22m
|
|
13
|
+
[2m Start at [22m 08:15:22
|
|
14
|
+
[2m Duration [22m 3.08s[2m (transform 1.25s, setup 0ms, import 1.68s, tests 517ms, environment 0ms)[22m
|
|
16
15
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @nebutra/support-deflector
|
|
2
2
|
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Publish registry package metadata under the MIT license.
|
|
8
|
+
|
|
9
|
+
- Updated dependencies []:
|
|
10
|
+
- @nebutra/capability-kit@0.2.1
|
|
11
|
+
- @nebutra/content-store@0.1.1
|
|
12
|
+
- @nebutra/errors@0.1.1
|
|
13
|
+
- @nebutra/event-log@0.1.1
|
|
14
|
+
- @nebutra/generation-context@0.1.1
|
|
15
|
+
- @nebutra/play-loader@0.1.1
|
|
16
|
+
|
|
3
17
|
## 0.1.1
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { mkdir } from "fs/promises";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
|
+
import { requireCapabilityTenant } from "@nebutra/capability-kit";
|
|
4
5
|
import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
|
|
5
6
|
import { ContentStore } from "@nebutra/content-store";
|
|
6
7
|
import { CapabilityError } from "@nebutra/errors";
|
|
7
8
|
import { EventLog } from "@nebutra/event-log";
|
|
8
9
|
import { assetId } from "@nebutra/generation-context";
|
|
9
|
-
function requireTenant(explicit, fallback) {
|
|
10
|
-
const tenantId = explicit ?? fallback;
|
|
11
|
-
if (!tenantId?.trim()) {
|
|
12
|
-
throw new CapabilityError("support-deflector", "Support Deflector requires tenant context", {
|
|
13
|
-
suggestion: "Pass tenantId with the ticket or construct SupportDeflector with tenantId.",
|
|
14
|
-
statusCode: 400
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
return tenantId;
|
|
18
|
-
}
|
|
19
10
|
function includesAny(text, terms) {
|
|
20
11
|
return terms.some((term) => text.includes(term));
|
|
21
12
|
}
|
|
@@ -99,7 +90,14 @@ var SupportDeflector = class _SupportDeflector {
|
|
|
99
90
|
return new _SupportDeflector({ ...options, tenantId, root, contentStore, eventLog });
|
|
100
91
|
}
|
|
101
92
|
async handleTicket(input) {
|
|
102
|
-
const tenantId =
|
|
93
|
+
const tenantId = requireCapabilityTenant({
|
|
94
|
+
explicit: input.ticket.tenantId,
|
|
95
|
+
fallback: this.#tenantId,
|
|
96
|
+
onMissing: () => new CapabilityError("support-deflector", "Support Deflector requires tenant context", {
|
|
97
|
+
suggestion: "Pass tenantId with the ticket or construct SupportDeflector with tenantId.",
|
|
98
|
+
statusCode: 400
|
|
99
|
+
})
|
|
100
|
+
});
|
|
103
101
|
const decision = decideTicket(input.ticket, input.articles, input.policy);
|
|
104
102
|
const artifactPath = `support/tickets/${input.ticket.id}.json`;
|
|
105
103
|
const content = `${JSON.stringify({ ticket: input.ticket, decision }, null, 2)}
|
|
@@ -155,4 +153,4 @@ export {
|
|
|
155
153
|
SupportDeflector,
|
|
156
154
|
readSupportDeflectorDebug
|
|
157
155
|
};
|
|
158
|
-
//# sourceMappingURL=chunk-
|
|
156
|
+
//# sourceMappingURL=chunk-EBB2GRYR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { requireCapabilityTenant } from \"@nebutra/capability-kit\";\nimport { appendCapabilityDebug, readCapabilityDebug } from \"@nebutra/capability-kit/debug\";\nimport { ContentStore } from \"@nebutra/content-store\";\nimport { CapabilityError } from \"@nebutra/errors\";\nimport { EventLog } from \"@nebutra/event-log\";\nimport { assetId } from \"@nebutra/generation-context\";\n\nexport type TicketCategory = \"billing\" | \"bug\" | \"how_to\" | \"sales\" | \"other\";\nexport type TicketSentiment = \"neutral\" | \"angry\" | \"positive\";\nexport type SupportAction = \"auto-answer\" | \"suggest-answer\" | \"escalate\";\n\nexport interface SupportTicket {\n readonly id: string;\n readonly tenantId?: string;\n readonly customer: {\n readonly id: string;\n readonly email: string;\n readonly plan?: string;\n };\n readonly subject: string;\n readonly body: string;\n}\n\nexport interface KnowledgeArticle {\n readonly id: string;\n readonly title: string;\n readonly body: string;\n}\n\nexport interface TicketClassification {\n readonly category: TicketCategory;\n readonly sentiment: TicketSentiment;\n readonly highValue: boolean;\n readonly complaint: boolean;\n}\n\nexport interface SupportPolicy {\n readonly autoReplyThreshold: number;\n readonly escalateOnComplaint?: boolean;\n readonly escalateOnHighValueCustomer?: boolean;\n}\n\nexport interface SupportReply {\n readonly subject: string;\n readonly body: string;\n readonly citations: readonly string[];\n}\n\nexport interface SupportDecision {\n readonly play: \"ticket_triage\";\n readonly action: SupportAction;\n readonly confidence: number;\n readonly classification: TicketClassification;\n readonly reply?: SupportReply;\n readonly escalationSummary?: string;\n readonly eventId?: string;\n}\n\nexport interface HandleTicketInput {\n readonly ticket: SupportTicket;\n readonly articles: readonly KnowledgeArticle[];\n readonly policy: SupportPolicy;\n}\n\nexport interface SupportDoctorReport {\n readonly capability: \"support-deflector\";\n readonly ok: boolean;\n readonly checkedAt: string;\n readonly plays: readonly string[];\n readonly mode: \"confidence-gated\";\n readonly channels: readonly { readonly provider: string; readonly ok: boolean }[];\n}\n\nexport interface SupportDeflectorOptions {\n readonly tenantId?: string;\n readonly root?: string;\n readonly debugRoot?: string;\n readonly contentStore?: ContentStore;\n readonly eventLog?: EventLog;\n}\n\nfunction includesAny(text: string, terms: readonly string[]): boolean {\n return terms.some((term) => text.includes(term));\n}\n\nexport function classifyTicket(ticket: SupportTicket): TicketClassification {\n const text = `${ticket.subject} ${ticket.body}`.toLowerCase();\n const category: TicketCategory = includesAny(text, [\"bug\", \"broke\", \"error\", \"production\"])\n ? \"bug\"\n : includesAny(text, [\"refund\", \"invoice\", \"billing\", \"cancel\"])\n ? \"billing\"\n : includesAny(text, [\"how\", \"setup\", \"configure\"])\n ? \"how_to\"\n : includesAny(text, [\"pricing\", \"demo\", \"sales\"])\n ? \"sales\"\n : \"other\";\n const angry = includesAny(text, [\"angry\", \"furious\", \"broken\", \"terrible\", \"lawsuit\"]);\n const positive = includesAny(text, [\"thanks\", \"great\", \"love\"]);\n const highValue = ticket.customer.plan === \"enterprise\" || includesAny(text, [\"enterprise\"]);\n return {\n category,\n sentiment: angry ? \"angry\" : positive ? \"positive\" : \"neutral\",\n highValue,\n complaint: angry || includesAny(text, [\"complaint\", \"refund now\", \"unacceptable\"]),\n };\n}\n\nfunction matchArticle(\n ticket: SupportTicket,\n articles: readonly KnowledgeArticle[],\n): KnowledgeArticle | undefined {\n const text = `${ticket.subject} ${ticket.body}`.toLowerCase();\n return (\n articles.find((article) =>\n article.title\n .toLowerCase()\n .split(/\\s+/)\n .filter((term) => term.length > 3)\n .some((term) => text.includes(term)),\n ) ??\n articles.find((article) =>\n article.body\n .toLowerCase()\n .split(/\\W+/)\n .filter((term) => term.length > 6)\n .some((term) => text.includes(term)),\n )\n );\n}\n\nfunction synthesizeReply(ticket: SupportTicket, article: KnowledgeArticle): SupportReply {\n const firstSentence = article.body.split(\".\")[0]?.trim() ?? article.body;\n return {\n subject: `Re: ${ticket.subject}`,\n body: `${firstSentence}. If you want, reply here and we will help with the next step.`,\n citations: [article.id],\n };\n}\n\nexport function decideTicket(\n ticket: SupportTicket,\n articles: readonly KnowledgeArticle[],\n policy: SupportPolicy,\n): SupportDecision {\n const classification = classifyTicket(ticket);\n const article = matchArticle(ticket, articles);\n const confidence = article ? (classification.category === \"other\" ? 0.72 : 0.9) : 0.35;\n const shouldEscalate =\n (policy.escalateOnComplaint ?? true) && classification.complaint\n ? true\n : (policy.escalateOnHighValueCustomer ?? true) && classification.highValue;\n if (shouldEscalate) {\n return {\n play: \"ticket_triage\",\n action: \"escalate\",\n confidence,\n classification,\n ...(article ? { reply: synthesizeReply(ticket, article) } : {}),\n escalationSummary: `${ticket.customer.email} needs founder attention for ${classification.category}.`,\n };\n }\n if (article && confidence >= policy.autoReplyThreshold) {\n return {\n play: \"ticket_triage\",\n action: \"auto-answer\",\n confidence,\n classification,\n reply: synthesizeReply(ticket, article),\n };\n }\n return {\n play: \"ticket_triage\",\n action: \"suggest-answer\",\n confidence,\n classification,\n ...(article ? { reply: synthesizeReply(ticket, article) } : {}),\n };\n}\n\nexport class SupportDeflector {\n readonly #tenantId: string | undefined;\n readonly #debugRoot: string;\n readonly #contentStore: ContentStore;\n readonly #eventLog: EventLog;\n\n private constructor(\n options: SupportDeflectorOptions & { contentStore: ContentStore; eventLog: EventLog },\n ) {\n this.#tenantId = options.tenantId;\n this.#debugRoot = options.debugRoot ?? process.cwd();\n this.#contentStore = options.contentStore;\n this.#eventLog = options.eventLog;\n }\n\n static async open(\n root = \".nebutra/support-deflector\",\n options: Omit<SupportDeflectorOptions, \"root\" | \"contentStore\" | \"eventLog\"> = {},\n ): Promise<SupportDeflector> {\n const tenantId = options.tenantId ?? \"local\";\n await mkdir(root, { recursive: true });\n const contentStore = await ContentStore.open(join(root, \"content\"), { tenantId });\n const eventLog = await EventLog.open(join(root, \"event-log\"), { tenantId });\n return new SupportDeflector({ ...options, tenantId, root, contentStore, eventLog });\n }\n\n async handleTicket(input: HandleTicketInput): Promise<SupportDecision> {\n const tenantId = requireCapabilityTenant({\n explicit: input.ticket.tenantId,\n fallback: this.#tenantId,\n onMissing: () =>\n new CapabilityError(\"support-deflector\", \"Support Deflector requires tenant context\", {\n suggestion: \"Pass tenantId with the ticket or construct SupportDeflector with tenantId.\",\n statusCode: 400,\n }),\n });\n const decision = decideTicket(input.ticket, input.articles, input.policy);\n const artifactPath = `support/tickets/${input.ticket.id}.json`;\n const content = `${JSON.stringify({ ticket: input.ticket, decision }, null, 2)}\\n`;\n await this.#contentStore.write(artifactPath, content);\n const eventId = await this.#eventLog.commit({\n traceId: assetId(\"support_ticket\", input.ticket.id),\n kind: \"content_write\",\n affected: [artifactPath],\n parent: null,\n snapshot: { [artifactPath]: content },\n });\n await this.#debug({\n type: \"ticket_decision\",\n tenantId,\n ticketId: input.ticket.id,\n action: decision.action,\n eventId,\n });\n return { ...decision, eventId };\n }\n\n async doctor(): Promise<SupportDoctorReport> {\n return {\n capability: \"support-deflector\",\n ok: true,\n checkedAt: new Date().toISOString(),\n plays: [\"ticket_triage\"],\n mode: \"confidence-gated\",\n channels: [\n { provider: \"local-ticket\", ok: true },\n { provider: \"chatwoot-bridge\", ok: false },\n { provider: \"email-bridge\", ok: false },\n ],\n };\n }\n\n async close(): Promise<void> {\n await this.#contentStore.close();\n }\n\n async #debug(entry: Record<string, unknown>): Promise<void> {\n await mkdir(dirname(join(this.#debugRoot, \".nebutra\", \"debug\", \"support-deflector.jsonl\")), {\n recursive: true,\n });\n await appendCapabilityDebug(\"support-deflector\", entry, { root: this.#debugRoot });\n }\n}\n\nexport async function readSupportDeflectorDebug(\n root = process.cwd(),\n limit = 20,\n): Promise<unknown[]> {\n return readCapabilityDebug(\"support-deflector\", { root, limit });\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebutra/support-deflector",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Confidence-gated customer support Play package for founder-owned support automation",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
".": "./src/index.ts"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@nebutra/capability-kit": "0.2.
|
|
33
|
-
"@nebutra/content-store": "0.1.
|
|
34
|
-
"@nebutra/errors": "0.1.
|
|
35
|
-
"@nebutra/event-log": "0.1.
|
|
36
|
-
"@nebutra/generation-context": "0.1.
|
|
37
|
-
"@nebutra/play-loader": "0.1.
|
|
32
|
+
"@nebutra/capability-kit": "0.2.1",
|
|
33
|
+
"@nebutra/content-store": "0.1.1",
|
|
34
|
+
"@nebutra/errors": "0.1.1",
|
|
35
|
+
"@nebutra/event-log": "0.1.1",
|
|
36
|
+
"@nebutra/generation-context": "0.1.1",
|
|
37
|
+
"@nebutra/play-loader": "0.1.1"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^22.19.15",
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir } from "node:fs/promises";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
+
import { requireCapabilityTenant } from "@nebutra/capability-kit";
|
|
3
4
|
import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
|
|
4
5
|
import { ContentStore } from "@nebutra/content-store";
|
|
5
6
|
import { CapabilityError } from "@nebutra/errors";
|
|
@@ -80,17 +81,6 @@ export interface SupportDeflectorOptions {
|
|
|
80
81
|
readonly eventLog?: EventLog;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
function requireTenant(explicit: string | undefined, fallback: string | undefined): string {
|
|
84
|
-
const tenantId = explicit ?? fallback;
|
|
85
|
-
if (!tenantId?.trim()) {
|
|
86
|
-
throw new CapabilityError("support-deflector", "Support Deflector requires tenant context", {
|
|
87
|
-
suggestion: "Pass tenantId with the ticket or construct SupportDeflector with tenantId.",
|
|
88
|
-
statusCode: 400,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
return tenantId;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
84
|
function includesAny(text: string, terms: readonly string[]): boolean {
|
|
95
85
|
return terms.some((term) => text.includes(term));
|
|
96
86
|
}
|
|
@@ -216,7 +206,15 @@ export class SupportDeflector {
|
|
|
216
206
|
}
|
|
217
207
|
|
|
218
208
|
async handleTicket(input: HandleTicketInput): Promise<SupportDecision> {
|
|
219
|
-
const tenantId =
|
|
209
|
+
const tenantId = requireCapabilityTenant({
|
|
210
|
+
explicit: input.ticket.tenantId,
|
|
211
|
+
fallback: this.#tenantId,
|
|
212
|
+
onMissing: () =>
|
|
213
|
+
new CapabilityError("support-deflector", "Support Deflector requires tenant context", {
|
|
214
|
+
suggestion: "Pass tenantId with the ticket or construct SupportDeflector with tenantId.",
|
|
215
|
+
statusCode: 400,
|
|
216
|
+
}),
|
|
217
|
+
});
|
|
220
218
|
const decision = decideTicket(input.ticket, input.articles, input.policy);
|
|
221
219
|
const artifactPath = `support/tickets/${input.ticket.id}.json`;
|
|
222
220
|
const content = `${JSON.stringify({ ticket: input.ticket, decision }, null, 2)}\n`;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { appendCapabilityDebug, readCapabilityDebug } from \"@nebutra/capability-kit/debug\";\nimport { ContentStore } from \"@nebutra/content-store\";\nimport { CapabilityError } from \"@nebutra/errors\";\nimport { EventLog } from \"@nebutra/event-log\";\nimport { assetId } from \"@nebutra/generation-context\";\n\nexport type TicketCategory = \"billing\" | \"bug\" | \"how_to\" | \"sales\" | \"other\";\nexport type TicketSentiment = \"neutral\" | \"angry\" | \"positive\";\nexport type SupportAction = \"auto-answer\" | \"suggest-answer\" | \"escalate\";\n\nexport interface SupportTicket {\n readonly id: string;\n readonly tenantId?: string;\n readonly customer: {\n readonly id: string;\n readonly email: string;\n readonly plan?: string;\n };\n readonly subject: string;\n readonly body: string;\n}\n\nexport interface KnowledgeArticle {\n readonly id: string;\n readonly title: string;\n readonly body: string;\n}\n\nexport interface TicketClassification {\n readonly category: TicketCategory;\n readonly sentiment: TicketSentiment;\n readonly highValue: boolean;\n readonly complaint: boolean;\n}\n\nexport interface SupportPolicy {\n readonly autoReplyThreshold: number;\n readonly escalateOnComplaint?: boolean;\n readonly escalateOnHighValueCustomer?: boolean;\n}\n\nexport interface SupportReply {\n readonly subject: string;\n readonly body: string;\n readonly citations: readonly string[];\n}\n\nexport interface SupportDecision {\n readonly play: \"ticket_triage\";\n readonly action: SupportAction;\n readonly confidence: number;\n readonly classification: TicketClassification;\n readonly reply?: SupportReply;\n readonly escalationSummary?: string;\n readonly eventId?: string;\n}\n\nexport interface HandleTicketInput {\n readonly ticket: SupportTicket;\n readonly articles: readonly KnowledgeArticle[];\n readonly policy: SupportPolicy;\n}\n\nexport interface SupportDoctorReport {\n readonly capability: \"support-deflector\";\n readonly ok: boolean;\n readonly checkedAt: string;\n readonly plays: readonly string[];\n readonly mode: \"confidence-gated\";\n readonly channels: readonly { readonly provider: string; readonly ok: boolean }[];\n}\n\nexport interface SupportDeflectorOptions {\n readonly tenantId?: string;\n readonly root?: string;\n readonly debugRoot?: string;\n readonly contentStore?: ContentStore;\n readonly eventLog?: EventLog;\n}\n\nfunction requireTenant(explicit: string | undefined, fallback: string | undefined): string {\n const tenantId = explicit ?? fallback;\n if (!tenantId?.trim()) {\n throw new CapabilityError(\"support-deflector\", \"Support Deflector requires tenant context\", {\n suggestion: \"Pass tenantId with the ticket or construct SupportDeflector with tenantId.\",\n statusCode: 400,\n });\n }\n return tenantId;\n}\n\nfunction includesAny(text: string, terms: readonly string[]): boolean {\n return terms.some((term) => text.includes(term));\n}\n\nexport function classifyTicket(ticket: SupportTicket): TicketClassification {\n const text = `${ticket.subject} ${ticket.body}`.toLowerCase();\n const category: TicketCategory = includesAny(text, [\"bug\", \"broke\", \"error\", \"production\"])\n ? \"bug\"\n : includesAny(text, [\"refund\", \"invoice\", \"billing\", \"cancel\"])\n ? \"billing\"\n : includesAny(text, [\"how\", \"setup\", \"configure\"])\n ? \"how_to\"\n : includesAny(text, [\"pricing\", \"demo\", \"sales\"])\n ? \"sales\"\n : \"other\";\n const angry = includesAny(text, [\"angry\", \"furious\", \"broken\", \"terrible\", \"lawsuit\"]);\n const positive = includesAny(text, [\"thanks\", \"great\", \"love\"]);\n const highValue = ticket.customer.plan === \"enterprise\" || includesAny(text, [\"enterprise\"]);\n return {\n category,\n sentiment: angry ? \"angry\" : positive ? \"positive\" : \"neutral\",\n highValue,\n complaint: angry || includesAny(text, [\"complaint\", \"refund now\", \"unacceptable\"]),\n };\n}\n\nfunction matchArticle(\n ticket: SupportTicket,\n articles: readonly KnowledgeArticle[],\n): KnowledgeArticle | undefined {\n const text = `${ticket.subject} ${ticket.body}`.toLowerCase();\n return (\n articles.find((article) =>\n article.title\n .toLowerCase()\n .split(/\\s+/)\n .filter((term) => term.length > 3)\n .some((term) => text.includes(term)),\n ) ??\n articles.find((article) =>\n article.body\n .toLowerCase()\n .split(/\\W+/)\n .filter((term) => term.length > 6)\n .some((term) => text.includes(term)),\n )\n );\n}\n\nfunction synthesizeReply(ticket: SupportTicket, article: KnowledgeArticle): SupportReply {\n const firstSentence = article.body.split(\".\")[0]?.trim() ?? article.body;\n return {\n subject: `Re: ${ticket.subject}`,\n body: `${firstSentence}. If you want, reply here and we will help with the next step.`,\n citations: [article.id],\n };\n}\n\nexport function decideTicket(\n ticket: SupportTicket,\n articles: readonly KnowledgeArticle[],\n policy: SupportPolicy,\n): SupportDecision {\n const classification = classifyTicket(ticket);\n const article = matchArticle(ticket, articles);\n const confidence = article ? (classification.category === \"other\" ? 0.72 : 0.9) : 0.35;\n const shouldEscalate =\n (policy.escalateOnComplaint ?? true) && classification.complaint\n ? true\n : (policy.escalateOnHighValueCustomer ?? true) && classification.highValue;\n if (shouldEscalate) {\n return {\n play: \"ticket_triage\",\n action: \"escalate\",\n confidence,\n classification,\n ...(article ? { reply: synthesizeReply(ticket, article) } : {}),\n escalationSummary: `${ticket.customer.email} needs founder attention for ${classification.category}.`,\n };\n }\n if (article && confidence >= policy.autoReplyThreshold) {\n return {\n play: \"ticket_triage\",\n action: \"auto-answer\",\n confidence,\n classification,\n reply: synthesizeReply(ticket, article),\n };\n }\n return {\n play: \"ticket_triage\",\n action: \"suggest-answer\",\n confidence,\n classification,\n ...(article ? { reply: synthesizeReply(ticket, article) } : {}),\n };\n}\n\nexport class SupportDeflector {\n readonly #tenantId: string | undefined;\n readonly #debugRoot: string;\n readonly #contentStore: ContentStore;\n readonly #eventLog: EventLog;\n\n private constructor(\n options: SupportDeflectorOptions & { contentStore: ContentStore; eventLog: EventLog },\n ) {\n this.#tenantId = options.tenantId;\n this.#debugRoot = options.debugRoot ?? process.cwd();\n this.#contentStore = options.contentStore;\n this.#eventLog = options.eventLog;\n }\n\n static async open(\n root = \".nebutra/support-deflector\",\n options: Omit<SupportDeflectorOptions, \"root\" | \"contentStore\" | \"eventLog\"> = {},\n ): Promise<SupportDeflector> {\n const tenantId = options.tenantId ?? \"local\";\n await mkdir(root, { recursive: true });\n const contentStore = await ContentStore.open(join(root, \"content\"), { tenantId });\n const eventLog = await EventLog.open(join(root, \"event-log\"), { tenantId });\n return new SupportDeflector({ ...options, tenantId, root, contentStore, eventLog });\n }\n\n async handleTicket(input: HandleTicketInput): Promise<SupportDecision> {\n const tenantId = requireTenant(input.ticket.tenantId, this.#tenantId);\n const decision = decideTicket(input.ticket, input.articles, input.policy);\n const artifactPath = `support/tickets/${input.ticket.id}.json`;\n const content = `${JSON.stringify({ ticket: input.ticket, decision }, null, 2)}\\n`;\n await this.#contentStore.write(artifactPath, content);\n const eventId = await this.#eventLog.commit({\n traceId: assetId(\"support_ticket\", input.ticket.id),\n kind: \"content_write\",\n affected: [artifactPath],\n parent: null,\n snapshot: { [artifactPath]: content },\n });\n await this.#debug({\n type: \"ticket_decision\",\n tenantId,\n ticketId: input.ticket.id,\n action: decision.action,\n eventId,\n });\n return { ...decision, eventId };\n }\n\n async doctor(): Promise<SupportDoctorReport> {\n return {\n capability: \"support-deflector\",\n ok: true,\n checkedAt: new Date().toISOString(),\n plays: [\"ticket_triage\"],\n mode: \"confidence-gated\",\n channels: [\n { provider: \"local-ticket\", ok: true },\n { provider: \"chatwoot-bridge\", ok: false },\n { provider: \"email-bridge\", ok: false },\n ],\n };\n }\n\n async close(): Promise<void> {\n await this.#contentStore.close();\n }\n\n async #debug(entry: Record<string, unknown>): Promise<void> {\n await mkdir(dirname(join(this.#debugRoot, \".nebutra\", \"debug\", \"support-deflector.jsonl\")), {\n recursive: true,\n });\n await appendCapabilityDebug(\"support-deflector\", entry, { root: this.#debugRoot });\n }\n}\n\nexport async function readSupportDeflectorDebug(\n root = process.cwd(),\n limit = 20,\n): Promise<unknown[]> {\n return readCapabilityDebug(\"support-deflector\", { root, limit });\n}\n"],"mappings":"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","names":[]}
|