diff --git a/inc/Engine/AI/Tools/ToolPolicyResolver.php b/inc/Engine/AI/Tools/ToolPolicyResolver.php index 679be0fc4..613200d6b 100644 --- a/inc/Engine/AI/Tools/ToolPolicyResolver.php +++ b/inc/Engine/AI/Tools/ToolPolicyResolver.php @@ -98,11 +98,12 @@ public function resolve( array $args ): array { return array(); } + $agent_policy = $agent_id > 0 ? $this->getAgentToolPolicy( $agent_id ) : null; + $args = $this->withAgentPolicyAllowOnly( $args, $agent_policy ); + // 1. Gather tools from Data Machine-owned sources. $args['modes'] = $modes; $tools = $this->gatherByModes( $modes, $args ); - - $agent_policy = $agent_id > 0 ? $this->getAgentToolPolicy( $agent_id ) : null; $tools = $this->filterRuntimeToolsByPolicyOptIn( $tools, $args, $agent_policy ); // 2. Delegate generic mode/allow/deny/category policy resolution to Agents API. @@ -188,6 +189,26 @@ private function filterRuntimeToolsByPolicyOptIn( array $tools, array $args, ?ar return $tools; } + /** + * Include explicit agent allow-policy tools in the early opt-in allow list. + * + * @param array $args Resolution args. + * @param array|null $agent_policy Optional persisted agent tool policy. + * @return array Resolution args. + */ + private function withAgentPolicyAllowOnly( array $args, ?array $agent_policy ): array { + if ( ! is_array( $agent_policy ) || \WP_Agent_Tool_Policy::MODE_ALLOW !== ( $agent_policy['mode'] ?? '' ) ) { + return $args; + } + + $args['allow_only'] = array_values( array_unique( array_merge( + $this->policy_filter->string_list( $args['allow_only'] ?? array() ), + $this->policy_filter->string_list( $agent_policy['tools'] ?? array() ) + ) ) ); + + return $args; + } + /** * Get tool policy from an agent's config. * @@ -295,7 +316,16 @@ public static function normalizeModes( mixed $modes ): array { * @return bool True when the tool should be included. */ public static function isOptInToolAllowed( array $tool_config, string $tool_name, array $args ): bool { - return empty( $tool_config['requires_opt_in'] ) - || in_array( $tool_name, $args['allow_only'] ?? array(), true ); + if ( empty( $tool_config['requires_opt_in'] ) ) { + return true; + } + + $allowed = is_array( $args['allow_only'] ?? null ) ? $args['allow_only'] : array(); + $policy = is_array( $args['tool_policy'] ?? null ) ? $args['tool_policy'] : null; + if ( is_array( $policy ) && \WP_Agent_Tool_Policy::MODE_ALLOW === ( $policy['mode'] ?? '' ) ) { + $allowed = array_merge( $allowed, is_array( $policy['tools'] ?? null ) ? $policy['tools'] : array() ); + } + + return in_array( $tool_name, $allowed, true ); } } diff --git a/tests/tool-source-registry-smoke.php b/tests/tool-source-registry-smoke.php index b76cc0036..11bebb3ee 100644 --- a/tests/tool-source-registry-smoke.php +++ b/tests/tool-source-registry-smoke.php @@ -287,6 +287,17 @@ static function ( array $sources ): array { array( 'allow_only' => array( 'durable_memory_tool' ) ) ); assert_source_equals( true, isset( $opt_in_baseline['durable_memory_tool'] ), 'pipeline mode picks up opt-in tool when allowlisted', $failures, $passes ); +$opt_in_tool_policy = resolve_source_tools( + ToolPolicyResolver::MODE_PIPELINE, + new SourcePolicyToolManager(), + array( + 'tool_policy' => array( + 'mode' => 'allow', + 'tools' => array( 'durable_memory_tool' ), + ), + ) +); +assert_source_equals( true, isset( $opt_in_tool_policy['durable_memory_tool'] ), 'pipeline mode picks up opt-in tool when allowed by tool policy', $failures, $passes ); $opt_in_no_allowlist = resolve_source_tools( ToolPolicyResolver::MODE_PIPELINE, new SourcePolicyToolManager()