@mccaskie-labs/workbench-config 0.3.1 → 0.4.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/CHANGELOG.md +12 -0
- package/capabilities/ai.json +212 -7
- package/capabilities/intake.json +212 -0
- package/capabilities/schema.json +4 -2
- package/dist/capabilities.gen.d.ts +471 -6
- package/dist/capabilities.gen.js +589 -6
- package/dist/capability-schema.d.ts +3 -1
- package/dist/capability-schema.js +1 -0
- package/package.json +1 -1
package/dist/capabilities.gen.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by `npm run capabilities:build` from capabilities/*.json. Do not edit.
|
|
2
2
|
// check:capabilities fails in Checks when this file is stale.
|
|
3
|
-
export const MANIFEST_VERSION = "
|
|
3
|
+
export const MANIFEST_VERSION = "7ad8cef4de58";
|
|
4
4
|
export const capabilities = {
|
|
5
5
|
"ai": {
|
|
6
6
|
"$schema": "./schema.json",
|
|
@@ -25,7 +25,9 @@ export const capabilities = {
|
|
|
25
25
|
"checkWriting",
|
|
26
26
|
"writingStops",
|
|
27
27
|
"writingNotes",
|
|
28
|
-
"writingFixNote"
|
|
28
|
+
"writingFixNote",
|
|
29
|
+
"Ai.reply",
|
|
30
|
+
"checkGuide"
|
|
29
31
|
]
|
|
30
32
|
},
|
|
31
33
|
"config": [
|
|
@@ -197,6 +199,13 @@ export const capabilities = {
|
|
|
197
199
|
"text": "Name a Workbench voice and spelling with the voice and spelling options, so the Gateway adds the text, or bring the app's own voice. Never paste Workbench's voice text into a prompt.",
|
|
198
200
|
"enforced_by": "gateway",
|
|
199
201
|
"probe": "ai.voice"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"id": "ai.reply-facts-from-code",
|
|
205
|
+
"kind": "must",
|
|
206
|
+
"text": "Draft replies with ai.reply from facts worked out by code (prices and dates from quote(), never from the AI), and show the draft in renderReplyBox for a person to check and copy. Never send a draft automatically, and never let a draft with needs_human_check go out unread.",
|
|
207
|
+
"enforced_by": "review",
|
|
208
|
+
"probe": "ai.reply"
|
|
200
209
|
}
|
|
201
210
|
],
|
|
202
211
|
"examples": [
|
|
@@ -213,6 +222,13 @@ export const capabilities = {
|
|
|
213
222
|
"lang": "ts",
|
|
214
223
|
"code": "const r = await ai.text({ model: 'claude-default', job: 'article.write', voice: 'conversational', spelling: 'en-AU', prompt })\nconst issues = checkWriting(r.text, { voice: 'conversational' })\nif (writingStops(issues).length > 0) await retryWith(prompt + writingFixNote(issues))",
|
|
215
224
|
"probe": "ai.voice"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"id": "ai.example.reply",
|
|
228
|
+
"title": "Draft a reply to an enquiry",
|
|
229
|
+
"lang": "ts",
|
|
230
|
+
"code": "import { REPLY_GUIDES } from './replies.generated'\nconst ai = createAi({ key: env.WORKBENCH_GATEWAY_KEY, env: workbenchEnv(env), url: env.WORKBENCH_GATEWAY_URL, replyGuides: REPLY_GUIDES })\nconst q = quote(settings, ask)\nconst draft = await ai.reply({\n guide: 'venue-quote',\n facts: { venue_name: venue.name, date: ask.date, available: q.available, packages: q.options.map((o) => ({ name: o.package, price: o.total.amount })), alternatives: q.alternatives.map((a) => a.date) },\n incoming: { text, intake: read },\n language: account.reply_language,\n})\nreturn html(renderReplyBox(draft, { againAction: `/enquiries/${id}/reply` }))",
|
|
231
|
+
"probe": "ai.reply"
|
|
216
232
|
}
|
|
217
233
|
],
|
|
218
234
|
"probes": [
|
|
@@ -292,20 +308,26 @@ export const capabilities = {
|
|
|
292
308
|
"half": "runtime",
|
|
293
309
|
"expect": "pass",
|
|
294
310
|
"cost_hint_usd": 0.008
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"id": "ai.reply",
|
|
314
|
+
"half": "runtime",
|
|
315
|
+
"expect": "pass",
|
|
316
|
+
"cost_hint_usd": 0.005
|
|
295
317
|
}
|
|
296
318
|
],
|
|
297
319
|
"offer": {
|
|
298
320
|
"pattern": "feature_or_content",
|
|
299
|
-
"when": "The client wants the app to write, summarise, sort, answer questions or pull details out of text with AI, as a feature visitors use, or wants wording for the site written once.",
|
|
321
|
+
"when": "The client wants the app to write, summarise, sort, answer questions or pull details out of text with AI, as a feature visitors use, or wants wording for the site written once, or wants ready-to-paste replies to the messages the business gets (quote requests, booking questions).",
|
|
300
322
|
"say": "I'll do this with Workbench AI, which uses Claude through Workbench, so the app holds no AI key and spending has a monthly cap.",
|
|
301
323
|
"answers_file": "workbench/ai/<name>.json",
|
|
302
324
|
"per": "feature or piece of wording",
|
|
303
|
-
"answers_format": "For a feature: `{ \"format\": 1, \"route\": \"feature\", \"name\", \"about\" (the client's words), \"who\", \"cap_usd\", \"keep\", \"job\" }`, where `job` is the `X-Workbench-Job` the feature's calls send (`<name>` with dots, such as `quotes.estimate`), so its spending shows on its own line in Control. Write it, then build the feature with the package. For wording made once: `npx workbench-ai pick <name> <round>-<draft>` writes it from the draft the client picked, with the round's request ids and cost; never write one from nothing. Then put the wording into the strings catalogue (the workbench-strings skill), keyed for where it is shown. Drafts and their previews stay in `workbench/ai/drafts/`, which isn't committed.",
|
|
325
|
+
"answers_format": "For a feature: `{ \"format\": 1, \"route\": \"feature\", \"name\", \"about\" (the client's words), \"who\", \"cap_usd\", \"keep\", \"job\" }`, where `job` is the `X-Workbench-Job` the feature's calls send (`<name>` with dots, such as `quotes.estimate`), so its spending shows on its own line in Control. Write it, then build the feature with the package. For wording made once: `npx workbench-ai pick <name> <round>-<draft>` writes it from the draft the client picked, with the round's request ids and cost; never write one from nothing. Then put the wording into the strings catalogue (the workbench-strings skill), keyed for where it is shown. Drafts and their previews stay in `workbench/ai/drafts/`, which isn't committed. For replies: write the guide itself to `workbench/replies/<name>.json` (format 1: `name`, `version`, `voice`, `spelling`, `max_words`, `max_questions`, `facts` as a small JSON Schema with `x-money` on amounts and `format: date` on dates, `ask_order`, `guide` lines, `examples`, `never`), then run `npx workbench-ai replies generate`, which checks it and writes `src/replies.generated.ts`; never edit that file. Offer samples with `npx workbench-ai replies drafts <name> --languages <the client's languages>` (two to four, with the development key; tell the client the cost it prints) and keep the one the client picks with `npx workbench-ai replies pick <name> <round>-<draft>`, which makes it the guide's first example.",
|
|
304
326
|
"questions": [
|
|
305
327
|
{
|
|
306
328
|
"id": "ai.route",
|
|
307
329
|
"sets": "route",
|
|
308
|
-
"ask": "Is this something visitors use on the site,
|
|
330
|
+
"ask": "Is this something visitors use on the site, something made once for the site, or replies to the messages you get?",
|
|
309
331
|
"style": "cards",
|
|
310
332
|
"answers": [
|
|
311
333
|
{
|
|
@@ -317,10 +339,15 @@ export const capabilities = {
|
|
|
317
339
|
"label": "Made once for the site",
|
|
318
340
|
"value": "content",
|
|
319
341
|
"means": "Wording for a page, written once and kept: an About page, a welcome, product descriptions."
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
"label": "Replies to messages",
|
|
345
|
+
"value": "reply",
|
|
346
|
+
"means": "A ready-to-paste reply to a customer's message, with your prices and dates filled in from your own details. A person checks it and copies it; nothing is sent by itself."
|
|
320
347
|
}
|
|
321
348
|
],
|
|
322
349
|
"must_ask": true,
|
|
323
|
-
"note": "Ask it even when the client's words seem to say, as a quick check: the two routes are built and paid for differently."
|
|
350
|
+
"note": "Ask it even when the client's words seem to say, as a quick check: the two routes are built and paid for differently. Replies are their own route: the facts (prices, dates) come from the app's own code, so ask where those facts come from before anything else."
|
|
324
351
|
},
|
|
325
352
|
{
|
|
326
353
|
"id": "ai.who",
|
|
@@ -453,6 +480,196 @@ export const capabilities = {
|
|
|
453
480
|
]
|
|
454
481
|
},
|
|
455
482
|
"note": "Name the piece after where it goes (about_us, home_blurb): lower case and underscores. The client's own words, with any facts they give (when they started, where they are), go in --about, because the drafts never invent facts and leave [gaps] for the owner instead."
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
"id": "ai.reply.purpose",
|
|
486
|
+
"sets": "reply.purpose",
|
|
487
|
+
"ask": "What are these replies answering?",
|
|
488
|
+
"style": "cards",
|
|
489
|
+
"answers": [
|
|
490
|
+
{
|
|
491
|
+
"label": "Quote and price requests",
|
|
492
|
+
"value": "quote",
|
|
493
|
+
"means": "\"How much for Dec 12, 15 pax?\": the reply gives the price and asks only for what's missing."
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
"label": "Availability questions",
|
|
497
|
+
"value": "availability",
|
|
498
|
+
"means": "\"Available pa po ba sa Sabado?\": the reply says yes or offers other dates."
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
"label": "Booking or appointment requests",
|
|
502
|
+
"value": "booking",
|
|
503
|
+
"means": "\"May slot po ba bukas?\": the reply lists the free times and asks which one."
|
|
504
|
+
}
|
|
505
|
+
],
|
|
506
|
+
"other": {
|
|
507
|
+
"kind": "text",
|
|
508
|
+
"min": 3,
|
|
509
|
+
"max": 200,
|
|
510
|
+
"description": "What the messages are, in a few words."
|
|
511
|
+
},
|
|
512
|
+
"must_ask": true,
|
|
513
|
+
"when": {
|
|
514
|
+
"id": "ai.route",
|
|
515
|
+
"is": [
|
|
516
|
+
"reply"
|
|
517
|
+
]
|
|
518
|
+
},
|
|
519
|
+
"note": "Name the guide after it (venue-quote, appointment-slots): lower case and dashes. Say that prices and dates always come from the app's own details (workbench-availability's quote() where it's a booking), never from the AI."
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
"id": "ai.reply.facts",
|
|
523
|
+
"sets": "reply.facts",
|
|
524
|
+
"ask": "Here are the details a reply can use. Keep, change or add?",
|
|
525
|
+
"style": "table",
|
|
526
|
+
"must_ask": true,
|
|
527
|
+
"when": {
|
|
528
|
+
"id": "ai.route",
|
|
529
|
+
"is": [
|
|
530
|
+
"reply"
|
|
531
|
+
]
|
|
532
|
+
},
|
|
533
|
+
"note": "Propose one row per fact from the client's words and what the app already holds: its name, what it is, where the app gets it, and whether it is money (x-money with the currency) or a date. Only these can appear in a reply; a fact the app doesn't have can't be quoted. Then ask which details a reply may ask the customer for, most important first (ask_order); intake's missing fields are asked from that list only."
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
"id": "ai.reply.language",
|
|
537
|
+
"sets": "reply.language",
|
|
538
|
+
"ask": "Which language should replies use?",
|
|
539
|
+
"style": "cards",
|
|
540
|
+
"answers": [
|
|
541
|
+
{
|
|
542
|
+
"label": "Match the message",
|
|
543
|
+
"value": "match",
|
|
544
|
+
"means": "English gets English, Taglish gets Taglish, Tagalog gets Tagalog. When it can't tell, it uses your fallback and asks you to check."
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
"label": "Always Taglish",
|
|
548
|
+
"value": "taglish"
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
"label": "Always English",
|
|
552
|
+
"value": "en"
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
"label": "Always Tagalog",
|
|
556
|
+
"value": "tl"
|
|
557
|
+
}
|
|
558
|
+
],
|
|
559
|
+
"when": {
|
|
560
|
+
"id": "ai.route",
|
|
561
|
+
"is": [
|
|
562
|
+
"reply"
|
|
563
|
+
]
|
|
564
|
+
},
|
|
565
|
+
"note": "This is the default. Where the app has accounts (a venue each), make it each account's own setting in its portal, defaulting to this, and pass it to ai.reply as language; with match, pass the account's fallback as fallbackLanguage."
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
"id": "ai.reply.voice",
|
|
569
|
+
"sets": "reply.voice",
|
|
570
|
+
"ask": "Which tone?",
|
|
571
|
+
"style": "cards",
|
|
572
|
+
"answers": [
|
|
573
|
+
{
|
|
574
|
+
"label": "Conversational",
|
|
575
|
+
"value": "conversational",
|
|
576
|
+
"means": "Warm and natural, like a helpful person at the front desk."
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
"label": "Casual",
|
|
580
|
+
"value": "casual",
|
|
581
|
+
"means": "Relaxed and friendly, like chatting with a regular."
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
"label": "Plain",
|
|
585
|
+
"value": "plain",
|
|
586
|
+
"means": "Short and clear, nothing extra."
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
"label": "Formal",
|
|
590
|
+
"value": "formal",
|
|
591
|
+
"means": "Polite and proper, for a business that keeps a formal tone."
|
|
592
|
+
}
|
|
593
|
+
],
|
|
594
|
+
"when": {
|
|
595
|
+
"id": "ai.route",
|
|
596
|
+
"is": [
|
|
597
|
+
"reply"
|
|
598
|
+
]
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
"id": "ai.reply.length",
|
|
603
|
+
"sets": "reply.max_words",
|
|
604
|
+
"ask": "How long can a reply be, at most?",
|
|
605
|
+
"style": "cards",
|
|
606
|
+
"answers": [
|
|
607
|
+
{
|
|
608
|
+
"label": "About 90 words",
|
|
609
|
+
"value": 90,
|
|
610
|
+
"means": "Room for a price or two and one question."
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
"label": "About 50 words",
|
|
614
|
+
"value": 50,
|
|
615
|
+
"means": "A quick chat reply."
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
"label": "About 150 words",
|
|
619
|
+
"value": 150,
|
|
620
|
+
"means": "Room for several packages or options."
|
|
621
|
+
}
|
|
622
|
+
],
|
|
623
|
+
"other": {
|
|
624
|
+
"kind": "whole",
|
|
625
|
+
"min": 20,
|
|
626
|
+
"max": 300,
|
|
627
|
+
"description": "Words, from 20 to 300."
|
|
628
|
+
},
|
|
629
|
+
"when": {
|
|
630
|
+
"id": "ai.route",
|
|
631
|
+
"is": [
|
|
632
|
+
"reply"
|
|
633
|
+
]
|
|
634
|
+
},
|
|
635
|
+
"note": "A reply up to a quarter over passes quietly; one over twice as long is sent back to be written again, then flagged for a person to check."
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
"id": "ai.reply.never",
|
|
639
|
+
"sets": "reply.never",
|
|
640
|
+
"ask": "Is there anything a reply must never say?",
|
|
641
|
+
"style": "cards",
|
|
642
|
+
"answers": [
|
|
643
|
+
{
|
|
644
|
+
"label": "The usual three",
|
|
645
|
+
"value": "usual",
|
|
646
|
+
"means": "Never promise a discount, confirm a booking, or ask for payment details. Code checks these as well as telling the AI."
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
"label": "The usual three, and more",
|
|
650
|
+
"value": "usual_and_more",
|
|
651
|
+
"means": "Those three plus things of your own (type them in Other)."
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
"label": "Nothing",
|
|
655
|
+
"value": "none",
|
|
656
|
+
"means": "No rules beyond Workbench's own content rules."
|
|
657
|
+
}
|
|
658
|
+
],
|
|
659
|
+
"other": {
|
|
660
|
+
"kind": "list",
|
|
661
|
+
"min": 1,
|
|
662
|
+
"max": 10,
|
|
663
|
+
"description": "Each thing a reply must never do, in a few words (give medical advice, mention the pool)."
|
|
664
|
+
},
|
|
665
|
+
"must_ask": true,
|
|
666
|
+
"when": {
|
|
667
|
+
"id": "ai.route",
|
|
668
|
+
"is": [
|
|
669
|
+
"reply"
|
|
670
|
+
]
|
|
671
|
+
},
|
|
672
|
+
"note": "Write each as a never line in the guide. Lines about payment details, discounts and confirming bookings are also checked by code; others go into the prompt only, so say so if the client's line matters a lot."
|
|
456
673
|
}
|
|
457
674
|
],
|
|
458
675
|
"not_asked": [
|
|
@@ -475,6 +692,9 @@ export const capabilities = {
|
|
|
475
692
|
"command": "npx workbench-ai drafts text <name> --about \"<what it is for, with the client's facts>\""
|
|
476
693
|
}
|
|
477
694
|
},
|
|
695
|
+
"styles": {
|
|
696
|
+
"prefix": "wb-reply-"
|
|
697
|
+
},
|
|
478
698
|
"delivery": {
|
|
479
699
|
"choice": "built_in",
|
|
480
700
|
"intro": "Writing, summarising and answering questions with Claude, with a monthly spending cap."
|
|
@@ -3579,6 +3799,369 @@ export const capabilities = {
|
|
|
3579
3799
|
"intro": "Pictures made by AI from a description."
|
|
3580
3800
|
}
|
|
3581
3801
|
},
|
|
3802
|
+
"intake": {
|
|
3803
|
+
"$schema": "./schema.json",
|
|
3804
|
+
"format": 1,
|
|
3805
|
+
"component": "intake",
|
|
3806
|
+
"title": "Message intake through the Gateway",
|
|
3807
|
+
"summary": "Reads one social media message (a Messenger chat, a group post or a comment), as a screenshot, pasted text or both, into one of the project's own intake types: the type, its fields, what is missing or reads two ways, and whether a person should check it. The types are files in the project's repo, sent with every call. Workbench keeps only the call's facts, never the image or the text; the project can keep them in its own bucket.",
|
|
3808
|
+
"status": "built",
|
|
3809
|
+
"surfaces": {
|
|
3810
|
+
"gateway": [
|
|
3811
|
+
"POST /v1/intake/read"
|
|
3812
|
+
],
|
|
3813
|
+
"package": [
|
|
3814
|
+
"Ai.intake",
|
|
3815
|
+
"defineIntakeTypes",
|
|
3816
|
+
"IntakeTypes",
|
|
3817
|
+
"IntakeResult"
|
|
3818
|
+
]
|
|
3819
|
+
},
|
|
3820
|
+
"config": [
|
|
3821
|
+
{
|
|
3822
|
+
"name": "WORKBENCH_GATEWAY_KEY",
|
|
3823
|
+
"required": true,
|
|
3824
|
+
"secret": true,
|
|
3825
|
+
"environments": [
|
|
3826
|
+
"staging",
|
|
3827
|
+
"production"
|
|
3828
|
+
],
|
|
3829
|
+
"description": "Project key with the ai scope, delivered as a Worker secret by gw_key_issue"
|
|
3830
|
+
}
|
|
3831
|
+
],
|
|
3832
|
+
"limits": [
|
|
3833
|
+
{
|
|
3834
|
+
"id": "intake.types-per-call",
|
|
3835
|
+
"value": 20,
|
|
3836
|
+
"unit": "types",
|
|
3837
|
+
"per": "request",
|
|
3838
|
+
"enforced_by": "gateway",
|
|
3839
|
+
"refusal": "invalid_request"
|
|
3840
|
+
},
|
|
3841
|
+
{
|
|
3842
|
+
"id": "intake.properties-per-type",
|
|
3843
|
+
"value": 40,
|
|
3844
|
+
"unit": "properties",
|
|
3845
|
+
"per": "type",
|
|
3846
|
+
"enforced_by": "gateway",
|
|
3847
|
+
"refusal": "invalid_request"
|
|
3848
|
+
},
|
|
3849
|
+
{
|
|
3850
|
+
"id": "intake.definition-size",
|
|
3851
|
+
"value": 16384,
|
|
3852
|
+
"unit": "bytes",
|
|
3853
|
+
"per": "type",
|
|
3854
|
+
"enforced_by": "gateway",
|
|
3855
|
+
"refusal": "invalid_request"
|
|
3856
|
+
},
|
|
3857
|
+
{
|
|
3858
|
+
"id": "intake.types-size",
|
|
3859
|
+
"value": 65536,
|
|
3860
|
+
"unit": "bytes",
|
|
3861
|
+
"per": "request",
|
|
3862
|
+
"enforced_by": "gateway",
|
|
3863
|
+
"refusal": "invalid_request"
|
|
3864
|
+
},
|
|
3865
|
+
{
|
|
3866
|
+
"id": "intake.text-length",
|
|
3867
|
+
"value": 20000,
|
|
3868
|
+
"unit": "characters",
|
|
3869
|
+
"per": "request",
|
|
3870
|
+
"enforced_by": "gateway",
|
|
3871
|
+
"refusal": "invalid_request"
|
|
3872
|
+
},
|
|
3873
|
+
{
|
|
3874
|
+
"id": "intake.image-size",
|
|
3875
|
+
"value": 5242880,
|
|
3876
|
+
"unit": "bytes",
|
|
3877
|
+
"per": "request",
|
|
3878
|
+
"enforced_by": "gateway",
|
|
3879
|
+
"refusal": "invalid_request"
|
|
3880
|
+
},
|
|
3881
|
+
{
|
|
3882
|
+
"id": "intake.images-per-call",
|
|
3883
|
+
"value": 1,
|
|
3884
|
+
"unit": "images",
|
|
3885
|
+
"per": "request",
|
|
3886
|
+
"enforced_by": "gateway",
|
|
3887
|
+
"refusal": "invalid_request"
|
|
3888
|
+
}
|
|
3889
|
+
],
|
|
3890
|
+
"errors": [
|
|
3891
|
+
{
|
|
3892
|
+
"code": "invalid_request",
|
|
3893
|
+
"http": 400,
|
|
3894
|
+
"retryable": false,
|
|
3895
|
+
"meaning": "No text or image, an image too large or of another kind, or a type definition that breaks the rules (the message names the type and the JSON path)"
|
|
3896
|
+
},
|
|
3897
|
+
{
|
|
3898
|
+
"code": "budget_exceeded",
|
|
3899
|
+
"http": 402,
|
|
3900
|
+
"retryable": false,
|
|
3901
|
+
"meaning": "The project's budget, its intake cap or the global ceiling is reached"
|
|
3902
|
+
},
|
|
3903
|
+
{
|
|
3904
|
+
"code": "content_refused",
|
|
3905
|
+
"http": 422,
|
|
3906
|
+
"retryable": false,
|
|
3907
|
+
"meaning": "The content rules, or Claude itself, refused the message or the reading"
|
|
3908
|
+
},
|
|
3909
|
+
{
|
|
3910
|
+
"code": "content_check_unavailable",
|
|
3911
|
+
"http": 503,
|
|
3912
|
+
"retryable": true,
|
|
3913
|
+
"meaning": "The content checker is down, so nothing was read"
|
|
3914
|
+
},
|
|
3915
|
+
{
|
|
3916
|
+
"code": "upstream_error",
|
|
3917
|
+
"http": 502,
|
|
3918
|
+
"retryable": true,
|
|
3919
|
+
"meaning": "Anthropic failed after the Gateway's retries"
|
|
3920
|
+
},
|
|
3921
|
+
{
|
|
3922
|
+
"code": "upstream_timeout",
|
|
3923
|
+
"http": 504,
|
|
3924
|
+
"retryable": true,
|
|
3925
|
+
"meaning": "Anthropic didn't answer in time"
|
|
3926
|
+
}
|
|
3927
|
+
],
|
|
3928
|
+
"rules": [
|
|
3929
|
+
{
|
|
3930
|
+
"id": "intake.types-from-files",
|
|
3931
|
+
"kind": "must",
|
|
3932
|
+
"text": "Keep each intake type as a file, workbench/intake/<name>.json, and pass intakeTypes from src/intake.generated.ts. Run npx workbench-ai intake generate after changing one, and keep npx workbench-ai intake generate --check in the project's Checks. Never write definitions inline.",
|
|
3933
|
+
"enforced_by": "types",
|
|
3934
|
+
"probe": "intake.read.text"
|
|
3935
|
+
},
|
|
3936
|
+
{
|
|
3937
|
+
"id": "intake.human-check",
|
|
3938
|
+
"kind": "must",
|
|
3939
|
+
"text": "Act on needsHumanCheck: show the reading to a person (or keep it marked to check) before it becomes a booking, a quote or a reply. It is the Gateway's rule, not the model's score.",
|
|
3940
|
+
"enforced_by": "review",
|
|
3941
|
+
"probe": "intake.read.unknown"
|
|
3942
|
+
},
|
|
3943
|
+
{
|
|
3944
|
+
"id": "intake.personal-marked",
|
|
3945
|
+
"kind": "must",
|
|
3946
|
+
"text": "Mark every field that holds a personal detail with x-personal (name, handle, phone, email, address, photo). An unmarked field whose name or format looks personal is refused, and personal details the type doesn't ask for are blanked in rawText.",
|
|
3947
|
+
"enforced_by": "gateway",
|
|
3948
|
+
"probe": "intake.read.personal-blanked"
|
|
3949
|
+
},
|
|
3950
|
+
{
|
|
3951
|
+
"id": "intake.no-content-kept",
|
|
3952
|
+
"kind": "must_not",
|
|
3953
|
+
"text": "Expect Workbench to keep the image or the text: it keeps only the call's facts. To keep them, set save_input to project in workbench/intake.json and pass store: env.INTAKE_STORE.",
|
|
3954
|
+
"enforced_by": "gateway",
|
|
3955
|
+
"probe": "intake.read.text"
|
|
3956
|
+
},
|
|
3957
|
+
{
|
|
3958
|
+
"id": "intake.subject-is-app-user",
|
|
3959
|
+
"kind": "must_not",
|
|
3960
|
+
"text": "Send the poster's name or number as subject or ref. subject is user:<id> of the app's own user; ref is the app's own record id, such as enquiry:4812.",
|
|
3961
|
+
"enforced_by": "review",
|
|
3962
|
+
"probe": "intake.read.text"
|
|
3963
|
+
},
|
|
3964
|
+
{
|
|
3965
|
+
"id": "intake.bad-type-refused",
|
|
3966
|
+
"kind": "must_not",
|
|
3967
|
+
"text": "Send a type that breaks the format: the Gateway refuses it with invalid_request before any spend.",
|
|
3968
|
+
"enforced_by": "gateway",
|
|
3969
|
+
"probe": "intake.read.bad-type-refused"
|
|
3970
|
+
}
|
|
3971
|
+
],
|
|
3972
|
+
"examples": [
|
|
3973
|
+
{
|
|
3974
|
+
"id": "intake.example.read",
|
|
3975
|
+
"title": "Read a pasted message or a screenshot",
|
|
3976
|
+
"lang": "ts",
|
|
3977
|
+
"code": "import { intakeTypes } from './intake.generated'\n\nconst r = await ai.intake({ types: intakeTypes, text, image, timeZone: country.timeZone.id, job: 'intake.quote-request', subject: `user:${user.id}`, ref: `enquiry:${enquiry.id}`, store: env.INTAKE_STORE })\nif (r.type === 'venue-quote-request') {\n await saveEnquiry({ ...r.data, toCheck: r.needsHumanCheck, missing: r.missing })\n}",
|
|
3978
|
+
"probe": "intake.read.text"
|
|
3979
|
+
}
|
|
3980
|
+
],
|
|
3981
|
+
"probes": [
|
|
3982
|
+
{
|
|
3983
|
+
"id": "intake.read.text",
|
|
3984
|
+
"half": "runtime",
|
|
3985
|
+
"expect": "pass",
|
|
3986
|
+
"smoke": true,
|
|
3987
|
+
"cost_hint_usd": 0.01,
|
|
3988
|
+
"note": "One of HmPo's sample messages as text: the type, the data and no human check"
|
|
3989
|
+
},
|
|
3990
|
+
{
|
|
3991
|
+
"id": "intake.read.unknown",
|
|
3992
|
+
"half": "runtime",
|
|
3993
|
+
"expect": "pass",
|
|
3994
|
+
"cost_hint_usd": 0.01,
|
|
3995
|
+
"note": "A thank-you review comes back unknown, with needs_human_check and unknown_type"
|
|
3996
|
+
},
|
|
3997
|
+
{
|
|
3998
|
+
"id": "intake.read.personal-blanked",
|
|
3999
|
+
"half": "runtime",
|
|
4000
|
+
"expect": "pass",
|
|
4001
|
+
"cost_hint_usd": 0.01,
|
|
4002
|
+
"note": "A message with a phone number the type doesn't ask for: rawText shows [phone]"
|
|
4003
|
+
},
|
|
4004
|
+
{
|
|
4005
|
+
"id": "intake.read.bad-type-refused",
|
|
4006
|
+
"half": "runtime",
|
|
4007
|
+
"expect": "refused_as_expected",
|
|
4008
|
+
"refusal": "invalid_request",
|
|
4009
|
+
"cost_hint_usd": 0,
|
|
4010
|
+
"note": "A type with an unmarked contact_phone field is refused before any spend"
|
|
4011
|
+
}
|
|
4012
|
+
],
|
|
4013
|
+
"offer": {
|
|
4014
|
+
"pattern": "settings",
|
|
4015
|
+
"when": "The client gets enquiries, orders or posts as messages (Messenger, a Facebook group, comments) and wants them read into a form or a list instead of typed up by hand.",
|
|
4016
|
+
"say": "I'll set this up with Workbench message intake: you paste a message or a screenshot and it fills in the details, and flags the ones a person should check. Workbench never keeps the message. Here are the details I'd pull out; tell me what to change.",
|
|
4017
|
+
"answers_file": "workbench/intake/<name>.json",
|
|
4018
|
+
"per": "kind of message",
|
|
4019
|
+
"generator": "npx workbench-ai intake generate",
|
|
4020
|
+
"answers_format": "One type per file, docs/message-intake.md §3: `{ \"format\": 1, \"name\", \"version\": 1, \"description\" (the client's words, made plain), \"not_this\", \"examples\" (1 to 5 real-looking messages with the data each should give), \"glossary\" (the trade's own shorthand), \"schema\" (the fields from intake.fields; required lists what the app can't do without), \"confidence_floor\", \"model\", \"save_input\" }`. The project's own setting is workbench/intake.json, `{ \"format\": 1, \"save_input\": \"none\" | \"project\" }`. Then run the generator and commit src/intake.generated.ts.",
|
|
4021
|
+
"questions": [
|
|
4022
|
+
{
|
|
4023
|
+
"id": "intake.kind",
|
|
4024
|
+
"sets": [
|
|
4025
|
+
"name",
|
|
4026
|
+
"description"
|
|
4027
|
+
],
|
|
4028
|
+
"ask": "What kind of message do you want read?",
|
|
4029
|
+
"style": "table",
|
|
4030
|
+
"other": {
|
|
4031
|
+
"kind": "text",
|
|
4032
|
+
"min": 3,
|
|
4033
|
+
"max": 200,
|
|
4034
|
+
"description": "In the client's words, such as \"quote requests on Messenger\" or \"orders in comments under our posts\"."
|
|
4035
|
+
},
|
|
4036
|
+
"must_ask": true,
|
|
4037
|
+
"note": "Only the client knows. Name the type after it in lower case with hyphens (venue-quote-request). Ask for two or three real messages (names blanked) to use as examples."
|
|
4038
|
+
},
|
|
4039
|
+
{
|
|
4040
|
+
"id": "intake.fields",
|
|
4041
|
+
"sets": [
|
|
4042
|
+
"schema",
|
|
4043
|
+
"examples"
|
|
4044
|
+
],
|
|
4045
|
+
"ask": "Here are the details I'd pull out of each message. Keep them, change some, or add or remove?",
|
|
4046
|
+
"style": "table",
|
|
4047
|
+
"other": {
|
|
4048
|
+
"kind": "list",
|
|
4049
|
+
"min": 1,
|
|
4050
|
+
"max": 40,
|
|
4051
|
+
"description": "Each detail: its name, what it is, whether the app can't do without it, and the choices if it is one of a list."
|
|
4052
|
+
},
|
|
4053
|
+
"must_ask": true,
|
|
4054
|
+
"note": "Propose from intake.kind, as a table of field, meaning, kind (text, number, date, one of a list), and needed. For an enquiry: the date, how many people, what for, budget, extras. Mark as needed only what the app can't act without; the rest are optional."
|
|
4055
|
+
},
|
|
4056
|
+
{
|
|
4057
|
+
"id": "intake.personal",
|
|
4058
|
+
"sets": "schema.x-personal",
|
|
4059
|
+
"ask": "Should it keep the person's name or contact details?",
|
|
4060
|
+
"style": "cards",
|
|
4061
|
+
"answers": [
|
|
4062
|
+
{
|
|
4063
|
+
"label": "No, leave them out",
|
|
4064
|
+
"value": "none",
|
|
4065
|
+
"means": "Names, numbers and handles are blanked in what it reads, and never kept."
|
|
4066
|
+
},
|
|
4067
|
+
{
|
|
4068
|
+
"label": "Their display name only",
|
|
4069
|
+
"value": "name",
|
|
4070
|
+
"means": "The name shown on the message, so the owner knows who asked."
|
|
4071
|
+
},
|
|
4072
|
+
{
|
|
4073
|
+
"label": "Name and how to reach them",
|
|
4074
|
+
"value": "contact",
|
|
4075
|
+
"means": "The name and a phone number or email when the message gives one."
|
|
4076
|
+
}
|
|
4077
|
+
],
|
|
4078
|
+
"must_ask": true,
|
|
4079
|
+
"note": "Each kept detail becomes a field marked x-personal with its kind (name, phone, email). Say that what the app keeps is under its own privacy notice."
|
|
4080
|
+
},
|
|
4081
|
+
{
|
|
4082
|
+
"id": "intake.save",
|
|
4083
|
+
"sets": "save_input",
|
|
4084
|
+
"ask": "Should your app keep a copy of each message (the screenshot and its text)?",
|
|
4085
|
+
"style": "cards",
|
|
4086
|
+
"answers": [
|
|
4087
|
+
{
|
|
4088
|
+
"label": "No, read it and let it go",
|
|
4089
|
+
"value": "none",
|
|
4090
|
+
"means": "Only the filled-in details are kept, by your app."
|
|
4091
|
+
},
|
|
4092
|
+
{
|
|
4093
|
+
"label": "Yes, in your own storage",
|
|
4094
|
+
"value": "project",
|
|
4095
|
+
"means": "Your app keeps the screenshot and text in its own private bucket. Workbench never keeps it."
|
|
4096
|
+
}
|
|
4097
|
+
],
|
|
4098
|
+
"must_ask": true,
|
|
4099
|
+
"note": "project sets save_input in workbench/intake.json and needs the INTAKE_STORE bucket bound to the app (a private R2 bucket <id>-intake per environment, in the projects account). Pass store: env.INTAKE_STORE on every call. Say who will be able to see the copies and for how long."
|
|
4100
|
+
},
|
|
4101
|
+
{
|
|
4102
|
+
"id": "intake.check",
|
|
4103
|
+
"sets": "app.when_unsure",
|
|
4104
|
+
"ask": "When it isn't sure, what should the app do?",
|
|
4105
|
+
"style": "cards",
|
|
4106
|
+
"answers": [
|
|
4107
|
+
{
|
|
4108
|
+
"label": "Ask you to check first",
|
|
4109
|
+
"value": "ask",
|
|
4110
|
+
"means": "The details wait for you to confirm before they are saved."
|
|
4111
|
+
},
|
|
4112
|
+
{
|
|
4113
|
+
"label": "Save it, marked to check",
|
|
4114
|
+
"value": "mark",
|
|
4115
|
+
"means": "It is saved straight away with a \"to check\" mark."
|
|
4116
|
+
},
|
|
4117
|
+
{
|
|
4118
|
+
"label": "Skip it",
|
|
4119
|
+
"value": "skip",
|
|
4120
|
+
"means": "Unsure readings aren't saved; you read the message yourself."
|
|
4121
|
+
}
|
|
4122
|
+
],
|
|
4123
|
+
"note": "This is the app's own behaviour when needsHumanCheck is true; nothing in the type file changes."
|
|
4124
|
+
},
|
|
4125
|
+
{
|
|
4126
|
+
"id": "intake.model",
|
|
4127
|
+
"sets": "model",
|
|
4128
|
+
"ask": "How careful should the reading be?",
|
|
4129
|
+
"style": "cards",
|
|
4130
|
+
"answers": [
|
|
4131
|
+
{
|
|
4132
|
+
"label": "Careful",
|
|
4133
|
+
"value": "claude-default",
|
|
4134
|
+
"means": "About US$0.01 a message."
|
|
4135
|
+
},
|
|
4136
|
+
{
|
|
4137
|
+
"label": "Quick and cheap",
|
|
4138
|
+
"value": "claude-fast",
|
|
4139
|
+
"means": "Cheaper, and misses more in long or messy chats."
|
|
4140
|
+
}
|
|
4141
|
+
],
|
|
4142
|
+
"note": "Recommend careful. Leave model out of the file for claude-default."
|
|
4143
|
+
}
|
|
4144
|
+
],
|
|
4145
|
+
"not_asked": [
|
|
4146
|
+
{
|
|
4147
|
+
"sets": "confidence_floor",
|
|
4148
|
+
"why": "0.75 suits a start; it is tuned from real results, not asked."
|
|
4149
|
+
},
|
|
4150
|
+
{
|
|
4151
|
+
"sets": "glossary",
|
|
4152
|
+
"why": "Claude adds the trade's own shorthand from the client's example messages (DU for day use); the Gateway's built-in Filipino and Taglish glossary covers everyday words."
|
|
4153
|
+
},
|
|
4154
|
+
{
|
|
4155
|
+
"sets": "budget",
|
|
4156
|
+
"why": "Intake has its own monthly cap in the Gateway, set with Control's gw_budget_set (tool intake); offer it only when the client asks about cost."
|
|
4157
|
+
}
|
|
4158
|
+
]
|
|
4159
|
+
},
|
|
4160
|
+
"delivery": {
|
|
4161
|
+
"choice": "optional",
|
|
4162
|
+
"intro": "Turns a pasted message or a screenshot of a chat into a filled-in form, so enquiries don't have to be typed up by hand."
|
|
4163
|
+
}
|
|
4164
|
+
},
|
|
3582
4165
|
"pdf": {
|
|
3583
4166
|
"$schema": "./schema.json",
|
|
3584
4167
|
"format": 1,
|