Skip to content

ctx.registerDockPanelKind

Register a kind of center-dock tab (like the terminal) that workspaces can open by id. Register these early in activate, before anything opens a panel of the kind.

ts
ctx.registerDockPanelKind<T extends object>(kind: DockPanelKind<T>): Disposable

Example

tsx
ctx.registerDockPanelKind({
  id: "acme.repl",
  component: ReplPanel, // receives DockPanelProps
});

The params generic T is inferred from the component's DockPanelProps<T> annotation, so a kind whose panels are opened with typed params registers without casts.

Render Chat sessions for an Agent Profile beta

A Chat-armed Agent Profile has no terminal to launch, so Silo needs somewhere to open it. Declare chatProfileHost and your panel becomes that somewhere:

tsx
ctx.registerDockPanelKind({
  id: "acme.chat",
  component: AcmeChatPanel,
  chatProfileHost: true,
});

Picking a Chat profile from a dock's + menu — or running its core.newAgent.<id> command — then opens your kind with params.profileId set to that profile's id, which you hand to ctx.agents.sessions.connect(). Terminal profiles are unaffected and still launch a terminal.

This is a declaration, not a privilege: the bundled Chat panel claims it the same way, so a third-party panel is a first-class way to start an agent rather than something reached by a separate route. With several kinds declaring it the first registered wins — disable the one you don't want on Settings → Extensions.

Declare the Agent Session your panel is showing beta

A panel that renders an Agent Session should say so, once it has one:

tsx
function AcmeChatPanel({ api }: DockPanelProps<{ profileId?: string }>) {
  const [sessionId, setSessionId] = useState<string>();
  // …connect() and setSessionId(handle.id)

  useEffect(() => {
    if (!sessionId) return;
    api.setAgentSession(sessionId);
    return () => api.setAgentSession(null);
  }, [api, sessionId]);
}

That one declaration is all the host lacks. From it, your tab gets the same activity badge and brand icon a terminal tab running the same agent gets (painted by whoever observes ctx.agents, so it honours their settings); ctx.agents.getActive() reports your session while your tab is the active one, so a turn the user watched raises no unread badge; and ctx.agents.close(id) closes your panel. Your panel implements none of that — see tab adornments.

The declaration is withdrawn automatically when the panel unmounts.

Host-drawn chrome: a breadcrumb + a contribution point

By default a dock panel gets a bare frame. Declare toolbar and the host draws the same strip an editor gets — a path breadcrumb, and a place other extensions can contribute toolbar buttons:

tsx
ctx.registerDockPanelKind({
  id: "acme.chat",
  component: AcmeChatPanel,
  toolbar: { breadcrumb: true },
});

function AcmeChatPanel({ api }: DockPanelProps) {
  useEffect(() => {
    api.setBreadcrumb({
      filePath: cwd,
      workspaceFolder: cwd,
      leafIcon: "folder",
    });
    return () => api.setBreadcrumb(null);
  }, [api, cwd]);
}

api.setBreadcrumb(crumb | null) fills in the crumbs — same shape as setAgentSession: your panel states the path, the host draws it. null shows no crumbs (a "hide breadcrumbs" setting of your own, say). toolbar: {} still reserves the strip for contributed items only.

Trailing buttons arrive through ctx.registerToolbarItem({ surface: "panel" }) — the same door your own controls use. Scope an item to your kind with when: (_keys, t) => t.kindId === "acme.chat", read instance data off t.params (your DockPanelProps["params"]), and act on the owning workspace via t.workspaceId. The built-in terminal declares toolbar too, so its toolbar items are ordinary "panel" items.

Your panel is never remounted — restore on onScreen

A dock panel mounts once, when its tab is created, and stays mounted until the tab closes. Deselecting its tab does not unmount it: the host detaches your panel's element from the document and re-attaches it when the tab comes back, so your React state and refs survive untouched. Backgrounding the whole workspace does not even detach — that dock simply stops being shown.

The catch is that a detached element loses everything the browser keeps on a layout box, scroll offsets first among them. So a panel that restores a scroll position, re-measures a canvas, or refits a terminal on mount will do it exactly once, when there is nothing yet to restore, and never again.

Do that work on onScreen instead. It is true only when your tab is the selected one in its group and its workspace is the one on screen — the host resolves both halves, so don't recombine DockPanelApi.isVisible (the tab half only: a panel in a backgrounded workspace still reports true) with ctx.workspaces yourself.

ts
function AcmeChatPanel({ params, onScreen }: DockPanelProps<{ scrollTop?: number }>) {
  const scroller = useRef<HTMLDivElement | null>(null);

  useLayoutEffect(() => {
    if (!onScreen || !scroller.current) return;
    scroller.current.scrollTop = params.scrollTop ?? 0;
  }, [onScreen, params.scrollTop]);

  return <div ref={scroller} />;
}

Read the position back only while onScreen is true — the element is already detached by the time the flag flips to false, and reads 0 there.

Recorded panels: reopen on restart experimental

By default a dock panel persists only as geometry in the saved dock layout — it comes back where you left it for the current session, but a transient panel (a picker, a preview) is fine with that. Declare persistence: "recorded" and every open panel of your kind becomes a DockPanelRecord on its workspace (Workspace.panels): workspace-scoped, enumerable, and reopened from its record after a restart — the same footing an editor or terminal tab has.

ts
ctx.registerDockPanelKind({
  id: "acme.chat",
  component: ChatPanel,
  persistence: "recorded",
});

On restore the host recreates your panel and hands DockPanelRecord.state back as its params. To restore content (a chat session, a scroll position), write that state through api.updateParameters({ … }) as it changes and read it back from props.params on the next launch — state is a serializable bag whose shape is your panel's own contract, not something the host inspects.

Types

Pass DockPanelKind.

Related: DockPanelApi, DockPanelRecord.

See also

Other Registration members on ctx.