API reference

The define* helpers and the types they use. Source of truth: decisive-sdk/src/define.ts.

Each define* is an identity function — it exists for type inference and to give Decisive's manifest extractor a stable shape. Import everything from @decisive/sdk.

definePlugin

The default export of every plugin.ts. Bundles capabilities + metadata + declared secrets.

interface PluginDefinition {
  name: string;            // unique slug within the workspace
  title?: string;
  description?: Md;
  icon?: string;           // Lucide icon name
  apiVersion: 1;           // host API version you target
  secrets?: SecretSpec[];
  actions?: ActionDefinition[];
  contextSources?: ContextSourceDefinition[];
  panels?: PanelDefinition[];
  webhooks?: WebhookDefinition[];
}

defineAction / defineTool

A capability. defineTool is defineAction with surfaces defaulting to ['ai-tool'].

interface ActionDefinition<I = unknown> {
  name: string;            // snake_case; the tool name the AI sees
  description: string;     // write it like a tool description
  input?: JsonSchema;      // JSON Schema for the argument
  surfaces?: Surface[];    // default ['ai-tool'] via defineTool
  needs?: string[];        // secret keys this action requires
  destructive?: boolean;   // requires a consent gate before running
  run(input: I, ctx: HostContext): Promise<Result>;
  onAction?(event: ActionEvent, ctx: HostContext): Promise<Result>;
}
defineTool<{ id: string }>({
  name: 'lookup_order',
  description: 'Look up an order by id.',
  input: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
  needs: ['SHOP_API_KEY'],
  async run(input, ctx) { /* ... */ return { data: {/*...*/} }; }
});

The generic types run's input. input is JSON Schema — same shape Decisive's core tools use — so the schema is what the AI sees and what validates the call.

defineContextSource

Pull external data into Decisive's context on a schedule. After syncing, records are ambient — the AI retrieves them automatically, no tool call needed.

interface ContextSourceDefinition {
  name: string;
  description: string;
  needs?: string[];
  schedule?: string;       // cron, e.g. '0 * * * *'; omit for manual/webhook-driven
  sync(ctx: HostContext): Promise<{ upserted: number }>;
}
defineContextSource({
  name: 'notion', description: 'Sync Notion docs into context.',
  needs: ['NOTION_TOKEN'], schedule: '0 * * * *',
  async sync(ctx) {
    const since = await ctx.store.get<string>('cursor');
    const docs = await fetchDocs(ctx, since);
    const { count } = await ctx.context.upsert(docs.map(toRecord));
    return { upserted: count };
  }
});

definePanel

A workspace tab/overlay that renders blocks (App-Home style).

interface PanelDefinition {
  name: string;
  title: string;
  icon?: string;           // Lucide icon name
  render(ctx: HostContext): Promise<{ blocks: Block[] }>;
  onAction?(event: ActionEvent, ctx: HostContext): Promise<Result>;
}

defineWebhook

An inbound, signature-verified webhook at /api/extensions/:workspace/:plugin/hooks/:name.

interface WebhookDefinition {
  name: string;
  verify?: { type: 'hmac-sha256'; header: string; secret: string };  // secret = a declared secret key
  handle(event: WebhookEvent, ctx: HostContext): Promise<{ status?: number }>;
}
interface WebhookEvent { headers: Record<string, string>; body: string; json: Json; }

Shared types

Surface

type Surface =
  | 'ai-tool'
  | 'agent-runner'
  | 'panel'
  | `slash-command:/${string}`
  | 'chat-action';

Result

What every handler returns. All fields optional — combine as needed.

interface Result {
  data?: Json;                 // returned to the AI/caller (the tool result)
  blocks?: Block[];            // rendered to humans
  openModal?: ModalView;       // open a dialog
  updateView?: { blocks: Block[] }; // replace the current view in place
  toast?: { text: string; style?: 'success' | 'error' | 'info' };
  error?: string;              // mark failure (AI sees it; humans get an error toast)
}

ActionEvent

Passed to onAction when a block element fires.

interface ActionEvent {
  id: string;                       // the element id that fired
  value?: Json;                     // a button's value, if any
  inputs: Record<string, Json>;     // current form state, keyed by input id
  state?: Json;                     // private metadata from the producing view/modal
}

SecretSpec

interface SecretSpec { key: string; label: string; help?: Md; required?: boolean; }

JsonSchema

The input shape — identical to Decisive's core tools:

interface JsonSchema {
  type: 'object';
  properties?: Record<string, JsonSchemaProperty>;
  required?: string[];
}

Block types

See the Block Kit reference for Block, ModalView, inputs, and buttons.

The resolved manifest

At sync time Decisive extracts a declarative manifest (tool schemas, surfaces, secrets, panels, webhooks) from your plugin and bundles the executable handlers separately. You don't write this — it's the contract between the sync pipeline and the runtime — but it's typed in decisive-sdk/src/manifest.ts if you want to see exactly what Decisive stores and renders from.