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 }>
querysearches across the workspace (tasks, chat, pages) and any ambient sources. Use it to ground actions in what the team already knows.upsertwrites records the AI retrieves automatically afterwards — the basis of a context source. Upserts byidwithin yoursource, 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— anAbortSignalthat 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.