PKU harness
SDK reference

dashboard

Data types for dashboard widgets. The Widget type, the Binding and WidgetSource schemas, and WidgetSize, also used by services.widgets.

This module defines the data structure of dashboard widgets. There are two kinds of widget. The agent builds the first kind, which shows a UI description (the spec) filled by data bindings. A plugin provides the second kind and draws it with native components. A plugin uses these types when it reads or writes widgets through services.widgets.

Source: packages/sdk/src/dashboard.ts. For the concepts, see Dashboard.

Types and schemas

import { z } from 'zod';

export const bindingSchema = z.object({
  tool: z.string().describe('read-only tool id, e.g. "calendar.today"'),
  args: z.record(z.string(), z.unknown()).optional(),
  /** Dot path into the tool result; empty = the whole result. */
  path: z.string().optional(),
  /** Cache lifetime in minutes (default 5). */
  ttlMinutes: z.number().min(0).max(24 * 60).optional(),
});
export type Binding = z.infer<typeof bindingSchema>;

export const widgetSizeSchema = z
  .enum(['sm', 'md', 'lg'])
  .describe('sm = half width tile, md = full width, lg = full width hero');
export type WidgetSize = z.infer<typeof widgetSizeSchema>;

/** A widget a plugin ships (`contributes.widgets`), placed on the dashboard by key. */
export const widgetSourceSchema = z.object({
  plugin: z.string(),
  widget: z.string().describe("key in the plugin's contributes.widgets"),
  config: z.record(z.string(), z.unknown()).optional(),
});
export type WidgetSource = z.infer<typeof widgetSourceSchema>;

/**
 * Two kinds, rendered side by side: AI-built (`spec` + `bindings`) or plugin-provided
 * (`source`, the plugin draws it natively; `spec`/`bindings` stay empty).
 */
export type Widget = {
  id: string;
  title: string;
  size: WidgetSize;
  /** json-render spec ({ root, elements, state? }). */
  spec: Record<string, unknown>;
  /** State path (JSON pointer, e.g. "/card") → data source. */
  bindings: Record<string, Binding>;
  source?: WidgetSource;
  position: number;
  createdAt: number;
  updatedAt: number;
};

An sm widget is a half-width tile, md fills the row, and lg is a large card that fills the row.

How bindings fill in data

Each binding calls a read-only tool and writes the result into the spec's state with setPointer. For example, a binding at /card with path: "balance" makes { "$state": "/card" } in the spec resolve to the balance.

Placing a plugin widget from a tool

To place a plugin's widget from a tool, build a Widget with a source and pass it to put:

const w: Widget = {
  id: ctx.services.widgets.newId(),
  title: '校园卡余额',
  size: 'md',
  spec: {},
  bindings: {},
  source: { plugin: 'wallet', widget: 'card', config: { warnBelow: 30 } },
  position: ctx.services.widgets.list().length,
  createdAt: Date.now(),
  updatedAt: Date.now(),
};
await ctx.services.widgets.put(w);

Most plugins never need to do this, because the agent places widgets itself with dashboard.addPluginWidget.

On this page