-
Notifications
You must be signed in to change notification settings - Fork 41
example ai ask
Five-minute tour of the three shapes plugin authors reach for.
wp.os.ready( async () => {
const res = await wp.os.ai.ask( 'where do I manage categories?' );
console.log( res.answer_type ); // 'navigation'
console.log( res.message ); // e.g. "Here's where you'll find them — Posts → Categories."
console.log( res.admin_links ); // [ { title, url, description, icon }, … ]
} );Same endpoint the built-in overlay uses. No JS framework required — plain await.
Scenario: A Home Assistant plugin registers /turn_lights. A chat plugin (or voice assistant, or automation) wants the AI to pick the command when the user says "turn on the lights."
wp.os.ready( () => {
wp.os.registerCommand( {
slug: 'turn_lights',
label: 'Turn lights on/off',
description: 'Toggle smart lights connected to Home Assistant.',
hint: 'ON or OFF',
aiCallable: true, // ← opt-in: AI can invoke this
owner: 'home-assistant-commands',
run: async ( args ) => {
const state = args.trim().toUpperCase();
if ( state !== 'ON' && state !== 'OFF' ) {
return `Usage: /turn_lights ON|OFF (got "${ args }")`;
}
await wp.os.fetch( '/api/ha/lights', {
method: 'POST',
body: JSON.stringify( { state } ),
headers: { 'Content-Type': 'application/json' },
}, { source: 'my-plugin/turn-lights' } );
return `Lights ${ state }.`;
},
} );
} );const res = await wp.os.ai.ask( 'hey turn on the lights', {
tools: 'aiCallable',
} );
// res.answer_type === 'tool_call'
// res.toolCall === { slug: 'turn_lights', args: 'ON', result: 'Lights ON.' }
// res.message === 'Lights ON.' // string returns lift into messageAdd followUp: true:
const res = await wp.os.ai.ask( 'hey turn on the lights', {
tools: 'aiCallable',
followUp: true,
} );
// res.message === 'Done — your office light is on now. Anything else?'
// res.toolCall.result === 'Lights ON.' // the raw run() return is preservedOne-shot mode (followUp: false, the default) sets res.message to whatever the command's run() returned. That's fine for short status strings, but if your command returns an object ({ total: 42, breakdown: [...] }) or if you're building a voice / chat surface that expects conversational replies, followUp: true lets the AI compose a sentence in the voice of your system prompt.
Cost: one extra provider round-trip per command invocation. Latency roughly doubles. For one-tap UI buttons, leave followUp off; for anything that talks back to the user, turn it on.
If the second leg fails (network, API), ask() does not throw — you get the one-shot message as a fallback and res.toolCall.result is preserved. The command ran regardless.
Pass an array of slugs or a predicate if you want only a subset offered to the model:
// Only these three:
await wp.os.ai.ask( prompt, {
tools: [ 'turn_lights', 'set_thermostat', 'play_music' ],
} );
// Or a predicate:
await wp.os.ai.ask( prompt, {
tools: ( slug ) => slug.startsWith( 'ha_' ) && ! slug.endsWith( '_delete' ),
} );Only commands with aiCallable: true are visible regardless — the option can narrow, never widen.
Give the AI domain context without touching PHP:
await wp.os.ai.ask( 'is the kitchen light on?', {
tools: 'aiCallable',
systemPrompt:
'You control a smart home. Rooms: kitchen, living room, bedroom, garage. ' +
'Prefer the turn_lights / set_thermostat tools for commands; for status ' +
'questions, use the get_state tool.',
} );String = append. For a full replace (admin-only by default):
await wp.os.ai.ask( 'status?', {
systemPrompt: { mode: 'replace', text: 'Only reply in a single short sentence.' },
} );Non-admin callers sending mode: 'replace' get a silent downgrade to append — text is never lost.
When the tool's logic is inherently server-side (database lookups, WooCommerce, WP-CLI wrappers), skip the command path and register a WordPress Ability. The Copilot offers the model every read-only ability on the site — its own built-ins plus yours — so there's just one step: register a read-only ability.
add_action( 'wp_abilities_api_init', function () {
wp_register_ability( 'my-plugin/list-recent-orders', array(
'label' => __( 'List recent orders', 'my-plugin' ),
'description' => 'Return the N most recent WooCommerce orders, newest first.',
'category' => 'openstation', // or your own registered category
'input_schema' => array(
'type' => 'object',
'additionalProperties' => false,
'required' => array( 'limit' ),
'properties' => array(
'limit' => array(
'type' => 'integer',
'description' => 'How many orders to return (1-20).',
),
),
),
'output_schema' => array( 'type' => 'object', 'additionalProperties' => true ),
// Mark it read-only so the assistant offers it (only read-only
// abilities are advertised — a search turn can be steered by
// attacker-controlled content).
'meta' => array( 'annotations' => array( 'readonly' => true ) ),
'permission_callback' => function () {
return current_user_can( 'manage_woocommerce' );
},
'execute_callback' => function ( $input ) {
$limit = min( 20, max( 1, (int) ( $input['limit'] ?? 5 ) ) );
$orders = wc_get_orders( array( 'limit' => $limit ) );
return array(
'orders' => array_map( static function ( $o ) {
return array(
'id' => $o->get_id(),
'status' => $o->get_status(),
'total' => (float) $o->get_total(),
);
}, $orders ),
);
},
) );
} );No JS required, and no opt-in step. The agent loop advertises the ability to the model and dispatches calls through wp_get_ability()->execute(), so users without manage_woocommerce get a clean permission error instead of a result.
Every call is trace-able via three actions that share a request_id:
add_action( 'openstation_ai_search_started', function ( $ctx ) {
// { query, user_id, request_id }
my_logger()->info( 'ai.started', $ctx );
} );
add_action( 'openstation_ai_tool_called', function ( $ctx ) {
// { tool_name, args, user_id, request_id }
my_logger()->debug( 'ai.tool', $ctx );
} );
add_action( 'openstation_ai_search_completed', function ( $ctx ) {
// { query, user_id, request_id, answer_type, iterations, usage, model }
// usage = { prompt, completion, total } tokens; model = { id, name } (or null).
my_logger()->info( 'ai.completed', $ctx );
} );
add_action( 'openstation_ai_search_error', function ( $err ) {
my_logger()->error( 'ai.error', $err );
} );const controller = new AbortController();
const timeout = setTimeout( () => controller.abort(), 8000 );
try {
const res = await wp.os.ai.ask( prompt, { signal: controller.signal } );
console.log( res.message );
} catch ( err ) {
if ( err instanceof DOMException && err.name === 'AbortError' ) {
console.log( 'cancelled' );
} else {
throw err;
}
} finally {
clearTimeout( timeout );
}This wiki is generated from the docs/ directory — edits made here are overwritten by the next sync.
To change a page, open a pull request against docs/.
Guides
- Development guide
- Releasing openstation
- Agents security model
- API Index
- Architecture
- Bridge protocol — wiring overview
- <os-*> component reference
- Native Desktop Host — Experimental
- Desktop themes
- Dock customization — two registries, one mental model
- The event-driven framework
- Files on the Desktop
- Folder sharing
- Getting Started
- Hooks Reference
- Icons
- JavaScript Reference
- The Living Tree — algorithm definition
- Mio
- Native Windows & Framework Interop
- Plugin compatibility layer
- Progressive Web App (PWA)
- Station Home
- Using openstation from your own plugin
Migration notes
- Migration: built-in activity channels move to the os/ namespace
- Migration: window, wallpaper and widget bundles load on demand
- Migration — the navigation model
- Migration: a native window's tabs move to the window chrome
All examples
- AI Agents — extend and invoke from a plugin
- wp.os.ai.ask() — programmatic AI Copilot
- Tune the AI model config
- Custom arrange-menu action
- Open a child window its owner can't cover
- Style a specific admin page inside the iframe
- Code Blue — register your plugin's log file
- Open a file in the Code editor (deep-link from any window)
- Connect to a window — title-bar button + iframe pub/sub
- Content changes — live-refresh every window listing your type
- Custom window chrome (Experimental)
- Register a custom unfocused-window effect
- Example: render a data table
- Real file storage — react to uploads, gate policy, share from PHP
- React to a window being set free onto the real desktop
- Cross-window devtools — instrumentation primitives
- Add a dock item with a badge
- Decorate the dock without forking the renderer
- Replace the dock rail entirely
- Retune the Drafts widget's AI writing assistant
- Gate OpenStation by role
- Iframe-initiated window opens
- Build a feed reader without the bookkeeping
- Inject data into openStationConfig
- Render a list without losing clicks — renderKeyedList()
- Example: layout primitives (body → panel → row → col)
- Use <os-*> components from a plugin that ships as a zip
- Restyle and drive Mio
- Add an action that works on a whole selection
- WP Explorer — custom post types and their folder
- Add an action button to a WP Explorer preview pane
- Example: native Posts window
- Example: native window with tabs
- Native windows
- Customize note → post conversion
- Send a notification
- OAuth relay — connect to an external service
- OS-file drop
- <os-flyout> — window-scoped sliding card
- Plugins window — extras
- Track who's around — wp.os.presence
- Example: progress bar
- PWA install — surface your own button
- React to window events
- Example: extend the Trash
- Register a slash-command
- Register a desktop theme from a plugin
- Register a game
- Example: register a desktop icon (Jorvy)
- Register a wallpaper
- Register a widget
- Related entities — extend the title bar's "Related" menu
- The native-window render ctx
- Programmatic folder sharing
- Share state across multi-bundle plugins — wp.os.createSharedStore()
- Example: loading spinner
- Add an opt-in card to Station Home
- Accept drops on your desktop icon
- Give a tile two icons, one per state
- Add a row to a window's ⋯ menu
- Example: window activity & the status ring
- Window controls
- Subscribe to window lifecycle events
- Window links — relate windows and restyle the ties (Experimental)
- Window loading state — spinner overlay & ready signal
- Show a banner at the top of a window
- Pulse a window's icon — Window.requestAttention()
- Register a custom window reveal
- Window slots
- Window themes
- Native window with bundle-bound config