Repository: All-Hands-AI/OpenHands
File: openhands/security/grayswan/analyzer.py (Python)

Code before:
            return ActionSecurityRisk.UNKNOWN
        except Exception as e:
            logger.error(f'GraySwan security analysis failed: {e}')
            return ActionSecurityRisk.UNKNOWN

    async def handle_api_request(self, request: Request) -> Any:
        """Handle incoming API requests for configuration or webhooks."""
        return {'status': 'ok', 'analyzer': 'grayswan'}

    async def security_risk(self, action: Action) -> ActionSecurityRisk:
        """Analyze action for security risks using GraySwan API."""
        logger.debug(
            f'Calling security_risk on GraySwanAnalyzer for action: {type(action).__name__}'
        )

        if not self.event_stream:
            logger.warning('No event stream available for GraySwan analysis')
            return ActionSecurityRisk.UNKNOWN

        try:
            all_events = list(self.event_stream.get_events())
            recent_events = (
                all_events[-self.history_limit :]
                if len(all_events) > self.history_limit
                else all_events
            )

            events_to_process = recent_events + [action]
            openai_messages = convert_events_to_openai_messages(events_to_process)

            if not openai_messages:
                logger.warning('No valid messages to analyze')
                return ActionSecurityRisk.UNKNOWN

            logger.debug(
                f'Converted {len(events_to_process)} events into {len(openai_messages)} OpenAI messages for GraySwan analysis'
            )
            return await self._call_grayswan_api(openai_messages)

        except Exception as e:
            logger.error(f'GraySwan security analysis failed: {e}')
            return ActionSecurityRisk.UNKNOWN

    async def close(self) -> None:
        """Clean up resources."""
        if self.session and not self.session.closed:
            await self.session.close()


Review comment (bug):
I think the assumption being made here is that the events in the stream are exactly what the LLM in the agent sees -- but that's not quite correct! Our context management system acts between the event stream and the agent to trim extra events, insert summaries, and sometimes mask out big observations.

If you want to be a little closer to what the agent's LLM is actually seeing you might want to use the `View` construct (defined [here](https://github.com/All-Hands-AI/OpenHands/blob/main/openhands/memory/view.py)):

```
all_events = View.from_events(self.event_stream.get_events())
```

That will apply the bulk of the context management. But if my assumption is wrong this change isn't necessary at all.

Code after:
                    logger.error(f'GraySwan API error {resp.status}: {error_text}')
                    return ActionSecurityRisk.UNKNOWN

        except asyncio.TimeoutError:
            logger.error('GraySwan API request timed out')
            return ActionSecurityRisk.UNKNOWN
        except Exception as e:
            logger.error(f'GraySwan security analysis failed: {e}')
            return ActionSecurityRisk.UNKNOWN

    async def handle_api_request(self, request: Request) -> Any:
        """Handle incoming API requests for configuration or webhooks."""
        return {'status': 'ok', 'analyzer': 'grayswan'}

    async def security_risk(self, action: Action) -> ActionSecurityRisk:
        """Analyze action for security risks using GraySwan API."""
        logger.debug(
            f'Calling security_risk on GraySwanAnalyzer for action: {type(action).__name__}'
        )

        if not self.event_stream:
            logger.warning('No event stream available for GraySwan analysis')
            return ActionSecurityRisk.UNKNOWN

        try:
            # Use View to get closer to what the agent's LLM actually sees
            # This applies context management (trimming, summaries, masking)
            view = View.from_events(list(self.event_stream.get_events()))
            recent_events = (
                list(view)[-self.history_limit :]
                if len(view) > self.history_limit
                else list(view)
            )

            events_to_process = recent_events + [action]
            openai_messages = convert_events_to_openai_messages(events_to_process)

            if not openai_messages:
                logger.warning('No valid messages to analyze')
                return ActionSecurityRisk.UNKNOWN

            logger.debug(
                f'Converted {len(events_to_process)} events into {len(openai_messages)} OpenAI messages for GraySwan analysis'
            )
            return await self._call_grayswan_api(openai_messages)

        except Exception as e:
            logger.error(f'GraySwan security analysis failed: {e}')
            return ActionSecurityRisk.UNKNOWN

    async def close(self) -> None: