From 79f318a73915c9b279c6684773f8f8111d40a32a Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Thu, 14 May 2026 09:15:17 -0400 Subject: [PATCH 1/4] feat: add active agent preference --- inc/Abilities/AgentAbilities.php | 298 ++++++++++++++++++- inc/Cli/AgentResolver.php | 42 ++- tests/cli-effective-agent-resolver-smoke.php | 17 ++ 3 files changed, 352 insertions(+), 5 deletions(-) diff --git a/inc/Abilities/AgentAbilities.php b/inc/Abilities/AgentAbilities.php index a3998096e..4a34c0b5d 100644 --- a/inc/Abilities/AgentAbilities.php +++ b/inc/Abilities/AgentAbilities.php @@ -27,6 +27,7 @@ class AgentAbilities { private static bool $registered = false; + private const ACTIVE_AGENT_META_KEY = 'datamachine_active_agent_slug'; public function __construct() { if ( self::$registered ) { @@ -164,6 +165,7 @@ private function registerAbilities(): void { 'type' => 'object', 'properties' => array( 'success' => array( 'type' => 'boolean' ), + 'active_agent_slug' => array( 'type' => array( 'string', 'null' ) ), 'agents' => array( 'type' => 'array', 'items' => array( @@ -176,6 +178,7 @@ private function registerAbilities(): void { 'site_scope' => array( 'type' => array( 'integer', 'null' ) ), 'description' => array( 'type' => 'string' ), 'is_owner' => array( 'type' => 'boolean' ), + 'is_active' => array( 'type' => 'boolean' ), 'user_role' => array( 'type' => array( 'string', 'null' ) ), ), ), @@ -188,6 +191,74 @@ private function registerAbilities(): void { ) ); + wp_register_ability( + 'datamachine/get-active-agent', + array( + 'label' => 'Get Active Agent', + 'description' => 'Return the current user\'s persisted active Data Machine agent preference, falling back to an unambiguous accessible agent.', + 'category' => 'datamachine-agent', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'user_id' => array( + 'type' => 'integer', + 'description' => 'Resolve active agent for this user. Non-admins are forced to themselves.', + ), + ), + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'success' => array( 'type' => 'boolean' ), + 'agent' => array( 'type' => array( 'object', 'null' ) ), + 'agent_slug' => array( 'type' => array( 'string', 'null' ) ), + 'source' => array( 'type' => 'string' ), + 'needs_choice' => array( 'type' => 'boolean' ), + 'error' => array( 'type' => 'string' ), + ), + ), + 'execute_callback' => array( self::class, 'getActiveAgent' ), + 'permission_callback' => fn() => PermissionHelper::can( 'chat' ) || PermissionHelper::can_manage(), + 'meta' => array( 'show_in_rest' => true ), + ) + ); + + wp_register_ability( + 'datamachine/set-active-agent', + array( + 'label' => 'Set Active Agent', + 'description' => 'Persist the active Data Machine agent preference for a user after validating access.', + 'category' => 'datamachine-agent', + 'input_schema' => array( + 'type' => 'object', + 'required' => array( 'agent' ), + 'properties' => array( + 'agent' => array( + 'type' => 'string', + 'description' => 'Agent slug or ID to make active.', + ), + 'user_id' => array( + 'type' => 'integer', + 'description' => 'Set active agent for this user. Non-admins are forced to themselves.', + ), + ), + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'success' => array( 'type' => 'boolean' ), + 'agent' => array( 'type' => 'object' ), + 'agent_slug' => array( 'type' => 'string' ), + 'user_id' => array( 'type' => 'integer' ), + 'error' => array( 'type' => 'string' ), + ), + ), + 'execute_callback' => array( self::class, 'setActiveAgent' ), + 'permission_callback' => fn() => PermissionHelper::can( 'chat' ) || PermissionHelper::can_manage(), + 'meta' => array( 'show_in_rest' => true ), + ) + ); + wp_register_ability( 'datamachine/create-agent', array( @@ -606,8 +677,9 @@ public static function listAgents( array $input ): array { } else { if ( $target_user_id <= 0 ) { return array( - 'success' => true, - 'agents' => array(), + 'success' => true, + 'active_agent_slug' => null, + 'agents' => array(), ); } @@ -652,6 +724,9 @@ static function ( $row ) use ( $site_id ) { ); } + $active = self::resolve_active_agent_for_user( $target_user_id, $candidates ); + $active_agent_slug = $active['agent'] ? (string) $active['agent']['agent_slug'] : null; + // ---- Role enrichment (optional) ---------------------------------- // Computed against $target_user_id so `include_role=true` reflects // the resolved user's role even when an admin queries on their behalf. @@ -671,6 +746,7 @@ static function ( $row ) use ( $site_id ) { 'site_scope' => isset( $row['site_scope'] ) ? (int) $row['site_scope'] : null, 'description' => $description, 'is_owner' => $target_user_id > 0 && $owner_id === $target_user_id, + 'is_active' => null !== $active_agent_slug && (string) $row['agent_slug'] === $active_agent_slug, ); if ( $include_role ) { @@ -688,8 +764,222 @@ static function ( $row ) use ( $site_id ) { } return array( - 'success' => true, - 'agents' => $agents, + 'success' => true, + 'active_agent_slug' => $active_agent_slug, + 'agents' => $agents, + ); + } + + /** + * Return a user's active agent preference with safe fallback metadata. + * + * @param array $input Ability input. + * @return array + */ + public static function getActiveAgent( array $input ): array { + $target_user_id = self::resolve_active_agent_user_id( isset( $input['user_id'] ) ? (int) $input['user_id'] : 0 ); + if ( is_wp_error( $target_user_id ) ) { + return array( + 'success' => false, + 'error' => $target_user_id->get_error_message(), + ); + } + + $active = self::resolve_active_agent_for_user( $target_user_id ); + + return array( + 'success' => true, + 'agent' => $active['agent'] ? self::format_active_agent_row( $active['agent'], $target_user_id ) : null, + 'agent_slug' => $active['agent'] ? (string) $active['agent']['agent_slug'] : null, + 'source' => $active['source'], + 'needs_choice' => (bool) $active['needs_choice'], + ); + } + + /** + * Persist a user's active agent preference after validating access. + * + * @param array $input Ability input. + * @return array + */ + public static function setActiveAgent( array $input ): array { + $target_user_id = self::resolve_active_agent_user_id( isset( $input['user_id'] ) ? (int) $input['user_id'] : 0 ); + if ( is_wp_error( $target_user_id ) ) { + return array( + 'success' => false, + 'error' => $target_user_id->get_error_message(), + ); + } + + $agent_id = self::resolve_agent_input_id( array( 'agent' => (string) ( $input['agent'] ?? '' ) ) ); + if ( is_wp_error( $agent_id ) ) { + return array( + 'success' => false, + 'error' => $agent_id->get_error_message(), + ); + } + + $agents_repo = new Agents(); + $agent = $agents_repo->get_agent( $agent_id ); + if ( ! $agent ) { + return array( + 'success' => false, + 'error' => 'Agent not found.', + ); + } + + if ( ! self::user_can_access_agent_row( $target_user_id, $agent ) ) { + return array( + 'success' => false, + 'error' => sprintf( 'User %d cannot access agent "%s".', $target_user_id, (string) $agent['agent_slug'] ), + ); + } + + update_user_meta( $target_user_id, self::ACTIVE_AGENT_META_KEY, (string) $agent['agent_slug'] ); + + return array( + 'success' => true, + 'agent' => self::format_active_agent_row( $agent, $target_user_id ), + 'agent_slug' => (string) $agent['agent_slug'], + 'user_id' => $target_user_id, + ); + } + + /** + * Resolve which user an active-agent ability may act on. + * + * @param int $requested_user_id Requested user ID, or zero for caller. + * @return int|\WP_Error + */ + private static function resolve_active_agent_user_id( int $requested_user_id ): int|\WP_Error { + $caller_id = PermissionHelper::acting_user_id(); + $is_admin = PermissionHelper::can_manage(); + + if ( $requested_user_id > 0 && $requested_user_id !== $caller_id && ! $is_admin ) { + return new \WP_Error( 'forbidden_user', 'Changing another user\'s active agent requires admin privileges.' ); + } + + $target_user_id = $requested_user_id > 0 ? $requested_user_id : $caller_id; + if ( $target_user_id <= 0 ) { + return new \WP_Error( 'missing_user', 'Could not determine acting user.' ); + } + + return $target_user_id; + } + + /** + * Resolve active agent row from persisted preference or safe fallback. + * + * @param int $user_id User ID. + * @param array|null $candidates Optional accessible agent rows. + * @return array{agent: array|null, source: string, needs_choice: bool} + */ + private static function resolve_active_agent_for_user( int $user_id, ?array $candidates = null ): array { + $candidates = null === $candidates ? self::get_accessible_agent_rows_for_user( $user_id ) : array_values( $candidates ); + $stored = self::get_active_agent_slug_for_user( $user_id ); + + if ( '' !== $stored ) { + foreach ( $candidates as $row ) { + if ( (string) ( $row['agent_slug'] ?? '' ) === $stored && self::user_can_access_agent_row( $user_id, $row ) ) { + return array( + 'agent' => $row, + 'source' => 'preference', + 'needs_choice' => false, + ); + } + } + } + + if ( 1 === count( $candidates ) ) { + return array( + 'agent' => $candidates[0], + 'source' => 'single_accessible_agent', + 'needs_choice' => false, + ); + } + + return array( + 'agent' => null, + 'source' => '' !== $stored ? 'invalid_preference' : 'none', + 'needs_choice' => count( $candidates ) > 1, + ); + } + + /** + * Read persisted active agent slug for a user. + * + * @param int $user_id User ID. + * @return string + */ + private static function get_active_agent_slug_for_user( int $user_id ): string { + $stored = get_user_meta( $user_id, self::ACTIVE_AGENT_META_KEY, true ); + return is_string( $stored ) ? sanitize_title( $stored ) : ''; + } + + /** + * Build accessible agent rows for a user from ownership plus grants. + * + * @param int $user_id User ID. + * @return array> + */ + private static function get_accessible_agent_rows_for_user( int $user_id ): array { + if ( $user_id <= 0 ) { + return array(); + } + + $agents_repo = new Agents(); + $access_repo = new AgentAccess(); + $owned = $agents_repo->get_all_by_owner_id( $user_id ); + $owned_ids = array_map( static fn( $agent ) => (int) $agent['agent_id'], $owned ); + $granted_ids = array_map( 'intval', $access_repo->get_agent_ids_for_user( $user_id ) ); + $extra_ids = array_values( array_diff( $granted_ids, $owned_ids ) ); + $granted_rows = ! empty( $extra_ids ) ? $agents_repo->get_agents_by_ids( $extra_ids ) : array(); + + return array_values( array_filter( array_merge( $owned, $granted_rows ), static fn( $row ) => is_array( $row ) ) ); + } + + /** + * Check whether a user can access an agent row without changing acting context. + * + * @param int $user_id User ID. + * @param array $agent Agent row. + * @return bool + */ + private static function user_can_access_agent_row( int $user_id, array $agent ): bool { + $agent_id = (int) ( $agent['agent_id'] ?? 0 ); + if ( $user_id <= 0 || $agent_id <= 0 ) { + return false; + } + + if ( (int) ( $agent['owner_id'] ?? 0 ) === $user_id ) { + return true; + } + + $grant = ( new AgentAccess() )->get_access( (string) $agent_id, $user_id ); + $can_access = $grant instanceof \WP_Agent_Access_Grant && $grant->role_meets( 'viewer' ); + + return (bool) apply_filters( 'datamachine_can_access_agent', $can_access, $agent_id, $user_id, 'viewer' ); + } + + /** + * Format active agent payload for ability responses. + * + * @param array $agent Agent row. + * @param int $user_id User ID. + * @return array + */ + private static function format_active_agent_row( array $agent, int $user_id ): array { + $config = is_array( $agent['agent_config'] ?? null ) ? $agent['agent_config'] : array(); + + return array( + 'agent_id' => (int) $agent['agent_id'], + 'agent_slug' => (string) $agent['agent_slug'], + 'agent_name' => (string) $agent['agent_name'], + 'owner_id' => (int) $agent['owner_id'], + 'site_scope' => isset( $agent['site_scope'] ) ? (int) $agent['site_scope'] : null, + 'description' => isset( $config['description'] ) ? (string) $config['description'] : '', + 'is_owner' => (int) $agent['owner_id'] === $user_id, + 'is_active' => true, ); } diff --git a/inc/Cli/AgentResolver.php b/inc/Cli/AgentResolver.php index 956912e8c..422891b08 100644 --- a/inc/Cli/AgentResolver.php +++ b/inc/Cli/AgentResolver.php @@ -23,6 +23,8 @@ class AgentResolver { + private const ACTIVE_AGENT_META_KEY = 'datamachine_active_agent_slug'; + /** * Resolve --agent flag to an agent_id. * @@ -111,7 +113,10 @@ public static function resolveEffectiveContext( array $assoc_args ): array { $user_id = UserResolver::resolve( $assoc_args ); $effective_user_id = $directory_manager->get_effective_user_id( $user_id ); - $agent_slug = self::resolveEffectiveAgentSlugForUser( $effective_user_id ); + $agent_slug = self::resolveActiveAgentSlugForUser( $effective_user_id ); + if ( '' === $agent_slug ) { + $agent_slug = self::resolveEffectiveAgentSlugForUser( $effective_user_id ); + } if ( '' === $agent_slug ) { return array( 'agent_id' => null, @@ -160,6 +165,41 @@ public static function buildScopingInput( array $assoc_args ): array { return array(); } + /** + * Resolve the user's persisted active agent slug when it is still valid. + * + * @param int $user_id WordPress user ID. + * @return string Active agent slug, or empty string when unset/invalid. + */ + private static function resolveActiveAgentSlugForUser( int $user_id ): string { + if ( $user_id <= 0 || ! function_exists( 'get_user_meta' ) ) { + return ''; + } + + $stored = get_user_meta( $user_id, self::ACTIVE_AGENT_META_KEY, true ); + $slug = is_string( $stored ) ? sanitize_title( $stored ) : ''; + if ( '' === $slug ) { + return ''; + } + + $agents_repo = new Agents(); + $agent = $agents_repo->get_by_slug( $slug ); + if ( ! $agent ) { + return ''; + } + + if ( (int) $agent['owner_id'] === $user_id ) { + return $slug; + } + + if ( ! class_exists( '\DataMachine\Core\Database\Agents\AgentAccess' ) ) { + return ''; + } + + $grant = ( new \DataMachine\Core\Database\Agents\AgentAccess() )->get_access( (string) (int) $agent['agent_id'], $user_id ); + return $grant instanceof \WP_Agent_Access_Grant && $grant->role_meets( 'viewer' ) ? $slug : ''; + } + /** * Resolve an effective agent slug for an owner using Agents API semantics. * diff --git a/tests/cli-effective-agent-resolver-smoke.php b/tests/cli-effective-agent-resolver-smoke.php index 79e67467d..6ff2451d3 100644 --- a/tests/cli-effective-agent-resolver-smoke.php +++ b/tests/cli-effective-agent-resolver-smoke.php @@ -25,6 +25,13 @@ public static function error( string $message ): void { throw new RuntimeException( $message ); } } + + $GLOBALS['__datamachine_test_user_meta'] = array(); + + function get_user_meta( int $user_id, string $key, bool $single = false ) { + unset( $single ); + return $GLOBALS['__datamachine_test_user_meta'][ $user_id ][ $key ] ?? ''; + } } namespace DataMachine\Core\Database\Agents { @@ -74,6 +81,8 @@ public static function resolve( array $assoc_args ): int { } namespace { + require_once __DIR__ . '/../inc/Core/Agents/AgentIdentity.php'; + require_once __DIR__ . '/../inc/Core/Agents/AgentIdentityResolver.php'; require_once __DIR__ . '/../inc/Cli/AgentResolver.php'; DataMachine\Core\Database\Agents\Agents::$rows = array( @@ -110,5 +119,13 @@ public static function resolve( array $assoc_args ): int { agents_api_smoke_assert_equals( true, str_contains( $e->getMessage(), 'ambiguous' ), 'ambiguous owner fallback is rejected', $failures, $passes ); } + $GLOBALS['__datamachine_test_user_meta'][1]['datamachine_active_agent_slug'] = 'intelligence-chubes4'; + $context = DataMachine\Cli\AgentResolver::resolveEffectiveContext( array( 'user' => 1 ) ); + agents_api_smoke_assert_equals( 2, $context['agent_id'], 'active preference resolves before ambiguous owner fallback', $failures, $passes ); + agents_api_smoke_assert_equals( 'intelligence-chubes4', $context['agent_slug'], 'active preference carries slug', $failures, $passes ); + + $context = DataMachine\Cli\AgentResolver::resolveEffectiveContext( array( 'agent' => 'admin', 'user' => 1 ) ); + agents_api_smoke_assert_equals( 1, $context['agent_id'], 'explicit --agent overrides active preference', $failures, $passes ); + agents_api_smoke_finish( 'Data Machine CLI effective agent resolver', $failures, $passes ); } From f88f8f825e70158c5a2fe9ee4d3b1578167b9d18 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Thu, 14 May 2026 09:32:41 -0400 Subject: [PATCH 2/4] fix: preserve principal active agent precedence --- inc/Abilities/AgentAbilities.php | 11 ++--- inc/Cli/AgentResolver.php | 47 +++++++++++++++----- tests/cli-effective-agent-resolver-smoke.php | 16 +++++++ 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/inc/Abilities/AgentAbilities.php b/inc/Abilities/AgentAbilities.php index 4a34c0b5d..e778759a7 100644 --- a/inc/Abilities/AgentAbilities.php +++ b/inc/Abilities/AgentAbilities.php @@ -724,7 +724,7 @@ static function ( $row ) use ( $site_id ) { ); } - $active = self::resolve_active_agent_for_user( $target_user_id, $candidates ); + $active = self::resolve_active_agent_for_user( $target_user_id, $candidates, 'all' !== $scope ); $active_agent_slug = $active['agent'] ? (string) $active['agent']['agent_slug'] : null; // ---- Role enrichment (optional) ---------------------------------- @@ -870,11 +870,12 @@ private static function resolve_active_agent_user_id( int $requested_user_id ): /** * Resolve active agent row from persisted preference or safe fallback. * - * @param int $user_id User ID. - * @param array|null $candidates Optional accessible agent rows. + * @param int $user_id User ID. + * @param array|null $candidates Optional accessible agent rows. + * @param bool $allow_single_fallback Whether a single candidate should become active by default. * @return array{agent: array|null, source: string, needs_choice: bool} */ - private static function resolve_active_agent_for_user( int $user_id, ?array $candidates = null ): array { + private static function resolve_active_agent_for_user( int $user_id, ?array $candidates = null, bool $allow_single_fallback = true ): array { $candidates = null === $candidates ? self::get_accessible_agent_rows_for_user( $user_id ) : array_values( $candidates ); $stored = self::get_active_agent_slug_for_user( $user_id ); @@ -890,7 +891,7 @@ private static function resolve_active_agent_for_user( int $user_id, ?array $can } } - if ( 1 === count( $candidates ) ) { + if ( $allow_single_fallback && 1 === count( $candidates ) ) { return array( 'agent' => $candidates[0], 'source' => 'single_accessible_agent', diff --git a/inc/Cli/AgentResolver.php b/inc/Cli/AgentResolver.php index 422891b08..e3a6c3302 100644 --- a/inc/Cli/AgentResolver.php +++ b/inc/Cli/AgentResolver.php @@ -113,9 +113,12 @@ public static function resolveEffectiveContext( array $assoc_args ): array { $user_id = UserResolver::resolve( $assoc_args ); $effective_user_id = $directory_manager->get_effective_user_id( $user_id ); - $agent_slug = self::resolveActiveAgentSlugForUser( $effective_user_id ); + $agent_slug = self::resolvePrincipalAgentSlug(); if ( '' === $agent_slug ) { - $agent_slug = self::resolveEffectiveAgentSlugForUser( $effective_user_id ); + $agent_slug = self::resolveActiveAgentSlugForUser( $effective_user_id ); + } + if ( '' === $agent_slug ) { + $agent_slug = self::resolveOwnerFallbackAgentSlugForUser( $effective_user_id ); } if ( '' === $agent_slug ) { return array( @@ -201,12 +204,41 @@ private static function resolveActiveAgentSlugForUser( int $user_id ): string { } /** - * Resolve an effective agent slug for an owner using Agents API semantics. + * Resolve an agent slug from the current Agents API execution principal. + * + * @return string Effective agent slug, or empty string when no principal exists. + */ + private static function resolvePrincipalAgentSlug(): string { + if ( ! class_exists( '\\AgentsAPI\\AI\\WP_Agent_Effective_Agent_Resolver' ) ) { + return ''; + } + + $principal_context = array(); + if ( class_exists( '\\AgentsAPI\\AI\\WP_Agent_Execution_Principal' ) ) { + $principal_context['request_context'] = \AgentsAPI\AI\WP_Agent_Execution_Principal::REQUEST_CONTEXT_CLI; + } + + try { + return \AgentsAPI\AI\WP_Agent_Effective_Agent_Resolver::resolve( + array( + 'resolve_principal' => true, + 'principal_context' => $principal_context, + ) + ); + } catch ( \InvalidArgumentException $e ) { + WP_CLI::error( $e->getMessage() ); + } + + return ''; + } + + /** + * Resolve an effective agent slug for an owner using the ambiguous-safe owner fallback. * * @param int $owner_user_id Owner WordPress user ID. * @return string Effective agent slug, or empty string when none exists. */ - private static function resolveEffectiveAgentSlugForUser( int $owner_user_id ): string { + private static function resolveOwnerFallbackAgentSlugForUser( int $owner_user_id ): string { if ( ! class_exists( '\\AgentsAPI\\AI\\WP_Agent_Effective_Agent_Resolver' ) ) { WP_CLI::error( 'Agents API effective agent resolver is unavailable. Update the agents-api dependency or pass --agent explicitly.' ); } @@ -215,18 +247,11 @@ private static function resolveEffectiveAgentSlugForUser( int $owner_user_id ): $owned = $agents_repo->get_all_by_owner_id( $owner_user_id ); $slugs = array_values( array_filter( array_map( static fn( $agent ) => (string) ( $agent['agent_slug'] ?? '' ), $owned ) ) ); - $principal_context = array(); - if ( class_exists( '\\AgentsAPI\\AI\\WP_Agent_Execution_Principal' ) ) { - $principal_context['request_context'] = \AgentsAPI\AI\WP_Agent_Execution_Principal::REQUEST_CONTEXT_CLI; - } - try { return \AgentsAPI\AI\WP_Agent_Effective_Agent_Resolver::resolve( array( 'owner_user_id' => $owner_user_id, 'owner_agent_slugs' => $slugs, - 'resolve_principal' => true, - 'principal_context' => $principal_context, ) ); } catch ( \InvalidArgumentException $e ) { diff --git a/tests/cli-effective-agent-resolver-smoke.php b/tests/cli-effective-agent-resolver-smoke.php index 6ff2451d3..0c37ab238 100644 --- a/tests/cli-effective-agent-resolver-smoke.php +++ b/tests/cli-effective-agent-resolver-smoke.php @@ -124,6 +124,22 @@ public static function resolve( array $assoc_args ): int { agents_api_smoke_assert_equals( 2, $context['agent_id'], 'active preference resolves before ambiguous owner fallback', $failures, $passes ); agents_api_smoke_assert_equals( 'intelligence-chubes4', $context['agent_slug'], 'active preference carries slug', $failures, $passes ); + add_filter( + 'agents_api_execution_principal', + static function ( $principal, $request_context ) { + unset( $principal, $request_context ); + return AgentsAPI\AI\WP_Agent_Execution_Principal::user_session( + 1, + 'admin', + AgentsAPI\AI\WP_Agent_Execution_Principal::REQUEST_CONTEXT_CLI + ); + }, + 10, + 2 + ); + $context = DataMachine\Cli\AgentResolver::resolveEffectiveContext( array( 'user' => 1 ) ); + agents_api_smoke_assert_equals( 1, $context['agent_id'], 'execution principal overrides active preference', $failures, $passes ); + $context = DataMachine\Cli\AgentResolver::resolveEffectiveContext( array( 'agent' => 'admin', 'user' => 1 ) ); agents_api_smoke_assert_equals( 1, $context['agent_id'], 'explicit --agent overrides active preference', $failures, $passes ); From 8780f6d5e976884b3dadf4818c5a5a93324c3a1f Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Thu, 14 May 2026 09:42:40 -0400 Subject: [PATCH 3/4] fix: align active agent lint formatting --- inc/Abilities/AgentAbilities.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/inc/Abilities/AgentAbilities.php b/inc/Abilities/AgentAbilities.php index e778759a7..f98eb32b9 100644 --- a/inc/Abilities/AgentAbilities.php +++ b/inc/Abilities/AgentAbilities.php @@ -26,7 +26,7 @@ class AgentAbilities { - private static bool $registered = false; + private static bool $registered = false; private const ACTIVE_AGENT_META_KEY = 'datamachine_active_agent_slug'; public function __construct() { @@ -164,9 +164,9 @@ private function registerAbilities(): void { 'output_schema' => array( 'type' => 'object', 'properties' => array( - 'success' => array( 'type' => 'boolean' ), + 'success' => array( 'type' => 'boolean' ), 'active_agent_slug' => array( 'type' => array( 'string', 'null' ) ), - 'agents' => array( + 'agents' => array( 'type' => 'array', 'items' => array( 'type' => 'object', @@ -724,7 +724,7 @@ static function ( $row ) use ( $site_id ) { ); } - $active = self::resolve_active_agent_for_user( $target_user_id, $candidates, 'all' !== $scope ); + $active = self::resolve_active_agent_for_user( $target_user_id, $candidates, 'all' !== $scope ); $active_agent_slug = $active['agent'] ? (string) $active['agent']['agent_slug'] : null; // ---- Role enrichment (optional) ---------------------------------- From 3118086e20599c402b7963267cfe051f96ccc54e Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Thu, 14 May 2026 09:50:29 -0400 Subject: [PATCH 4/4] fix: match active agent property alignment --- inc/Abilities/AgentAbilities.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Abilities/AgentAbilities.php b/inc/Abilities/AgentAbilities.php index f98eb32b9..6ebb0a8ce 100644 --- a/inc/Abilities/AgentAbilities.php +++ b/inc/Abilities/AgentAbilities.php @@ -26,7 +26,7 @@ class AgentAbilities { - private static bool $registered = false; + private static bool $registered = false; private const ACTIVE_AGENT_META_KEY = 'datamachine_active_agent_slug'; public function __construct() {