Kibitz

← The Kibitz Engine · deep dive

Host-menu seam — a brand-injected, agent-enabled in-call menu

A generic way for an agent to surface a brand-owned menu inside a Kibitz call (e.g. "Rate the agent"). Kibitz stays agnostic: it only frames a page on a build-fixed origin and passes non-secret call context. The page is the brand's own origin, so it talks to the brand's backend directly. Rating is the first consumer; the mechanism is general (tip, survey, notes, …).

Why this shape

The agent must NOT be in the menu's data path — for rating, the agent is the rated party and never holds the user's wallet/coupon. So the menu is the brand's page (its own origin → its own storage + backend), and the agent can only turn it on. Kibitz locks the origin so a malicious agent can't point the frame at a phishing site.

1. Agent enables it (in its agent-actions@1 manifest)

The agent already publishes registerSchema('agent-actions', …). Add a hostMenu:

{
  "kind": "agent-actions@1",
  "agent": "Expert 🧐",
  "actions": [ /* … normal agent actions … */ ],
  "hostMenu": {
    "path": "rate",            // relative path on the brand origin (required)
    "label": "Rate the agent", // button text (≤40 chars)
    "placement": "controls",   // controls | stage | tile | chat  (default: chat)
    "agent": "kibitzer"        // the agent's OWN marketplace id → passed to the page as ?agent=
  }
}

2. Build configures the allowed origin

menuOrigin — the ONLY origin Kibitz will frame. Via the composable mount (Kibitz.mount({ menuOrigin })) or the Widget menuOrigin prop. Must be https:. Unset → host menus are disabled (default; kibitz.chat unaffected). A rebrand can later source it from VITE_BRAND_MENU_ORIGIN.

3. Kibitz renders + frames it (src/widget/hostMenu.ts, HostMenuBar.tsx)

4. The brand page does the rest (on its own origin)

It reads its own storage (e.g. the coupon) and calls its own backend — Kibitz never sees any of it. For rating, the page POSTs { roomId, agentId, coupon, stars } to its control plane.

5. The page can close itself

window.parent.postMessage({ type: 'kibitz:hostmenu', action: 'close' }, '<the page's own origin>') dismisses the panel (e.g. after submitting). Kibitz only honors messages whose event.origin is the locked menuOrigin. Escape and a backdrop click also close it.

Other uses

Nothing here is rating-specific — Kibitz only frames a trusted page and passes room/agent. The same seam fits a tip jar, a post-call survey, a shared notes/CRM panel, a booking/upsell step, etc. The brand owns the page and the logic; Kibitz owns only the origin lock and the frame.

Security summary

Where it lives

src/widget/hostMenu.ts (pure parse + origin-lock; tests in hostMenu.test.ts), HostMenuBar.tsx (the trigger + iframe panel), the Widget/mount menuOrigin option, and brand.menuOrigin (VITE_BRAND_MENU_ORIGIN). Off by default — a build with no menuOrigin has no host menus.