Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: changed

Use new Agents Manager state endpoint in Agents Manager
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ public function should_use_unified_experience() {
}
}

// On WoA and Garden sites, delegate to wpcom via the /me/preferences endpoint.
// On WoA and Garden sites, delegate to wpcom via the /agents-manager/state endpoint.
// This avoids duplicating rollout logic and handles cases where
// wpcom-specific functions (like get_user_attribute) aren't available.
if ( $this->fetch_unified_experience_preference() ) {
Expand Down Expand Up @@ -530,7 +530,7 @@ private function has_unified_chat_opt_in_enabled( $user_id ) {
* Used on Atomic sites to delegate the decision to wpcom, which has
* access to user attributes and can evaluate the rollout logic.
*
* Calls /me/preferences endpoint which is accessible via Jetpack user tokens.
* Calls /agents-manager/state endpoint which is accessible via Jetpack user tokens.
*
* @return bool Whether user should see unified experience.
*/
Expand All @@ -552,9 +552,9 @@ private function fetch_unified_experience_preference() {
return false;
}

// Call /me/preferences via wpcom/v2 namespace (works with Jetpack user tokens).
// Call dedicated agents-manager/state endpoint (limits token scope to only needed preferences).
$wpcom_request = \Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_user(
'/me/preferences?preference_key=unified_ai_chat',
'/agents-manager/state?key=unified_ai_chat',
'2',
array( 'method' => 'GET' )
);
Expand All @@ -574,7 +574,7 @@ private function fetch_unified_experience_preference() {
$body = wp_remote_retrieve_body( $wpcom_request );
$decoded_body = json_decode( $body, true );

// The response is the value of the preference directly when using preference_key.
// The response is { "unified_ai_chat": true/false } when using key param.
$result = ! empty( $decoded_body );
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The response parsing logic appears incorrect. According to the comment, the response should be { "unified_ai_chat": true/false } when using the key param, but the code uses ! empty( $decoded_body ) which will return true for any non-empty response object, regardless of the actual value of unified_ai_chat.

For example, if the API returns { "unified_ai_chat": false }, this code will incorrectly evaluate to true because the array is not empty. The code should explicitly check the value: $result = ! empty( $decoded_body['unified_ai_chat'] );

Suggested change
$result = ! empty( $decoded_body );
$result = is_array( $decoded_body ) && ! empty( $decoded_body['unified_ai_chat'] );

Copilot uses AI. Check for mistakes.

// Cache for 1 minute.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,24 @@ public function register_rest_route() {
}

/**
* Get chat_id and last_chat_id from user preferences.
* Default state values.
*/
private const DEFAULTS = array(
'agents_manager_open' => false,
'agents_manager_docked' => false,
'agents_manager_floating_position' => 'right',
'agents_manager_router_history' => null,
);

/**
* Get Agents Manager state from user preferences.
*
* Uses the dedicated wpcom/v2/agents-manager/state endpoint
* which only exposes the specific preferences needed.
*/
public function get_state() {
// Forward the request body to the support chat endpoint.
$body = Client::wpcom_json_api_request_as_user(
'/me/preferences',
'/agents-manager/state',
'2',
array( 'method' => 'GET' )
);
Expand All @@ -61,85 +73,67 @@ public function get_state() {
return $body;
}

$response = json_decode( wp_remote_retrieve_body( $body ) );

$calypso_preferences = $response->calypso_preferences ?? (object) array();
$response = json_decode( wp_remote_retrieve_body( $body ), true );

$is_open = $calypso_preferences->agents_manager_open ?? false;
$is_docked = $calypso_preferences->agents_manager_docked ?? false;
$floating_position = $calypso_preferences->agents_manager_floating_position ?? 'right';
$router_history = $calypso_preferences->agents_manager_router_history ?? null;
if ( ! is_array( $response ) ) {
return rest_ensure_response( self::DEFAULTS );
}

$projected_response = array(
'calypso_preferences' => array(
'agents_manager_open' => (bool) $is_open,
'agents_manager_docked' => (bool) $is_docked,
'agents_manager_floating_position' => $floating_position,
'agents_manager_router_history' => $router_history,
),
return rest_ensure_response(
array(
'agents_manager_open' => (bool) ( $response['agents_manager_open'] ?? self::DEFAULTS['agents_manager_open'] ),
'agents_manager_docked' => (bool) ( $response['agents_manager_docked'] ?? self::DEFAULTS['agents_manager_docked'] ),
'agents_manager_floating_position' => $response['agents_manager_floating_position'] ?? self::DEFAULTS['agents_manager_floating_position'],
'agents_manager_router_history' => $response['agents_manager_router_history'] ?? self::DEFAULTS['agents_manager_router_history'],
)
);

return rest_ensure_response( $projected_response );
}

/**
* Set chat_id or last_chat_id from user preferences.
* Set Agents Manager state in user preferences.
*
* Uses the dedicated wpcom/v2/agents-manager/state endpoint
* which only allows updating the specific preferences needed.
*
* @param \WP_REST_Request $request The request sent to the API.
*/
public function set_state( \WP_REST_Request $request ) {
$state = $request['agents_manager_open'];
$router_history = $request['agents_manager_router_history'];
$docked = $request['agents_manager_docked'];
$floating_position = $request['agents_manager_floating_position'];

$data = array(
'calypso_preferences' => array(),
);

if ( $request->has_param( 'agents_manager_open' ) ) {
$data['calypso_preferences']['agents_manager_open'] = $state;
}
$state = array();

if ( $request->has_param( 'agents_manager_router_history' ) ) {
$data['calypso_preferences']['agents_manager_router_history'] = $router_history;
}

if ( $request->has_param( 'agents_manager_docked' ) ) {
$data['calypso_preferences']['agents_manager_docked'] = $docked;
}

if ( $request->has_param( 'agents_manager_floating_position' ) ) {
$data['calypso_preferences']['agents_manager_floating_position'] = $floating_position;
foreach ( array_keys( self::DEFAULTS ) as $key ) {
if ( $request->has_param( $key ) ) {
$state[ $key ] = $request[ $key ];
}
}

$body = Client::wpcom_json_api_request_as_user(
'/me/preferences',
'/agents-manager/state',
'2',
array( 'method' => 'POST' ),
$data
array( 'state' => $state )
);

if ( is_wp_error( $body ) ) {
return $body;
}

$response = json_decode( wp_remote_retrieve_body( $body ) );
$response = json_decode( wp_remote_retrieve_body( $body ), true );

$is_open = $response->calypso_preferences->agents_manager_open ?? false;
$is_docked = $response->calypso_preferences->agents_manager_docked ?? false;
$floating_position = $response->calypso_preferences->agents_manager_floating_position ?? 'right';
$router_history = $response->calypso_preferences->agents_manager_router_history ?? null;
if ( ! is_array( $response ) ) {
return new \WP_Error(
'invalid_response',
'Invalid response from WPCOM endpoint',
array( 'status' => 500 )
);
}

$projected_response = array(
'calypso_preferences' => array(
'agents_manager_open' => (bool) $is_open,
'agents_manager_docked' => (bool) $is_docked,
'agents_manager_floating_position' => $floating_position,
'agents_manager_router_history' => $router_history,
),
return rest_ensure_response(
array(
'agents_manager_open' => (bool) ( $response['agents_manager_open'] ?? self::DEFAULTS['agents_manager_open'] ),
'agents_manager_docked' => (bool) ( $response['agents_manager_docked'] ?? self::DEFAULTS['agents_manager_docked'] ),
'agents_manager_floating_position' => $response['agents_manager_floating_position'] ?? self::DEFAULTS['agents_manager_floating_position'],
'agents_manager_router_history' => $response['agents_manager_router_history'] ?? self::DEFAULTS['agents_manager_router_history'],
)
);

return rest_ensure_response( $projected_response );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,7 @@ public function test_fetch_unified_experience_preference_caches_failures() {
* @return array The mocked response.
*/
public function mock_preferences_api_enabled( $response, $args, $url ) {
if ( strpos( $url, '/me/preferences' ) === false ) {
if ( strpos( $url, '/agents-manager/state' ) === false ) {
return $response;
}

Expand All @@ -992,7 +992,7 @@ public function mock_preferences_api_enabled( $response, $args, $url ) {
* @return array The mocked response.
*/
public function mock_preferences_api_disabled( $response, $args, $url ) {
if ( strpos( $url, '/me/preferences' ) === false ) {
if ( strpos( $url, '/agents-manager/state' ) === false ) {
return $response;
}

Expand All @@ -1014,7 +1014,7 @@ public function mock_preferences_api_disabled( $response, $args, $url ) {
* @return \WP_Error The mocked error response.
*/
public function mock_preferences_api_error( $response, $args, $url ) {
if ( strpos( $url, '/me/preferences' ) === false ) {
if ( strpos( $url, '/agents-manager/state' ) === false ) {
return $response;
}

Expand Down