@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.
@@ -1,3 +1,3 @@
1
- {"at":"2026-05-18T16:46:10.788Z","type":"reindex","tenantId":"tenant_a","files":0}
2
- {"at":"2026-05-18T16:46:10.831Z","type":"write","tenantId":"tenant_a","path":"support/tickets/ticket_1.json"}
3
- {"at":"2026-05-18T16:46:10.908Z","type":"reindex","tenantId":"tenant_a","files":0}
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":"support_ticket_ticket_1_mpbfrfrw","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":"c866b242070e6b1d8dd5d449a1026391d4ba6b1f6616c45a9b6dbc219c4eff14","tenantId":"tenant_a","at":"2026-05-18T16:46:10.858Z","summary":"content_write touched 1 path(s)","objectHashes":["e79f9ed0f7be6b3229b5eff19a9f5e242fb2575fe56c38dfdda06bf56d882706"]}
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-18T16:46:10.867Z","type":"ticket_decision","tenantId":"tenant_a","ticketId":"ticket_1","action":"auto-answer","eventId":"c866b242070e6b1d8dd5d449a1026391d4ba6b1f6616c45a9b6dbc219c4eff14"}
1
+ {"at":"2026-05-19T08:15:25.043Z","type":"ticket_decision","tenantId":"tenant_a","ticketId":"ticket_1","action":"auto-answer","eventId":"85337bfc5848725ef392012dc2a6e07585d8bcb6e352e97b5abbae05c60cdd71"}
@@ -1,5 +1,5 @@
1
1
 
2
- > @nebutra/support-deflector@0.1.1 build /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/support-deflector
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
  CLI Building entry: src/cli.ts, src/index.ts
@@ -11,12 +11,12 @@
11
11
  ESM Build start
12
12
  ESM dist/cli.js 1.65 KB
13
13
  ESM dist/index.js 248.00 B
14
- ESM dist/chunk-OEGPSIYO.js 5.97 KB
14
+ ESM dist/chunk-EBB2GRYR.js 5.97 KB
15
15
  ESM dist/cli.js.map 2.80 KB
16
16
  ESM dist/index.js.map 71.00 B
17
- ESM dist/chunk-OEGPSIYO.js.map 13.29 KB
18
- ESM ⚡️ Build success in 88ms
17
+ ESM dist/chunk-EBB2GRYR.js.map 13.22 KB
18
+ ESM ⚡️ Build success in 89ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 7575ms
20
+ DTS ⚡️ Build success in 9998ms
21
21
  DTS dist/cli.d.ts 13.00 B
22
22
  DTS dist/index.d.ts 3.08 KB
@@ -1,16 +1,15 @@
1
1
 
2
- > @nebutra/support-deflector@0.1.1 test /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/support-deflector
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
   RUN  v4.1.4 /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/support-deflector
7
7
 
8
- (node:11843) ExperimentalWarning: SQLite is an experimental feature and might change at any time
9
- (Use `node --trace-warnings ...` to show where the warning was created)
10
- ✓ src/index.test.ts (5 tests) 287ms
8
+ ✓ src/index.test.ts (5 tests) 517ms
9
+ ✓ runs ticket_triage and records the decision  318ms
11
10
 
12
11
   Test Files  1 passed (1)
13
12
   Tests  5 passed (5)
14
-  Start at  16:46:08
15
-  Duration  2.64s (transform 1.06s, setup 0ms, import 1.36s, tests 287ms, environment 0ms)
13
+  Start at  08:15:22
14
+  Duration  3.08s (transform 1.25s, setup 0ms, import 1.68s, tests 517ms, environment 0ms)
16
15
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @nebutra/support-deflector@0.1.1 typecheck /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/support-deflector
2
+ > @nebutra/support-deflector@0.1.2 typecheck /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/support-deflector
3
3
  > tsc --noEmit
4
4
 
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 = requireTenant(input.ticket.tenantId, this.#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-OEGPSIYO.js.map
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
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SupportDeflector,
3
3
  readSupportDeflectorDebug
4
- } from "./chunk-OEGPSIYO.js";
4
+ } from "./chunk-EBB2GRYR.js";
5
5
 
6
6
  // src/cli.ts
7
7
  var command = process.argv[2] ?? "doctor";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  classifyTicket,
4
4
  decideTicket,
5
5
  readSupportDeflectorDebug
6
- } from "./chunk-OEGPSIYO.js";
6
+ } from "./chunk-EBB2GRYR.js";
7
7
  export {
8
8
  SupportDeflector,
9
9
  classifyTicket,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nebutra/support-deflector",
3
- "version": "0.1.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.0",
33
- "@nebutra/content-store": "0.1.0",
34
- "@nebutra/errors": "0.1.0",
35
- "@nebutra/event-log": "0.1.0",
36
- "@nebutra/generation-context": "0.1.0",
37
- "@nebutra/play-loader": "0.1.0"
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 = requireTenant(input.ticket.tenantId, this.#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":[]}