showcase/shell-docs/src/content/docs/frontends/angular/inspector.mdx
The Inspector is a debugging overlay for the live connection between your Angular application and your agents. It opens from a floating launcher and reports what the application and the runtime exchange as a run happens.
Its navigation has three groups — Threads, Agents, and Learning — and opens on Threads, whose contents depend on the runtime's license state. The agent-debugging views sit under Agents:
| View | What it shows |
|---|---|
| AG-UI Events | The raw event stream between your application and the agent. |
| Available Agents | The agents the runtime advertises to your application. |
| Agent State | The selected agent's state as it updates. |
| Frontend Tools | The tools you registered, with their parameter schemas. |
| Context | The context you sent to the agent, including readables and documents. |
The Inspector is cpk-web-inspector, a framework-agnostic web component in
@copilotkit/web-inspector. @copilotkit/angular does not depend on that
package and does not mount the element, so an Angular application creates it and
supplies the core itself.
Install the package as a dev dependency to keep it out of your production dependency graph:
npm install --save-dev @copilotkit/web-inspector
Add a component that owns the element's lifecycle. It reuses an existing element
or creates one after the first browser render, supplies the core, appends the
element to document.body, and removes it when the component is destroyed:
import { afterNextRender, Component, DestroyRef, inject } from "@angular/core";
import { CopilotKit } from "@copilotkit/angular";
import { WEB_INSPECTOR_TAG } from "@copilotkit/web-inspector";
import type { WebInspectorElement } from "@copilotkit/web-inspector";
@Component({
selector: "app-web-inspector",
template: "",
})
export class WebInspector {
readonly #copilotKit = inject(CopilotKit);
readonly #destroyRef = inject(DestroyRef);
constructor() {
afterNextRender(() => {
const existing =
document.querySelector<WebInspectorElement>(WEB_INSPECTOR_TAG);
const inspector =
existing ??
(document.createElement(WEB_INSPECTOR_TAG) as WebInspectorElement);
// Supply the application's core instead of letting the element find one.
inspector.core = this.#copilotKit.core;
inspector.setAttribute("auto-attach-core", "false");
if (!existing) {
document.body.appendChild(inspector);
}
this.#destroyRef.onDestroy(() => {
if (inspector.isConnected) {
inspector.remove();
}
});
});
}
}
Render the component once, from the root component, behind a development-only
@defer:
import { Component, isDevMode } from "@angular/core";
import { WebInspector } from "./web-inspector";
@Component({
selector: "app-root",
imports: [WebInspector],
template: `
<!-- your application -->
@defer (when isDev) {
<app-web-inspector />
}
`,
})
export class App {
protected readonly isDev = isDevMode();
}
inspector.core = copilotKit.core is what makes the Inspector report your
application rather than show an empty panel. Without an assigned core, the
element searches development globals such as window.__COPILOTKIT_CORE__ for
one. Setting auto-attach-core="false" disables that search, so the element
observes the core you assigned and nothing else.
Assign core directly. It is a property, not an attribute, and
auto-attach-core is the only attribute the element observes.
The launcher defaults to the top-right corner, and the element positions itself with an inline transform. Override both from your global stylesheet. A bottom-left corner keeps the launcher clear of the close button on a chat panel or sidebar:
cpk-web-inspector {
/* The panel is draggable and sets an inline transform. Neutralize it before
choosing a corner. */
transform: none !important;
top: auto !important;
bottom: 1rem !important;
left: 1rem !important;
right: auto !important;
}
The element has no production guard of its own, so exclude it in the same place
you mount it. @defer (when isDev) compiles the component and its
@copilotkit/web-inspector import into a lazy chunk, and isDevMode() returns
false in a production build, so that chunk is never requested. Removing the
component and its <app-web-inspector /> usage removes the Inspector entirely.
afterNextRender runs only in the browser, so the element is never created
during a server render. The deferred import also keeps the package out of the
server bundle. Both matter: the web component registers itself against
customElements, which does not exist on the server.
The element lives in document.body, outside the component's own view, so
Angular does not remove it. DestroyRef.onDestroy removes it explicitly.
Without that cleanup, a route change that destroys the component leaves an
orphaned panel bound to a core the application no longer uses.