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/agents/store.php +404 -111 0.9.81.1.10 View file →
@@ -1,10 +1,10 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — Agents: definition store (user meta on the agent row).
3 + * OpenStation — Agents: definition store (user meta on the agent row).
4 4 *
5 5 * Everything that defines an agent beyond its `wp_users` row lives as
6 - * user meta on that row, in one `_desktop_mode_agent_*` key family:
6 + * user meta on that row, in one `_openstation_agent_*` key family:
7 7 *
8 8 * - `_desktop_mode_agent` marker ('1') — the existence test
9 9 * - `_desktop_mode_agent_description` "when to use" short text
10 10 * - `_desktop_mode_agent_instructions` system prompt (markdown)
@@ -16,9 +16,9 @@
16 16 * - `_desktop_mode_agent_rate_limit` invocations/hour, 0 = default
17 17 * - `_desktop_mode_agent_created_by` creating user id (audit aid)
18 18 *
19 19 * User meta has no revisions — the audit trail for definition changes
20 - * is the `desktop_mode_agent_{created,updated,deleted}` actions fired
20 + * is the `openstation_agent_{created,updated,deleted}` actions fired
21 21 * from this module's orchestrators, each carrying before/after values
22 22 * so logging plugins can persist a history.
23 23 *
24 24 * This module owns every key: constants, `register_meta()` calls,
@@ -24,47 +24,130 @@
24 24 * This module owns every key: constants, `register_meta()` calls,
25 25 * sanitization, getters/setters, and the create/update orchestrators
26 26 * the REST surface calls. `identity.php` owns the user row itself.
27 27 *
28 - * @package WPDesktopMode
28 + * @package OpenStation
29 29 */
30 30
31 31 defined( 'ABSPATH' ) || exit;
32 32
33 -require_once DESKTOP_MODE_DIR . 'includes/agents/guard.php';
33 +require_once OPENSTATION_DIR . 'includes/agents/guard.php';
34 34
35 35 /**
36 36 * Meta keys owned by the agents store. Constants so the other layer
37 37 * files reuse them instead of typing the literals.
38 38 *
39 - * `DESKTOP_MODE_AGENT_USER_MARKER_META` is the exception — it lives in
39 + * `OPENSTATION_AGENT_USER_MARKER_META` is the exception — it lives in
40 40 * guard.php, which loads unconditionally, because the agent test has to
41 41 * resolve even when this module does not load.
42 + *
43 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
44 + * persisted or externally-visible identifier, so renaming it would
45 + * orphan data already written by live installs (or break a live
46 + * URL). The mismatch between this constant's name and its value is
47 + * deliberate — it is NOT a half-finished rename.
42 48 */
43 -const DESKTOP_MODE_AGENT_DESCRIPTION_META = '_desktop_mode_agent_description';
44 -const DESKTOP_MODE_AGENT_INSTRUCTIONS_META = '_desktop_mode_agent_instructions';
45 -const DESKTOP_MODE_AGENT_ABILITIES_META = '_desktop_mode_agent_abilities';
46 -const DESKTOP_MODE_AGENT_TRIGGERS_META = '_desktop_mode_agent_triggers';
47 -const DESKTOP_MODE_AGENT_MODEL_META = '_desktop_mode_agent_model';
48 -const DESKTOP_MODE_AGENT_RATE_LIMIT_META = '_desktop_mode_agent_rate_limit';
49 -const DESKTOP_MODE_AGENT_CREATED_BY_META = '_desktop_mode_agent_created_by';
49 +const OPENSTATION_AGENT_DESCRIPTION_META = '_desktop_mode_agent_description';
50 +/**
51 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
52 + * persisted or externally-visible identifier, so renaming it would
53 + * orphan data already written by live installs (or break a live
54 + * URL). The mismatch between this constant's name and its value is
55 + * deliberate — it is NOT a half-finished rename.
56 + */
57 +const OPENSTATION_AGENT_INSTRUCTIONS_META = '_desktop_mode_agent_instructions';
58 +/**
59 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
60 + * persisted or externally-visible identifier, so renaming it would
61 + * orphan data already written by live installs (or break a live
62 + * URL). The mismatch between this constant's name and its value is
63 + * deliberate — it is NOT a half-finished rename.
64 + */
65 +const OPENSTATION_AGENT_ABILITIES_META = '_desktop_mode_agent_abilities';
66 +/**
67 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
68 + * persisted or externally-visible identifier, so renaming it would
69 + * orphan data already written by live installs (or break a live
70 + * URL). The mismatch between this constant's name and its value is
71 + * deliberate — it is NOT a half-finished rename.
72 + */
73 +const OPENSTATION_AGENT_TRIGGERS_META = '_desktop_mode_agent_triggers';
74 +/**
75 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
76 + * persisted or externally-visible identifier, so renaming it would
77 + * orphan data already written by live installs (or break a live
78 + * URL). The mismatch between this constant's name and its value is
79 + * deliberate — it is NOT a half-finished rename.
80 + */
81 +const OPENSTATION_AGENT_MODEL_META = '_desktop_mode_agent_model';
82 +/**
83 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
84 + * persisted or externally-visible identifier, so renaming it would
85 + * orphan data already written by live installs (or break a live
86 + * URL). The mismatch between this constant's name and its value is
87 + * deliberate — it is NOT a half-finished rename.
88 + */
89 +const OPENSTATION_AGENT_RATE_LIMIT_META = '_desktop_mode_agent_rate_limit';
90 +/**
91 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
92 + * persisted or externally-visible identifier, so renaming it would
93 + * orphan data already written by live installs (or break a live
94 + * URL). The mismatch between this constant's name and its value is
95 + * deliberate — it is NOT a half-finished rename.
96 + */
97 +const OPENSTATION_AGENT_CREATED_BY_META = '_desktop_mode_agent_created_by';
98 +/**
99 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
100 + * persisted or externally-visible identifier, so renaming it would
101 + * orphan data already written by live installs (or break a live
102 + * URL). The mismatch between this constant's name and its value is
103 + * deliberate — it is NOT a half-finished rename.
104 + */
105 +const OPENSTATION_AGENT_VIBES_META = '_desktop_mode_agent_vibes';
50 106
51 107 /**
108 + * Longest voice line an agent may carry.
109 + *
110 + * Short enough that it stays a voice rather than becoming a second
111 + * instruction block by volume.
112 + */
113 +const OPENSTATION_AGENT_VIBES_MAX_LENGTH = 120;
114 +/**
115 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
116 + * persisted or externally-visible identifier, so renaming it would
117 + * orphan data already written by live installs (or break a live
118 + * URL). The mismatch between this constant's name and its value is
119 + * deliberate — it is NOT a half-finished rename.
120 + */
121 +const OPENSTATION_AGENT_FACE_META = '_desktop_mode_agent_face';
122 +/**
123 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
124 + * persisted or externally-visible identifier, so renaming it would
125 + * orphan data already written by live installs (or break a live
126 + * URL). The mismatch between this constant's name and its value is
127 + * deliberate — it is NOT a half-finished rename.
128 + */
129 +const OPENSTATION_AGENT_FACE_SEED_META = '_desktop_mode_agent_face_seed';
130 +
131 +/**
52 132 * Every meta key the store writes — the privacy eraser and any future
53 133 * cleanup path iterate this list instead of re-typing the constants.
54 134 *
55 135 * @return string[]
56 136 */
57 -function desktop_mode_agent_meta_keys() {
137 +function openstation_agent_meta_keys() {
58 138 return array(
59 - DESKTOP_MODE_AGENT_USER_MARKER_META,
60 - DESKTOP_MODE_AGENT_DESCRIPTION_META,
61 - DESKTOP_MODE_AGENT_INSTRUCTIONS_META,
62 - DESKTOP_MODE_AGENT_ABILITIES_META,
63 - DESKTOP_MODE_AGENT_TRIGGERS_META,
64 - DESKTOP_MODE_AGENT_MODEL_META,
65 - DESKTOP_MODE_AGENT_RATE_LIMIT_META,
66 - DESKTOP_MODE_AGENT_CREATED_BY_META,
139 + OPENSTATION_AGENT_USER_MARKER_META,
140 + OPENSTATION_AGENT_DESCRIPTION_META,
141 + OPENSTATION_AGENT_INSTRUCTIONS_META,
142 + OPENSTATION_AGENT_ABILITIES_META,
143 + OPENSTATION_AGENT_TRIGGERS_META,
144 + OPENSTATION_AGENT_MODEL_META,
145 + OPENSTATION_AGENT_RATE_LIMIT_META,
146 + OPENSTATION_AGENT_CREATED_BY_META,
147 + OPENSTATION_AGENT_VIBES_META,
148 + OPENSTATION_AGENT_FACE_META,
149 + OPENSTATION_AGENT_FACE_SEED_META,
67 150 );
68 151 }
69 152
70 153 /**
@@ -75,9 +158,9 @@
75 158 * never exposes agent definitions.
76 159 *
77 160 * @return void
78 161 */
79 -function desktop_mode_agents_register_user_meta() {
162 +function openstation_agents_register_user_meta() {
80 163 $auth = static function () {
81 164 return current_user_can( 'edit_users' );
82 165 };
83 166
@@ -82,9 +165,9 @@
82 165 };
83 166
84 167 register_meta(
85 168 'user',
86 - DESKTOP_MODE_AGENT_DESCRIPTION_META,
169 + OPENSTATION_AGENT_DESCRIPTION_META,
87 170 array(
88 171 'type' => 'string',
89 172 'single' => true,
90 173 'default' => '',
@@ -94,9 +177,9 @@
94 177 )
95 178 );
96 179 register_meta(
97 180 'user',
98 - DESKTOP_MODE_AGENT_INSTRUCTIONS_META,
181 + OPENSTATION_AGENT_INSTRUCTIONS_META,
99 182 array(
100 183 'type' => 'string',
101 184 'single' => true,
102 185 'default' => '',
@@ -106,33 +189,33 @@
106 189 )
107 190 );
108 191 register_meta(
109 192 'user',
110 - DESKTOP_MODE_AGENT_ABILITIES_META,
193 + OPENSTATION_AGENT_ABILITIES_META,
111 194 array(
112 195 'type' => 'string',
113 196 'single' => true,
114 197 'default' => '',
115 198 'show_in_rest' => false,
116 - 'sanitize_callback' => 'desktop_mode_agent_sanitize_abilities_json',
199 + 'sanitize_callback' => 'openstation_agent_sanitize_abilities_json',
117 200 'auth_callback' => $auth,
118 201 )
119 202 );
120 203 register_meta(
121 204 'user',
122 - DESKTOP_MODE_AGENT_TRIGGERS_META,
205 + OPENSTATION_AGENT_TRIGGERS_META,
123 206 array(
124 207 'type' => 'string',
125 208 'single' => true,
126 209 'default' => '',
127 210 'show_in_rest' => false,
128 - 'sanitize_callback' => 'desktop_mode_agent_sanitize_triggers_json',
211 + 'sanitize_callback' => 'openstation_agent_sanitize_triggers_json',
129 212 'auth_callback' => $auth,
130 213 )
131 214 );
132 215 register_meta(
133 216 'user',
134 - DESKTOP_MODE_AGENT_MODEL_META,
217 + OPENSTATION_AGENT_MODEL_META,
135 218 array(
136 219 'type' => 'string',
137 220 'single' => true,
138 221 'default' => '',
@@ -142,9 +225,9 @@
142 225 )
143 226 );
144 227 register_meta(
145 228 'user',
146 - DESKTOP_MODE_AGENT_RATE_LIMIT_META,
229 + OPENSTATION_AGENT_RATE_LIMIT_META,
147 230 array(
148 231 'type' => 'integer',
149 232 'single' => true,
150 233 'default' => 0,
@@ -152,10 +235,46 @@
152 235 'sanitize_callback' => 'absint',
153 236 'auth_callback' => $auth,
154 237 )
155 238 );
239 + register_meta(
240 + 'user',
241 + OPENSTATION_AGENT_VIBES_META,
242 + array(
243 + 'type' => 'string',
244 + 'single' => true,
245 + 'default' => '',
246 + 'show_in_rest' => false,
247 + 'sanitize_callback' => 'openstation_agent_sanitize_vibes',
248 + 'auth_callback' => $auth,
249 + )
250 + );
251 + register_meta(
252 + 'user',
253 + OPENSTATION_AGENT_FACE_META,
254 + array(
255 + 'type' => 'string',
256 + 'single' => true,
257 + 'default' => '',
258 + 'show_in_rest' => false,
259 + 'sanitize_callback' => 'openstation_agent_sanitize_face_json',
260 + 'auth_callback' => $auth,
261 + )
262 + );
263 + register_meta(
264 + 'user',
265 + OPENSTATION_AGENT_FACE_SEED_META,
266 + array(
267 + 'type' => 'integer',
268 + 'single' => true,
269 + 'default' => 0,
270 + 'show_in_rest' => false,
271 + 'sanitize_callback' => 'absint',
272 + 'auth_callback' => $auth,
273 + )
274 + );
156 275 }
157 -add_action( 'init', 'desktop_mode_agents_register_user_meta' );
276 +add_action( 'init', 'openstation_agents_register_user_meta' );
158 277
159 278 // ---------------------------------------------------------------------------
160 279 // Sanitizers
161 280 // ---------------------------------------------------------------------------
@@ -165,9 +284,9 @@
165 284 *
166 285 * @param mixed $value Incoming list.
167 286 * @return string[]
168 287 */
169 -function desktop_mode_agents_sanitize_ability_slugs( $value ) {
288 +function openstation_agents_sanitize_ability_slugs( $value ) {
170 289 if ( is_string( $value ) ) {
171 290 $decoded = json_decode( $value, true );
172 291 $value = is_array( $decoded ) ? $decoded : array();
173 292 }
@@ -194,13 +313,118 @@
194 313 *
195 314 * @param mixed $value Incoming value (array or JSON string).
196 315 * @return string JSON-encoded slug list.
197 316 */
198 -function desktop_mode_agent_sanitize_abilities_json( $value ) {
199 - return (string) wp_json_encode( desktop_mode_agents_sanitize_ability_slugs( $value ) );
317 +function openstation_agent_sanitize_abilities_json( $value ) {
318 + return (string) wp_json_encode( openstation_agents_sanitize_ability_slugs( $value ) );
200 319 }
201 320
202 321 /**
322 + * Sanitize an agent's voice line.
323 + *
324 + * One short line of character: "blunt, precise, no sugarcoating". It
325 + * is appended to the agent's instructions at run time, so it reaches a
326 + * language model.
327 + *
328 + * **That is not a new privilege boundary.** Writing it needs
329 + * `edit_users`, the same capability that already lets you write
330 + * `instructions`, which is the entire system prompt. A 120-character
331 + * tone line is strictly less reach than that, so it deliberately sits
332 + * behind the same gate rather than a stricter one, and this note
333 + * exists so nobody "hardens" it later into a confusing split.
334 + *
335 + * Two structural guards it does need. `sanitize_text_field()` strips
336 + * line breaks, which is load-bearing: the runner marks operator turns
337 + * in the composed prompt, and a multi-line voice line could otherwise
338 + * fake a turn boundary. And the length cap keeps a "voice" from
339 + * becoming a second instruction block by volume.
340 + *
341 + * @param mixed $value Incoming line.
342 + * @return string
343 + */
344 +function openstation_agent_sanitize_vibes( $value ) {
345 + if ( ! is_scalar( $value ) ) {
346 + return '';
347 + }
348 + $clean = sanitize_text_field( (string) $value );
349 + return mb_substr( $clean, 0, OPENSTATION_AGENT_VIBES_MAX_LENGTH );
350 +}
351 +
352 +/**
353 + * Sanitize an agent's face for storage.
354 + *
355 + * The narrowing itself is `openstation_mio_narrow_look()`, which the
356 + * WP Explorer config also calls to preview the shipped cast while the
357 + * feature flag is off. Shared on purpose: two copies of "clamp, then
358 + * keep what was carried" is how a preview starts drawing a face the
359 + * seeder would never store. What this adds on top is the storage
360 + * shape — a JSON string, and an empty one when nothing was set, so an
361 + * agent with no opinion keeps no row at all rather than an empty blob.
362 + *
363 + * @param mixed $value Incoming look (array or JSON string).
364 + * @return string JSON, or an empty string when nothing was set.
365 + */
366 +function openstation_agent_sanitize_face_json( $value ) {
367 + if ( is_string( $value ) ) {
368 + $decoded = json_decode( $value, true );
369 + $value = is_array( $decoded ) ? $decoded : array();
370 + }
371 + $out = openstation_mio_narrow_look( $value );
372 + if ( empty( $out['appearance'] ) && empty( $out['physics'] ) ) {
373 + return '';
374 + }
375 + return (string) wp_json_encode( $out );
376 +}
377 +
378 +/**
379 + * Read an agent's voice line.
380 + *
381 + * @param int $user_id Agent user id.
382 + * @return string
383 + */
384 +function openstation_agent_get_vibes( $user_id ) {
385 + return (string) get_user_meta( (int) $user_id, OPENSTATION_AGENT_VIBES_META, true );
386 +}
387 +
388 +/**
389 + * Read an agent's stored face.
390 + *
391 + * A partial look: only what was overridden. Empty means "no face
392 + * chosen", which the avatar resolver reads as the shipped robot.
393 + *
394 + * @param int $user_id Agent user id.
395 + * @return array {
396 + * @type array $appearance Partial appearance overrides.
397 + * @type array $physics Partial silhouette overrides.
398 + * }
399 + */
400 +function openstation_agent_get_face( $user_id ) {
401 + $raw = (string) get_user_meta( (int) $user_id, OPENSTATION_AGENT_FACE_META, true );
402 + if ( '' === $raw ) {
403 + return array(
404 + 'appearance' => array(),
405 + 'physics' => array(),
406 + );
407 + }
408 + return openstation_sanitize_mio_look( json_decode( $raw, true ) );
409 +}
410 +
411 +/**
412 + * Read the seed an agent's face was rolled from.
413 + *
414 + * Kept alongside the face rather than instead of it. The face is what
415 + * gets drawn; the seed is provenance, and it is what lets a future
416 + * change to the randomizer's ranges re-roll every agent in one
417 + * migration instead of stranding them on an old palette.
418 + *
419 + * @param int $user_id Agent user id.
420 + * @return int Seed, or 0 when none was recorded.
421 + */
422 +function openstation_agent_get_face_seed( $user_id ) {
423 + return (int) get_user_meta( (int) $user_id, OPENSTATION_AGENT_FACE_SEED_META, true );
424 +}
425 +
426 +/**
203 427 * Sanitize the triggers array.
204 428 *
205 429 * Validates each row against the kind catalogue. Drops any row that
206 430 * doesn't match a known kind — one bad row never rejects the whole
@@ -208,9 +432,9 @@
208 432 *
209 433 * @param mixed $value Incoming triggers array (or JSON string).
210 434 * @return array
211 435 */
212 -function desktop_mode_agent_sanitize_triggers( $value ) {
436 +function openstation_agent_sanitize_triggers( $value ) {
213 437 if ( is_string( $value ) ) {
214 438 $decoded = json_decode( $value, true );
215 439 $value = is_array( $decoded ) ? $decoded : array();
216 440 }
@@ -218,9 +442,9 @@
218 442 return array();
219 443 }
220 444
221 445 $known_kinds = array();
222 - foreach ( desktop_mode_agent_trigger_kinds() as $kind ) {
446 + foreach ( openstation_agent_trigger_kinds() as $kind ) {
223 447 $known_kinds[ $kind['slug'] ] = $kind;
224 448 }
225 449
226 450 $out = array();
@@ -233,9 +457,9 @@
233 457 continue;
234 458 }
235 459
236 460 $config = isset( $row['config'] ) && is_array( $row['config'] ) ? $row['config'] : array();
237 - $config = desktop_mode_agent_sanitize_trigger_config_deep( $config );
461 + $config = openstation_agent_sanitize_trigger_config_deep( $config );
238 462
239 463 $out[] = array(
240 464 'kind' => $kind,
241 465 'config' => $config,
@@ -250,10 +474,10 @@
250 474 *
251 475 * @param mixed $value Incoming value.
252 476 * @return string JSON-encoded triggers list.
253 477 */
254 -function desktop_mode_agent_sanitize_triggers_json( $value ) {
255 - return (string) wp_json_encode( desktop_mode_agent_sanitize_triggers( $value ) );
478 +function openstation_agent_sanitize_triggers_json( $value ) {
479 + return (string) wp_json_encode( openstation_agent_sanitize_triggers( $value ) );
256 480 }
257 481
258 482 /**
259 483 * Recursively coerce trigger-config values into safe primitives.
@@ -266,9 +490,9 @@
266 490 *
267 491 * @param mixed $value Arbitrary input.
268 492 * @return mixed
269 493 */
270 -function desktop_mode_agent_sanitize_trigger_config_deep( $value ) {
494 +function openstation_agent_sanitize_trigger_config_deep( $value ) {
271 495 if ( is_array( $value ) ) {
272 496 $out = array();
273 497 foreach ( $value as $k => $v ) {
274 498 if ( is_string( $k ) ) {
@@ -278,9 +502,9 @@
278 502 }
279 503 } else {
280 504 $key = (int) $k;
281 505 }
282 - $out[ $key ] = desktop_mode_agent_sanitize_trigger_config_deep( $v );
506 + $out[ $key ] = openstation_agent_sanitize_trigger_config_deep( $v );
283 507 }
284 508 return $out;
285 509 }
286 510 if ( is_bool( $value ) || is_int( $value ) ) {
@@ -305,15 +529,15 @@
305 529 * `chat`, `send-to`, and `drag` are wired; the other kinds are declared so the
306 530 * Triggers pane can already store configuration for them, and later
307 531 * phases add the intake plumbing without a storage migration.
308 532 *
309 - * Plugins can extend the list via the `desktop_mode_agent_trigger_kinds`
533 + * Plugins can extend the list via the `openstation_agent_trigger_kinds`
310 534 * filter — each entry must declare a `slug`, `label`, and a JSON-Schema
311 535 * `config_schema` describing the shape of `trigger.config`.
312 536 *
313 537 * @return array<int, array{slug:string,wired:bool,label:string,description:string,icon:string,config_schema:array}>
314 538 */
315 -function desktop_mode_agent_trigger_kinds() {
539 +function openstation_agent_trigger_kinds() {
316 540 $kinds = array(
317 541 array(
318 542 'slug' => 'chat',
319 543 'wired' => true,
@@ -420,9 +644,9 @@
420 644 * Filter the trigger kinds available to agents.
421 645 *
422 646 * @param array $kinds Default trigger kinds.
423 647 */
424 - $filtered = apply_filters( 'desktop_mode_agent_trigger_kinds', $kinds );
648 + $filtered = apply_filters( 'openstation_agent_trigger_kinds', $kinds );
425 649 if ( ! is_array( $filtered ) ) {
426 650 return $kinds;
427 651 }
428 652 return array_values( $filtered );
@@ -436,9 +660,9 @@
436 660 * hook name.
437 661 *
438 662 * @return array<int, array{hook:string, when:string}>
439 663 */
440 -function desktop_mode_agent_hooks_catalogue() {
664 +function openstation_agent_hooks_catalogue() {
441 665 $hooks = array(
442 666 array(
443 667 'hook' => 'save_post',
444 668 'when' => __( 'Every time a post is saved.', 'desktop-mode' ),
@@ -478,9 +702,9 @@
478 702 * trigger configurator.
479 703 *
480 704 * @param array $hooks Default catalogue.
481 705 */
482 - $filtered = apply_filters( 'desktop_mode_agent_hooks_catalogue', $hooks );
706 + $filtered = apply_filters( 'openstation_agent_hooks_catalogue', $hooks );
483 707 return is_array( $filtered ) ? array_values( $filtered ) : $hooks;
484 708 }
485 709
486 710 /**
@@ -507,9 +731,9 @@
507 731 *
508 732 * @param string $role Role slug being assigned.
509 733 * @return bool
510 734 */
511 -function desktop_mode_agent_actor_can_assign_role( $role ) {
735 +function openstation_agent_actor_can_assign_role( $role ) {
512 736 $role = sanitize_key( (string) $role );
513 737 $can = current_user_can( 'promote_users' );
514 738
515 739 if ( $can && 'administrator' === $role ) {
@@ -533,9 +757,9 @@
533 757 * @param string $role Role slug being assigned.
534 758 * @param int $user_id Acting user id (0 when there is none).
535 759 */
536 760 return (bool) apply_filters(
537 - 'desktop_mode_agent_actor_can_assign_role',
761 + 'openstation_agent_actor_can_assign_role',
538 762 $can,
539 763 $role,
540 764 get_current_user_id()
541 765 );
@@ -546,14 +770,14 @@
546 770 * can actually hand out.
547 771 *
548 772 * The whitelist keeps agents in the standard content-role band; each
549 773 * survivor is then run through
550 - * {@see desktop_mode_agent_actor_can_assign_role()}, which is where the
774 + * {@see openstation_agent_actor_can_assign_role()}, which is where the
551 775 * real gating lives.
552 776 *
553 777 * @return string[] Role slugs.
554 778 */
555 -function desktop_mode_agent_allowed_roles() {
779 +function openstation_agent_allowed_roles() {
556 780 $whitelist = array( 'administrator', 'editor', 'author', 'contributor' );
557 781
558 782 /**
559 783 * Filter the roles an agent may be assigned.
@@ -564,9 +788,9 @@
564 788 * to clear both constraints.
565 789 *
566 790 * @param string[] $whitelist Default role slugs.
567 791 */
568 - $whitelist = apply_filters( 'desktop_mode_agent_allowed_roles', $whitelist );
792 + $whitelist = apply_filters( 'openstation_agent_allowed_roles', $whitelist );
569 793 if ( ! is_array( $whitelist ) ) {
570 794 return array();
571 795 }
572 796
@@ -578,9 +802,9 @@
578 802 $candidates = array_intersect( array_map( 'strval', $whitelist ), $editable );
579 803
580 804 $allowed = array();
581 805 foreach ( $candidates as $role ) {
582 - if ( desktop_mode_agent_actor_can_assign_role( $role ) ) {
806 + if ( openstation_agent_actor_can_assign_role( $role ) ) {
583 807 $allowed[] = $role;
584 808 }
585 809 }
586 810
@@ -596,10 +820,10 @@
596 820 *
597 821 * @param int $user_id Agent user id.
598 822 * @return string
599 823 */
600 -function desktop_mode_agent_get_description( $user_id ) {
601 - return (string) get_user_meta( (int) $user_id, DESKTOP_MODE_AGENT_DESCRIPTION_META, true );
824 +function openstation_agent_get_description( $user_id ) {
825 + return (string) get_user_meta( (int) $user_id, OPENSTATION_AGENT_DESCRIPTION_META, true );
602 826 }
603 827
604 828 /**
605 829 * Read the system prompt.
@@ -606,10 +830,10 @@
606 830 *
607 831 * @param int $user_id Agent user id.
608 832 * @return string
609 833 */
610 -function desktop_mode_agent_get_instructions( $user_id ) {
611 - return (string) get_user_meta( (int) $user_id, DESKTOP_MODE_AGENT_INSTRUCTIONS_META, true );
834 +function openstation_agent_get_instructions( $user_id ) {
835 + return (string) get_user_meta( (int) $user_id, OPENSTATION_AGENT_INSTRUCTIONS_META, true );
612 836 }
613 837
614 838 /**
615 839 * Read the ability allowlist.
@@ -616,14 +840,14 @@
616 840 *
617 841 * @param int $user_id Agent user id.
618 842 * @return string[]
619 843 */
620 -function desktop_mode_agent_get_abilities( $user_id ) {
621 - $raw = get_user_meta( (int) $user_id, DESKTOP_MODE_AGENT_ABILITIES_META, true );
844 +function openstation_agent_get_abilities( $user_id ) {
845 + $raw = get_user_meta( (int) $user_id, OPENSTATION_AGENT_ABILITIES_META, true );
622 846 if ( '' === $raw || null === $raw ) {
623 847 return array();
624 848 }
625 - return desktop_mode_agents_sanitize_ability_slugs( $raw );
849 + return openstation_agents_sanitize_ability_slugs( $raw );
626 850 }
627 851
628 852 /**
629 853 * Read triggers.
@@ -630,14 +854,14 @@
630 854 *
631 855 * @param int $user_id Agent user id.
632 856 * @return array
633 857 */
634 -function desktop_mode_agent_get_triggers( $user_id ) {
635 - $raw = get_user_meta( (int) $user_id, DESKTOP_MODE_AGENT_TRIGGERS_META, true );
858 +function openstation_agent_get_triggers( $user_id ) {
859 + $raw = get_user_meta( (int) $user_id, OPENSTATION_AGENT_TRIGGERS_META, true );
636 860 if ( '' === $raw || null === $raw ) {
637 861 return array();
638 862 }
639 - return desktop_mode_agent_sanitize_triggers( $raw );
863 + return openstation_agent_sanitize_triggers( $raw );
640 864 }
641 865
642 866 /**
643 867 * Read the model override.
@@ -644,10 +868,10 @@
644 868 *
645 869 * @param int $user_id Agent user id.
646 870 * @return string Empty string if not set.
647 871 */
648 -function desktop_mode_agent_get_model( $user_id ) {
649 - return (string) get_user_meta( (int) $user_id, DESKTOP_MODE_AGENT_MODEL_META, true );
872 +function openstation_agent_get_model( $user_id ) {
873 + return (string) get_user_meta( (int) $user_id, OPENSTATION_AGENT_MODEL_META, true );
650 874 }
651 875
652 876 /**
653 877 * Read the rate limit (invocations per hour).
@@ -654,10 +878,10 @@
654 878 *
655 879 * @param int $user_id Agent user id.
656 880 * @return int Zero when no per-agent override is set.
657 881 */
658 -function desktop_mode_agent_get_rate_limit( $user_id ) {
659 - return (int) get_user_meta( (int) $user_id, DESKTOP_MODE_AGENT_RATE_LIMIT_META, true );
882 +function openstation_agent_get_rate_limit( $user_id ) {
883 + return (int) get_user_meta( (int) $user_id, OPENSTATION_AGENT_RATE_LIMIT_META, true );
660 884 }
661 885
662 886 // ---------------------------------------------------------------------------
663 887 // Per-agent invocation gate
@@ -673,11 +897,11 @@
673 897 * @param string $source Invocation source slug.
674 898 * @return array|null Trigger row, or null when the agent declares none
675 899 * for this source.
676 900 */
677 -function desktop_mode_agent_trigger_for_source( $agent_user_id, $source ) {
901 +function openstation_agent_trigger_for_source( $agent_user_id, $source ) {
678 902 $source = sanitize_key( (string) $source );
679 - foreach ( desktop_mode_agent_get_triggers( (int) $agent_user_id ) as $trigger ) {
903 + foreach ( openstation_agent_get_triggers( (int) $agent_user_id ) as $trigger ) {
680 904 if ( isset( $trigger['kind'] ) && $source === $trigger['kind'] ) {
681 905 return $trigger;
682 906 }
683 907 }
@@ -686,9 +910,9 @@
686 910
687 911 /**
688 912 * Whether the current user may invoke THIS agent through THIS source.
689 913 *
690 - * The route-level `desktop_mode_agents_user_can_invoke()` check is
914 + * The route-level `openstation_agents_user_can_invoke()` check is
691 915 * site-wide — it answers "may this user invoke agents at all". This is
692 916 * the per-agent half: a trigger may declare a `capability` in its
693 917 * config, and until it is enforced here the field is decorative. The
694 918 * Triggers pane collects it and the store persists it, so an
@@ -703,10 +927,10 @@
703 927 * @param int $agent_user_id Agent user id.
704 928 * @param string $source Invocation source slug.
705 929 * @return bool
706 930 */
707 -function desktop_mode_agent_user_can_invoke_agent( $agent_user_id, $source = 'chat' ) {
708 - $trigger = desktop_mode_agent_trigger_for_source( $agent_user_id, $source );
931 +function openstation_agent_user_can_invoke_agent( $agent_user_id, $source = 'chat' ) {
932 + $trigger = openstation_agent_trigger_for_source( $agent_user_id, $source );
709 933 $capability = '';
710 934 if ( is_array( $trigger ) && isset( $trigger['config']['capability'] ) ) {
711 935 $capability = trim( (string) $trigger['config']['capability'] );
712 936 }
@@ -721,9 +945,9 @@
721 945 * @param string $source Invocation source slug.
722 946 * @param array|null $trigger The matching trigger row, if any.
723 947 */
724 948 return (bool) apply_filters(
725 - 'desktop_mode_agent_user_can_invoke_agent',
949 + 'openstation_agent_user_can_invoke_agent',
726 950 $can,
727 951 (int) $agent_user_id,
728 952 (string) $source,
729 953 $trigger
@@ -739,11 +963,11 @@
739 963 *
740 964 * @param array $args Optional overrides merged into the `get_users()` query.
741 965 * @return WP_User[]
742 966 */
743 -function desktop_mode_agent_get_agents( $args = array() ) {
967 +function openstation_agent_get_agents( $args = array() ) {
744 968 $defaults = array(
745 - 'meta_key' => DESKTOP_MODE_AGENT_USER_MARKER_META, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
969 + 'meta_key' => OPENSTATION_AGENT_USER_MARKER_META, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
746 970 'meta_value' => '1', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
747 971 'orderby' => 'display_name',
748 972 'order' => 'ASC',
749 973 'number' => 200,
@@ -757,22 +981,22 @@
757 981
758 982 /**
759 983 * Create an agent: synthetic user row + definition meta.
760 984 *
761 - * @param array{name:string, role:string, slug?:string, description?:string, instructions?:string, abilities?:array} $args Creation args.
985 + * @param array{name:string, role:string, slug?:string, description?:string, instructions?:string, abilities?:array, triggers?:array, vibes?:string, face?:array|string, faceSeed?:int} $args Creation args.
762 986 * @return WP_User|WP_Error
763 987 */
764 -function desktop_mode_agent_create( $args ) {
988 +function openstation_agent_create( $args ) {
765 989 $role = isset( $args['role'] ) ? sanitize_key( (string) $args['role'] ) : '';
766 - $allowed = desktop_mode_agent_allowed_roles();
990 + $allowed = openstation_agent_allowed_roles();
767 991 if ( '' === $role || ! in_array( $role, $allowed, true ) ) {
768 992 return new WP_Error(
769 - 'desktop_mode_agent_invalid_role',
993 + 'openstation_agent_invalid_role',
770 994 __( 'Pick a role you are allowed to assign to an agent.', 'desktop-mode' )
771 995 );
772 996 }
773 997
774 - $user = desktop_mode_agent_create_user( $args );
998 + $user = openstation_agent_create_user( $args );
775 999 if ( is_wp_error( $user ) ) {
776 1000 return $user;
777 1001 }
778 1002
@@ -777,20 +1001,41 @@
777 1001 }
778 1002
779 1003 $description = isset( $args['description'] ) ? sanitize_text_field( (string) $args['description'] ) : '';
780 1004 $instructions = isset( $args['instructions'] ) ? wp_kses_post( (string) $args['instructions'] ) : '';
781 - $abilities = isset( $args['abilities'] ) ? desktop_mode_agents_sanitize_ability_slugs( $args['abilities'] ) : array();
1005 + $abilities = isset( $args['abilities'] ) ? openstation_agents_sanitize_ability_slugs( $args['abilities'] ) : array();
1006 + $triggers = isset( $args['triggers'] ) ? openstation_agent_sanitize_triggers( $args['triggers'] ) : array();
1007 + $vibes = isset( $args['vibes'] ) ? openstation_agent_sanitize_vibes( $args['vibes'] ) : '';
1008 + $face = isset( $args['face'] ) ? openstation_agent_sanitize_face_json( $args['face'] ) : '';
1009 + // Every agent gets a seed even when nobody chose a face, so the
1010 + // backfill has something deterministic to roll from and two admins
1011 + // racing it land on the same portrait. `crc32` of the login is
1012 + // stable, cheap, and already unique per agent.
1013 + $seed = isset( $args['faceSeed'] ) ? absint( $args['faceSeed'] ) : 0;
1014 + if ( 0 === $seed ) {
1015 + $seed = crc32( (string) $user->user_login );
1016 + }
782 1017
783 1018 if ( '' !== $description ) {
784 - update_user_meta( $user->ID, DESKTOP_MODE_AGENT_DESCRIPTION_META, $description );
1019 + update_user_meta( $user->ID, OPENSTATION_AGENT_DESCRIPTION_META, $description );
785 1020 }
786 1021 if ( '' !== $instructions ) {
787 - update_user_meta( $user->ID, DESKTOP_MODE_AGENT_INSTRUCTIONS_META, $instructions );
1022 + update_user_meta( $user->ID, OPENSTATION_AGENT_INSTRUCTIONS_META, $instructions );
788 1023 }
789 1024 if ( ! empty( $abilities ) ) {
790 - update_user_meta( $user->ID, DESKTOP_MODE_AGENT_ABILITIES_META, wp_json_encode( $abilities ) );
1025 + update_user_meta( $user->ID, OPENSTATION_AGENT_ABILITIES_META, wp_json_encode( $abilities ) );
791 1026 }
792 - update_user_meta( $user->ID, DESKTOP_MODE_AGENT_CREATED_BY_META, get_current_user_id() );
1027 + if ( ! empty( $triggers ) ) {
1028 + update_user_meta( $user->ID, OPENSTATION_AGENT_TRIGGERS_META, wp_json_encode( $triggers ) );
1029 + }
1030 + if ( '' !== $vibes ) {
1031 + update_user_meta( $user->ID, OPENSTATION_AGENT_VIBES_META, $vibes );
1032 + }
1033 + if ( '' !== $face ) {
1034 + update_user_meta( $user->ID, OPENSTATION_AGENT_FACE_META, $face );
1035 + }
1036 + update_user_meta( $user->ID, OPENSTATION_AGENT_FACE_SEED_META, $seed );
1037 + update_user_meta( $user->ID, OPENSTATION_AGENT_CREATED_BY_META, get_current_user_id() );
793 1038
794 1039 /**
795 1040 * Fires after an agent is created.
796 1041 *
@@ -799,9 +1044,9 @@
799 1044 * description, instructions, abilities).
800 1045 * @param int $actor_id User who created the agent.
801 1046 */
802 1047 do_action(
803 - 'desktop_mode_agent_created',
1048 + 'openstation_agent_created',
804 1049 (int) $user->ID,
805 1050 array(
806 1051 'name' => (string) $user->display_name,
807 1052 'role' => $role,
@@ -807,8 +1052,11 @@
807 1052 'role' => $role,
808 1053 'description' => $description,
809 1054 'instructions' => $instructions,
810 1055 'abilities' => $abilities,
1056 + 'vibes' => $vibes,
1057 + 'face' => $face,
1058 + 'faceSeed' => $seed,
811 1059 ),
812 1060 get_current_user_id()
813 1061 );
814 1062
@@ -816,23 +1064,24 @@
816 1064 }
817 1065
818 1066 /**
819 1067 * Update an agent's definition. Accepts any subset of the recognized
820 - * fields, applies the valid ones, and fires `desktop_mode_agent_updated`
1068 + * fields, applies the valid ones, and fires `openstation_agent_updated`
821 1069 * once with a before/after map of everything that changed.
822 1070 *
823 1071 * Recognized fields: `name`, `role`, `description`, `instructions`,
824 - * `abilities`, `triggers`, `model`, `rateLimit`.
1072 + * `abilities`, `triggers`, `model`, `rateLimit`, `vibes`, `face`,
1073 + * `faceSeed`.
825 1074 *
826 1075 * @param int $user_id Agent user id.
827 1076 * @param array $fields Field map.
828 1077 * @return true|WP_Error
829 1078 */
830 -function desktop_mode_agent_update( $user_id, array $fields ) {
1079 +function openstation_agent_update( $user_id, array $fields ) {
831 1080 $user = get_userdata( (int) $user_id );
832 - if ( ! $user || ! desktop_mode_agent_is_agent( $user ) ) {
1081 + if ( ! $user || ! openstation_agent_is_agent( $user ) ) {
833 1082 return new WP_Error(
834 - 'desktop_mode_agent_not_found',
1083 + 'openstation_agent_not_found',
835 1084 __( 'Agent not found.', 'desktop-mode' )
836 1085 );
837 1086 }
838 1087
@@ -841,9 +1090,9 @@
841 1090 if ( isset( $fields['name'] ) ) {
842 1091 $name = sanitize_text_field( (string) $fields['name'] );
843 1092 if ( '' === $name ) {
844 1093 return new WP_Error(
845 - 'desktop_mode_agent_invalid_name',
1094 + 'openstation_agent_invalid_name',
846 1095 __( 'Agent name cannot be empty.', 'desktop-mode' )
847 1096 );
848 1097 }
849 1098 if ( $name !== (string) $user->display_name ) {
@@ -862,11 +1111,11 @@
862 1111 }
863 1112
864 1113 if ( isset( $fields['role'] ) ) {
865 1114 $role = sanitize_key( (string) $fields['role'] );
866 - if ( ! in_array( $role, desktop_mode_agent_allowed_roles(), true ) ) {
1115 + if ( ! in_array( $role, openstation_agent_allowed_roles(), true ) ) {
867 1116 return new WP_Error(
868 - 'desktop_mode_agent_invalid_role',
1117 + 'openstation_agent_invalid_role',
869 1118 __( 'Pick a role you are allowed to assign to an agent.', 'desktop-mode' )
870 1119 );
871 1120 }
872 1121 $current_role = is_array( $user->roles ) && ! empty( $user->roles ) ? (string) reset( $user->roles ) : '';
@@ -880,57 +1129,57 @@
880 1129 }
881 1130
882 1131 if ( isset( $fields['description'] ) ) {
883 1132 $description = sanitize_text_field( (string) $fields['description'] );
884 - $before = desktop_mode_agent_get_description( $user->ID );
1133 + $before = openstation_agent_get_description( $user->ID );
885 1134 if ( $description !== $before ) {
886 1135 $changed['description'] = array(
887 1136 'from' => $before,
888 1137 'to' => $description,
889 1138 );
890 - update_user_meta( $user->ID, DESKTOP_MODE_AGENT_DESCRIPTION_META, $description );
1139 + update_user_meta( $user->ID, OPENSTATION_AGENT_DESCRIPTION_META, $description );
891 1140 }
892 1141 }
893 1142
894 1143 if ( isset( $fields['instructions'] ) ) {
895 1144 $instructions = wp_kses_post( (string) $fields['instructions'] );
896 - $before = desktop_mode_agent_get_instructions( $user->ID );
1145 + $before = openstation_agent_get_instructions( $user->ID );
897 1146 if ( $instructions !== $before ) {
898 1147 $changed['instructions'] = array(
899 1148 'from' => $before,
900 1149 'to' => $instructions,
901 1150 );
902 - update_user_meta( $user->ID, DESKTOP_MODE_AGENT_INSTRUCTIONS_META, $instructions );
1151 + update_user_meta( $user->ID, OPENSTATION_AGENT_INSTRUCTIONS_META, $instructions );
903 1152 }
904 1153 }
905 1154
906 1155 if ( isset( $fields['abilities'] ) ) {
907 - $abilities = desktop_mode_agents_sanitize_ability_slugs( $fields['abilities'] );
908 - $before = desktop_mode_agent_get_abilities( $user->ID );
1156 + $abilities = openstation_agents_sanitize_ability_slugs( $fields['abilities'] );
1157 + $before = openstation_agent_get_abilities( $user->ID );
909 1158 if ( $abilities !== $before ) {
910 1159 $changed['abilities'] = array(
911 1160 'from' => $before,
912 1161 'to' => $abilities,
913 1162 );
914 - update_user_meta( $user->ID, DESKTOP_MODE_AGENT_ABILITIES_META, wp_json_encode( $abilities ) );
1163 + update_user_meta( $user->ID, OPENSTATION_AGENT_ABILITIES_META, wp_json_encode( $abilities ) );
915 1164 }
916 1165 }
917 1166
918 1167 if ( isset( $fields['triggers'] ) ) {
919 - $triggers = desktop_mode_agent_sanitize_triggers( $fields['triggers'] );
920 - $before = desktop_mode_agent_get_triggers( $user->ID );
1168 + $triggers = openstation_agent_sanitize_triggers( $fields['triggers'] );
1169 + $before = openstation_agent_get_triggers( $user->ID );
921 1170 if ( $triggers !== $before ) {
922 1171 $changed['triggers'] = array(
923 1172 'from' => $before,
924 1173 'to' => $triggers,
925 1174 );
926 - update_user_meta( $user->ID, DESKTOP_MODE_AGENT_TRIGGERS_META, wp_json_encode( $triggers ) );
1175 + update_user_meta( $user->ID, OPENSTATION_AGENT_TRIGGERS_META, wp_json_encode( $triggers ) );
927 1176 }
928 1177 }
929 1178
930 1179 if ( isset( $fields['model'] ) ) {
931 1180 $model = sanitize_text_field( (string) $fields['model'] );
932 - $before = desktop_mode_agent_get_model( $user->ID );
1181 + $before = openstation_agent_get_model( $user->ID );
933 1182 if ( $model !== $before ) {
934 1183 $changed['model'] = array(
935 1184 'from' => $before,
936 1185 'to' => $model,
@@ -935,11 +1184,11 @@
935 1184 'from' => $before,
936 1185 'to' => $model,
937 1186 );
938 1187 if ( '' === $model ) {
939 - delete_user_meta( $user->ID, DESKTOP_MODE_AGENT_MODEL_META );
1188 + delete_user_meta( $user->ID, OPENSTATION_AGENT_MODEL_META );
940 1189 } else {
941 - update_user_meta( $user->ID, DESKTOP_MODE_AGENT_MODEL_META, $model );
1190 + update_user_meta( $user->ID, OPENSTATION_AGENT_MODEL_META, $model );
942 1191 }
943 1192 }
944 1193 }
945 1194
@@ -944,9 +1193,9 @@
944 1193 }
945 1194
946 1195 if ( isset( $fields['rateLimit'] ) ) {
947 1196 $rate = max( 0, (int) $fields['rateLimit'] );
948 - $before = desktop_mode_agent_get_rate_limit( $user->ID );
1197 + $before = openstation_agent_get_rate_limit( $user->ID );
949 1198 if ( $rate !== $before ) {
950 1199 $changed['rateLimit'] = array(
951 1200 'from' => $before,
952 1201 'to' => $rate,
@@ -951,15 +1200,59 @@
951 1200 'from' => $before,
952 1201 'to' => $rate,
953 1202 );
954 1203 if ( 0 === $rate ) {
955 - delete_user_meta( $user->ID, DESKTOP_MODE_AGENT_RATE_LIMIT_META );
1204 + delete_user_meta( $user->ID, OPENSTATION_AGENT_RATE_LIMIT_META );
956 1205 } else {
957 - update_user_meta( $user->ID, DESKTOP_MODE_AGENT_RATE_LIMIT_META, $rate );
1206 + update_user_meta( $user->ID, OPENSTATION_AGENT_RATE_LIMIT_META, $rate );
958 1207 }
959 1208 }
960 1209 }
961 1210
1211 + if ( isset( $fields['vibes'] ) ) {
1212 + $vibes = openstation_agent_sanitize_vibes( $fields['vibes'] );
1213 + $before = openstation_agent_get_vibes( $user->ID );
1214 + if ( $vibes !== $before ) {
1215 + $changed['vibes'] = array(
1216 + 'from' => $before,
1217 + 'to' => $vibes,
1218 + );
1219 + if ( '' === $vibes ) {
1220 + delete_user_meta( $user->ID, OPENSTATION_AGENT_VIBES_META );
1221 + } else {
1222 + update_user_meta( $user->ID, OPENSTATION_AGENT_VIBES_META, $vibes );
1223 + }
1224 + }
1225 + }
1226 +
1227 + if ( isset( $fields['face'] ) ) {
1228 + $face = openstation_agent_sanitize_face_json( $fields['face'] );
1229 + $before = (string) get_user_meta( $user->ID, OPENSTATION_AGENT_FACE_META, true );
1230 + if ( $face !== $before ) {
1231 + $changed['face'] = array(
1232 + 'from' => $before,
1233 + 'to' => $face,
1234 + );
1235 + if ( '' === $face ) {
1236 + delete_user_meta( $user->ID, OPENSTATION_AGENT_FACE_META );
1237 + } else {
1238 + update_user_meta( $user->ID, OPENSTATION_AGENT_FACE_META, $face );
1239 + }
1240 + }
1241 + }
1242 +
1243 + if ( isset( $fields['faceSeed'] ) ) {
1244 + $seed = absint( $fields['faceSeed'] );
1245 + $before = openstation_agent_get_face_seed( $user->ID );
1246 + if ( $seed !== $before ) {
1247 + $changed['faceSeed'] = array(
1248 + 'from' => $before,
1249 + 'to' => $seed,
1250 + );
1251 + update_user_meta( $user->ID, OPENSTATION_AGENT_FACE_SEED_META, $seed );
1252 + }
1253 + }
1254 +
962 1255 if ( ! empty( $changed ) ) {
963 1256 /**
964 1257 * Fires after an agent's definition changed.
965 1258 *
@@ -969,9 +1262,9 @@
969 1262 * @param int $user_id Agent user id.
970 1263 * @param array $changed Map of field => { from, to }.
971 1264 * @param int $actor_id User who made the change.
972 1265 */
973 - do_action( 'desktop_mode_agent_updated', (int) $user->ID, $changed, get_current_user_id() );
1266 + do_action( 'openstation_agent_updated', (int) $user->ID, $changed, get_current_user_id() );
974 1267 }
975 1268
976 1269 return true;
977 1270 }