Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/desk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
},
"WHATSAPP_WABA_ID": {
"description": "The WhatsApp Business Account ID accepted by this deployment's webhook."
},
"SHOPIFY_CUSTOMER_CLIENT_ID": {
"description": "Optional Customer Account API public client ID enabling store-account sign-in on the support portal."
}
}
},
Expand Down
21 changes: 21 additions & 0 deletions apps/desk/scripts/run-voice-evals.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,27 @@ try {
assertNoRepeatedSentence(outageReply, 'order outage reply')
console.log(`PASS order_lookup_unavailable: ${outageReply}`)

// A signed-in store customer asking about "my order" without a number is
// served from list_my_orders — never asked to type the order number first.
const signedInOrder = await turn(
[{ role: 'user', content: 'Where is my order?' }],
{ fixtures: [orderFixture] },
{ signedIn: true, stream: true },
)
const signedInReply = String(signedInOrder.text ?? '')
const signedInTools = Array.isArray(signedInOrder.toolCalls) ? signedInOrder.toolCalls : []
assert(
signedInTools.some((call) => call?.name === 'list_my_orders'),
`signed-in order question must call list_my_orders: ${JSON.stringify(signedInOrder)}`,
)
assert(/4021/.test(signedInReply), `signed-in reply should reference the caller's order: ${signedInReply}`)
assert(
!/what is the order number|order number from your confirmation/i.test(signedInReply),
`signed-in caller must not be asked for the number first: ${signedInReply}`,
)
assertNoRepeatedSentence(signedInReply, 'signed-in order reply')
console.log(`PASS signed_in_order_list: ${signedInReply}`)

// Knowledge-grounded answering: the assistant must consult the help centre
// and answer strictly from article content.
const kbArticle = {
Expand Down
14 changes: 12 additions & 2 deletions apps/desk/scripts/voice-eval-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export default {
kb?: unknown
stream?: unknown
contact?: unknown
signedIn?: unknown
} | null
const messages = messagesFrom(body?.messages)
if (!messages) return Response.json({ error: 'invalid_messages' }, { status: 400 })
Expand All @@ -54,8 +55,10 @@ export default {
: null
// Mirrors production's deferred identity: cases opt into the anonymous
// branch with contact: false, which swaps the ticket/order tools for
// request_contact and flips the prompt branch.
// request_contact and flips the prompt branch. signedIn: true mirrors a
// store-account session, which adds list_my_orders.
const contactPresent = body?.contact !== false
const signedInCase = body?.signedIn === true && contactPresent

const latest = messages.at(-1)
const directResponse = latest?.role === 'user' && typeof latest.content === 'string'
Expand All @@ -75,7 +78,7 @@ export default {
reasoning_effort: null,
chat_template_kwargs: { enable_thinking: false },
}),
system: voiceAgentSystemPrompt('Example Company', { orders: Boolean(ordersCase), contact: contactPresent }),
system: voiceAgentSystemPrompt('Example Company', { orders: Boolean(ordersCase), contact: contactPresent, signedIn: signedInCase }),
messages: prepareVoiceModelMessages(messages as Array<{ role: 'user' | 'assistant'; content: string }>),
tools: {
// Mirrors production: the help-centre tool is always registered.
Expand All @@ -92,6 +95,13 @@ export default {
return articles.length > 0 ? { status: 'ok', articles } : { status: 'no_match' }
},
}),
...(signedInCase && ordersCase ? {
list_my_orders: tool({
description: "List the signed-in caller's most recent orders: names, dates, payment and fulfillment status, totals, and tracking. Use when they ask about an order without giving a number, then confirm which order they mean. Returns only the signed-in caller's own orders.",
inputSchema: z.object({}),
execute: async () => ({ status: 'ok', orders: Array.isArray(ordersCase.fixtures) ? ordersCase.fixtures : [] }),
}),
} : {}),
...(ordersCase && contactPresent ? {
get_order_status: tool({
description: "Look up one order by the caller's order number. The server matches the number together with this session's contact email and returns not_found unless both match; it never reveals whether a number exists for a different email.",
Expand Down
2 changes: 2 additions & 0 deletions apps/desk/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export type Env = Cloudflare.Env & {
SHOPIFY_CLIENT_SECRET?: string
/** Legacy custom-app Admin token; still honored as an alternative to the client credentials grant. */
SHOPIFY_ADMIN_TOKEN?: string
/** Customer Account API public client ID enabling optional customer sign-in on the support portal. */
SHOPIFY_CUSTOMER_CLIENT_ID?: string
/** Test-only deterministic voice verification code; never bind this in a deployed environment. */
VOICE_TEST_OTP_CODE?: string
/** Meta callback token used only for the WhatsApp webhook GET challenge. */
Expand Down
Loading
Loading