@pithy-sh/support 0.6.2 → 0.8.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 +3 -1
- package/dist/capability.d.ts +1 -1
- package/dist/capability.d.ts.map +1 -1
- package/dist/capability.js +5 -4
- package/dist/config/config.d.ts +7 -5
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +6 -5
- package/dist/http/handlers.d.ts +2 -2
- package/dist/http/handlers.d.ts.map +1 -1
- package/dist/http/handlers.js +3 -2
- package/dist/http/responses.d.ts +9 -5
- package/dist/http/responses.d.ts.map +1 -1
- package/dist/http/responses.js +10 -12
- package/dist/http/routes.d.ts +3 -3
- package/dist/http/routes.d.ts.map +1 -1
- package/dist/http/routes.js +4 -3
- package/dist/http/schemas.d.ts +11 -9
- package/dist/http/schemas.d.ts.map +1 -1
- package/dist/http/schemas.js +15 -8
- package/dist/http/scopes.d.ts.map +1 -1
- package/dist/http/scopes.js +2 -1
- package/dist/provision/provisionSupport.d.ts +19 -20
- package/dist/provision/provisionSupport.d.ts.map +1 -1
- package/dist/provision/provisionSupport.js +14 -17
- package/dist/provision/sendingCheck.d.ts +7 -0
- package/dist/provision/sendingCheck.d.ts.map +1 -0
- package/dist/provision/sendingCheck.js +63 -0
- package/dist/reply/fromName.d.ts +20 -0
- package/dist/reply/fromName.d.ts.map +1 -0
- package/dist/reply/fromName.js +29 -0
- package/dist/reply/send.d.ts +10 -1
- package/dist/reply/send.d.ts.map +1 -1
- package/dist/reply/send.js +16 -2
- package/dist/reply/sender.d.ts +16 -0
- package/dist/reply/sender.d.ts.map +1 -0
- package/dist/reply/sender.js +27 -0
- package/dist/store/threads.d.ts +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/package.json +8 -8
- package/pithy.manifest.json +2 -2
- package/src/capability.ts +18 -15
- package/src/config/config.ts +13 -5
- package/src/http/handlers.ts +4 -3
- package/src/http/responses.ts +18 -13
- package/src/http/routes.ts +9 -8
- package/src/http/schemas.ts +23 -13
- package/src/http/scopes.ts +2 -1
- package/src/provision/provisionSupport.ts +20 -21
- package/src/provision/sendingCheck.ts +69 -0
- package/src/reply/fromName.ts +37 -0
- package/src/reply/send.ts +31 -3
- package/src/reply/sender.ts +35 -0
- package/src/store/threads.ts +1 -1
- package/src/version.generated.ts +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,9 @@ pithy add support
|
|
|
12
12
|
|
|
13
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
14
|
|
|
15
|
-
**Search, by words and by meaning.** `
|
|
15
|
+
**Search, by words and by meaning.** `POST /support/threads/search` takes, in its body, `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
|
+
|
|
17
|
+
**Replies come from the inbox.** A mailed reply is sent from the address the customer wrote to, under the name of whoever answered: `"Jim Scott" <support@help.example.com>`, with that inbox as the `Reply-To`. `reply.fromName` is the display name, with `{agent}` replaced by the agent's name (default `{agent}`); with no agent name, the email capability's `fromName` is used. A fixed `reply.fromName` with no `{agent}` is used for every reply. Support declares the addresses it sends as, `inboundAddresses` and `reply.replyToAddress`, through `email().enqueueAs` (`@pithy-sh/email` ^0.9.0; an older one is refused at compose). A thread whose inbox is no longer claimed is answered from `reply.replyToAddress`, or refused when none is set. A domain Email Service cannot send from fails the job with the domain named, and nothing is sent from another address in its place. `pithy doctor` names each sending domain, inbound or reply-to, that cannot send.
|
|
16
18
|
|
|
17
19
|
_Everything else is on the site. `pithy.sh/docs` is canonical — new prose goes there, not here._
|
|
18
20
|
|
package/dist/capability.d.ts
CHANGED
|
@@ -46,7 +46,7 @@ export interface SupportWiring {
|
|
|
46
46
|
enqueueEmail: EmailCapability["enqueue"] | undefined;
|
|
47
47
|
/**
|
|
48
48
|
* The optional peers the sender link reads through — `auth()`'s and `payments()`'s surfaces, set by
|
|
49
|
-
* `compose` when each is composed. Found, never imported
|
|
49
|
+
* `compose` when each is composed. Found, never imported: see `link/sender.ts`.
|
|
50
50
|
*/
|
|
51
51
|
peers: SenderPeers;
|
|
52
52
|
/**
|
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;
|
|
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;AAGtE,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;AAMjD,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,CAyIvE;AAED,2GAA2G;AAC3G,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,IAAI,iBAAiB,CAE3F"}
|
package/dist/capability.js
CHANGED
|
@@ -15,13 +15,14 @@ import { createSupportEmailHandler } from "./inbound/handler.js";
|
|
|
15
15
|
import { senderPeers } from "./link/peers.js";
|
|
16
16
|
import { SUPPORT_MIGRATIONS } from "./migrations/set.js";
|
|
17
17
|
import { supportPresence } from "./presence/room.js";
|
|
18
|
+
import { supportSendingSettings } from "./provision/sendingCheck.js";
|
|
18
19
|
import { supportUserPurge } from "./purge/contribution.js";
|
|
20
|
+
import { replySender } from "./reply/sender.js";
|
|
19
21
|
import { supportExampleSeed } from "./seeds/example.js";
|
|
20
22
|
import { PACKAGE_NAME, PACKAGE_VERSION } from "./version.generated.js";
|
|
21
23
|
import { noopEmit } from "@pithy-sh/core/src/audit/recorder";
|
|
22
24
|
import { defineCapability } from "@pithy-sh/core/src/capability/capability";
|
|
23
25
|
import { workflowBindings } from "@pithy-sh/core/src/workflow/bindings";
|
|
24
|
-
import { isEmailCapability } from "@pithy-sh/email/src/capability";
|
|
25
26
|
//#region src/capability.ts
|
|
26
27
|
/**
|
|
27
28
|
* Sort order of the support migrations within the app database, relative to other capabilities.
|
|
@@ -164,7 +165,8 @@ function support(options = {}) {
|
|
|
164
165
|
inapplicableSecrets: inapplicableAttachmentSecrets(resolved),
|
|
165
166
|
workflows: supportWorkflows,
|
|
166
167
|
requiredBindings,
|
|
167
|
-
|
|
168
|
+
inboundRouting: { addresses: resolved.inboundAddresses },
|
|
169
|
+
settings: supportSendingSettings(resolved),
|
|
168
170
|
databases: { app: {
|
|
169
171
|
binding: "DB",
|
|
170
172
|
tables: supportTables,
|
|
@@ -182,8 +184,7 @@ function support(options = {}) {
|
|
|
182
184
|
...resolved.staff ? { powers: [resolved.staff.readPower, resolved.staff.assignPower] } : {},
|
|
183
185
|
compose: ({ capabilities }) => {
|
|
184
186
|
assertStaffComposition(resolved.staff, capabilities);
|
|
185
|
-
|
|
186
|
-
wiring.enqueueEmail = email?.enqueue;
|
|
187
|
+
wiring.enqueueEmail = replySender(capabilities, resolved);
|
|
187
188
|
wiring.peers = senderPeers(capabilities, resolved);
|
|
188
189
|
},
|
|
189
190
|
client: () => clientProjection(resolved, mountPath),
|
package/dist/config/config.d.ts
CHANGED
|
@@ -128,6 +128,7 @@ export type SupportSubmissionConfig = z.output<typeof SupportSubmissionConfig>;
|
|
|
128
128
|
export declare const SupportReplyConfig: z.ZodObject<{
|
|
129
129
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
130
130
|
replyToAddress: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
|
|
131
|
+
fromName: z.ZodDefault<z.ZodString>;
|
|
131
132
|
deliverInApp: z.ZodDefault<z.ZodBoolean>;
|
|
132
133
|
snippets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
133
134
|
label: z.ZodString;
|
|
@@ -179,7 +180,7 @@ export type SupportSearchConfig = z.output<typeof SupportSearchConfig>;
|
|
|
179
180
|
export declare function supportSemantic(search: SupportSearchConfig): SupportSemanticConfig | undefined;
|
|
180
181
|
/** The full support configuration. */
|
|
181
182
|
/**
|
|
182
|
-
* **Who of the app's own signed-in users works the inbox
|
|
183
|
+
* **Who of the app's own signed-in users works the inbox.** Off until named.
|
|
183
184
|
*
|
|
184
185
|
* The inbox holds every customer's correspondence, so a signed-in user reaches it only through a power the
|
|
185
186
|
* adopter names in their own role catalog, held in the organization the request acts for — the same seam
|
|
@@ -238,6 +239,7 @@ export declare const SupportConfig: z.ZodObject<{
|
|
|
238
239
|
reply: z.ZodPrefault<z.ZodObject<{
|
|
239
240
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
240
241
|
replyToAddress: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
|
|
242
|
+
fromName: z.ZodDefault<z.ZodString>;
|
|
241
243
|
deliverInApp: z.ZodDefault<z.ZodBoolean>;
|
|
242
244
|
snippets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
243
245
|
label: z.ZodString;
|
|
@@ -269,10 +271,10 @@ export type SupportConfigInput = z.input<typeof SupportConfig>;
|
|
|
269
271
|
* All three default `true`, so the ordinary project is unaffected by this predicate existing.
|
|
270
272
|
*
|
|
271
273
|
* Two callers, and they must agree: `capability.ts` declares the binding only when this is true, and
|
|
272
|
-
* `pithy support provision` creates the bucket only when this is true.
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
274
|
+
* `pithy support provision` creates the bucket only when this is true. Were they to ask different
|
|
275
|
+
* questions, a project that wanted uploads but no mail attachments would get a binding pointing at a
|
|
276
|
+
* bucket nothing had created, and every submitted file would be dropped with a warning. One predicate is
|
|
277
|
+
* what makes that unrepresentable.
|
|
276
278
|
*/
|
|
277
279
|
export declare function supportNeedsBucket(config: SupportConfig): boolean;
|
|
278
280
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -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
|
|
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;;;;;;;;;;iBA2C5B,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
|
@@ -128,6 +128,7 @@ const SupportSubmissionConfig = z.object({
|
|
|
128
128
|
const SupportReplyConfig = z.object({
|
|
129
129
|
enabled: z.boolean().default(true).describe("Allow replies from the control-plane route. Off makes the inbox read-only, which is a reasonable setting for a project that answers support from its own mail client."),
|
|
130
130
|
replyToAddress: z.string().optional().transform((address) => address === void 0 ? void 0 : parseAddress(address) ?? normalizeAddress(address)).describe("The `Reply-To` a reply carries — the address the customer's answer comes back to, which must be one of `inboundAddresses` or the conversation ends there. Defaults to the inbox address the thread arrived on, which is almost always what you want."),
|
|
131
|
+
fromName: z.string().min(1).max(120).default("{agent}").describe("The display name a mailed reply is sent under, with `{agent}` replaced by the answering agent's name: `{agent} at Acme` sends `\"Jim Scott at Acme\" <support@help.example.com>`. The address is the inbox the thread arrived at, or `reply.replyToAddress` for a thread whose inbox is not in `inboundAddresses` or that arrived at none. With no agent name the email capability's `fromName` is used instead; a name with no `{agent}`, such as `Acme Support`, is used for every reply."),
|
|
131
132
|
deliverInApp: z.boolean().default(false).describe("Answer an `app` thread by storing the reply for the submitter to read in the app, instead of mailing it. **A choice, not a fallback.** Turning on Email Routing takes over the zone's MX, so a project already running mail on that domain cannot receive support replies without consequences everywhere else on it — and for a submitter who is a signed-in user sitting on the screen they wrote from, the answer is better placed there anyway. In-app delivery also happens automatically when there is no address to reply from and no email capability to send with, whatever this says; the setting is what makes it reachable for a project whose mail works fine. It never applies to an `email` thread: a mail thread's sender has no read-back, so a stored answer there is one nobody would ever see."),
|
|
132
133
|
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
134
|
}).describe("Reply settings — whether replies are allowed, where the answer goes, and what to offer as a starting point.");
|
|
@@ -176,7 +177,7 @@ function supportSemantic(search) {
|
|
|
176
177
|
}
|
|
177
178
|
/** The full support configuration. */
|
|
178
179
|
/**
|
|
179
|
-
* **Who of the app's own signed-in users works the inbox
|
|
180
|
+
* **Who of the app's own signed-in users works the inbox.** Off until named.
|
|
180
181
|
*
|
|
181
182
|
* The inbox holds every customer's correspondence, so a signed-in user reaches it only through a power the
|
|
182
183
|
* adopter names in their own role catalog, held in the organization the request acts for — the same seam
|
|
@@ -217,10 +218,10 @@ const SupportConfig = z.object({
|
|
|
217
218
|
* All three default `true`, so the ordinary project is unaffected by this predicate existing.
|
|
218
219
|
*
|
|
219
220
|
* Two callers, and they must agree: `capability.ts` declares the binding only when this is true, and
|
|
220
|
-
* `pithy support provision` creates the bucket only when this is true.
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
221
|
+
* `pithy support provision` creates the bucket only when this is true. Were they to ask different
|
|
222
|
+
* questions, a project that wanted uploads but no mail attachments would get a binding pointing at a
|
|
223
|
+
* bucket nothing had created, and every submitted file would be dropped with a warning. One predicate is
|
|
224
|
+
* what makes that unrepresentable.
|
|
224
225
|
*/
|
|
225
226
|
function supportNeedsBucket(config) {
|
|
226
227
|
return config.attachments.enabled || config.attachments.retainRaw || config.submission.attachments.enabled;
|
package/dist/http/handlers.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { type SenderPeers } from "../link/sender";
|
|
|
15
15
|
import { sendReply } from "../reply/send";
|
|
16
16
|
import type { SupportReplySnippets } from "../reply/snippets";
|
|
17
17
|
import type { SupportAssignResponse, SupportFlagsResponse, SupportMyThreadResponse, SupportMyThreadsResponse, SupportReclassifiedResponse, SupportReplySentResponse, SupportReplyView, SupportSearchResponse, SupportSubmissionResponse, SupportThreadResponse, SupportThreadsResponse, SupportThreadView } from "./responses";
|
|
18
|
-
import type { ArchiveThreadInput, AssignThreadInput, FlagsInput, ListThreadsQuery, MyThreadsQuery, RepliesQuery, ReplyInput,
|
|
18
|
+
import type { ArchiveThreadInput, AssignThreadInput, FlagsInput, ListThreadsQuery, MyThreadsQuery, RepliesQuery, ReplyInput, SearchThreadsBody, SubmitFeedbackInput } from "./schemas";
|
|
19
19
|
/**
|
|
20
20
|
* The support handlers — every one of them behind the `control-plane` gate, and every one of them
|
|
21
21
|
* taking already-validated values.
|
|
@@ -71,7 +71,7 @@ export declare function listInbox(deps: HandlerDeps, query: ListThreadsQuery, vi
|
|
|
71
71
|
* Search the inbox: keyword and semantic, ranked as one. The semantic seams are whatever this Worker
|
|
72
72
|
* was given — absent bindings skip that backend and say so, rather than failing the search.
|
|
73
73
|
*/
|
|
74
|
-
export declare function searchInbox(deps: HandlerDeps, query:
|
|
74
|
+
export declare function searchInbox(deps: HandlerDeps, query: SearchThreadsBody, viewer: string): Promise<SupportSearchResponse>;
|
|
75
75
|
/** Read one conversation in full. */
|
|
76
76
|
export declare function readConversation(deps: HandlerDeps, threadId: string): Promise<SupportThreadResponse>;
|
|
77
77
|
/** Mark a conversation done, or reopen it. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/http/handlers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAGnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAGpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAErE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAQ9D,OAAO,KAAK,EACV,qBAAqB,EAErB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,2BAA2B,EAC3B,wBAAwB,EACxB,gBAAgB,EAChB,qBAAqB,EACrB,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,UAAU,EACV,
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/http/handlers.ts"],"names":[],"mappings":"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"}
|
package/dist/http/handlers.js
CHANGED
|
@@ -55,7 +55,7 @@ async function searchInbox(deps, query, viewer) {
|
|
|
55
55
|
q: query.q,
|
|
56
56
|
...query.mode !== void 0 ? { mode: query.mode } : {},
|
|
57
57
|
...query.limit !== void 0 ? { limit: query.limit } : {},
|
|
58
|
-
archived: query.archived ===
|
|
58
|
+
archived: query.archived === true,
|
|
59
59
|
category: query.category,
|
|
60
60
|
declaredCategory: query.declaredCategory,
|
|
61
61
|
priority: query.priority,
|
|
@@ -91,7 +91,8 @@ async function presentAttachments(deps, attachments) {
|
|
|
91
91
|
size: attachment.size,
|
|
92
92
|
sha256: attachment.sha256,
|
|
93
93
|
inline: attachment.inline,
|
|
94
|
-
url
|
|
94
|
+
url,
|
|
95
|
+
messageId: attachment.messageId
|
|
95
96
|
};
|
|
96
97
|
}));
|
|
97
98
|
}
|
package/dist/http/responses.d.ts
CHANGED
|
@@ -138,6 +138,7 @@ export declare const SupportAttachmentView: z.ZodObject<{
|
|
|
138
138
|
sha256: z.ZodString;
|
|
139
139
|
inline: z.ZodBoolean;
|
|
140
140
|
url: z.ZodNullable<z.ZodString>;
|
|
141
|
+
messageId: z.ZodOptional<z.ZodString>;
|
|
141
142
|
}, z.core.$strip>;
|
|
142
143
|
export type SupportAttachmentView = z.output<typeof SupportAttachmentView>;
|
|
143
144
|
/** One of the sender's purchases, flattened to what a support console renders. */
|
|
@@ -326,7 +327,7 @@ export declare const SupportSearchResultView: z.ZodObject<{
|
|
|
326
327
|
similarity: z.ZodOptional<z.ZodNumber>;
|
|
327
328
|
}, z.core.$strip>;
|
|
328
329
|
export type SupportSearchResultView = z.output<typeof SupportSearchResultView>;
|
|
329
|
-
/** `
|
|
330
|
+
/** `POST {base}/threads/search`. */
|
|
330
331
|
export declare const SupportSearchResponse: z.ZodObject<{
|
|
331
332
|
mode: z.ZodEnum<{
|
|
332
333
|
hybrid: "hybrid";
|
|
@@ -503,6 +504,7 @@ export declare const SupportThreadResponse: z.ZodObject<{
|
|
|
503
504
|
sha256: z.ZodString;
|
|
504
505
|
inline: z.ZodBoolean;
|
|
505
506
|
url: z.ZodNullable<z.ZodString>;
|
|
507
|
+
messageId: z.ZodOptional<z.ZodString>;
|
|
506
508
|
}, z.core.$strip>>;
|
|
507
509
|
sender: z.ZodObject<{
|
|
508
510
|
authenticated: z.ZodBoolean;
|
|
@@ -700,9 +702,8 @@ export type SupportFlagsResponse = z.output<typeof SupportFlagsResponse>;
|
|
|
700
702
|
* Below is the same projection for the other consumer: a management client reading a Worker **it does
|
|
701
703
|
* not control**. That client is crossing a trust boundary — a fork, a bug, a half-finished deploy, or a
|
|
702
704
|
* hostile Worker each put a member here that no enum of ours declares — and under the strict schema one
|
|
703
|
-
* such token costs it the entire response
|
|
704
|
-
*
|
|
705
|
-
* then had to widen our shape locally, which is the hand-held mirror `#113` exists to forbid.
|
|
705
|
+
* such token costs it the entire response: one unrecognized `channel` and a support pane renders none of
|
|
706
|
+
* its conversations, and the client is left widening our shape locally, the hand-held mirror the kit forbids.
|
|
706
707
|
*
|
|
707
708
|
* So the tolerance is published rather than reimplemented, by the one pattern
|
|
708
709
|
* `@pithy-sh/core/src/projection/asRead` states for the whole kit: same objects, every enum read as a
|
|
@@ -843,7 +844,7 @@ export declare const SupportThreadsResponseAsRead: z.ZodObject<{
|
|
|
843
844
|
nextCursor: z.ZodNullable<z.ZodString>;
|
|
844
845
|
}, z.core.$strip>;
|
|
845
846
|
export type SupportThreadsResponseAsRead = z.output<typeof SupportThreadsResponseAsRead>;
|
|
846
|
-
/** `
|
|
847
|
+
/** `POST {base}/threads/search`, as a client that does not control the Worker must read it. */
|
|
847
848
|
export declare const SupportSearchResponseAsRead: z.ZodObject<{
|
|
848
849
|
mode: z.ZodString;
|
|
849
850
|
backends: z.ZodObject<{
|
|
@@ -953,6 +954,7 @@ export declare const SupportThreadResponseAsRead: z.ZodObject<{
|
|
|
953
954
|
sha256: z.ZodString;
|
|
954
955
|
inline: z.ZodBoolean;
|
|
955
956
|
url: z.ZodNullable<z.ZodString>;
|
|
957
|
+
messageId: z.ZodOptional<z.ZodString>;
|
|
956
958
|
}, z.core.$strip>>;
|
|
957
959
|
sender: z.ZodObject<{
|
|
958
960
|
authenticated: z.ZodBoolean;
|
|
@@ -1111,6 +1113,7 @@ export declare const SupportMyMessageView: z.ZodObject<{
|
|
|
1111
1113
|
sha256: z.ZodString;
|
|
1112
1114
|
inline: z.ZodBoolean;
|
|
1113
1115
|
url: z.ZodNullable<z.ZodString>;
|
|
1116
|
+
messageId: z.ZodOptional<z.ZodString>;
|
|
1114
1117
|
}, z.core.$strip>>;
|
|
1115
1118
|
sentAt: z.ZodISODateTime;
|
|
1116
1119
|
}, z.core.$strip>;
|
|
@@ -1168,6 +1171,7 @@ export declare const SupportMyThreadResponse: z.ZodObject<{
|
|
|
1168
1171
|
sha256: z.ZodString;
|
|
1169
1172
|
inline: z.ZodBoolean;
|
|
1170
1173
|
url: z.ZodNullable<z.ZodString>;
|
|
1174
|
+
messageId: z.ZodOptional<z.ZodString>;
|
|
1171
1175
|
}, z.core.$strip>>;
|
|
1172
1176
|
sentAt: z.ZodISODateTime;
|
|
1173
1177
|
}, z.core.$strip>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../src/http/responses.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../src/http/responses.ts"],"names":[],"mappings":"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"}
|
package/dist/http/responses.js
CHANGED
|
@@ -27,16 +27,14 @@ import { asRead } from "@pithy-sh/core/src/projection/asRead";
|
|
|
27
27
|
*
|
|
28
28
|
* **A browser imports this file, so it reaches no module that needs the Workers runtime.** That is not
|
|
29
29
|
* a preference about bundle size; it is what "a client validates what comes back" means once the client
|
|
30
|
-
* is a Vite program with the DOM lib and no Workers types
|
|
31
|
-
*
|
|
32
|
-
* which took `pithy-sh/dashboard`'s client build down on an error in `@pithy-sh/auth` (#419). The rule is
|
|
33
|
-
* held by `tooling/browser-scopes`, which compiles this module with `types: []` and asks the compiler
|
|
30
|
+
* is a Vite program with the DOM lib and no Workers types: an import of `link/sender.ts` would bring D1,
|
|
31
|
+
* Kysely and two capabilities' table maps into that build. The rule is held by `tooling/browser-scopes`, which compiles this module with `types: []` and asks the compiler
|
|
34
32
|
* which files that program included.
|
|
35
33
|
*
|
|
36
34
|
* **A field added here later is `.optional()`, not merely `.nullable()`.** This module is read across a
|
|
37
35
|
* version boundary — a management client validates a response with this schema against a customer's
|
|
38
36
|
* Worker at whatever kit version it is on — so an additive required key fails `safeParse` for everyone
|
|
39
|
-
* below that release and takes the whole pane with it
|
|
37
|
+
* below that release and takes the whole pane with it. Absent then means *this Worker cannot
|
|
40
38
|
* say*, which is a different fact from `null`.
|
|
41
39
|
*/
|
|
42
40
|
/** Where a page resumes, or the end of the list. */
|
|
@@ -104,7 +102,8 @@ const SupportAttachmentView = z.object({
|
|
|
104
102
|
size: z.number().int().describe("The size in bytes."),
|
|
105
103
|
sha256: z.string().describe("The content digest, so a client can tell two identical attachments apart from one."),
|
|
106
104
|
inline: z.boolean().describe("Whether it was referenced inline in the HTML body rather than attached."),
|
|
107
|
-
url: z.string().nullable().describe("A signed URL valid for a few minutes, or null when no credentials were available to sign one. The `storageKey` is never here: it is server-derived precisely so a client cannot name an object or guess the one beside it.")
|
|
105
|
+
url: z.string().nullable().describe("A signed URL valid for a few minutes, or null when no credentials were available to sign one. The `storageKey` is never here: it is server-derived precisely so a client cannot name an object or guess the one beside it."),
|
|
106
|
+
messageId: z.string().optional().describe("The id of the message it arrived on, so a client can show each file under the message that carried it. Absent from a Worker that predates it.")
|
|
108
107
|
}).describe("One attachment as a client receives it — metadata and a signed URL, never the storage key.");
|
|
109
108
|
/** One of the sender's purchases, flattened to what a support console renders. */
|
|
110
109
|
const SenderPurchaseView = z.object({
|
|
@@ -185,7 +184,7 @@ const SupportSearchResultView = z.object({
|
|
|
185
184
|
bm25: z.number().optional().describe("That message's FTS5 `bm25`, raw: lower is better, and comparable only within one search. Absent when FTS5 did not run or did not match it — including on the `LIKE` scan, which has no score."),
|
|
186
185
|
similarity: z.number().optional().describe("That message's similarity to the query in the semantic index, raw: higher is closer. Absent when semantic search did not run or did not match it. Never normalized against `bm25` — the two are reported as they are.")
|
|
187
186
|
}).describe("One thread a search found, named by its best-matching message, with that message's raw scores.");
|
|
188
|
-
/** `
|
|
187
|
+
/** `POST {base}/threads/search`. */
|
|
189
188
|
const SupportSearchResponse = z.object({
|
|
190
189
|
mode: z.enum([
|
|
191
190
|
"keyword",
|
|
@@ -218,7 +217,7 @@ const SupportAssignmentView = z.object({
|
|
|
218
217
|
const SupportThreadResponse = z.object({
|
|
219
218
|
thread: SupportThreadView.describe("The conversation."),
|
|
220
219
|
messages: z.array(SupportMessageView).describe("Its messages, oldest first — a conversation reads downward."),
|
|
221
|
-
attachments: z.array(SupportAttachmentView).describe("Its attachments, across every message
|
|
220
|
+
attachments: z.array(SupportAttachmentView).describe("Its attachments, across every message, each naming the message it arrived on in `messageId`."),
|
|
222
221
|
sender: SenderContextView.describe("What the app already knows about the sender."),
|
|
223
222
|
replies: z.array(SupportReplyView).describe("The canned replies, ordered for this thread's category."),
|
|
224
223
|
assignments: z.array(SupportAssignmentView).optional().describe("Every change of the assignee, oldest first. Empty when nobody was ever assigned; absent from a Worker that predates assignment.")
|
|
@@ -277,9 +276,8 @@ const SupportFlagsResponse = z.object({ ok: z.literal(true).describe("Always tru
|
|
|
277
276
|
* Below is the same projection for the other consumer: a management client reading a Worker **it does
|
|
278
277
|
* not control**. That client is crossing a trust boundary — a fork, a bug, a half-finished deploy, or a
|
|
279
278
|
* hostile Worker each put a member here that no enum of ours declares — and under the strict schema one
|
|
280
|
-
* such token costs it the entire response
|
|
281
|
-
*
|
|
282
|
-
* then had to widen our shape locally, which is the hand-held mirror `#113` exists to forbid.
|
|
279
|
+
* such token costs it the entire response: one unrecognized `channel` and a support pane renders none of
|
|
280
|
+
* its conversations, and the client is left widening our shape locally, the hand-held mirror the kit forbids.
|
|
283
281
|
*
|
|
284
282
|
* So the tolerance is published rather than reimplemented, by the one pattern
|
|
285
283
|
* `@pithy-sh/core/src/projection/asRead` states for the whole kit: same objects, every enum read as a
|
|
@@ -303,7 +301,7 @@ const SupportListedThreadViewAsRead = asRead(SupportListedThreadView);
|
|
|
303
301
|
const SupportMessageViewAsRead = asRead(SupportMessageView);
|
|
304
302
|
/** `GET {base}/threads`, as a client that does not control the Worker must read it. */
|
|
305
303
|
const SupportThreadsResponseAsRead = asRead(SupportThreadsResponse);
|
|
306
|
-
/** `
|
|
304
|
+
/** `POST {base}/threads/search`, as a client that does not control the Worker must read it. */
|
|
307
305
|
const SupportSearchResponseAsRead = asRead(SupportSearchResponse);
|
|
308
306
|
/** `GET {base}/threads/:id`, as a client that does not control the Worker must read it. */
|
|
309
307
|
const SupportThreadResponseAsRead = asRead(SupportThreadResponse);
|
package/dist/http/routes.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { type HandlerDeps } from "./handlers";
|
|
|
8
8
|
* The support routes, their verification strategies, and what each accepts:
|
|
9
9
|
*
|
|
10
10
|
* GET /support/threads → the inbox (control-plane: support:threads:read) query: ListThreadsQuery
|
|
11
|
-
*
|
|
11
|
+
* POST /support/threads/search → search (control-plane: support:threads:read) json: SearchThreadsBody
|
|
12
12
|
* GET /support/threads/:id → one thread (control-plane: support:threads:read) param: ThreadIdParam
|
|
13
13
|
* POST /support/threads/:id/archive → done/reopen (control-plane: support:threads:archive) param + json
|
|
14
14
|
* POST /support/threads/:id/assign → who has it (control-plane: support:threads:assign) param + json
|
|
@@ -58,8 +58,8 @@ import { type HandlerDeps } from "./handlers";
|
|
|
58
58
|
* person who most needs to reach support is often the one whose access is broken, and a role the kit
|
|
59
59
|
* invented would make one adopter's account model a condition on everybody's ability to report a bug.
|
|
60
60
|
*
|
|
61
|
-
* But an adopter whose *own* model makes some submissions act-on-behalf-of —
|
|
62
|
-
*
|
|
61
|
+
* But an adopter whose *own* model makes some submissions act-on-behalf-of — a discount application made
|
|
62
|
+
* for an organization, which a member may not make — needs somewhere to
|
|
63
63
|
* put that check, and it must not be the client. **The seam for it already exists, in the composition
|
|
64
64
|
* contract rather than in this capability's config**, and it is documented here because it was not
|
|
65
65
|
* discoverable rather than because it was missing:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/http/routes.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAC;AAM7E,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EAGL,KAAK,WAAW,EAWjB,MAAM,YAAY,CAAC;AAoCpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkGG;AAEH,2CAA2C;AAC3C,MAAM,WAAW,oBAAoB;IACnC,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qDAAqD;IACrD,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAChE;;;OAGG;IACH,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC5B;AAuCD,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,oBAAoB,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,KAAK,IAAI,
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/http/routes.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAC;AAM7E,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EAGL,KAAK,WAAW,EAWjB,MAAM,YAAY,CAAC;AAoCpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkGG;AAEH,2CAA2C;AAC3C,MAAM,WAAW,oBAAoB;IACnC,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qDAAqD;IACrD,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAChE;;;OAGG;IACH,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC5B;AAuCD,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,oBAAoB,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,KAAK,IAAI,CAsNtG"}
|
package/dist/http/routes.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { requireAuth, requireStaff } from "./guards.js";
|
|
4
4
|
import { archiveConversation, assignConversation, listInbox, listMyThreads, listReplies, readConversation, readMyThread, reclassifyConversation, replyToConversation, searchInbox, submitFeedbackRequest, updateFlags } from "./handlers.js";
|
|
5
5
|
import { SupportArchiveResponse, SupportAssignResponse, SupportFlagsResponse, SupportMyThreadResponse, SupportMyThreadsResponse, SupportReclassifiedResponse, SupportRepliesResponse, SupportReplySentResponse, SupportSearchResponse, SupportSubmissionResponse, SupportThreadResponse, SupportThreadsResponse } from "./responses.js";
|
|
6
|
-
import { ArchiveThreadInput, AssignThreadInput, FlagsInput, ListThreadsQuery, MyThreadsQuery, RepliesQuery, ReplyInput,
|
|
6
|
+
import { ArchiveThreadInput, AssignThreadInput, FlagsInput, ListThreadsQuery, MyThreadsQuery, RepliesQuery, ReplyInput, SearchThreadsBody, SubmitFeedbackInput, ThreadIdParam } from "./schemas.js";
|
|
7
7
|
import { SUPPORT_THREADS_ARCHIVE_SCOPE, SUPPORT_THREADS_ASSIGN_SCOPE, SUPPORT_THREADS_FLAG_SCOPE, SUPPORT_THREADS_READ_SCOPE, SUPPORT_THREADS_RECLASSIFY_SCOPE, SUPPORT_THREADS_REPLY_SCOPE } from "./scopes.js";
|
|
8
8
|
import { InternalError } from "@pithy-sh/core/src/error/pithyError";
|
|
9
9
|
import { requireControlPlane } from "@pithy-sh/core/src/controlPlane/http/guard";
|
|
@@ -54,12 +54,13 @@ function registerSupportRoutes(options) {
|
|
|
54
54
|
scopes: [SUPPORT_THREADS_READ_SCOPE],
|
|
55
55
|
responses: { 200: SupportThreadsResponse }
|
|
56
56
|
}), requireControlPlane(SUPPORT_THREADS_READ_SCOPE), validate("query", ListThreadsQuery), async (c) => c.json(await listInbox(await resolve(c), c.req.valid("query"), viewer(c))));
|
|
57
|
-
app.
|
|
57
|
+
app.post(`${base}/threads/search`, describeRoute({
|
|
58
58
|
summary: "Search the inbox, by words and by meaning, ranked as one.",
|
|
59
|
+
description: "The words travel in the body, because they are often a customer's address or order number and a query string is written to every request log between the client and the Worker. Read-only.",
|
|
59
60
|
verification: "control-plane",
|
|
60
61
|
scopes: [SUPPORT_THREADS_READ_SCOPE],
|
|
61
62
|
responses: { 200: SupportSearchResponse }
|
|
62
|
-
}), requireControlPlane(SUPPORT_THREADS_READ_SCOPE), validate("
|
|
63
|
+
}), requireControlPlane(SUPPORT_THREADS_READ_SCOPE), validate("json", SearchThreadsBody), async (c) => c.json(await searchInbox(await resolve(c), c.req.valid("json"), viewer(c))));
|
|
63
64
|
app.get(`${base}/replies`, describeRoute({
|
|
64
65
|
summary: "List the canned replies.",
|
|
65
66
|
verification: "control-plane",
|
package/dist/http/schemas.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export type ThreadIdParam = z.infer<typeof ThreadIdParam>;
|
|
|
21
21
|
/**
|
|
22
22
|
* The inbox listing query.
|
|
23
23
|
*
|
|
24
|
-
* **Strict, and there is no `q`.** Listing lists; `
|
|
24
|
+
* **Strict, and there is no `q`.** Listing lists; `POST {basePath}/threads/search` searches. An unknown
|
|
25
25
|
* key is refused as `validation/invalid_input` rather than ignored, so a client sending `q` is told so
|
|
26
26
|
* instead of getting the unfiltered inbox back as though it were the answer.
|
|
27
27
|
*/
|
|
@@ -60,12 +60,13 @@ export declare const SupportSearchMode: z.ZodEnum<{
|
|
|
60
60
|
semantic: "semantic";
|
|
61
61
|
}>;
|
|
62
62
|
export type SupportSearchMode = z.infer<typeof SupportSearchMode>;
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
/**
|
|
64
|
+
* The body of `POST {basePath}/threads/search`: the words, the mode, how many, and the inbox listing's filters.
|
|
65
|
+
* A body, not a query string, because the words are often a customer's address or order number and a URL is
|
|
66
|
+
* written to every request log between the client and the Worker. Strict, so a misspelled filter is refused
|
|
67
|
+
* rather than silently widening the search.
|
|
68
|
+
*/
|
|
69
|
+
export declare const SearchThreadsBody: z.ZodObject<{
|
|
69
70
|
category: z.ZodOptional<z.ZodString>;
|
|
70
71
|
declaredCategory: z.ZodOptional<z.ZodString>;
|
|
71
72
|
priority: z.ZodOptional<z.ZodEnum<{
|
|
@@ -91,9 +92,10 @@ export declare const SearchThreadsQuery: z.ZodObject<{
|
|
|
91
92
|
keyword: "keyword";
|
|
92
93
|
semantic: "semantic";
|
|
93
94
|
}>>;
|
|
94
|
-
limit: z.ZodOptional<z.
|
|
95
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
96
|
+
archived: z.ZodOptional<z.ZodBoolean>;
|
|
95
97
|
}, z.core.$strict>;
|
|
96
|
-
export type
|
|
98
|
+
export type SearchThreadsBody = z.infer<typeof SearchThreadsBody>;
|
|
97
99
|
/** The archive/unarchive body. */
|
|
98
100
|
export declare const ArchiveThreadInput: z.ZodObject<{
|
|
99
101
|
archived: z.ZodBoolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/http/schemas.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/http/schemas.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAcxB;;;;;;;;;;;GAWG;AAEH,+BAA+B;AAC/B,eAAO,MAAM,aAAa;;iBAOsC,CAAC;AACjE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAyC1D;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;kBAiBmE,CAAC;AACjG,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,yBAAyB;AACzB,eAAO,MAAM,iBAAiB;;;;EAI3B,CAAC;AACJ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAwBsF,CAAC;AACrH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,kCAAkC;AAClC,eAAO,MAAM,kBAAkB;;iBAQuB,CAAC;AACvD,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,mCAAmC;AACnC,eAAO,MAAM,aAAa;;;iBAOoE,CAAC;AAC/F,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,yDAAyD;AACzD,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,iGAAiG;AACjG,eAAO,MAAM,iBAAiB;;;;;;iBAeiC,CAAC;AAChE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,sBAAsB;AACtB,eAAO,MAAM,UAAU;;;iBAmB0B,CAAC;AAClD,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,iCAAiC;AACjC,eAAO,MAAM,UAAU;;;iBAWmC,CAAC;AAC3D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB;;;;iBA4B8D,CAAC;AACpG,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;iBAiCqF,CAAC;AACtH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,uCAAuC;AACvC,eAAO,MAAM,cAAc;;;iBAiBxB,CAAC;AACJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,sCAAsC;AACtC,eAAO,MAAM,YAAY;;iBAM4B,CAAC;AACtD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC"}
|
package/dist/http/schemas.js
CHANGED
|
@@ -6,6 +6,7 @@ import { UNASSIGNED } from "../data/assignment.js";
|
|
|
6
6
|
import { SupportSubmissionContext } from "../data/message.js";
|
|
7
7
|
import "../store/paging.js";
|
|
8
8
|
import { z } from "zod";
|
|
9
|
+
import { freeText } from "@pithy-sh/core/src/http/freeText";
|
|
9
10
|
import { PresenceParticipantName, PresenceParticipantRef } from "@pithy-sh/core/src/presence/frames";
|
|
10
11
|
//#region src/http/schemas.ts
|
|
11
12
|
/** An assignee reference, as the listing filters on it and an assignment names it. */
|
|
@@ -40,7 +41,7 @@ const threadFilterFields = {
|
|
|
40
41
|
/**
|
|
41
42
|
* The inbox listing query.
|
|
42
43
|
*
|
|
43
|
-
* **Strict, and there is no `q`.** Listing lists; `
|
|
44
|
+
* **Strict, and there is no `q`.** Listing lists; `POST {basePath}/threads/search` searches. An unknown
|
|
44
45
|
* key is refused as `validation/invalid_input` rather than ignored, so a client sending `q` is told so
|
|
45
46
|
* instead of getting the unfiltered inbox back as though it were the answer.
|
|
46
47
|
*/
|
|
@@ -55,12 +56,18 @@ const SupportSearchMode = z.enum([
|
|
|
55
56
|
"semantic",
|
|
56
57
|
"hybrid"
|
|
57
58
|
]).describe("`keyword` finds the exact words — FTS5 when `search.fts` is on, the `LIKE` scan otherwise. `semantic` finds the meaning, through the message index. `hybrid` runs both and ranks them as one.");
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The body of `POST {basePath}/threads/search`: the words, the mode, how many, and the inbox listing's filters.
|
|
61
|
+
* A body, not a query string, because the words are often a customer's address or order number and a URL is
|
|
62
|
+
* written to every request log between the client and the Worker. Strict, so a misspelled filter is refused
|
|
63
|
+
* rather than silently widening the search.
|
|
64
|
+
*/
|
|
65
|
+
const SearchThreadsBody = z.object({
|
|
66
|
+
...threadFilterFields,
|
|
67
|
+
q: freeText(z.string().trim().min(1).max(200)).describe("What to search for, in the customer's words or yours. An order number, an error, or a description."),
|
|
61
68
|
mode: SupportSearchMode.optional().describe("Which backends to run. Absent runs every one that is on, as `hybrid`. `semantic` or `hybrid` while `search.semantic` is off is refused, naming the flag."),
|
|
62
|
-
limit: z.
|
|
63
|
-
|
|
69
|
+
limit: z.number().int().min(1).max(50).optional().describe(`How many threads to return, best first. Default 20, at most 50. No cursor: a ranked list has a top, not pages.`),
|
|
70
|
+
archived: z.boolean().optional().describe("`true` for the done pile, `false` or absent for the open inbox — which is the default on purpose.")
|
|
64
71
|
}).strict().describe("A search over the inbox: the words, the mode, how many, and the same filters the inbox listing takes.");
|
|
65
72
|
/** The archive/unarchive body. */
|
|
66
73
|
const ArchiveThreadInput = z.object({ archived: z.boolean().describe("`true` marks the conversation done; `false` reopens it. Explicit rather than a toggle, so a retried request lands on the state the caller meant rather than flipping it back.") }).describe("Mark a conversation done, or reopen it.");
|
|
@@ -79,7 +86,7 @@ const AssignThreadInput = z.object({
|
|
|
79
86
|
/** The reply body. */
|
|
80
87
|
const ReplyInput = z.object({
|
|
81
88
|
body: z.string().min(1).max(5e4).describe("The reply text, as a human wrote and edited it. Rendered HTML-escaped into the adopter's email shell — it is prose, never markup."),
|
|
82
|
-
agentName: z.string().min(1).max(80).optional().describe("Who is answering, signed at the bottom. Omitted rather than guessed.")
|
|
89
|
+
agentName: z.string().min(1).max(80).regex(/^[^@<>"]*$/, "A name may not contain @, <, > or a double quote.").optional().describe("Who is answering, signed at the bottom and written into the reply's From line. Omitted rather than guessed. May not contain `@`, `<`, `>` or `\"`, so the display name cannot pass for an address.")
|
|
83
90
|
}).describe("An answer to send to the customer.");
|
|
84
91
|
/** The per-viewer flags body. */
|
|
85
92
|
const FlagsInput = z.object({
|
|
@@ -127,4 +134,4 @@ const MyThreadsQuery = z.object({
|
|
|
127
134
|
/** The canned-reply catalog query. */
|
|
128
135
|
const RepliesQuery = z.object({ category: CategoryFilter.optional().describe("Order the catalog for this category — its snippets first, then the general-purpose ones. Ordering, never filtering: a misclassified thread must not hide the snippet its operator actually needs.") }).describe("How to order the canned reply catalog.");
|
|
129
136
|
//#endregion
|
|
130
|
-
export { ArchiveThreadInput, AssignThreadInput, AssigneeInput, FlagsInput, ListThreadsQuery, MAX_ASSIGNMENT_NOTE_CHARS, MyThreadsQuery, RepliesQuery, ReplyInput,
|
|
137
|
+
export { ArchiveThreadInput, AssignThreadInput, AssigneeInput, FlagsInput, ListThreadsQuery, MAX_ASSIGNMENT_NOTE_CHARS, MyThreadsQuery, RepliesQuery, ReplyInput, SearchThreadsBody, SubmitFeedbackInput, SubmittedAttachmentInput, SupportSearchMode, ThreadIdParam };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../src/http/scopes.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sDAAsD,CAAC;AAClG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6CAA6C,CAAC;AAErF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,iGAAiG;AACjG,eAAO,MAAM,0BAA0B,EAAE,iBAA0C,CAAC;AAEpF,oFAAoF;AACpF,eAAO,MAAM,6BAA6B,EAAE,iBAA6C,CAAC;AAE1F;;;GAGG;AACH,eAAO,MAAM,2BAA2B,EAAE,iBAA2C,CAAC;AAEtF,8EAA8E;AAC9E,eAAO,MAAM,gCAAgC,EAAE,iBAAgD,CAAC;AAEhG,wGAAwG;AACxG,eAAO,MAAM,0BAA0B,EAAE,iBAA0C,CAAC;AAEpF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,EAAE,iBAA4C,CAAC;AAExF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,EAAE,SAAS,iBAAiB,EAOpE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../src/http/scopes.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sDAAsD,CAAC;AAClG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6CAA6C,CAAC;AAErF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,iGAAiG;AACjG,eAAO,MAAM,0BAA0B,EAAE,iBAA0C,CAAC;AAEpF,oFAAoF;AACpF,eAAO,MAAM,6BAA6B,EAAE,iBAA6C,CAAC;AAE1F;;;GAGG;AACH,eAAO,MAAM,2BAA2B,EAAE,iBAA2C,CAAC;AAEtF,8EAA8E;AAC9E,eAAO,MAAM,gCAAgC,EAAE,iBAAgD,CAAC;AAEhG,wGAAwG;AACxG,eAAO,MAAM,0BAA0B,EAAE,iBAA0C,CAAC;AAEpF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,EAAE,iBAA4C,CAAC;AAExF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,EAAE,SAAS,iBAAiB,EAOpE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,EAAE,CA4E5E"}
|
package/dist/http/scopes.js
CHANGED
|
@@ -76,11 +76,12 @@ function supportAdminRoutes(basePath) {
|
|
|
76
76
|
summary: "List the inbox, newest first, filtered by category, priority, assignee, and archived."
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
|
-
method: "
|
|
79
|
+
method: "POST",
|
|
80
80
|
operation: "threads.search",
|
|
81
81
|
path: `${basePath}/threads/search`,
|
|
82
82
|
scope: SUPPORT_THREADS_READ_SCOPE,
|
|
83
83
|
kind: "reads",
|
|
84
|
+
readInBody: true,
|
|
84
85
|
summary: "Search the inbox by words and by meaning, ranked as one, with the same filters as the list."
|
|
85
86
|
},
|
|
86
87
|
{
|