Skip to content

feat: plugin-generated custom UI / web preview extension points #21

@espetro

Description

@espetro

Summary

Plugins currently cannot generate custom UI or interactive surfaces. The universal manifest has no UI/component/preview/webview extension points, and ToolResult/HookResult only support text output. This issue tracks designing a minimal, progressive universal extension point for plugin-generated custom UI, including auto-generated web previews.

Motivation

Users want plugins to be able to:

  • Render rich output (markdown, HTML, iframe) from tools/hooks without the user manually setting up a Python/Node/Vite HTTP server.
  • Contribute panels, widgets, or preview surfaces to the agent UI.
  • Generate web previews automatically from plugin code.

Current State

  • ToolResult in packages/core/src/types.ts is string | { title?, output: string, metadata? }.
  • HookResult only supports systemMessage, additionalContext, block, reason, suppressOutput.
  • Only Pi Mono has an explicit runtime UI API (ctx.ui.toast()); no other adapter exposes UI surfaces.
  • Target harness survey:
    • Claude Code: CSS themes only (themes manifest field).
    • Codex, Copilot CLI, Gemini, Kimi, OpenCode: no UI contribution surface.
    • Pi Mono: rich runtime UI API, but only partially reflected in adapter codegen.

Proposed Direction

Design a minimal, opt-in universal UI extension point that degrades gracefully on platforms that do not support it.

Option A — Rich ToolResult / HookResult attachments (recommended first step)

export type UiRenderSpec =
  | { type: 'markdown'; content: string }
  | { type: 'html'; content: string; baseUrl?: string }
  | { type: 'iframe'; url: string; title?: string }
  | { type: 'panel'; title: string; content: string }
  | { type: 'widget'; widgetType: string; props?: Record<string, unknown> };

export interface ToolResult {
  title?: string;
  output: string;
  metadata?: Record<string, unknown>;
  render?: UiRenderSpec | UiRenderSpec[];
}

export interface HookResult {
  // ...existing fields
  ui?: UiRenderSpec | UiRenderSpec[];
}

Option B — Top-level manifest ui section (future)

export interface PluginManifest {
  // ...existing fields
  ui?: {
    pages?: UiPage[];
    panels?: UiPanel[];
    webPreviews?: WebPreview[];
    components?: UiComponent[];
  };
}

Acceptance Criteria

  • UiRenderSpec type added to packages/core/src/types.ts.
  • ToolResult and HookResult extended to support optional render / ui fields.
  • Adapters updated to validate and either pass through or strip unsupported render specs (with build-time warnings).
  • Pi Mono adapter wires ctx.ui to the universal shape where possible.
  • Docs updated with platform capability matrix for UI features.
  • Tests cover validation and graceful degradation.

Classification

  • Type: feature
  • Effort: L
  • Status: For Refinement

Related

  • Research plan: .agents/plans/2026-06-15-builtins-ui-research.md

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions