Third screen, scoped to the signed-in human: the calls Meera transferred to
them, with the handover brief, the live conversation as it happens, and the
co-pilot's quiet suggestions to relay.
The scoping is the server's, not this component's. Each transferred call is a
co-pilot session OWNED BY the human it was routed to, and agents-backend
authorizes every read with `session.user_id == token.sub`. A renderer app-user
token carries the platform user id in `sub`, so someone else's call is a 403 —
no filtering happens in the browser, and none could.
Worth knowing: this talks to agents-backend (/api/agent-sessions) rather than
the app's core/view services, but with the SAME token. agents-backend validates
it against the shared secret and falls back to a DB lookup for user-service
tokens, which omit org_user_id. No new endpoint, no CORS change — it is already
on the console's base URL.
Rendering notes:
- "[live call] …" lines are speech the AI overheard on the bridged call, from
both sides; they render as overheard rather than as chat.
- A lone dash is the co-pilot deliberately staying silent. Dropped, because an
empty suggestion box reads as a bug.
- The server only lists sessions with >= 1 user message, and a co-pilot
session's user messages ARE the overheard lines — so a transfer where the
listener never attached will not appear here even though the bridge worked.
tsc -b and vite build clean.
Demo is for Mahindra, so the console carries their branding and their cars:
BE 6, XEV 9e and XEV 9S replace the placeholder models, in step with the voice
agent's own brief — a model offered in the dropdown that the agent has never
heard of is a call where she has to improvise.
Brand, persona (Meera) and the demo credentials move into config.ts rather than
being scattered through the pages. Accent colour follows.
Login is simplified: credentials are prefilled and the button takes focus, so
signing in during a demo is one keystroke. Both fields stay editable — this is
a dev demo tenant, not a real account.
useState is explicitly typed <string>: DEMO_LOGIN is `as const`, so inferring
from it narrowed the state to a literal type and setEmail/setPassword refused
any other value.
tsc -b and vite build both clean.
Two screens over Zino app 583:
Place a call — lead name, model, and the two numbers that matter: the
customer Aria dials, and the colleague the transfer bridges to. Submits the
Add Lead workflow activity as the signed-in user; the workflow's trigger
places the call server-side, so the zvk_ agent key never reaches the browser.
Live monitor — polls the leads record view every 3s. "Transferred to human"
is call_end_reason == handover, called out on its own because it is the thing
the demo exists to show.
src/api.ts deliberately bypasses two SDK methods; both are SDK bugs:
- workflows.startWorkflow() posts workflow_id, but core's StartRequest reads
workflow_uuid and rejects the call.
- views.getTabularView() does GET /view/recordview, the legacy unscoped route;
the app-scoped one is POST /app/{id}/view/recordview with the query in the
body.
Auth still goes through the SDK, so there is one token and one session.
Base URL is the root, not an app path: login is /usr/login while the app APIs
are under /app/583, so they sit at different depths.
Frontgen scaffold generated by the service's own ScaffoldReactProject, so the
build pipeline conventions (relative base, BASE_HREF placeholder, runtime
config.js) are byte-identical rather than reimplemented.
vite build + tsc -b both clean.