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
10 changes: 9 additions & 1 deletion apps/desk/scripts/run-voice-evals.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { spawn } from 'node:child_process'
import { createRequire } from 'node:module'
import { dirname, join } from 'node:path'

const port = Number(process.env.VOICE_EVAL_PORT ?? 8794)
if (!Number.isInteger(port) || port < 1024 || port > 65_535) throw new Error('VOICE_EVAL_PORT must be an unprivileged TCP port')
Expand Down Expand Up @@ -57,8 +59,14 @@ function assertNoRepeatedSentence(reply, label) {
}
}

// npm workspaces may hoist wrangler to the repository root, so resolve the
// package instead of assuming a workspace-local node_modules path. The bin
// entry is not in wrangler's export map, so locate it from the package root.
const require = createRequire(import.meta.url)
const wranglerEntry = join(dirname(require.resolve('wrangler/package.json')), 'bin', 'wrangler.js')

const worker = spawn(process.execPath, [
'node_modules/wrangler/bin/wrangler.js',
wranglerEntry,
'dev',
'--config',
'wrangler.voice-eval.jsonc',
Expand Down
15 changes: 15 additions & 0 deletions apps/desk/src/security/public-write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ function normalizedHostname(value: string): string {
return value.trim().toLowerCase().replace(/\.$/, '')
}

// Cloudflare's documented Turnstile testing secret keys accept or reject every
// token, but their siteverify responses do not echo the action or hostname
// claims, so the strict claim checks would fail every proof and leave the
// widget flow untestable outside production. On the local development surface
// only, a successful verification under a documented testing secret is
// accepted without claim checks. Non-local surfaces keep strict verification
// even when a testing secret is configured, so a production misconfiguration
// still fails closed.
const TURNSTILE_TESTING_SECRETS = new Set([
'1x0000000000000000000000000000000AA',
'2x0000000000000000000000000000000AA',
'3x0000000000000000000000000000000AA',
])

export async function verifyTurnstileProof(
input: {
token: string | null | undefined
Expand Down Expand Up @@ -58,6 +72,7 @@ export async function verifyTurnstileProof(
return { ok: false, reason: 'turnstile_unavailable' }
}
if (!result.success) return { ok: false, reason: result['error-codes']?.[0] ?? 'turnstile_failed' }
if (input.local && TURNSTILE_TESTING_SECRETS.has(env.TURNSTILE_SECRET_KEY)) return { ok: true }
if (result.action !== input.action) return { ok: false, reason: 'turnstile_action_mismatch' }
if (!result.hostname || normalizedHostname(result.hostname) !== normalizedHostname(input.hostname)) {
return { ok: false, reason: 'turnstile_hostname_mismatch' }
Expand Down
4 changes: 2 additions & 2 deletions apps/desk/src/voice/conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function voiceAgentSystemPrompt(
const contact = options.contact !== false
const ordersAvailable = options.orders === true
const orderCapability = ordersAvailable && contact
? `\nYou can look up the caller's order. For order questions, ask the caller for the order number from their order confirmation email. If any caller message already includes an order number, reuse it and call get_order_status — never ask for it twice. Never ask the caller for an email address; the server already holds this session's email and matches the order automatically. If the tool returns not_found, say you could not find that order for the email on this session: suggest double-checking the number, or restarting the chat with the email used at checkout, and offer to open a ticket instead. If the tool returns unavailable, say you are having trouble checking orders right now — never say the order could not be found — and offer to open a ticket so the team can follow up. Answer order questions only from tool data — never invent order details, and never speculate about whether an order number exists for a different email. When a product question has no documented answer, offer to check the caller's order so a ticket for the team carries the exact product and purchase date. If the caller's latest message only confirms that they shared their name and email after that undocumented-product handoff, reuse an order number from any earlier caller message and call get_order_status; if none exists, ask only for the order number. Do not create a ticket or claim an order check is underway before the lookup.`
? `\nYou can look up the caller's order. For order questions, ask the caller for the order number from their order confirmation email. If any caller message already includes an order number, reuse it and call get_order_status — never ask for it twice. Never ask the caller for an email address; the server already holds this session's email and matches the order automatically. If the tool returns not_found, say you could not find that order for the email on this session: suggest double-checking the number, or restarting the chat with the email used at checkout. If the tool returns unavailable, say you are having trouble checking orders right now — never say the order could not be found. In both cases the reply must end by offering to open a support ticket for the team; never omit that offer. Answer order questions only from tool data — never invent order details, and never speculate about whether an order number exists for a different email. When a product question has no documented answer, offer to check the caller's order so a ticket for the team carries the exact product and purchase date. If the caller's latest message only confirms that they shared their name and email after that undocumented-product handoff, reuse an order number from any earlier caller message and call get_order_status; if none exists, ask only for the order number. Do not create a ticket or claim an order check is underway before the lookup.`
: ordersAvailable
? ''
: `\nOrder lookup is not available in this workspace. Never claim that you can check an order, shipping status, product purchase, or purchase date. For order questions, say you cannot check orders here and offer to open a support ticket for team follow-up.`
Expand All @@ -94,7 +94,7 @@ A question about warranty or another support policy is in scope even when it doe

CAPABILITIES
You can answer support questions from the published help-centre articles and open a support ticket for the caller. Never ask the user for their name or email in tool calls; identity is enforced by the server.
For policy or warranty questions, call search_help_center first. For how-to, product care, shipping, or troubleshooting questions, also call search_help_center first with a short topic query of two to six words. Answer only from the returned article content in at most two short sentences. The matching articles are shown to the caller as links automatically, so point them to the linked guide for the full steps. If the search returns no_match, say you do not have a documented answer for that and offer to open a ticket — do not answer such questions from memory. If it returns unavailable, say you cannot check the help articles right now and offer a ticket. Never include a URL or a link in your reply — the matching articles are already linked for the caller.
For policy or warranty questions, call search_help_center first. For how-to, product care, shipping, or troubleshooting questions, also call search_help_center first with a short topic query of two to six words. Even when you do not recognize the product or the question sounds unusual, search before deciding: never call a product or device question unsupported or out of scope without a search_help_center result for it, and never say you lack information or a documented answer unless search_help_center already returned no_match in this turn. Answer only from the returned article content in at most two short sentences. The matching articles are shown to the caller as links automatically, so point them to the linked guide for the full steps. If the search returns no_match, say you do not have a documented answer for that and offer to open a ticket — do not answer such questions from memory. If it returns unavailable, say you cannot check the help articles right now and offer a ticket. Never include a URL or a link in your reply — the matching articles are already linked for the caller.
${actionCapability}
You cannot look up or report ticket status in this channel. If the caller asks about an existing ticket's status, say that updates arrive by email through their private case link and that you cannot check status here. Never invent or guess a status. Offer to open a new ticket only if they describe a new problem.

Expand Down
Loading
Loading