PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
← All changes | includes/ai-copilot/abilities.php +112 -84 0.9.61.1.10 View file →
@@ -1,20 +1,20 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — AI Copilot abilities.
3 + * OpenStation — AI Copilot abilities.
4 4 *
5 5 * The Copilot's tools are WordPress Abilities API abilities: the agent loop
6 6 * offers the model every registered read-only ability (see
7 - * {@see desktop_mode_ai_search_ability_names()}) and runs a chosen one through
7 + * {@see openstation_ai_search_ability_names()}) and runs a chosen one through
8 8 * `wp_get_ability()->execute()` — permission checks and input validation
9 9 * happen inside `WP_Ability::execute()`.
10 10 *
11 11 * Every ability's `execute_callback` delegates to the existing query handlers
12 - * (via {@see desktop_mode_ai_search_dispatch_tool()} / the comment scorer) so
12 + * (via {@see openstation_ai_search_dispatch_tool()} / the comment scorer) so
13 13 * there is a single implementation of each tool. The ability is the source of
14 14 * truth for the model-facing description + input schema.
15 15 *
16 - * @package WPDesktopMode
16 + * @package OpenStation
17 17 */
18 18
19 19 defined( 'ABSPATH' ) || exit;
20 20
@@ -20,31 +20,29 @@
20 20
21 21 /**
22 22 * Ability category slug shared by every Copilot ability.
23 23 */
24 -const DESKTOP_MODE_AI_ABILITY_CATEGORY = 'desktop-mode';
24 +const OPENSTATION_AI_ABILITY_CATEGORY = 'openstation';
25 25
26 26 /**
27 - * Registers the `desktop-mode` ability category.
27 + * Registers the `openstation` ability category.
28 28 *
29 - * @since 0.9.4
30 - *
31 29 * @return void
32 30 */
33 -function desktop_mode_ai_register_ability_category() {
31 +function openstation_ai_register_ability_category() {
34 32 if ( ! function_exists( 'wp_register_ability_category' ) ) {
35 33 return;
36 34 }
37 35
38 36 wp_register_ability_category(
39 - DESKTOP_MODE_AI_ABILITY_CATEGORY,
37 + OPENSTATION_AI_ABILITY_CATEGORY,
40 38 array(
41 - 'label' => __( 'Desktop Mode', 'desktop-mode' ),
42 - 'description' => __( 'Read-only content search and wp-admin navigation abilities powering the Desktop Mode AI assistant.', 'desktop-mode' ),
39 + 'label' => __( 'OpenStation', 'desktop-mode' ),
40 + 'description' => __( 'Read-only content search and wp-admin navigation abilities powering the OpenStation AI assistant.', 'desktop-mode' ),
43 41 )
44 42 );
45 43 }
46 -add_action( 'wp_abilities_api_categories_init', 'desktop_mode_ai_register_ability_category' );
44 +add_action( 'wp_abilities_api_categories_init', 'openstation_ai_register_ability_category' );
47 45
48 46 /**
49 47 * The ability names the Copilot offers the model as tools.
50 48 *
@@ -58,13 +56,11 @@
58 56 * driven by attacker-controlled content (comment / post text that lands in a
59 57 * tool result), so the model is never handed an ability that could change the
60 58 * site.
61 59 *
62 - * @since 0.9.4
63 - *
64 60 * @return string[] Fully-namespaced ability names.
65 61 */
66 -function desktop_mode_ai_search_ability_names() {
62 +function openstation_ai_search_ability_names() {
67 63 if ( ! function_exists( 'wp_get_abilities' ) ) {
68 64 return array();
69 65 }
70 66
@@ -90,14 +86,12 @@
90 86 * reproduces the Copilot's historical tool names (`desktop-mode/search-posts`
91 87 * → `search_posts`), so progress labels, the system prompt, and the answer
92 88 * schema keep referring to the same names across the abilities migration.
93 89 *
94 - * @since 0.9.4
95 - *
96 90 * @param string $ability_name Fully-namespaced ability name.
97 91 * @return string
98 92 */
99 -function desktop_mode_ai_ability_tool_name( $ability_name ) {
93 +function openstation_ai_ability_tool_name( $ability_name ) {
100 94 $slug = (string) $ability_name;
101 95 $pos = strpos( $slug, '/' );
102 96 if ( false !== $pos ) {
103 97 $slug = substr( $slug, $pos + 1 );
@@ -115,13 +109,11 @@
115 109 *
116 110 * Mirrors the read-only search/navigation tools, which were ungated beyond the
117 111 * Copilot's own logged-in requirement.
118 112 *
119 - * @since 0.9.4
120 - *
121 113 * @return bool
122 114 */
123 -function desktop_mode_ai_ability_can_read() {
115 +function openstation_ai_ability_can_read() {
124 116 return is_user_logged_in() && current_user_can( 'read' );
125 117 }
126 118
127 119 /**
@@ -128,14 +120,12 @@
128 120 * A loose object output schema: typed at the top level, permissive on the rest
129 121 * so `WP_Ability::execute()`'s output validation never rejects a valid handler
130 122 * return (the shapes carry optional/nested fields we don't want to freeze).
131 123 *
132 - * @since 0.9.4
133 - *
134 124 * @param array<string,array<string,mixed>> $properties Documented top-level props.
135 125 * @return array<string,mixed>
136 126 */
137 -function desktop_mode_ai_ability_output_schema( array $properties = array() ) {
127 +function openstation_ai_ability_output_schema( array $properties = array() ) {
138 128 return array(
139 129 'type' => 'object',
140 130 'additionalProperties' => true,
141 131 // Keep as a plain (associative) array: WordPress's schema validator
@@ -148,13 +138,11 @@
148 138
149 139 /**
150 140 * Registers every Copilot ability.
151 141 *
152 - * @since 0.9.4
153 - *
154 142 * @return void
155 143 */
156 -function desktop_mode_ai_register_abilities() {
144 +function openstation_ai_register_abilities() {
157 145 if ( ! function_exists( 'wp_register_ability' ) ) {
158 146 return;
159 147 }
160 148
@@ -173,27 +161,48 @@
173 161 ),
174 162 ),
175 163 );
176 164
177 - $search_output = desktop_mode_ai_ability_output_schema(
165 + $search_output = openstation_ai_ability_output_schema(
178 166 array(
179 - 'items' => array( 'type' => 'array', 'description' => 'Matching entities with identity, excerpt, and URLs.' ),
180 - 'count' => array( 'type' => 'integer', 'description' => 'Number of items in this batch.' ),
181 - 'total' => array( 'type' => 'integer', 'description' => 'Total matches across all batches.' ),
182 - 'has_more' => array( 'type' => 'boolean', 'description' => 'Whether another batch is available at the next offset.' ),
167 + 'items' => array(
168 + 'type' => 'array',
169 + 'description' => 'Matching entities with identity, excerpt, and URLs.',
170 + ),
171 + 'count' => array(
172 + 'type' => 'integer',
173 + 'description' => 'Number of items in this batch.',
174 + ),
175 + 'total' => array(
176 + 'type' => 'integer',
177 + 'description' => 'Total matches across all batches.',
178 + ),
179 + 'has_more' => array(
180 + 'type' => 'boolean',
181 + 'description' => 'Whether another batch is available at the next offset.',
182 + ),
183 183 )
184 184 );
185 185
186 186 $readonly_meta = array(
187 - 'annotations' => array( 'readonly' => true, 'idempotent' => true ),
187 + 'annotations' => array(
188 + 'readonly' => true,
189 + 'idempotent' => true,
190 + ),
188 191 'show_in_rest' => true,
189 - 'mcp' => array( 'public' => true, 'type' => 'tool' ),
192 + 'mcp' => array(
193 + 'public' => true,
194 + 'type' => 'tool',
195 + ),
190 196 );
191 197
192 198 // Admin-only abilities are still read-only, but must not be exposed to
193 199 // external agents over MCP.
194 200 $readonly_private_meta = array(
195 - 'annotations' => array( 'readonly' => true, 'idempotent' => true ),
201 + 'annotations' => array(
202 + 'readonly' => true,
203 + 'idempotent' => true,
204 + ),
196 205 'show_in_rest' => true,
197 206 );
198 207
199 208 wp_register_ability(
@@ -200,15 +209,15 @@
200 209 'desktop-mode/search-posts',
201 210 array(
202 211 'label' => __( 'Search posts', 'desktop-mode' ),
203 212 'description' => 'Keyword-searches published WordPress blog posts by title and content (WordPress native search). Use this when the user is looking for content they or someone else wrote as a post or article. Pass the key search terms as `query`. Returns up to 10 matching posts with their title, a content excerpt, date, and URLs. If has_more is true, call again with the next offset.',
204 - 'category' => DESKTOP_MODE_AI_ABILITY_CATEGORY,
213 + 'category' => OPENSTATION_AI_ABILITY_CATEGORY,
205 214 'input_schema' => $query_offset_input,
206 215 'output_schema' => $search_output,
207 216 'execute_callback' => static function ( $input ) {
208 - return desktop_mode_ai_search_dispatch_tool( 'search_posts', (array) $input );
217 + return openstation_ai_search_dispatch_tool( 'search_posts', (array) $input );
209 218 },
210 - 'permission_callback' => 'desktop_mode_ai_ability_can_read',
219 + 'permission_callback' => 'openstation_ai_ability_can_read',
211 220 'meta' => $readonly_meta,
212 221 )
213 222 );
214 223
@@ -216,15 +225,15 @@
216 225 'desktop-mode/search-pages',
217 226 array(
218 227 'label' => __( 'Search pages', 'desktop-mode' ),
219 228 'description' => 'Keyword-searches published WordPress pages (About, Contact, Services, Portfolio, etc.) by title and content. Use this when the user is looking for a static page, landing page, or informational page on the site. Pass the key search terms as `query`. Returns up to 10 matching pages with their title, a content excerpt, and URLs. If has_more is true, call again with the next offset.',
220 - 'category' => DESKTOP_MODE_AI_ABILITY_CATEGORY,
229 + 'category' => OPENSTATION_AI_ABILITY_CATEGORY,
221 230 'input_schema' => $query_offset_input,
222 231 'output_schema' => $search_output,
223 232 'execute_callback' => static function ( $input ) {
224 - return desktop_mode_ai_search_dispatch_tool( 'search_pages', (array) $input );
233 + return openstation_ai_search_dispatch_tool( 'search_pages', (array) $input );
225 234 },
226 - 'permission_callback' => 'desktop_mode_ai_ability_can_read',
235 + 'permission_callback' => 'openstation_ai_ability_can_read',
227 236 'meta' => $readonly_meta,
228 237 )
229 238 );
230 239
@@ -231,16 +240,16 @@
231 240 wp_register_ability(
232 241 'desktop-mode/search-comments',
233 242 array(
234 243 'label' => __( 'Search comments', 'desktop-mode' ),
235 - 'description' => 'Keyword-searches approved WordPress comments across ALL posts by their text (WordPress native search). Use this when the user remembers something a reader said but does not know which post it was on. Pass the distinctive words from the comment as `query`. Returns up to 10 matching comments with an excerpt, parent post title, and URLs. If has_more is true, call again with the next offset.',
236 - 'category' => DESKTOP_MODE_AI_ABILITY_CATEGORY,
244 + 'description' => 'Keyword-searches approved WordPress comments by their text (WordPress native search), across all posts the requesting user is allowed to read — comments on private, draft, or password-protected posts the user cannot access are excluded. Use this when the user remembers something a reader said but does not know which post it was on. Pass the distinctive words from the comment as `query`. Returns up to 10 matching comments with an excerpt, parent post title, and URLs. If has_more is true, call again with the next offset.',
245 + 'category' => OPENSTATION_AI_ABILITY_CATEGORY,
237 246 'input_schema' => $query_offset_input,
238 247 'output_schema' => $search_output,
239 248 'execute_callback' => static function ( $input ) {
240 - return desktop_mode_ai_search_dispatch_tool( 'search_comments', (array) $input );
249 + return openstation_ai_search_dispatch_tool( 'search_comments', (array) $input );
241 250 },
242 - 'permission_callback' => 'desktop_mode_ai_ability_can_read',
251 + 'permission_callback' => 'openstation_ai_ability_can_read',
243 252 'meta' => $readonly_meta,
244 253 )
245 254 );
246 255
@@ -247,10 +256,10 @@
247 256 wp_register_ability(
248 257 'desktop-mode/search-comments-by-post',
249 258 array(
250 259 'label' => __( 'Search comments on a post', 'desktop-mode' ),
251 - 'description' => 'Keyword-searches approved comments on a SPECIFIC post by its WordPress ID. Use this when you have already identified a post (via search-posts) and the user\'s query also mentions something a reader said on that post — e.g. "I remember a comment on my Málaga post asking about the Alcazaba at night." Call search-posts first to find the post ID, then call this tool with that ID and the distinctive words as `query`. Much more precise than search-comments when the parent post is known. If has_more is true, call again with the next offset.',
252 - 'category' => DESKTOP_MODE_AI_ABILITY_CATEGORY,
260 + 'description' => 'Keyword-searches approved comments on a SPECIFIC post by its WordPress ID (the post must be readable by the requesting user; an unreadable or nonexistent post returns an empty result). Use this when you have already identified a post (via search-posts) and the user\'s query also mentions something a reader said on that post — e.g. "I remember a comment on my Málaga post asking about the Alcazaba at night." Call search-posts first to find the post ID, then call this tool with that ID and the distinctive words as `query`. Much more precise than search-comments when the parent post is known. If has_more is true, call again with the next offset.',
261 + 'category' => OPENSTATION_AI_ABILITY_CATEGORY,
253 262 'input_schema' => array(
254 263 'type' => 'object',
255 264 'additionalProperties' => false,
256 265 'required' => array( 'post_id', 'query', 'offset' ),
@@ -270,11 +279,11 @@
270 279 ),
271 280 ),
272 281 'output_schema' => $search_output,
273 282 'execute_callback' => static function ( $input ) {
274 - return desktop_mode_ai_search_dispatch_tool( 'search_comments_by_post', (array) $input );
283 + return openstation_ai_search_dispatch_tool( 'search_comments_by_post', (array) $input );
275 284 },
276 - 'permission_callback' => 'desktop_mode_ai_ability_can_read',
285 + 'permission_callback' => 'openstation_ai_ability_can_read',
277 286 'meta' => $readonly_meta,
278 287 )
279 288 );
280 289
@@ -281,10 +290,16 @@
281 290 wp_register_ability(
282 291 'desktop-mode/list-admin-pages',
283 292 array(
284 293 'label' => __( 'List admin pages', 'desktop-mode' ),
285 - 'description' => 'Returns the full catalog of WordPress admin (wp-admin) destinations — pages for managing posts, categories, users, plugins, themes, settings, etc. Call this when the user asks "where can I find X?", "how do I get to Y?", "where are the settings for Z?" — any navigational question about the admin UI. Once you have the catalog, select the 1-3 most relevant entries for the user\'s query and include them in your answer under admin_links with answer_type="navigation". The catalog is small and stable so one call is enough.',
286 - 'category' => DESKTOP_MODE_AI_ABILITY_CATEGORY,
294 + // Describes the TOOL, not the caller's answer format: agents
295 + // and the Copilot consume the same registry, and the
296 + // Copilot's `admin_links` / `answer_type` contract exists in
297 + // its own system prompt and answer schema. Naming those
298 + // fields here would instruct an agent to emit a shape its
299 + // answer schema does not have.
300 + 'description' => 'Returns the full catalog of WordPress admin (wp-admin) destinations — pages for managing posts, categories, users, plugins, themes, settings, etc. Call this when the user asks "where can I find X?", "how do I get to Y?", "where are the settings for Z?" — any navigational question about the admin UI. Each entry carries a title, url, icon, and description, so pick the few most relevant to the query. The catalog is small and stable so one call is enough.',
301 + 'category' => OPENSTATION_AI_ABILITY_CATEGORY,
287 302 'input_schema' => array(
288 303 'type' => 'object',
289 304 'additionalProperties' => false,
290 305 'required' => array(),
@@ -289,15 +304,20 @@
289 304 'additionalProperties' => false,
290 305 'required' => array(),
291 306 'properties' => (object) array(),
292 307 ),
293 - 'output_schema' => desktop_mode_ai_ability_output_schema(
294 - array( 'pages' => array( 'type' => 'array', 'description' => 'Admin destinations with title/url/icon/description.' ) )
308 + 'output_schema' => openstation_ai_ability_output_schema(
309 + array(
310 + 'pages' => array(
311 + 'type' => 'array',
312 + 'description' => 'Admin destinations with title/url/icon/description.',
313 + ),
314 + )
295 315 ),
296 316 'execute_callback' => static function ( $input ) {
297 - return desktop_mode_ai_search_dispatch_tool( 'list_admin_pages', (array) $input );
317 + return openstation_ai_search_dispatch_tool( 'list_admin_pages', (array) $input );
298 318 },
299 - 'permission_callback' => 'desktop_mode_ai_ability_can_read',
319 + 'permission_callback' => 'openstation_ai_ability_can_read',
300 320 'meta' => $readonly_meta,
301 321 )
302 322 );
303 323
@@ -304,10 +324,10 @@
304 324 wp_register_ability(
305 325 'desktop-mode/search-wporg-plugins',
306 326 array(
307 327 'label' => __( 'Search WordPress.org plugins', 'desktop-mode' ),
308 - 'description' => 'Searches the official WordPress.org plugin directory. Use this when the user asks for a plugin recommendation — e.g. "is there a plugin for SEO?", "find me a backup plugin", "a caching plugin", "form builder". Returns up to 10 plugins with name, description, rating, active install count, and an admin URL that opens the plugin-info / install screen directly. Present the results as admin_links with answer_type="navigation", titled "Plugin Name · 5M+ installs · 4.8★".',
309 - 'category' => DESKTOP_MODE_AI_ABILITY_CATEGORY,
328 + 'description' => 'Searches the official WordPress.org plugin directory. Use this when the user asks for a plugin recommendation — e.g. "is there a plugin for SEO?", "find me a backup plugin", "a caching plugin", "form builder". Returns up to 10 plugins with name, description, rating, active install count, and an admin URL that opens the plugin-info / install screen directly.',
329 + 'category' => OPENSTATION_AI_ABILITY_CATEGORY,
310 330 'input_schema' => array(
311 331 'type' => 'object',
312 332 'additionalProperties' => false,
313 333 'required' => array( 'query' ),
@@ -317,18 +337,21 @@
317 337 'description' => 'Plain-language search terms — e.g. "seo", "backup", "caching", "woocommerce", "contact form".',
318 338 ),
319 339 ),
320 340 ),
321 - 'output_schema' => desktop_mode_ai_ability_output_schema(
341 + 'output_schema' => openstation_ai_ability_output_schema(
322 342 array(
323 - 'results' => array( 'type' => 'array', 'description' => 'Matching plugins with name, description, rating, installs, and admin URL.' ),
343 + 'results' => array(
344 + 'type' => 'array',
345 + 'description' => 'Matching plugins with name, description, rating, installs, and admin URL.',
346 + ),
324 347 'count' => array( 'type' => 'integer' ),
325 348 )
326 349 ),
327 350 'execute_callback' => static function ( $input ) {
328 - return desktop_mode_ai_search_dispatch_tool( 'search_wporg_plugins', (array) $input );
351 + return openstation_ai_search_dispatch_tool( 'search_wporg_plugins', (array) $input );
329 352 },
330 - 'permission_callback' => 'desktop_mode_ai_ability_can_read',
353 + 'permission_callback' => 'openstation_ai_ability_can_read',
331 354 'meta' => $readonly_meta,
332 355 )
333 356 );
334 357
@@ -336,9 +359,9 @@
336 359 'desktop-mode/get-php-error-log',
337 360 array(
338 361 'label' => __( 'Read PHP error log', 'desktop-mode' ),
339 362 'description' => 'Reads the most recent entries from the site\'s PHP error log — typically wp-content/debug.log when WP_DEBUG_LOG is enabled, or the path set by the PHP error_log directive. Use this when the user asks "are there any errors?", "check the logs", "what went wrong?", or is troubleshooting a white screen / 500. Each entry is parsed into { timestamp, level, message } so you can summarise them. Administrators only.',
340 - 'category' => DESKTOP_MODE_AI_ABILITY_CATEGORY,
363 + 'category' => OPENSTATION_AI_ABILITY_CATEGORY,
341 364 'input_schema' => array(
342 365 'type' => 'object',
343 366 'additionalProperties' => false,
344 367 'required' => array( 'lines' ),
@@ -348,16 +371,19 @@
348 371 'description' => 'How many recent log lines to return (1-500). Use 20-50 for a quick look, 100-200 for wider context.',
349 372 ),
350 373 ),
351 374 ),
352 - 'output_schema' => desktop_mode_ai_ability_output_schema(
375 + 'output_schema' => openstation_ai_ability_output_schema(
353 376 array(
354 377 'log_available' => array( 'type' => 'boolean' ),
355 - 'entries' => array( 'type' => 'array', 'description' => 'Parsed log lines: { timestamp, level, message }.' ),
378 + 'entries' => array(
379 + 'type' => 'array',
380 + 'description' => 'Parsed log lines: { timestamp, level, message }.',
381 + ),
356 382 )
357 383 ),
358 384 'execute_callback' => static function ( $input ) {
359 - return desktop_mode_ai_search_dispatch_tool( 'get_php_error_log', (array) $input );
385 + return openstation_ai_search_dispatch_tool( 'get_php_error_log', (array) $input );
360 386 },
361 387 // Admin-only — mirrors the previous in-dispatcher manage_options gate.
362 388 'permission_callback' => static function () {
363 389 return current_user_can( 'manage_options' );
@@ -365,31 +391,32 @@
365 391 'meta' => $readonly_private_meta,
366 392 )
367 393 );
368 394
369 - desktop_mode_ai_register_comment_analysis_ability();
395 + openstation_ai_register_comment_analysis_ability();
370 396 }
371 -add_action( 'wp_abilities_api_init', 'desktop_mode_ai_register_abilities' );
397 +add_action( 'wp_abilities_api_init', 'openstation_ai_register_abilities' );
372 398
373 399 /**
374 400 * Registers the comment-spam analysis ability.
375 401 *
376 402 * Not offered to the model during a search turn (see
377 - * {@see desktop_mode_ai_search_ability_names()}); the moderation pipeline
378 - * resolves and executes it directly ({@see desktop_mode_ai_analyze_comment_now()}
379 - * runs through it). Exposed in the abilities catalog for observability + reuse.
403 + * {@see openstation_ai_search_ability_names()}) — a search turn can be driven
404 + * by attacker-controlled comment text, and this one spends provider tokens.
405 + * It is an on-demand ability: a caller with `moderate_comments` runs it for one
406 + * comment and gets the verdict back. Automatic scoring on comment save was
407 + * removed (`docs/migration-comments-ai-scoring.md`), so nothing in the plugin
408 + * invokes it on its own.
380 409 *
381 - * @since 0.9.4
382 - *
383 410 * @return void
384 411 */
385 -function desktop_mode_ai_register_comment_analysis_ability() {
412 +function openstation_ai_register_comment_analysis_ability() {
386 413 wp_register_ability(
387 414 'desktop-mode/analyze-comment',
388 415 array(
389 416 'label' => __( 'Analyze comment for spam', 'desktop-mode' ),
390 - 'description' => 'Runs the AI spam/harm analysis for a single comment and returns its structured verdict ({ topic, ai_summary, harmful, spam }). Used by comment moderation to score incoming comments.',
391 - 'category' => DESKTOP_MODE_AI_ABILITY_CATEGORY,
417 + 'description' => 'Runs the AI spam/harm analysis for a single comment and returns its structured verdict ({ topic, ai_summary, harmful, spam }). On-demand only: nothing in the plugin runs it automatically.',
418 + 'category' => OPENSTATION_AI_ABILITY_CATEGORY,
392 419 'input_schema' => array(
393 420 'type' => 'object',
394 421 'additionalProperties' => false,
395 422 'required' => array( 'comment_id' ),
@@ -399,9 +426,9 @@
399 426 'description' => 'The WordPress ID of the comment to analyze.',
400 427 ),
401 428 ),
402 429 ),
403 - 'output_schema' => desktop_mode_ai_ability_output_schema(
430 + 'output_schema' => openstation_ai_ability_output_schema(
404 431 array(
405 432 'topic' => array( 'type' => 'string' ),
406 433 'ai_summary' => array( 'type' => 'string' ),
407 434 'harmful' => array( 'type' => 'boolean' ),
@@ -407,14 +434,17 @@
407 434 'harmful' => array( 'type' => 'boolean' ),
408 435 'spam' => array( 'type' => 'boolean' ),
409 436 )
410 437 ),
411 - 'execute_callback' => 'desktop_mode_ai_ability_analyze_comment',
438 + 'execute_callback' => 'openstation_ai_ability_analyze_comment',
412 439 'permission_callback' => static function () {
413 440 return current_user_can( 'moderate_comments' );
414 441 },
415 442 'meta' => array(
416 - 'annotations' => array( 'readonly' => true, 'idempotent' => true ),
443 + 'annotations' => array(
444 + 'readonly' => true,
445 + 'idempotent' => true,
446 + ),
417 447 'show_in_rest' => true,
418 448 ),
419 449 )
420 450 );
@@ -422,18 +452,16 @@
422 452
423 453 /**
424 454 * Execute callback for the `desktop-mode/analyze-comment` ability.
425 455 *
426 - * @since 0.9.4
427 - *
428 456 * @param array<string,mixed> $input Validated input (`comment_id`).
429 457 * @return array|WP_Error Structured verdict, or an error.
430 458 */
431 -function desktop_mode_ai_ability_analyze_comment( $input ) {
459 +function openstation_ai_ability_analyze_comment( $input ) {
432 460 $comment_id = isset( $input['comment_id'] ) ? (int) $input['comment_id'] : 0;
433 461 $comment = $comment_id > 0 ? get_comment( $comment_id ) : null;
434 462 if ( ! $comment instanceof WP_Comment ) {
435 - return new WP_Error( 'desktop_mode_ai_comment_not_found', __( 'Comment not found.', 'desktop-mode' ) );
463 + return new WP_Error( 'openstation_ai_comment_not_found', __( 'Comment not found.', 'desktop-mode' ) );
436 464 }
437 465
438 - return desktop_mode_ai_analyze_comment_now( $comment, (int) $comment->user_id );
466 + return openstation_ai_analyze_comment_now( $comment, (int) $comment->user_id );
439 467 }