CoworkkitWatch is a drop-in developer panel you mount during a build to see what the agent perceives and did. It reads the same internal session state the on-screen button does (no new wire, no instrumentation on your side) and renders it as a floating, resizable panel over your app. Because it reads the Provider's context, mount it anywhere inside CoworkkitProvider; where it sits in the tree doesn't matter.
It's the fastest way to answer the two questions every integration raises: does the agent actually see the surfaces and elements I declared, and did my actions fire, and gate, the way I intended?
Drop it in
It ships as a deep import so it stays tree-shakeable: the panel and its code never reach a bundle that doesn't import it. Add one line to the Provider you already have:
"use client";
import { CoworkkitProvider } from "@coworkkit/react";
import { CoworkkitWatch } from "@coworkkit/react/watch";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<CoworkkitProvider
getToken={async (ctx) => {
const res = await fetch("/api/session", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(ctx ?? {}),
});
if (!res.ok) {
// Check before parsing, and guard the parse: an error page (a proxy 404,
// a framework 500) is often HTML, and a throw here would lose both fields.
const body = await res.json().catch(() => ({}));
// Pass the reason AND the status through. That is what lets the button say
// "Setup needed" for a bad key instead of a generic "Can't connect":
throw Object.assign(new Error(body.error ?? "session mint failed"), {
reason: body.reason,
status: res.status,
});
}
return res.json();
}}
>
{children}
{/* Mounts only in development; ships nothing to a production build. */}
<CoworkkitWatch />
</CoworkkitProvider>
);
}Watch renders only in development, and it doesn't open on its own; it's a panel you toggle. Open it from the watch icon in the on-screen button's control row, or press ctrl+shift+k. If you've mounted it and don't see the watch icon, you're almost certainly on a production build, where Watch hides itself by default. That's expected, not a missing feature; It's a dev surface (below) shows how to force it on when you need it.
Props
All optional. Mount it bare and these defaults apply:
defaultOpen?: boolean; // default false (closed); true starts it opened
position?: "bottom-left" | "bottom-right" | "top-left" | "top-right"; // default "bottom-left"
hotkey?: string; // default "ctrl+shift+k"
initialTab?: "timeline" | "catalogue" | "transcript" | "metrics"; // default "timeline" ("state" is accepted and resolves to it: the State strip is always pinned)
maxEntries?: number; // default 1000: cap on entries shown and exported
redact?: RedactProfile; // masking policy for transcripts and tool I/O
visibleTo?: () => boolean; // predicate over your own auth; false mounts nothing
enableInProduction?: boolean; // default false: the QA escape hatchWhat it shows
A pinned State strip across the top (session phase, mic, Hand-mode arm state, the active surface, session duration and idle time), plus four tabs:
- Timeline: the live event stream, turn by turn. Each action call, how it was gated (allowed, Hand-blocked, or held for a confirmation), and its outcome. This is where you watch an action actually fire, or catch the
needs_hand_modethat explains why it didn't. - Catalogue: the live declared-exposure snapshot. The current surface, every mounted element (including see-only ones), and the full action catalogue with each entry's
kindandcontrol. This is what proves the agent sees what you declared. It also marks the declared-vs-live distinction, so a snapshot taken before the agent joins isn't mistaken for "the agent already knows". - Transcript: the conversation, both sides, as the agent heard and spoke it.
- Metrics: per-turn latency, a turn table, and a session summary, for spotting a slow leg without reaching for a profiler.
It's a dev surface
Watch is gated to NODE_ENV === "development" by default. In a production build it mounts nothing, so leaving <CoworkkitWatch /> in your tree is safe. Two escapes exist for when you need it past dev: enableInProduction for a QA build, and visibleTo, a predicate over your own auth that must return true before anything renders (pair it with the redact policy to mask transcripts and tool I/O).
The header's copy and download buttons emit the whole session (state, timeline, transcript, metrics) as a single JSON blob. That's how you hand a teammate the exact observable state you're looking at: send them the export instead of narrating what you saw.