Host API reference

Everything a plugin can see or do is on the ctx object passed to every handler. This is the stable, versioned boundary — nothing outside ctx is in scope.

Source of truth: decisive-sdk/src/host.ts.

interface HostContext {
  apiVersion: 1;
  workspace: { slug: string; title: string };
  actor: Actor;            // who/what triggered this run

  context: ContextApi;
  chat: ChatApi;
  tasks: TasksApi;
  pages: PagesApi;
  members: MembersApi;
  ai: AiApi;
  secrets: SecretsApi;
  http: HttpApi;
  store: StoreApi;
  ui: UiApi;

  signal: AbortSignal;     // aborts on cancel / tab close
  log(...args: unknown[]): void;
}

ctx.actor

Who triggered the invocation. Branch on it when behavior should differ by caller.

type Actor =
  | { kind: 'ai'; memberId: string }
  | { kind: 'human'; memberId: string }
  | { kind: 'agent-runner' }
  | { kind: 'schedule' }
  | { kind: 'webhook' };

ctx.context

Read workspace knowledge, and deposit ambient context the AI can use later.

context.query(opts: { q?: string; sources?: string[]; limit?: number }): Promise<ContextHit[]>
context.upsert(records: ContextRecord[]): Promise<{ count: number }>
context.delete(opts: { source: string; ids?: string[] }): Promise<{ count: number }>
  • query searches across the workspace (tasks, chat, pages) and any ambient sources. Use it to ground actions in what the team already knows.
  • upsert writes records the AI retrieves automatically afterwards — the basis of a context source. Upserts by id within your source, so re-syncs don't duplicate.
interface ContextRecord {
  id?: string; source: string; kind?: string;
  title?: string; text: string; url?: string;
  meta?: Record<string, Json>; updatedAt?: string;
}
interface ContextHit {
  id: string; source: string; title?: string;
  text: string; score: number; url?: string; meta?: Record<string, Json>;
}

ctx.chat

chat.send(text: string, opts?: { blocks?: Block[]; threadId?: string }): Promise<{ id: string }>
chat.recent(opts?: { limit?: number }): Promise<ChatMessage[]>

Messages are authored as the AI member. Pass blocks to attach native UI.

ctx.tasks

tasks.list(opts?: { status?: string; assigneeId?: string; limit?: number }): Promise<Task[]>
tasks.get(id: string): Promise<Task | null>
tasks.create(input: { title: string; body?: string; status?: string; priority?: string; assigneeIds?: string[] }): Promise<Task>

ctx.pages

pages.list(opts?: { limit?: number }): Promise<Page[]>
pages.create(input: { title: string; body?: string }): Promise<Page>   // body is Markdown

ctx.members

members.list(): Promise<Member[]>   // { id, name, isAi }

ctx.ai

Use Decisive's own LLM with workspace context. Your plugin brings no model key.

ai.complete(opts: {
  system?: string;
  prompt?: string;
  context?: ContextHit[] | string;   // ground the model
  maxTokens?: number;
}): Promise<string>
const notes = await ctx.context.query({ q: topic, limit: 20 });
const draft = await ctx.ai.complete({ system: 'Write a release note.', context: notes });

ctx.secrets

Read-only map of the secrets you declared on the plugin, filled by an admin.

type SecretsApi = Readonly<Record<string, string | undefined>>;
const token = ctx.secrets.SLACK_TOKEN; // undefined if not set — handle it

ctx.http

fetch, subject to your extension's egress policy (default-deny + allowlist). Standard fetch signature.

const res = await ctx.http('https://api.example.com/x', {
  headers: { authorization: `Bearer ${ctx.secrets.API_KEY}` }
});

ctx.store

A tiny per-plugin, per-workspace key/value store for cursors and small state.

store.get<T = Json>(key: string): Promise<T | null>
store.set(key: string, value: Json): Promise<void>
store.delete(key: string): Promise<void>
const cursor = await ctx.store.get<string>('cursor');
// ...sync...
await ctx.store.set('cursor', latest);

ctx.ui

UI affordances usable mid-handler. Most UI is expressed via the returned Result; use these for imperative cases.

ui.openModal(view: ModalView): Promise<void>
ui.toast(text: string, style?: 'success' | 'error' | 'info'): void

ctx.signal & ctx.log

  • ctx.signal — an AbortSignal that fires on cancel / tab close. Pass it to long work and honor it.
  • ctx.log(...args) — structured logging surfaced in the extension's run log.