diff --git a/packages/editor/src/index.tsx b/packages/editor/src/index.tsx index d8888cdb66..2722c069d9 100644 --- a/packages/editor/src/index.tsx +++ b/packages/editor/src/index.tsx @@ -253,6 +253,7 @@ export { TerrainSculptPanel } from './components/ui/controls/terrain-sculpt-pane export { ToggleControl } from './components/ui/controls/toggle-control' export { ToolOptionsPanel } from './components/ui/controls/tool-options-panel' export { FloatingLevelSelector } from './components/ui/floating-level-selector' +export { IconRefGlyph, IconRefImage } from './components/ui/icon-ref' export { CATALOG_ITEMS } from './components/ui/item-catalog/catalog-items' // A base plate's panel (one connected footprint's floor and foundation); the // slab kind's panel hands base plates to it. @@ -725,6 +726,7 @@ export { resolvePlanarCursorPosition, } from './lib/planar-cursor-placement' export type { + ChatStartProps, ChatToolCardProps, ChatToolOutput, PluginChatExtension, diff --git a/packages/editor/src/lib/plugin-chat.ts b/packages/editor/src/lib/plugin-chat.ts index d7edfe821c..9833c2a612 100644 --- a/packages/editor/src/lib/plugin-chat.ts +++ b/packages/editor/src/lib/plugin-chat.ts @@ -28,6 +28,19 @@ export type ChatToolCardProps = { } } +/** + * What a plugin's start screen does through the host's chat, before anything is said. `send` sends + * a message now, its pictures by reference (a host-stored image), and `attach` puts files on the + * next message, in the composer: both at the level that runs the plugin's tools. `requirePro` only + * asks whether the person's plan includes that level ("Pro" is the hosted product's name for it): + * it answers false after offering the upgrade, and changes nothing in the chat. + */ +export type ChatStartProps = { + send(text: string, images?: readonly { url: string; name: string }[]): void + attach(files: readonly File[]): void + requirePro(): boolean +} + /** * A plugin's contribution to a host's chat, exported from its browser entry beside its panels and * views; its tool contracts are its `./agent-tools` export. Which tools a turn offers, and to whom, @@ -47,4 +60,6 @@ export type PluginChatExtension = { actions?: readonly { id: string; label: string; hint: string; prompt: string }[] /** File extensions or MIME types the plugin's tools read, which the composer then accepts. */ accepts?: readonly string[] + /** What the chat shows while its thread is empty, loaded lazily. */ + start?: () => Promise<{ default: ComponentType }> } diff --git a/wiki/architecture/plugin-authoring.md b/wiki/architecture/plugin-authoring.md index 1579bc093c..0b45be1d67 100644 --- a/wiki/architecture/plugin-authoring.md +++ b/wiki/architecture/plugin-authoring.md @@ -358,6 +358,7 @@ export const signageChat: PluginChatExtension = { cards: { signage_schedule: () => import('./schedule-card') }, actions: [{ id: 'exit-signs', label: 'Exit signs', hint: 'Sign every exit', prompt: 'Place exit signs on every level.' }], accepts: ['.csv'], + start: () => import('./chat-start'), } ``` @@ -368,6 +369,7 @@ export const signageChat: PluginChatExtension = { - **The host decides.** Which tools a turn offers, to whom and at what cost, whether a `chatOnly` request is honoured, and how far `timeoutMs` may go are host policy, like a panel's `access`. Being in `installedPlugins` is necessary, not sufficient. These packages define the shapes; a host keeps its own catalog of the plugins it serves. - **Trusted code.** These exports run with the application's privileges: a zod schema is code, and an executor or a card runs in the page. Hosts load reviewed, pinned plugins. The purity rule keeps contracts loadable in a sandboxed workflow; it does not isolate them. - **Cards and outputs.** A card renders from the persisted call alone (`toolName`, `input`, `output`, `state`), so a reloaded chat shows the same card; its `actions` open a view, select nodes or send a prompt. A `ChatToolOutput` carries images by reference (`ref`), never bytes. `actions` add entries to the composer's `/` menu; `accepts` lists the file types the plugin's tools read. +- **A start screen.** `start` is what the host's chat shows while its thread is empty. It acts only through its `ChatStartProps`: `send` a message (pictures by reference) or `attach` files to the composer, both at the level the plugin's tools run at, and `requirePro()` before a step that needs that level: it answers false after the host offered the upgrade, and changes nothing in the chat. Render registry icons with `IconRefGlyph`, as the editor does. The rules both surfaces follow for these tools are in [agent surfaces](agent-surfaces.md#plugin-tools).