@pithy-sh/support 0.5.1 → 0.6.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/README.md +8 -0
- package/dist/audit/actions.d.ts +9 -0
- package/dist/audit/actions.d.ts.map +1 -1
- package/dist/audit/actions.js +9 -0
- package/dist/capability.d.ts +2 -1
- package/dist/capability.d.ts.map +1 -1
- package/dist/capability.js +30 -6
- package/dist/config/config.d.ts +63 -2
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +63 -5
- package/dist/data/assignment.d.ts +43 -0
- package/dist/data/assignment.d.ts.map +1 -0
- package/dist/data/assignment.js +38 -0
- package/dist/data/embedding.d.ts +21 -0
- package/dist/data/embedding.d.ts.map +1 -0
- package/dist/data/embedding.js +23 -0
- package/dist/data/tables.d.ts +28 -0
- package/dist/data/tables.d.ts.map +1 -1
- package/dist/data/tables.js +10 -2
- package/dist/data/thread.d.ts +9 -4
- package/dist/data/thread.d.ts.map +1 -1
- package/dist/data/thread.js +9 -4
- package/dist/http/guards.d.ts +32 -14
- package/dist/http/guards.d.ts.map +1 -1
- package/dist/http/guards.js +44 -16
- package/dist/http/handlers.d.ts +35 -2
- package/dist/http/handlers.d.ts.map +1 -1
- package/dist/http/handlers.js +81 -8
- package/dist/http/resolve.d.ts +8 -0
- package/dist/http/resolve.d.ts.map +1 -1
- package/dist/http/resolve.js +20 -1
- package/dist/http/responses.d.ts +355 -0
- package/dist/http/responses.d.ts.map +1 -1
- package/dist/http/responses.js +64 -2
- package/dist/http/routes.d.ts +25 -11
- package/dist/http/routes.d.ts.map +1 -1
- package/dist/http/routes.js +34 -5
- package/dist/http/schemas.d.ts +67 -3
- package/dist/http/schemas.d.ts.map +1 -1
- package/dist/http/schemas.js +44 -5
- package/dist/http/scopes.d.ts +10 -5
- package/dist/http/scopes.d.ts.map +1 -1
- package/dist/http/scopes.js +42 -6
- package/dist/http/views.d.ts +9 -1
- package/dist/http/views.d.ts.map +1 -1
- package/dist/http/views.js +40 -1
- package/dist/inbound/ingest.d.ts +1 -1
- package/dist/inbound/ingest.d.ts.map +1 -1
- package/dist/inbound/ingest.js +3 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/migrations/0002_assignment.d.ts +20 -0
- package/dist/migrations/0002_assignment.d.ts.map +1 -0
- package/dist/migrations/0002_assignment.js +45 -0
- package/dist/migrations/0003_embeddings.d.ts +15 -0
- package/dist/migrations/0003_embeddings.d.ts.map +1 -0
- package/dist/migrations/0003_embeddings.js +23 -0
- package/dist/migrations/set.d.ts +13 -0
- package/dist/migrations/set.d.ts.map +1 -0
- package/dist/migrations/set.js +21 -0
- package/dist/presence/room.d.ts +35 -0
- package/dist/presence/room.d.ts.map +1 -0
- package/dist/presence/room.js +66 -0
- package/dist/provision/provisionSupport.d.ts +32 -0
- package/dist/provision/provisionSupport.d.ts.map +1 -1
- package/dist/provision/provisionSupport.js +10 -2
- package/dist/provision/resolveSupportConfig.d.ts.map +1 -1
- package/dist/provision/resolveSupportConfig.js +13 -2
- package/dist/purge/contribution.d.ts +49 -0
- package/dist/purge/contribution.d.ts.map +1 -0
- package/dist/purge/contribution.js +164 -0
- package/dist/secret/registry.d.ts +26 -2
- package/dist/secret/registry.d.ts.map +1 -1
- package/dist/secret/registry.js +15 -3
- package/dist/semantic/documents.d.ts +21 -0
- package/dist/semantic/documents.d.ts.map +1 -0
- package/dist/semantic/documents.js +128 -0
- package/dist/semantic/embed.d.ts +59 -0
- package/dist/semantic/embed.d.ts.map +1 -0
- package/dist/semantic/embed.js +59 -0
- package/dist/semantic/messageIndex.d.ts +46 -0
- package/dist/semantic/messageIndex.d.ts.map +1 -0
- package/dist/semantic/messageIndex.js +67 -0
- package/dist/semantic/query.d.ts +37 -0
- package/dist/semantic/query.d.ts.map +1 -0
- package/dist/semantic/query.js +46 -0
- package/dist/staff/staff.d.ts +22 -0
- package/dist/staff/staff.d.ts.map +1 -0
- package/dist/staff/staff.js +64 -0
- package/dist/store/assignment.d.ts +63 -0
- package/dist/store/assignment.d.ts.map +1 -0
- package/dist/store/assignment.js +86 -0
- package/dist/store/hybrid.d.ts +134 -0
- package/dist/store/hybrid.d.ts.map +1 -0
- package/dist/store/hybrid.js +185 -0
- package/dist/store/paging.d.ts +10 -0
- package/dist/store/paging.d.ts.map +1 -1
- package/dist/store/paging.js +11 -1
- package/dist/store/provisionIndex.d.ts +21 -0
- package/dist/store/provisionIndex.d.ts.map +1 -0
- package/dist/store/provisionIndex.js +36 -0
- package/dist/store/search.d.ts +18 -9
- package/dist/store/search.d.ts.map +1 -1
- package/dist/store/search.js +36 -34
- package/dist/store/searchIndex.d.ts +4 -4
- package/dist/store/searchIndex.js +4 -4
- package/dist/store/threads.d.ts +174 -8
- package/dist/store/threads.d.ts.map +1 -1
- package/dist/store/threads.js +33 -40
- package/dist/submission/submit.d.ts +1 -1
- package/dist/submission/submit.d.ts.map +1 -1
- package/dist/submission/submit.js +2 -1
- package/dist/test-utils/seed.d.ts +33 -0
- package/dist/test-utils/seed.d.ts.map +1 -0
- package/dist/test-utils/seed.js +102 -0
- package/dist/test-utils/semantic.d.ts +21 -0
- package/dist/test-utils/semantic.d.ts.map +1 -0
- package/dist/test-utils/semantic.js +107 -0
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/workflows/specs.d.ts +23 -0
- package/dist/workflows/specs.d.ts.map +1 -1
- package/dist/workflows/specs.js +27 -7
- package/dist/workflows/worker.d.ts +15 -2
- package/dist/workflows/worker.d.ts.map +1 -1
- package/dist/workflows/worker.js +48 -4
- package/package.json +9 -7
- package/pithy.manifest.json +21 -3
- package/src/audit/actions.ts +9 -0
- package/src/capability.ts +43 -4
- package/src/config/config.ts +104 -5
- package/src/data/assignment.ts +55 -0
- package/src/data/embedding.ts +27 -0
- package/src/data/tables.ts +8 -0
- package/src/data/thread.ts +26 -4
- package/src/http/guards.ts +52 -15
- package/src/http/handlers.ts +125 -4
- package/src/http/resolve.ts +19 -0
- package/src/http/responses.ts +154 -0
- package/src/http/routes.ts +98 -12
- package/src/http/schemas.ts +117 -28
- package/src/http/scopes.ts +43 -6
- package/src/http/views.ts +44 -0
- package/src/inbound/ingest.ts +5 -2
- package/src/index.ts +5 -0
- package/src/migrations/0002_assignment.ts +65 -0
- package/src/migrations/0003_embeddings.ts +29 -0
- package/src/migrations/set.ts +21 -0
- package/src/presence/room.ts +94 -0
- package/src/provision/provisionSupport.ts +33 -1
- package/src/provision/resolveSupportConfig.ts +24 -13
- package/src/purge/contribution.ts +294 -0
- package/src/secret/registry.ts +17 -2
- package/src/semantic/documents.ts +178 -0
- package/src/semantic/embed.ts +99 -0
- package/src/semantic/messageIndex.ts +74 -0
- package/src/semantic/query.ts +66 -0
- package/src/staff/staff.ts +128 -0
- package/src/store/assignment.ts +179 -0
- package/src/store/hybrid.ts +314 -0
- package/src/store/paging.ts +14 -0
- package/src/store/provisionIndex.ts +46 -0
- package/src/store/search.ts +58 -37
- package/src/store/searchIndex.ts +4 -4
- package/src/store/threads.ts +91 -91
- package/src/submission/submit.ts +3 -2
- package/src/test-utils/seed.ts +141 -0
- package/src/test-utils/semantic.ts +110 -0
- package/src/version.generated.ts +1 -1
- package/src/workflows/specs.ts +34 -0
- package/src/workflows/worker.ts +84 -8
- package/src/workflows/wrangler.jsonc +8 -3
package/README.md
CHANGED
|
@@ -10,8 +10,16 @@ pithy add support
|
|
|
10
10
|
|
|
11
11
|
**Documentation: [pithy.sh/docs/capabilities/support](https://pithy.sh/docs/capabilities/support).** Overview, adding it, using it, and the reference: the MX constraint, provisioning, classification, provenance.
|
|
12
12
|
|
|
13
|
+
**A shared inbox.** Assign a conversation to somebody, filter the inbox by who has it, and see who else has a thread open before you reply: `POST /support/threads/:id/assign` behind `support:threads:assign`, `assignee=<ref>` or `assignee=none` on the listing, every handover kept in the thread's history, and one `support.thread` presence room per thread, with `viewing` and `replying`. Your own signed-in staff reach both through `support({ staff: { readPower, assignPower } })`, on unassignable roles other than the one ownership transfer confers, so only an operator can confer them. The seam is [`docs/CONTROL-PLANE.md`](../../docs/CONTROL-PLANE.md).
|
|
14
|
+
|
|
15
|
+
**Search, by words and by meaning.** `GET /support/threads/search` takes `q`, a `mode` (`keyword`, `semantic` or `hybrid`), a `limit` (20, at most 50) and the inbox list's filters, and returns ranked threads, each named by its best-matching message with that message's raw `bm25` and similarity. Keyword is FTS5 with `search.fts` on and the `LIKE` scan otherwise; hybrid fuses it with semantic search by rank, so a paraphrase is found and an order number still comes first. The inbox list no longer takes `q`. **Semantic search costs a second inference per inbound message on your own Workers AI binding**, on top of classification — roughly double the per-message AI cost — so `search: { semantic: true }` is off until you turn it on. `pithy support provision` creates the Vectorize index and backfills it once, and `pithy vector reprocess --index support-messages` re-embeds after a model change, with no `vector()` composed. Vectors carry message ids only. See [`docs/commands/support.md`](../../docs/commands/support.md#search).
|
|
16
|
+
|
|
13
17
|
_Everything else is on the site. `pithy.sh/docs` is canonical — new prose goes there, not here._
|
|
14
18
|
|
|
19
|
+
## A person's data
|
|
20
|
+
|
|
21
|
+
**Erased.** When an account is purged, support deletes every thread linked to the account, or sent from its address and linked to no other account, with every message, attachment and raw mail in R2, classification, read state and assignment history, and each message's vector in the semantic index with its model stamp, and an export hands the threads, messages, attachments and assignment history back first. The export leaves out who made each assignment: that is the operator's, and the audit trail keeps it with its actor. A staff member's purge takes their id off every handover they made as a signed-in user on someone else's thread, and their export lists those handovers by thread and time, without the assignee or the note. Who held a thread is kept until the customer's thread goes: an assignee is an opaque reference and a name the assigner chose, and support cannot tell which reference is a person. See [`@pithy-sh/auth`'s account lifecycle](../auth/docs/account-lifecycle.md#what-each-capability-does-with-a-persons-rows).
|
|
22
|
+
|
|
15
23
|
## License
|
|
16
24
|
|
|
17
25
|
MIT.
|
package/dist/audit/actions.d.ts
CHANGED
|
@@ -29,6 +29,15 @@ export declare const SupportAuditActions: {
|
|
|
29
29
|
readonly threadArchived: "support/thread_archived";
|
|
30
30
|
/** A thread was taken back out of the archive. Recorded separately, because undoing is its own decision. */
|
|
31
31
|
readonly threadUnarchived: "support/thread_unarchived";
|
|
32
|
+
/**
|
|
33
|
+
* A thread was given to somebody, or handed from one person to another (#816). Audited because who has
|
|
34
|
+
* a customer's conversation decides who answers them, and the thread keeps only the current answer.
|
|
35
|
+
* Metadata: the previous assignee, the new one, and the assigner's note — staff words, never mail.
|
|
36
|
+
* Reassigning the current assignee changes nothing and emits nothing.
|
|
37
|
+
*/
|
|
38
|
+
readonly threadAssigned: "support/thread_assigned";
|
|
39
|
+
/** A thread's assignee was cleared. Its own action, because giving a conversation up is its own decision. */
|
|
40
|
+
readonly threadUnassigned: "support/thread_unassigned";
|
|
32
41
|
/**
|
|
33
42
|
* A reply went out. Audited because it leaves under the adopter's domain and their DKIM: to the
|
|
34
43
|
* recipient it is indistinguishable from the founder writing it, so who actually sent it is a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/audit/actions.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,mBAAmB;IAC9B;;;;OAIG;aACH,cAAc,EAAE,yBAAyB;IACzC,4GAA4G;aAC5G,gBAAgB,EAAE,2BAA2B;IAC7C;;;;OAIG;aACH,SAAS,EAAE,oBAAoB;IAC/B;;;OAGG;aACH,kBAAkB,EAAE,6BAA6B;IACjD;;;;;;;OAOG;aACH,eAAe,EAAE,0BAA0B;IAC3C;;;;;;;;;;;;;OAaG;aACH,kBAAkB,EAAE,6BAA6B;CACzC,CAAC;AAEX,6CAA6C;AAC7C,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/audit/actions.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,mBAAmB;IAC9B;;;;OAIG;aACH,cAAc,EAAE,yBAAyB;IACzC,4GAA4G;aAC5G,gBAAgB,EAAE,2BAA2B;IAC7C;;;;;OAKG;aACH,cAAc,EAAE,yBAAyB;IACzC,6GAA6G;aAC7G,gBAAgB,EAAE,2BAA2B;IAC7C;;;;OAIG;aACH,SAAS,EAAE,oBAAoB;IAC/B;;;OAGG;aACH,kBAAkB,EAAE,6BAA6B;IACjD;;;;;;;OAOG;aACH,eAAe,EAAE,0BAA0B;IAC3C;;;;;;;;;;;;;OAaG;aACH,kBAAkB,EAAE,6BAA6B;CACzC,CAAC;AAEX,6CAA6C;AAC7C,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC"}
|
package/dist/audit/actions.js
CHANGED
|
@@ -31,6 +31,15 @@ const SupportAuditActions = {
|
|
|
31
31
|
/** A thread was taken back out of the archive. Recorded separately, because undoing is its own decision. */
|
|
32
32
|
threadUnarchived: "support/thread_unarchived",
|
|
33
33
|
/**
|
|
34
|
+
* A thread was given to somebody, or handed from one person to another (#816). Audited because who has
|
|
35
|
+
* a customer's conversation decides who answers them, and the thread keeps only the current answer.
|
|
36
|
+
* Metadata: the previous assignee, the new one, and the assigner's note — staff words, never mail.
|
|
37
|
+
* Reassigning the current assignee changes nothing and emits nothing.
|
|
38
|
+
*/
|
|
39
|
+
threadAssigned: "support/thread_assigned",
|
|
40
|
+
/** A thread's assignee was cleared. Its own action, because giving a conversation up is its own decision. */
|
|
41
|
+
threadUnassigned: "support/thread_unassigned",
|
|
42
|
+
/**
|
|
34
43
|
* A reply went out. Audited because it leaves under the adopter's domain and their DKIM: to the
|
|
35
44
|
* recipient it is indistinguishable from the founder writing it, so who actually sent it is a
|
|
36
45
|
* security-relevant fact with no other record.
|
package/dist/capability.d.ts
CHANGED
|
@@ -80,7 +80,8 @@ export interface SupportCapability extends Capability<DatabaseSpecMap, KvNamespa
|
|
|
80
80
|
* The support capability — an inbound support inbox in the adopter's own D1, classified on their own
|
|
81
81
|
* Workers AI binding.
|
|
82
82
|
*
|
|
83
|
-
* It contributes
|
|
83
|
+
* It contributes its tables (and optionally a full-text index, and a semantic message index in Vectorize) to the
|
|
84
|
+
* app `DB`, an `email()` handler
|
|
84
85
|
* that claims the configured addresses out of the Worker's single inbound entry, the classification
|
|
85
86
|
* Workflow that handler dispatches to, and a control-plane admin surface for a dashboard to read and
|
|
86
87
|
* act on.
|
package/dist/capability.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAGnE,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,0CAA0C,CAAC;AAC7F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAE3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAItE,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EAGxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAO9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAGnE,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,0CAA0C,CAAC;AAC7F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAE3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAItE,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EAGxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAO9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAIjD,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAW7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,+FAA+F;AAC/F,MAAM,MAAM,cAAc,GAAG,kBAAkB,GAAG;IAChD,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,8BAA8B;IAC9B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B;;;;;OAKG;IACH,YAAY,EAAE,eAAe,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IACrD;;;OAGG;IACH,KAAK,EAAE,WAAW,CAAC;IACnB;;;;;;;;OAQG;IACH,IAAI,EAAE,SAAS,CAAC;CACjB;AA4ED;;;;;GAKG;AACH,MAAM,WAAW,iBACf,SAAQ,UAAU,CAAC,eAAe,EAAE,kBAAkB,EAAE,SAAS,EAAE,OAAO,gBAAgB,CAAC;IAC3F,mCAAmC;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,wGAAwG;IACxG,UAAU,EAAE,iBAAiB,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,iBAAiB,CAuIvE;AAED,2GAA2G;AAC3G,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,IAAI,iBAAiB,CAE3F"}
|
package/dist/capability.js
CHANGED
|
@@ -5,13 +5,17 @@ import { SupportConfig, supportNeedsBucket } from "./config/config.js";
|
|
|
5
5
|
import { resolveCategories } from "./data/categories.js";
|
|
6
6
|
import { supportTables } from "./data/tables.js";
|
|
7
7
|
import { SUPPORT_BUCKET_BINDING, inapplicableAttachmentSecrets, supportSecretsRegistry } from "./secret/registry.js";
|
|
8
|
+
import { SUPPORT_MESSAGES_INDEX, SUPPORT_VECTORIZE_BINDING, supportMessageIndex } from "./semantic/messageIndex.js";
|
|
8
9
|
import { supportWorkflows } from "./workflows/specs.js";
|
|
9
10
|
import { makeResolveDeps } from "./http/resolve.js";
|
|
11
|
+
import { assertStaffComposition } from "./staff/staff.js";
|
|
10
12
|
import { supportAdminRoutes } from "./http/scopes.js";
|
|
11
13
|
import { registerSupportRoutes } from "./http/routes.js";
|
|
12
14
|
import { createSupportEmailHandler } from "./inbound/handler.js";
|
|
13
15
|
import { senderPeers } from "./link/peers.js";
|
|
14
|
-
import {
|
|
16
|
+
import { SUPPORT_MIGRATIONS } from "./migrations/set.js";
|
|
17
|
+
import { supportPresence } from "./presence/room.js";
|
|
18
|
+
import { supportUserPurge } from "./purge/contribution.js";
|
|
15
19
|
import { supportExampleSeed } from "./seeds/example.js";
|
|
16
20
|
import { PACKAGE_NAME, PACKAGE_VERSION } from "./version.generated.js";
|
|
17
21
|
import { noopEmit } from "@pithy-sh/core/src/audit/recorder";
|
|
@@ -98,7 +102,8 @@ function clientProjection(config, basePath) {
|
|
|
98
102
|
* The support capability — an inbound support inbox in the adopter's own D1, classified on their own
|
|
99
103
|
* Workers AI binding.
|
|
100
104
|
*
|
|
101
|
-
* It contributes
|
|
105
|
+
* It contributes its tables (and optionally a full-text index, and a semantic message index in Vectorize) to the
|
|
106
|
+
* app `DB`, an `email()` handler
|
|
102
107
|
* that claims the configured addresses out of the Worker's single inbound entry, the classification
|
|
103
108
|
* Workflow that handler dispatches to, and a control-plane admin surface for a dashboard to read and
|
|
104
109
|
* act on.
|
|
@@ -127,7 +132,7 @@ function support(options = {}) {
|
|
|
127
132
|
peers: {},
|
|
128
133
|
emit: noopEmit
|
|
129
134
|
};
|
|
130
|
-
const migrations =
|
|
135
|
+
const migrations = SUPPORT_MIGRATIONS;
|
|
131
136
|
const requiredBindings = [
|
|
132
137
|
{
|
|
133
138
|
type: "d1",
|
|
@@ -138,7 +143,17 @@ function support(options = {}) {
|
|
|
138
143
|
name: SUPPORT_BUCKET_BINDING,
|
|
139
144
|
optional: true
|
|
140
145
|
}] : [],
|
|
141
|
-
...workflowBindings(supportWorkflows)
|
|
146
|
+
...workflowBindings(supportWorkflows),
|
|
147
|
+
...supportMessageIndex(resolved.search) ? [{
|
|
148
|
+
type: "vectorize",
|
|
149
|
+
name: SUPPORT_VECTORIZE_BINDING,
|
|
150
|
+
optional: true,
|
|
151
|
+
remote: true
|
|
152
|
+
}, {
|
|
153
|
+
type: "ai",
|
|
154
|
+
name: "AI",
|
|
155
|
+
remote: true
|
|
156
|
+
}] : []
|
|
142
157
|
];
|
|
143
158
|
const capability = defineCapability({
|
|
144
159
|
name: "support",
|
|
@@ -164,20 +179,29 @@ function support(options = {}) {
|
|
|
164
179
|
* able to answer is a coherent and useful thing, so a missing email capability degrades one
|
|
165
180
|
* route instead of refusing to boot.
|
|
166
181
|
*/
|
|
182
|
+
...resolved.staff ? { powers: [resolved.staff.readPower, resolved.staff.assignPower] } : {},
|
|
167
183
|
compose: ({ capabilities }) => {
|
|
184
|
+
assertStaffComposition(resolved.staff, capabilities);
|
|
168
185
|
const email = capabilities.find(isEmailCapability);
|
|
169
186
|
wiring.enqueueEmail = email?.enqueue;
|
|
170
187
|
wiring.peers = senderPeers(capabilities, resolved);
|
|
171
188
|
},
|
|
172
189
|
client: () => clientProjection(resolved, mountPath),
|
|
173
190
|
adminRoutes: supportAdminRoutes(mountPath),
|
|
191
|
+
...supportMessageIndex(resolved.search) ? { vectorIndexes: [{
|
|
192
|
+
name: SUPPORT_MESSAGES_INDEX,
|
|
193
|
+
reprocessJob: "reprocess"
|
|
194
|
+
}] } : {},
|
|
195
|
+
presence: supportPresence(mountPath, wiring),
|
|
174
196
|
routes: registerSupportRoutes({
|
|
175
197
|
basePath: mountPath,
|
|
176
198
|
submission: resolved.submission.enabled,
|
|
177
|
-
resolveDeps: makeResolveDeps(wiring)
|
|
199
|
+
resolveDeps: makeResolveDeps(wiring),
|
|
200
|
+
staff: resolved.staff
|
|
178
201
|
}),
|
|
179
202
|
email: createSupportEmailHandler(wiring),
|
|
180
|
-
seeds: [supportExampleSeed]
|
|
203
|
+
seeds: [supportExampleSeed],
|
|
204
|
+
purge: [supportUserPurge("DB", { semantic: supportMessageIndex(resolved.search) !== void 0 })]
|
|
181
205
|
});
|
|
182
206
|
return Object.assign(capability, {
|
|
183
207
|
supportConfig: resolved,
|
package/dist/config/config.d.ts
CHANGED
|
@@ -136,17 +136,70 @@ export declare const SupportReplyConfig: z.ZodObject<{
|
|
|
136
136
|
}, z.core.$strip>>>;
|
|
137
137
|
}, z.core.$strip>;
|
|
138
138
|
export type SupportReplyConfig = z.output<typeof SupportReplyConfig>;
|
|
139
|
+
/**
|
|
140
|
+
* The default embedding model for semantic search — the kit's own default (`DEFAULT_EMBEDDING_MODEL` in
|
|
141
|
+
* `@pithy-sh/cloudflare/src/ai/aiManager.ts`), restated rather than imported because a Worker cannot reach
|
|
142
|
+
* the REST client package and should not.
|
|
143
|
+
*
|
|
144
|
+
* **Multilingual, and that is why.** An inbox takes mail in whatever language the customer writes, and an
|
|
145
|
+
* English-only model would embed a Spanish complaint into the wrong neighborhood with no error.
|
|
146
|
+
*/
|
|
147
|
+
export declare const DEFAULT_EMBEDDING_MODEL = "@cf/baai/bge-m3";
|
|
148
|
+
/**
|
|
149
|
+
* How many components `bge-m3` produces. It is not a Vectorize preset, so the index states its own shape;
|
|
150
|
+
* a model override that produces another width states its `dimensions` beside it.
|
|
151
|
+
*/
|
|
152
|
+
export declare const DEFAULT_EMBEDDING_DIMENSIONS = 1024;
|
|
153
|
+
/** The `{ model }` form of `search.semantic`: semantic search on, with a model of your choosing. */
|
|
154
|
+
export declare const SupportSemanticConfig: z.ZodObject<{
|
|
155
|
+
model: z.ZodDefault<z.ZodString>;
|
|
156
|
+
dimensions: z.ZodDefault<z.ZodNumber>;
|
|
157
|
+
}, z.core.$strip>;
|
|
158
|
+
export type SupportSemanticConfig = z.output<typeof SupportSemanticConfig>;
|
|
139
159
|
/**
|
|
140
160
|
* How text search is answered.
|
|
141
161
|
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
162
|
+
* FTS5 by default, with the `LIKE` scan kept for a project that wants no virtual table at all. Semantic
|
|
163
|
+
* search is off by default because it costs a second inference per inbound message.
|
|
144
164
|
*/
|
|
145
165
|
export declare const SupportSearchConfig: z.ZodObject<{
|
|
166
|
+
semantic: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
167
|
+
model: z.ZodDefault<z.ZodString>;
|
|
168
|
+
dimensions: z.ZodDefault<z.ZodNumber>;
|
|
169
|
+
}, z.core.$strip>]>>;
|
|
146
170
|
fts: z.ZodDefault<z.ZodBoolean>;
|
|
147
171
|
}, z.core.$strip>;
|
|
148
172
|
export type SupportSearchConfig = z.output<typeof SupportSearchConfig>;
|
|
173
|
+
/**
|
|
174
|
+
* The semantic model and width this configuration asks for, or `undefined` when semantic search is off.
|
|
175
|
+
*
|
|
176
|
+
* The one reading of the three-shaped flag — `false`, `true`, `{ model }` — so the capability's bindings,
|
|
177
|
+
* the search route, the classification Workflow and provisioning cannot each decide what `true` means.
|
|
178
|
+
*/
|
|
179
|
+
export declare function supportSemantic(search: SupportSearchConfig): SupportSemanticConfig | undefined;
|
|
149
180
|
/** The full support configuration. */
|
|
181
|
+
/**
|
|
182
|
+
* **Who of the app's own signed-in users works the inbox (#816).** Off until named.
|
|
183
|
+
*
|
|
184
|
+
* The inbox holds every customer's correspondence, so a signed-in user reaches it only through a power the
|
|
185
|
+
* adopter names in their own role catalog, held in the organization the request acts for — the same seam
|
|
186
|
+
* `media:manage` and `storage:manage` ask, and the same rule `artifacts({ managePower })` follows: a
|
|
187
|
+
* capability never reserves a power name in a catalog that is the adopter's.
|
|
188
|
+
*
|
|
189
|
+
* **Both powers must sit only on roles no organization can hand itself** — listed in
|
|
190
|
+
* `defineRoles({ unassignable })`, and not the role `organization({ ownership })` confers — and `support()`
|
|
191
|
+
* refuses the composition otherwise. A role is the catalog's, not one organization's, and an organization's
|
|
192
|
+
* own administrator may give any assignable role to their own members — so a support power on an assignable
|
|
193
|
+
* role would let any customer's administrator make themselves staff and read every other customer's mail.
|
|
194
|
+
* The ownership role is unassignable and still self-served: anybody may found an organization, nominate
|
|
195
|
+
* themselves its owner, and accept. On any other unassignable role it is conferred by an operator alone,
|
|
196
|
+
* through the control plane.
|
|
197
|
+
*/
|
|
198
|
+
export declare const SupportStaffConfig: z.ZodObject<{
|
|
199
|
+
readPower: z.ZodString;
|
|
200
|
+
assignPower: z.ZodString;
|
|
201
|
+
}, z.core.$strip>;
|
|
202
|
+
export type SupportStaffConfig = z.output<typeof SupportStaffConfig>;
|
|
150
203
|
export declare const SupportConfig: z.ZodObject<{
|
|
151
204
|
inboundAddresses: z.ZodDefault<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
152
205
|
categories: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
@@ -193,8 +246,16 @@ export declare const SupportConfig: z.ZodObject<{
|
|
|
193
246
|
}, z.core.$strip>>>;
|
|
194
247
|
}, z.core.$strip>>;
|
|
195
248
|
search: z.ZodPrefault<z.ZodObject<{
|
|
249
|
+
semantic: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
250
|
+
model: z.ZodDefault<z.ZodString>;
|
|
251
|
+
dimensions: z.ZodDefault<z.ZodNumber>;
|
|
252
|
+
}, z.core.$strip>]>>;
|
|
196
253
|
fts: z.ZodDefault<z.ZodBoolean>;
|
|
197
254
|
}, z.core.$strip>>;
|
|
255
|
+
staff: z.ZodOptional<z.ZodObject<{
|
|
256
|
+
readPower: z.ZodString;
|
|
257
|
+
assignPower: z.ZodString;
|
|
258
|
+
}, z.core.$strip>>;
|
|
198
259
|
}, z.core.$strip>;
|
|
199
260
|
export type SupportConfig = z.output<typeof SupportConfig>;
|
|
200
261
|
export type SupportConfigInput = z.input<typeof SupportConfig>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,mCAAmC,CAAC;AAEvE,0DAA0D;AAC1D,eAAO,MAAM,eAAe;;;;;iBA+BkF,CAAC;AAC/G,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAE/D,+DAA+D;AAC/D,eAAO,MAAM,wBAAwB;;;;;iBA6BkF,CAAC;AACxH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEjF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;;;iBA8C8D,CAAC;AAC9F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAErE;;;;;;;;;;;;;;;GAeG;AACH;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C,eAAO,MAAM,kCAAkC;;;;;iBAkC5C,CAAC;AACJ,MAAM,MAAM,kCAAkC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAErG;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAChD,2FAA2F;AAC3F,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAEjD;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;;;;iBAwCjC,CAAC;AACJ,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE/E,uCAAuC;AACvC,eAAO,MAAM,kBAAkB;;;;;;;;;iBAmC5B,CAAC;AACJ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAErE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,mCAAmC,CAAC;AAEvE,0DAA0D;AAC1D,eAAO,MAAM,eAAe;;;;;iBA+BkF,CAAC;AAC/G,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAE/D,+DAA+D;AAC/D,eAAO,MAAM,wBAAwB;;;;;iBA6BkF,CAAC;AACxH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEjF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;;;iBA8C8D,CAAC;AAC9F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAErE;;;;;;;;;;;;;;;GAeG;AACH;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C,eAAO,MAAM,kCAAkC;;;;;iBAkC5C,CAAC;AACJ,MAAM,MAAM,kCAAkC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAErG;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAChD,2FAA2F;AAC3F,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAEjD;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;;;;iBAwCjC,CAAC;AACJ,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE/E,uCAAuC;AACvC,eAAO,MAAM,kBAAkB;;;;;;;;;iBAmC5B,CAAC;AACJ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAErE;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,oBAAoB,CAAC;AAEzD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,oGAAoG;AACpG,eAAO,MAAM,qBAAqB;;;iBAmB2B,CAAC;AAC9D,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE3E;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;;;;;iBAiB7B,CAAC;AACJ,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEvE;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,qBAAqB,GAAG,SAAS,CAI9F;AAED,sCAAsC;AACtC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB;;;iBAiB5B,CAAC;AACJ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAErE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoCvB,CAAC;AACJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAC3D,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE/D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAEjE"}
|
package/dist/config/config.js
CHANGED
|
@@ -132,13 +132,70 @@ const SupportReplyConfig = z.object({
|
|
|
132
132
|
snippets: z.record(z.string(), SupportReplySnippet).default({}).describe("Your own canned replies, merged over the ones Pithy ships and winning on a key collision. These are *starting points a human picks and edits* in the dashboard, not automatic replies — nothing here is ever sent without somebody pressing send. Author them with `defineSupportReplies` so a malformed one fails where it is written.")
|
|
133
133
|
}).describe("Reply settings — whether replies are allowed, where the answer goes, and what to offer as a starting point.");
|
|
134
134
|
/**
|
|
135
|
+
* The default embedding model for semantic search — the kit's own default (`DEFAULT_EMBEDDING_MODEL` in
|
|
136
|
+
* `@pithy-sh/cloudflare/src/ai/aiManager.ts`), restated rather than imported because a Worker cannot reach
|
|
137
|
+
* the REST client package and should not.
|
|
138
|
+
*
|
|
139
|
+
* **Multilingual, and that is why.** An inbox takes mail in whatever language the customer writes, and an
|
|
140
|
+
* English-only model would embed a Spanish complaint into the wrong neighborhood with no error.
|
|
141
|
+
*/
|
|
142
|
+
const DEFAULT_EMBEDDING_MODEL = "@cf/baai/bge-m3";
|
|
143
|
+
/**
|
|
144
|
+
* How many components `bge-m3` produces. It is not a Vectorize preset, so the index states its own shape;
|
|
145
|
+
* a model override that produces another width states its `dimensions` beside it.
|
|
146
|
+
*/
|
|
147
|
+
const DEFAULT_EMBEDDING_DIMENSIONS = 1024;
|
|
148
|
+
/** The `{ model }` form of `search.semantic`: semantic search on, with a model of your choosing. */
|
|
149
|
+
const SupportSemanticConfig = z.object({
|
|
150
|
+
model: z.string().min(1).default(DEFAULT_EMBEDDING_MODEL).describe("The Workers AI embedding model the message index is built and queried with. Pinned: change it and `pithy vector reprocess --index support-messages` re-embeds what the old one wrote."),
|
|
151
|
+
dimensions: z.number().int().min(1).max(1536).default(DEFAULT_EMBEDDING_DIMENSIONS).describe("How many components the model produces — 1024 for `bge-m3`, 768 for `bge-base-en-v1.5`. Fixed when the index is created, so a model of another width needs the index dropped and recreated.")
|
|
152
|
+
}).describe("Semantic search with a chosen embedding model.");
|
|
153
|
+
/**
|
|
135
154
|
* How text search is answered.
|
|
136
155
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
156
|
+
* FTS5 by default, with the `LIKE` scan kept for a project that wants no virtual table at all. Semantic
|
|
157
|
+
* search is off by default because it costs a second inference per inbound message.
|
|
139
158
|
*/
|
|
140
|
-
const SupportSearchConfig = z.object({
|
|
159
|
+
const SupportSearchConfig = z.object({
|
|
160
|
+
semantic: z.union([z.boolean(), SupportSemanticConfig]).default(false).describe("Search by meaning as well as by words: `true` for the default model (`@cf/baai/bge-m3`), or `{ model }` for your own. **Off by default, and the reason is cost.** Every inbound message is embedded once, on your own Workers AI binding, in the classification Workflow — a second inference per message on top of classification, which roughly doubles this capability's per-message AI cost. The volume guard bounds messages, not spend. On, `pithy support provision` creates a Vectorize index and backfills it once; off, it drops the index. Vectors carry message ids and nothing else."),
|
|
161
|
+
fts: z.boolean().default(true).describe("Build a SQLite FTS5 index over message subjects and bodies — a ranked word search instead of an unindexed `LIKE` scan. **On by default.** A plain `wrangler d1 export` of a database holding an FTS5 table fails outright, so `pithy backup` leaves the index and its triggers out and `pithy restore` rebuilds them from `pithy_support_messages`; the bare `wrangler` route has to skip it by hand. The index is derived rather than migrated, so `pithy support provision` creates or drops it to match this flag, and until a project re-provisions, search falls back to the `LIKE` scan and `pithy doctor` names the environment.")
|
|
162
|
+
}).describe("Search settings — FTS5 by default, the `LIKE` scan when it is off or not yet provisioned, and semantic search an opt-in priced in resources you own.");
|
|
163
|
+
/**
|
|
164
|
+
* The semantic model and width this configuration asks for, or `undefined` when semantic search is off.
|
|
165
|
+
*
|
|
166
|
+
* The one reading of the three-shaped flag — `false`, `true`, `{ model }` — so the capability's bindings,
|
|
167
|
+
* the search route, the classification Workflow and provisioning cannot each decide what `true` means.
|
|
168
|
+
*/
|
|
169
|
+
function supportSemantic(search) {
|
|
170
|
+
if (search.semantic === false) return void 0;
|
|
171
|
+
if (search.semantic === true) return {
|
|
172
|
+
model: DEFAULT_EMBEDDING_MODEL,
|
|
173
|
+
dimensions: DEFAULT_EMBEDDING_DIMENSIONS
|
|
174
|
+
};
|
|
175
|
+
return search.semantic;
|
|
176
|
+
}
|
|
141
177
|
/** The full support configuration. */
|
|
178
|
+
/**
|
|
179
|
+
* **Who of the app's own signed-in users works the inbox (#816).** Off until named.
|
|
180
|
+
*
|
|
181
|
+
* The inbox holds every customer's correspondence, so a signed-in user reaches it only through a power the
|
|
182
|
+
* adopter names in their own role catalog, held in the organization the request acts for — the same seam
|
|
183
|
+
* `media:manage` and `storage:manage` ask, and the same rule `artifacts({ managePower })` follows: a
|
|
184
|
+
* capability never reserves a power name in a catalog that is the adopter's.
|
|
185
|
+
*
|
|
186
|
+
* **Both powers must sit only on roles no organization can hand itself** — listed in
|
|
187
|
+
* `defineRoles({ unassignable })`, and not the role `organization({ ownership })` confers — and `support()`
|
|
188
|
+
* refuses the composition otherwise. A role is the catalog's, not one organization's, and an organization's
|
|
189
|
+
* own administrator may give any assignable role to their own members — so a support power on an assignable
|
|
190
|
+
* role would let any customer's administrator make themselves staff and read every other customer's mail.
|
|
191
|
+
* The ownership role is unassignable and still self-served: anybody may found an organization, nominate
|
|
192
|
+
* themselves its owner, and accept. On any other unassignable role it is conferred by an operator alone,
|
|
193
|
+
* through the control plane.
|
|
194
|
+
*/
|
|
195
|
+
const SupportStaffConfig = z.object({
|
|
196
|
+
readPower: z.string().min(1).describe("The power that lets a signed-in user see who else has a thread open — what the `support.thread` presence room's session ticket asks. Your own name, declared in your role catalog on unassignable roles only, and not on the role ownership transfer confers, e.g. `support:read`."),
|
|
197
|
+
assignPower: z.string().min(1).describe("The power that lets a signed-in user assign a thread, to themselves or to somebody else, at `POST {basePath}/staff/threads/:id/assign`. Your own name, declared in your role catalog on unassignable roles only, and not on the role ownership transfer confers, e.g. `support:assign`.")
|
|
198
|
+
}).describe("Who of your app's own signed-in users works the inbox, by the organization powers their role holds. Requires `organization`, and both powers on unassignable roles other than the one ownership transfer confers. Absent, no signed-in user can assign a thread or join a thread's presence room — management clients still can, through the control plane.");
|
|
142
199
|
const SupportConfig = z.object({
|
|
143
200
|
inboundAddresses: z.array(z.string().min(3).describe("One address this inbox accepts mail for, e.g. `support@help.example.com`.").transform((address) => parseAddress(address) ?? normalizeAddress(address))).default([]).describe("The addresses this capability claims. Every capability's `email()` handler sees every message the Worker receives, so this list is how support tells its mail apart from the bounce handler's — and it is a claim, not a route: the routing rule that delivers to this Worker is created by `pithy support provision`. **Use a subdomain, never your apex**: Email Routing takes over the zone's MX, and enabling it on the apex moves your real mail off your provider. Empty means the inbox is inert."),
|
|
144
201
|
categories: z.record(z.string(), z.string()).default({}).describe("Your own categories, merged over the eight Pithy ships and winning on a key collision. A key is `snake_case`; the value is the instruction a model reads when deciding whether this is the one, and it lands in the prompt verbatim. Author it with `defineSupportCategories` so a typo fails where it is written."),
|
|
@@ -147,7 +204,8 @@ const SupportConfig = z.object({
|
|
|
147
204
|
guard: SupportGuardConfig.prefault({}).describe("Inbound mail size and rate bounds."),
|
|
148
205
|
submission: SupportSubmissionConfig.prefault({}).describe("The in-app submission channel."),
|
|
149
206
|
reply: SupportReplyConfig.prefault({}).describe("Reply settings."),
|
|
150
|
-
search: SupportSearchConfig.prefault({}).describe("How text search is answered.")
|
|
207
|
+
search: SupportSearchConfig.prefault({}).describe("How text search is answered."),
|
|
208
|
+
staff: SupportStaffConfig.optional().describe("Who of your app's signed-in users works the inbox, by organization power. Absent, the staff session routes refuse everyone.")
|
|
151
209
|
}).describe("Configuration for the support capability — which addresses it claims, and how it classifies and bounds them.");
|
|
152
210
|
/**
|
|
153
211
|
* Would this configuration ever put a byte in `SUPPORT_BUCKET`?
|
|
@@ -168,4 +226,4 @@ function supportNeedsBucket(config) {
|
|
|
168
226
|
return config.attachments.enabled || config.attachments.retainRaw || config.submission.attachments.enabled;
|
|
169
227
|
}
|
|
170
228
|
//#endregion
|
|
171
|
-
export { DEFAULT_CLASSIFY_MODEL, MAX_SUBMISSION_ATTACHMENTS, MAX_SUBMISSION_BODY_CHARS, MAX_SUBMISSION_SUBJECT_CHARS, SupportAiConfig, SupportAttachmentsConfig, SupportConfig, SupportGuardConfig, SupportReplyConfig, SupportSearchConfig, SupportSubmissionAttachmentsConfig, SupportSubmissionConfig, supportNeedsBucket };
|
|
229
|
+
export { DEFAULT_CLASSIFY_MODEL, DEFAULT_EMBEDDING_DIMENSIONS, DEFAULT_EMBEDDING_MODEL, MAX_SUBMISSION_ATTACHMENTS, MAX_SUBMISSION_BODY_CHARS, MAX_SUBMISSION_SUBJECT_CHARS, SupportAiConfig, SupportAttachmentsConfig, SupportConfig, SupportGuardConfig, SupportReplyConfig, SupportSearchConfig, SupportSemanticConfig, SupportStaffConfig, SupportSubmissionAttachmentsConfig, SupportSubmissionConfig, supportNeedsBucket, supportSemantic };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
/**
|
|
5
|
+
* The `assignee` filter value meaning *nobody has it*. Reserved: an assignment to a ref spelled this way is
|
|
6
|
+
* refused at the boundary (`http/schemas.ts`), so the filter can never mean two things.
|
|
7
|
+
*/
|
|
8
|
+
export declare const UNASSIGNED = "none";
|
|
9
|
+
/**
|
|
10
|
+
* Who assigned a thread: a management client through the control plane, or a signed-in user of the app
|
|
11
|
+
* through support's staff power. Recorded per change, because the two are different evidence about who
|
|
12
|
+
* acted, exactly as the audit trail's `actorType` is.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SupportAssignmentActorType: z.ZodEnum<{
|
|
15
|
+
"control-plane": "control-plane";
|
|
16
|
+
user: "user";
|
|
17
|
+
}>;
|
|
18
|
+
export type SupportAssignmentActorType = z.output<typeof SupportAssignmentActorType>;
|
|
19
|
+
/**
|
|
20
|
+
* One change of a thread's assignee in `pithy_support_assignments` — append-only, one row per change.
|
|
21
|
+
*
|
|
22
|
+
* The thread carries the **current** assignee because that is what the inbox filters on; this carries
|
|
23
|
+
* **every** change, the same split the classification table has with the thread. Nothing here is updated
|
|
24
|
+
* and nothing is deleted while the thread stands. A change that changed nothing — the current assignee
|
|
25
|
+
* assigned again — writes no row, so every row here is a real handover.
|
|
26
|
+
*/
|
|
27
|
+
export declare const SupportAssignment: z.ZodObject<{
|
|
28
|
+
id: z.ZodString;
|
|
29
|
+
threadId: z.ZodString;
|
|
30
|
+
assigneeRef: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
31
|
+
assigneeName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
32
|
+
previousRef: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
33
|
+
previousName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
34
|
+
actorType: z.ZodEnum<{
|
|
35
|
+
"control-plane": "control-plane";
|
|
36
|
+
user: "user";
|
|
37
|
+
}>;
|
|
38
|
+
assignedBy: z.ZodString;
|
|
39
|
+
note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
40
|
+
createdAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
|
|
41
|
+
}, z.core.$strip>;
|
|
42
|
+
export type SupportAssignment = z.output<typeof SupportAssignment>;
|
|
43
|
+
//# sourceMappingURL=assignment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assignment.d.ts","sourceRoot":"","sources":["../../src/data/assignment.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,UAAU,SAAS,CAAC;AAEjC;;;;GAIG;AACH,eAAO,MAAM,0BAA0B;;;EAIpC,CAAC;AACJ,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAErF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;iBAqBsE,CAAC;AACrG,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { SQLiteDate } from "@pithy-sh/core/src/data/codecs";
|
|
5
|
+
//#region src/data/assignment.ts
|
|
6
|
+
/**
|
|
7
|
+
* The `assignee` filter value meaning *nobody has it*. Reserved: an assignment to a ref spelled this way is
|
|
8
|
+
* refused at the boundary (`http/schemas.ts`), so the filter can never mean two things.
|
|
9
|
+
*/
|
|
10
|
+
const UNASSIGNED = "none";
|
|
11
|
+
/**
|
|
12
|
+
* Who assigned a thread: a management client through the control plane, or a signed-in user of the app
|
|
13
|
+
* through support's staff power. Recorded per change, because the two are different evidence about who
|
|
14
|
+
* acted, exactly as the audit trail's `actorType` is.
|
|
15
|
+
*/
|
|
16
|
+
const SupportAssignmentActorType = z.enum(["control-plane", "user"]).describe("How the assigner was verified: `control-plane` for a management client's scoped credential, `user` for a signed-in user of the app holding support's staff power.");
|
|
17
|
+
/**
|
|
18
|
+
* One change of a thread's assignee in `pithy_support_assignments` — append-only, one row per change.
|
|
19
|
+
*
|
|
20
|
+
* The thread carries the **current** assignee because that is what the inbox filters on; this carries
|
|
21
|
+
* **every** change, the same split the classification table has with the thread. Nothing here is updated
|
|
22
|
+
* and nothing is deleted while the thread stands. A change that changed nothing — the current assignee
|
|
23
|
+
* assigned again — writes no row, so every row here is a real handover.
|
|
24
|
+
*/
|
|
25
|
+
const SupportAssignment = z.object({
|
|
26
|
+
id: z.string().describe("UUID primary key."),
|
|
27
|
+
threadId: z.string().describe("The `pithy_support_threads.id` whose assignee changed."),
|
|
28
|
+
assigneeRef: z.string().nullish().describe("Who has the thread after this change, as an opaque reference, or null when it was unassigned."),
|
|
29
|
+
assigneeName: z.string().nullish().describe("How that assignee was shown at the time, or null when unassigned."),
|
|
30
|
+
previousRef: z.string().nullish().describe("Who had the thread before this change, or null when nobody did."),
|
|
31
|
+
previousName: z.string().nullish().describe("How the previous assignee was shown, or null when nobody had it."),
|
|
32
|
+
actorType: SupportAssignmentActorType.describe("How the person or client making the change was verified."),
|
|
33
|
+
assignedBy: z.string().describe("Who made the change — the control-plane subject, or the signed-in user's id."),
|
|
34
|
+
note: z.string().nullish().describe("Why, in the assigner's own words, when they gave a reason. Staff-written, never customer text, and shown to anybody who can read the thread."),
|
|
35
|
+
createdAt: SQLiteDate.describe("When the change was made.")
|
|
36
|
+
}).describe("One append-only change of a support thread's assignee in `pithy_support_assignments`.");
|
|
37
|
+
//#endregion
|
|
38
|
+
export { SupportAssignment, SupportAssignmentActorType, UNASSIGNED };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
/**
|
|
5
|
+
* Which model embedded a message into the semantic index, in `pithy_support_embeddings`.
|
|
6
|
+
*
|
|
7
|
+
* **Its own table, not a column on the message**, for two reasons. A released migration is never edited, and
|
|
8
|
+
* a stamp belongs to a derived index rather than to the mail: drop the index and every stamp is a lie, so they
|
|
9
|
+
* go together, and nothing about a message changes. It is what `pithy vector reprocess --index
|
|
10
|
+
* support-messages` reads to re-embed exactly the rows whose model differs from the configured one — the same
|
|
11
|
+
* job `pithy_vector_documents.model` does for an index `vector()` owns.
|
|
12
|
+
*
|
|
13
|
+
* A message with no row was never embedded, which is the set a backfill and a repair both pick up.
|
|
14
|
+
*/
|
|
15
|
+
export declare const SupportEmbedding: z.ZodObject<{
|
|
16
|
+
messageId: z.ZodString;
|
|
17
|
+
model: z.ZodString;
|
|
18
|
+
embeddedAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
export type SupportEmbedding = z.output<typeof SupportEmbedding>;
|
|
21
|
+
//# sourceMappingURL=embedding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding.d.ts","sourceRoot":"","sources":["../../src/data/embedding.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB;;;;iBAQiD,CAAC;AAC/E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { SQLiteDate } from "@pithy-sh/core/src/data/codecs";
|
|
5
|
+
//#region src/data/embedding.ts
|
|
6
|
+
/**
|
|
7
|
+
* Which model embedded a message into the semantic index, in `pithy_support_embeddings`.
|
|
8
|
+
*
|
|
9
|
+
* **Its own table, not a column on the message**, for two reasons. A released migration is never edited, and
|
|
10
|
+
* a stamp belongs to a derived index rather than to the mail: drop the index and every stamp is a lie, so they
|
|
11
|
+
* go together, and nothing about a message changes. It is what `pithy vector reprocess --index
|
|
12
|
+
* support-messages` reads to re-embed exactly the rows whose model differs from the configured one — the same
|
|
13
|
+
* job `pithy_vector_documents.model` does for an index `vector()` owns.
|
|
14
|
+
*
|
|
15
|
+
* A message with no row was never embedded, which is the set a backfill and a repair both pick up.
|
|
16
|
+
*/
|
|
17
|
+
const SupportEmbedding = z.object({
|
|
18
|
+
messageId: z.string().describe("The `pithy_support_messages.id` this stamp is for — also the vector's id in the index."),
|
|
19
|
+
model: z.string().describe("The Workers AI embedding model that produced the vector now in the index."),
|
|
20
|
+
embeddedAt: SQLiteDate.describe("When the vector was written.")
|
|
21
|
+
}).describe("The model stamp for one message's vector in the semantic index.");
|
|
22
|
+
//#endregion
|
|
23
|
+
export { SupportEmbedding };
|
package/dist/data/tables.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export declare const SUPPORT_ATTACHMENTS_TABLE = "pithySupportAttachments";
|
|
|
18
18
|
export declare const SUPPORT_CLASSIFICATIONS_TABLE = "pithySupportClassifications";
|
|
19
19
|
/** Per-viewer read/snooze state. `pithy_support_thread_flags`. */
|
|
20
20
|
export declare const SUPPORT_FLAGS_TABLE = "pithySupportThreadFlags";
|
|
21
|
+
/** Append-only assignee history. `pithy_support_assignments`. */
|
|
22
|
+
export declare const SUPPORT_ASSIGNMENTS_TABLE = "pithySupportAssignments";
|
|
23
|
+
/** The semantic index's per-message model stamps. `pithy_support_embeddings`. */
|
|
24
|
+
export declare const SUPPORT_EMBEDDINGS_TABLE = "pithySupportEmbeddings";
|
|
21
25
|
/**
|
|
22
26
|
* The FTS5 full-text index. Not a Kysely table and deliberately not in {@link supportTables}: it is a
|
|
23
27
|
* virtual table with no Zod schema and no rows of its own worth typing, reached through raw `sql`
|
|
@@ -64,6 +68,10 @@ export declare const supportTables: {
|
|
|
64
68
|
archived: z.ZodCodec<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodBoolean, z.ZodString]>, z.ZodBoolean>;
|
|
65
69
|
archivedAt: z.ZodOptional<z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>>>;
|
|
66
70
|
archivedBy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
71
|
+
assigneeRef: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
72
|
+
assigneeName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
73
|
+
assignedAt: z.ZodOptional<z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>>>;
|
|
74
|
+
assignedBy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
67
75
|
messageCount: z.ZodNumber;
|
|
68
76
|
firstMessageAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
|
|
69
77
|
lastMessageAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
|
|
@@ -146,6 +154,26 @@ export declare const supportTables: {
|
|
|
146
154
|
createdAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
|
|
147
155
|
updatedAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
|
|
148
156
|
}, z.core.$strip>;
|
|
157
|
+
pithySupportAssignments: z.ZodObject<{
|
|
158
|
+
id: z.ZodString;
|
|
159
|
+
threadId: z.ZodString;
|
|
160
|
+
assigneeRef: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
161
|
+
assigneeName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
162
|
+
previousRef: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
163
|
+
previousName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
164
|
+
actorType: z.ZodEnum<{
|
|
165
|
+
"control-plane": "control-plane";
|
|
166
|
+
user: "user";
|
|
167
|
+
}>;
|
|
168
|
+
assignedBy: z.ZodString;
|
|
169
|
+
note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
170
|
+
createdAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
|
|
171
|
+
}, z.core.$strip>;
|
|
172
|
+
pithySupportEmbeddings: z.ZodObject<{
|
|
173
|
+
messageId: z.ZodString;
|
|
174
|
+
model: z.ZodString;
|
|
175
|
+
embeddedAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
|
|
176
|
+
}, z.core.$strip>;
|
|
149
177
|
};
|
|
150
178
|
/** The support tables, as a type. */
|
|
151
179
|
export type SupportTables = typeof supportTables;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/data/tables.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/data/tables.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAS7B;;;GAGG;AAEH,8CAA8C;AAC9C,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAC3D,gEAAgE;AAChE,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAC7D,wDAAwD;AACxD,eAAO,MAAM,yBAAyB,4BAA4B,CAAC;AACnE,2EAA2E;AAC3E,eAAO,MAAM,6BAA6B,gCAAgC,CAAC;AAC3E,kEAAkE;AAClE,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAC7D,iEAAiE;AACjE,eAAO,MAAM,yBAAyB,4BAA4B,CAAC;AACnE,iFAAiF;AACjF,eAAO,MAAM,wBAAwB,2BAA2B,CAAC;AAEjE;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAE3D,oEAAoE;AACpE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAQa,CAAC;AAExC,qCAAqC;AACrC,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC;AAEjD,gFAAgF;AAChF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;AAEpE,sDAAsD;AACtD,wBAAgB,eAAe,CAAC,EAAE,EAAE,UAAU,GAAG,eAAe,CAE/D"}
|
package/dist/data/tables.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { SupportAssignment } from "./assignment.js";
|
|
3
4
|
import { SupportAttachment } from "./attachment.js";
|
|
4
5
|
import { SupportClassification } from "./classification.js";
|
|
6
|
+
import { SupportEmbedding } from "./embedding.js";
|
|
5
7
|
import { SupportThreadFlag } from "./flag.js";
|
|
6
8
|
import { SupportMessage } from "./message.js";
|
|
7
9
|
import { SupportThread } from "./thread.js";
|
|
@@ -21,6 +23,10 @@ const SUPPORT_ATTACHMENTS_TABLE = "pithySupportAttachments";
|
|
|
21
23
|
const SUPPORT_CLASSIFICATIONS_TABLE = "pithySupportClassifications";
|
|
22
24
|
/** Per-viewer read/snooze state. `pithy_support_thread_flags`. */
|
|
23
25
|
const SUPPORT_FLAGS_TABLE = "pithySupportThreadFlags";
|
|
26
|
+
/** Append-only assignee history. `pithy_support_assignments`. */
|
|
27
|
+
const SUPPORT_ASSIGNMENTS_TABLE = "pithySupportAssignments";
|
|
28
|
+
/** The semantic index's per-message model stamps. `pithy_support_embeddings`. */
|
|
29
|
+
const SUPPORT_EMBEDDINGS_TABLE = "pithySupportEmbeddings";
|
|
24
30
|
/**
|
|
25
31
|
* The FTS5 full-text index. Not a Kysely table and deliberately not in {@link supportTables}: it is a
|
|
26
32
|
* virtual table with no Zod schema and no rows of its own worth typing, reached through raw `sql`
|
|
@@ -36,11 +42,13 @@ const supportTables = {
|
|
|
36
42
|
[SUPPORT_MESSAGES_TABLE]: SupportMessage,
|
|
37
43
|
[SUPPORT_ATTACHMENTS_TABLE]: SupportAttachment,
|
|
38
44
|
[SUPPORT_CLASSIFICATIONS_TABLE]: SupportClassification,
|
|
39
|
-
[SUPPORT_FLAGS_TABLE]: SupportThreadFlag
|
|
45
|
+
[SUPPORT_FLAGS_TABLE]: SupportThreadFlag,
|
|
46
|
+
[SUPPORT_ASSIGNMENTS_TABLE]: SupportAssignment,
|
|
47
|
+
[SUPPORT_EMBEDDINGS_TABLE]: SupportEmbedding
|
|
40
48
|
};
|
|
41
49
|
/** Build the support database from a `DB` binding. */
|
|
42
50
|
function supportDatabase(d1) {
|
|
43
51
|
return createDatabase(d1, supportTables);
|
|
44
52
|
}
|
|
45
53
|
//#endregion
|
|
46
|
-
export { SUPPORT_ATTACHMENTS_TABLE, SUPPORT_CLASSIFICATIONS_TABLE, SUPPORT_FLAGS_TABLE, SUPPORT_MESSAGES_TABLE, SUPPORT_SEARCH_TABLE, SUPPORT_THREADS_TABLE, supportDatabase, supportTables };
|
|
54
|
+
export { SUPPORT_ASSIGNMENTS_TABLE, SUPPORT_ATTACHMENTS_TABLE, SUPPORT_CLASSIFICATIONS_TABLE, SUPPORT_EMBEDDINGS_TABLE, SUPPORT_FLAGS_TABLE, SUPPORT_MESSAGES_TABLE, SUPPORT_SEARCH_TABLE, SUPPORT_THREADS_TABLE, supportDatabase, supportTables };
|