VendoOverlay to power an inline widget.
@vendoai/vendo/react, which is the import that needs no direct @vendoai/vendo/ui dependency — with one exception. useApprovalModal ships only on @vendoai/vendo/ui/chrome:
The shared read shape
Collection hooks all return the same read fields, so one pattern renders loading, error, and empty states across the whole surface.undefined, so threads.length === 0 means empty or not loaded yet. isLoading is what tells those apart, and it only ever flips for the first fetch — a first-mount spinner never flashes again on a poll.
A failed read keeps the last good collection, so you can render a retry affordance without blanking the surface.
useApp is the exception. It returns a single document, so its app is AppDocument | undefined.
Every hook
There is no generic
execute callback. Hooks that write name their verbs: decide, revoke, disconnect, enable, disable, create, remove, fork, importApp, edit, dryRun, stopRun.
Each returns a promise, and the ones that change the collection refresh it when they resolve. exportApp is the exception, being a pure read that refreshes nothing.
A hook’s isLoading still tracks only the first read, so drive mutation-pending UI from your own await.
Context accessors
There is no
useVendo. useVendoProvider is the React-context accessor, because useVendoContext(data) owns the agent-context name.
Polling
PasspollMs to keep a value fresh without a manual refresh. Polls are self-scheduling rather than interval-driven, so the next tick arms only after the current refresh settles and a slow server never stacks requests.
pollMs for a one-shot fetch on mount. Polling does not pause when the tab is hidden, so pick a cadence you are willing to pay for in the background.
useApp and useVendoStatus never poll. useApp still returns refresh(); useVendoStatus reads once per mount, and remounting is the only way to re-read it.
useSlotApp is the opposite: it polls every 5 seconds by default, because a placement made in the conversation surface has to appear in the slot on its own. Pass { pollMs } to change the cadence, or { enabled: false } to stand it down.
useApprovals and useSlotApp each share one poller per client across every mounted instance, so a page with ten slots still makes one request.
Threads
useThreads reads the same summaries VendoOverlay uses, so a custom conversation list keeps parity with the shipped chrome.
ThreadSummary.title is always a string, so no ?? "Untitled" fallback is needed.
Pair it with useVendoThread(threadId) to drive the streaming turn. It wraps the AI SDK’s useChat, so its vocabulary is the AI SDK’s.
selectedThreadId on purpose. The hook also returns threadId, so destructuring into that name while passing it in is a use-before-declaration error.
setMessages is what an edit-last affordance is built on: drop the last user turn and anything after it, then refill your input from that message. This is the flow the shipped chrome’s Edit affordance uses.
sendMessage mid-stream hands the message straight to the AI SDK.
The “type while it is answering, and it sends when the reply lands” behavior belongs to the shipped chrome’s composer, which holds the draft and re-sends it on the busy edge. Reproduce it by watching status.
Standalone agent chat
useVendoChat is useVendoThread’s thinner sibling, for a page talking to an @vendoai/vendo mount. No provider, no client, no embed chrome, no [Context] block — just the transport, the thread-id round trip, and the two things you have to render for an agent that asks permission.
options is { api, threadId?, onThreadId? }, where api is where you mounted handler().
It keeps nothing in the browser. Reopening a thread reads the transcript back through the mount’s own route, so an approval parked before a reload comes back in interruptions with no client state to have lost.
resume(decisions) posts to the mount’s approvals wire rather than the AI SDK’s local approval channel: the guard’s decision is what unblocks the parked call, and flipping the part in the browser would change what the page draws and nothing about what the agent is doing. A decision that does not land — a 409 for an ask already answered or expired — throws instead of being swallowed.
Apps export and import
useApps exposes exportApp(appId) and importApp(bytes) beside the read fields, so a custom drawer can round-trip an AppDocument without hand-rolling calls to /apps/:id/export and /apps/import.
app_ id and carries over no data, grants, or authority.
Overlay control
useVendoOverlay gives your own chrome programmatic control over VendoOverlay.
useVendoOverlay(options?) accepts one option, defaultOpen?: boolean. Spread overlayProps onto the component and call open, close, or toggle from your own shortcut.
document.body, locks page scroll, marks the page behind the scrim inert, and restores focus to the invoking element on close.
Closing hides the panel without discarding the conversation, so reopening within the same page session restores the prior messages. Call overlay.newConversation() to start fresh.
Approval modal
useApprovalModal is the mount seam for the screen-initiated approval modal, the centered ask a person sees when a button inside a generated view parks on the guard.
The shipped chrome already mounts it on VendoSlot, on in-thread app cards and the workspace stage, on the chat embeds, and on mounted remix forks. Reach for the hook when you render a TreeView, AppFrame, or a bespoke slot yourself.
approval.onParked down to whichever component fires it, and render approval.modal alongside.
refusalCopy
refusalCopy(reason) maps an error from approvals.decide to the same user-voice sentence the built-in approval card renders when a decision fails to land.
Hooks or chrome
Reach forVendoOverlay and the other chrome components when you want the shipped surface with brand tokens applied.
Reach for hooks when you need counts, badges, or lists inside your own layout, or when your chrome has to react to Vendo state without rendering the overlay at all.
Both paths speak the same wire, so mixing them in one app is safe.
If you run your own agent loop and spread in the guarded tool pack, a separate set of components renders Vendo inside that chat instead: VendoToolResult, VendoAppEmbed, and VendoApprovalEmbed, all on Embeds and envelopes.