| @@ -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 | } |