Commit f562e34
Make assistant support flows survive real customers and real developers (#14)
## Why
A live customer conversation on the browser assistant surfaced four
compounding failures around one delayed-delivery question:
1. **"My delivery is delayed." never reached the deterministic order
intake** — the trigger required the literal word "order", so the model
improvised, gluing its own question onto the scripted contact-card line.
2. **A dropped WebSocket erased the flow** — contact details, the
pending order lookup, and the conversation history all lived on the
connection, and mobile browsers drop sockets constantly. After
reconnecting, the customer's order number hit the off-topic guardrail
dead end.
3. **A bare order-number reply was not recognized** outside the scripted
continuation sentinel.
4. **Store order-name formats containing "/"** (e.g. fiscal-year formats
such as `#2026-27/7903`) could not be parsed at all, so lookups were
impossible for every order in such a store.
There was also no way to test any of this before production, because
Turnstile's strict claim verification rejects Cloudflare's documented
testing keys — and the `eval:voice` gate turned out to be unrunnable on
a fresh clone (workspace-local wrangler path that npm hoists away).
## What
- **Durable session state**: contact, verification, pending escalation,
and pending order flow move from per-connection state into Durable
Object session storage. A dropped socket keeps history and state for a
10-minute grace period and resumes mid-flow on reconnect; a scheduled
cleanup wipes everything if nobody returns.
- **Broader deterministic intake**: shipment nouns (delivery, package,
parcel, shipment) near tracking signals trigger the scripted order flow,
not just "order".
- **Bare order numbers always enter the order flow**: with contact on
file they look up directly; without, they request the contact card once.
"/" is accepted in order numbers end to end.
- **Local Turnstile test mode**: on the local surface only, a successful
siteverify under Cloudflare's documented testing secrets is accepted
without claim checks. Non-local surfaces keep strict verification, so a
production misconfiguration still fails closed.
- **Working eval harness + prompt hardening**: `eval:voice` resolves
wrangler through the package; order-result guidance is strengthened
(ticket offer is now non-optional). Evals pass **12/13**; the remaining
case (an unfamiliar-product how-to that must search before answering) is
model-flaky and pre-existing — a deterministic forced-search fix was
tried and reverted because it conflicts with the no-tools-on-off-topic
bar. Tracked as follow-up.
- **Developer-machine hygiene**: the vitest pool pins bindings a local
`.dev.vars` would leak into tests, and the public-readiness scan judges
what git can publish (`git ls-files`) instead of failing on properly
ignored local state.
## Validation
- `npm run check` green: 219 tests (including new reconnect-resume,
bare-number, trigger-phrase, and testing-secret coverage), all
typechecks, generated-file check, and the public scan — with a
`.dev.vars` present.
- `npm run eval:voice`: 12/13 with the known-flaky case above.
- Live WebSocket replay of the original customer scenario against
`wrangler dev`: deterministic card on the delivery phrasing, contact
survives a simulated connection drop, and the bare order number after
reconnect performs a real lookup with a truthful provider-failure reply
instead of the off-topic refusal.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Morrow Contributors <codeyogi911@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>1 parent d1dfa78 commit f562e34
10 files changed
Lines changed: 321 additions & 72 deletions
File tree
- apps/desk
- scripts
- src
- security
- voice
- test
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
| 3 | + | |
2 | 4 | | |
3 | 5 | | |
4 | 6 | | |
| |||
57 | 59 | | |
58 | 60 | | |
59 | 61 | | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
60 | 68 | | |
61 | | - | |
| 69 | + | |
62 | 70 | | |
63 | 71 | | |
64 | 72 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
33 | 47 | | |
34 | 48 | | |
35 | 49 | | |
| |||
58 | 72 | | |
59 | 73 | | |
60 | 74 | | |
| 75 | + | |
61 | 76 | | |
62 | 77 | | |
63 | 78 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
77 | | - | |
| 77 | + | |
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| |||
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
97 | | - | |
| 97 | + | |
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
| |||
0 commit comments