PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.12
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.12
1.1.12 1.1.11 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 All 36 releases
← All changes | includes/agents/rest.php +124 -50 1.0.0 → 1.1.12 View file →
@@ -73,8 +73,35 @@
73 73 'type' => 'array',
74 74 'default' => array(),
75 75 'items' => array( 'type' => 'string' ),
76 76 ),
77 + // Like `face`, deliberately schema-light. Each row
78 + // is validated against the live trigger-kind
79 + // catalogue by openstation_agent_sanitize_triggers(),
80 + // which drops rows it does not recognise rather
81 + // than rejecting the whole create.
82 + 'triggers' => array(
83 + 'type' => 'array',
84 + 'default' => array(),
85 + ),
86 + 'vibes' => array(
87 + 'type' => 'string',
88 + 'default' => '',
89 + ),
90 + // `face` carries no schema beyond "object" and no
91 + // sanitize_callback on purpose. The real validator is
92 + // openstation_agent_sanitize_face_json(), which clamps
93 + // every number; a partial JSON Schema here would only
94 + // suggest the route had checked it.
95 + 'face' => array(
96 + 'type' => 'object',
97 + 'default' => null,
98 + ),
99 + 'faceSeed' => array(
100 + 'type' => 'integer',
101 + 'default' => 0,
102 + 'sanitize_callback' => 'absint',
103 + ),
77 104 ),
78 105 ),
79 106 )
80 107 );
@@ -90,8 +117,26 @@
90 117 );
91 118
92 119 register_rest_route(
93 120 $namespace,
121 + '/agents/draft',
122 + array(
123 + 'methods' => WP_REST_Server::CREATABLE,
124 + 'permission_callback' => 'openstation_agents_rest_write_permission',
125 + 'callback' => 'openstation_agents_rest_draft',
126 + 'args' => array(
127 + 'brief' => array(
128 + 'type' => 'string',
129 + 'required' => true,
130 + 'sanitize_callback' => 'sanitize_textarea_field',
131 + 'validate_callback' => 'openstation_agents_rest_validate_brief',
132 + ),
133 + ),
134 + )
135 + );
136 +
137 + register_rest_route(
138 + $namespace,
94 139 '/agents/trigger-kinds',
95 140 array(
96 141 'methods' => WP_REST_Server::READABLE,
97 142 'permission_callback' => 'openstation_agents_rest_read_permission',
@@ -148,14 +193,22 @@
148 193 'methods' => WP_REST_Server::CREATABLE,
149 194 'permission_callback' => 'openstation_agents_rest_invoke_permission',
150 195 'callback' => 'openstation_agents_rest_invoke',
151 196 'args' => array(
152 - 'message' => array(
197 + 'async' => array(
198 + 'type' => 'boolean',
199 + 'default' => false,
200 + ),
201 + 'requestId' => array(
202 + 'type' => 'string',
203 + 'format' => 'uuid',
204 + ),
205 + 'message' => array(
153 206 'type' => 'string',
154 207 'required' => true,
155 208 'sanitize_callback' => 'sanitize_textarea_field',
156 209 ),
157 - 'source' => array(
210 + 'source' => array(
158 211 'type' => 'string',
159 212 'default' => 'chat',
160 213 'enum' => array( 'chat', 'drag', 'send-to' ),
161 214 'sanitize_callback' => 'sanitize_key',
@@ -163,9 +216,9 @@
163 216 // Prior conversation turns, oldest first. Without these
164 217 // every message is a contextless run — a follow-up like
165 218 // "yes, do it" would be resolved against nothing and the
166 219 // agent could act on the wrong entity entirely.
167 - 'history' => array(
220 + 'history' => array(
168 221 'type' => 'array',
169 222 'default' => array(),
170 223 'items' => array(
171 224 'type' => 'object',
@@ -185,53 +238,16 @@
185 238 add_action( 'rest_api_init', 'openstation_agents_register_rest_routes' );
186 239
187 240 // ---------------------------------------------------------------------------
188 241 // Permissions
242 +//
243 +// The three capability gates themselves (`openstation_agents_user_can_read`
244 +// / `_manage` / `_invoke`) live in bootstrap.php: the WP Explorer
245 +// integration loads while the feature flag is off, and this file does
246 +// not.
189 247 // ---------------------------------------------------------------------------
190 248
191 249 /**
192 - * Whether the current user can see agents.
193 - *
194 - * @return bool
195 - */
196 -function openstation_agents_user_can_read() {
197 - /**
198 - * Filter whether the current user can read OpenStation agents.
199 - *
200 - * @param bool $can Default: `edit_posts` capability.
201 - */
202 - return (bool) apply_filters( 'openstation_agents_user_can_read', current_user_can( 'edit_posts' ) );
203 -}
204 -
205 -/**
206 - * Whether the current user can create / edit / delete agents.
207 - *
208 - * @return bool
209 - */
210 -function openstation_agents_user_can_manage() {
211 - /**
212 - * Filter whether the current user can manage OpenStation agents.
213 - *
214 - * @param bool $can Default: `edit_users` capability.
215 - */
216 - return (bool) apply_filters( 'openstation_agents_user_can_manage', current_user_can( 'edit_users' ) );
217 -}
218 -
219 -/**
220 - * Whether the current user can invoke agents.
221 - *
222 - * @return bool
223 - */
224 -function openstation_agents_user_can_invoke() {
225 - /**
226 - * Filter whether the current user can invoke OpenStation agents.
227 - *
228 - * @param bool $can Default: `edit_posts` capability.
229 - */
230 - return (bool) apply_filters( 'openstation_agents_user_can_invoke', current_user_can( 'edit_posts' ) );
231 -}
232 -
233 -/**
234 250 * Read-route permission callback.
235 251 *
236 252 * @return bool|WP_Error
237 253 */
@@ -327,8 +343,13 @@
327 343 * @param WP_REST_Request $request REST request.
328 344 * @return WP_REST_Response|WP_Error
329 345 */
330 346 function openstation_agents_rest_create( WP_REST_Request $request ) {
347 + // Every field the route declares is forwarded. `vibes`, `face` and
348 + // `faceSeed` are the character half of an agent, and a create that
349 + // took the name and dropped the portrait is how an agent ends up
350 + // wearing the fallback glyph seconds after someone picked a face
351 + // for it. `openstation_agent_create()` sanitizes each one.
331 352 $user = openstation_agent_create(
332 353 array(
333 354 'name' => (string) $request['name'],
334 355 'role' => (string) $request['role'],
@@ -334,8 +355,12 @@
334 355 'role' => (string) $request['role'],
335 356 'description' => (string) $request['description'],
336 357 'instructions' => (string) $request['instructions'],
337 358 'abilities' => (array) $request['abilities'],
359 + 'triggers' => (array) $request['triggers'],
360 + 'vibes' => (string) $request['vibes'],
361 + 'face' => $request['face'],
362 + 'faceSeed' => (int) $request['faceSeed'],
338 363 )
339 364 );
340 365 if ( is_wp_error( $user ) ) {
341 366 $data = $user->get_error_data();
@@ -374,9 +399,21 @@
374 399 $body = array();
375 400 }
376 401
377 402 $fields = array();
378 - $allowed = array( 'name', 'role', 'description', 'instructions', 'abilities', 'triggers', 'model', 'rateLimit' );
403 + $allowed = array(
404 + 'name',
405 + 'role',
406 + 'description',
407 + 'instructions',
408 + 'abilities',
409 + 'triggers',
410 + 'model',
411 + 'rateLimit',
412 + 'vibes',
413 + 'face',
414 + 'faceSeed',
415 + );
379 416 foreach ( $allowed as $field ) {
380 417 if ( array_key_exists( $field, $body ) ) {
381 418 $fields[ $field ] = $body[ $field ];
382 419 }
@@ -442,10 +479,11 @@
442 479
443 480 $source = (string) $request['source'];
444 481
445 482 // Per-agent gate. The route's `permission_callback` cannot run this
446 - // one: it has no access to the resolved agent, and the capability an
447 - // agent requires is a property of that agent's trigger config.
483 + // one: it has no access to the resolved agent, and the capabilities an
484 + // agent requires are a property of that agent's trigger config. The
485 + // gate applies all of them whatever `source` the request names.
448 486 if ( ! openstation_agent_user_can_invoke_agent( (int) $user->ID, $source ) ) {
449 487 return new WP_Error(
450 488 'openstation_agents_forbidden',
451 489 __( 'You do not have permission to invoke this agent.', 'desktop-mode' ),
@@ -452,8 +490,12 @@
452 490 array( 'status' => rest_authorization_required_code() )
453 491 );
454 492 }
455 493
494 + if ( $request['async'] ) {
495 + return openstation_agents_rest_enqueue_job( $request );
496 + }
497 +
456 498 $result = openstation_agent_invoke(
457 499 (int) $user->ID,
458 500 (string) $request['message'],
459 501 array(
@@ -481,8 +523,37 @@
481 523 return rest_ensure_response( openstation_agents_abilities_catalogue() );
482 524 }
483 525
484 526 /**
527 + * `brief` must carry words and fit the drafting cap.
528 + *
529 + * @param mixed $value Raw param.
530 + * @return bool
531 + */
532 +function openstation_agents_rest_validate_brief( $value ) {
533 + return is_string( $value )
534 + && '' !== trim( $value )
535 + && mb_strlen( $value ) <= OPENSTATION_AGENT_DRAFT_BRIEF_MAX;
536 +}
537 +
538 +/**
539 + * POST /agents/draft — draft a definition from a brief.
540 + *
541 + * Nothing is created: the wizard shows the draft for review and the
542 + * create route is still the only way an agent comes to exist.
543 + *
544 + * @param WP_REST_Request $request Request.
545 + * @return WP_REST_Response|WP_Error
546 + */
547 +function openstation_agents_rest_draft( WP_REST_Request $request ) {
548 + $draft = openstation_agent_draft( (string) $request['brief'], get_current_user_id() );
549 + if ( is_wp_error( $draft ) ) {
550 + return $draft;
551 + }
552 + return rest_ensure_response( $draft );
553 +}
554 +
555 +/**
485 556 * GET /agents/trigger-kinds — the trigger-kinds catalogue.
486 557 *
487 558 * @return WP_REST_Response
488 559 */
@@ -538,15 +609,15 @@
538 609 }
539 610
540 611 $avatar = get_avatar_url( $user->ID, array( 'size' => 96 ) );
541 612 if ( ! is_string( $avatar ) || '' === $avatar ) {
542 - $avatar = openstation_agent_avatar_url();
613 + $avatar = openstation_agent_avatar_url( (int) $user->ID );
543 614 }
544 615
545 616 return array(
546 617 'id' => (int) $user->ID,
547 618 'slug' => $slug,
548 - 'name' => (string) $user->display_name,
619 + 'name' => openstation_plain_text_title( $user->display_name ),
549 620 'description' => openstation_agent_get_description( (int) $user->ID ),
550 621 'instructions' => openstation_agent_get_instructions( (int) $user->ID ),
551 622 'role' => $role,
552 623 'abilities' => openstation_agent_get_abilities( (int) $user->ID ),
@@ -552,7 +623,10 @@
552 623 'abilities' => openstation_agent_get_abilities( (int) $user->ID ),
553 624 'triggers' => openstation_agent_get_triggers( (int) $user->ID ),
554 625 'model' => openstation_agent_get_model( (int) $user->ID ),
555 626 'rateLimit' => openstation_agent_get_rate_limit( (int) $user->ID ),
627 + 'vibes' => openstation_agent_get_vibes( (int) $user->ID ),
628 + 'face' => openstation_agent_get_face( (int) $user->ID ),
629 + 'faceSeed' => openstation_agent_get_face_seed( (int) $user->ID ),
556 630 'avatarUrl' => $avatar,
557 631 );
558 632 }