mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
* feat: Initial work on security analyzer * feat: Add remote invariant client * chore: improve fault tolerance of client * feat: Add button to enable Invariant Security Analyzer * [feat] confirmation mode for bash actions * feat: Add Invariant Tab with security risk outputs * feat: Add modal setting for Confirmation Mode * fix: frontend tests for confirmation mode switch * fix: add missing CONFIRMATION_MODE value in SettingsModal.test.tsx * fix: update test to integrate new setting * feat: Initial work on security analyzer * feat: Add remote invariant client * chore: improve fault tolerance of client * feat: Add button to enable Invariant Security Analyzer * feat: Add Invariant Tab with security risk outputs * feat: integrate security analyzer with confirmation mode * feat: improve invariant analyzer tab * feat: Implement user confirmation for running bash/python code * fix: don't display rejected actions * fix: make confirmation show only on assistant messages * feat: download traces, update policy, implement settings, auto-approve based on defined risk * Fix: low risk not being shown because it's 0 * fix: duplicate logs in tab * fix: log duplication * chore: prepare for merge, remove logging * Merge confirmation_mode from OpenDevin main * test: update tests to pass * chore: finish merging changes, security analyzer now operational again * feat: document Security Analyzers * refactor: api, monitor * chore: lint, fix risk None, revert policy * fix: check security_risk for None * refactor: rename instances of invariant to security analyzer * feat: add /api/options/security-analyzers endpoint * Move security analyzer from tab to modal * Temporary fix lock when security analyzer is not chosen * feat: don't show lock at all when security analyzer is not enabled * refactor: - Frontend: * change type of SECURITY_ANALYZER from bool to string * add combobox to select SECURITY_ANALYZER, current options are "invariant and "" (no security analyzer) * Security is now a modal, lock in bottom right is visible only if there's a security analyzer selected - Backend: * add close to SecurityAnalyzer * instantiate SecurityAnalyzer based on provided string from frontend * fix: update close to be async, to be consistent with other close on resources * fix: max height of modal (prevent overflow) * feat: add logo * small fixes * update docs for creating a security analyzer module * fix linting * update timeout for http client * fix: move security_analyzer config from agent to session * feat: add security_risk to browser actions * add optional remark on combobox * fix: asdict not called on dataclass, remove invariant dependency * fix: exclude None values when serializing * feat: take default policy from invariant-server instead of being hardcoded * fix: check if policy is None * update image name * test: fix some failing runs * fix: security analyzer tests * refactor: merge confirmation_mode and security_analyzer into SecurityConfig. Change invariant error message for docker * test: add tests for invariant parsing actions / observations * fix: python linting for test_security.py * Apply suggestions from code review Co-authored-by: Engel Nyst <enyst@users.noreply.github.com> * use ActionSecurityRisk | None intead of Optional * refactor action parsing * add extra check * lint parser.py * test: add field keep_prompt to test_security * docs: add information about how to enable the analyzer * test: Remove trailing whitespace in README.md text --------- Co-authored-by: Mislav Balunovic <mislav.balunovic@gmail.com> Co-authored-by: Engel Nyst <enyst@users.noreply.github.com> Co-authored-by: Xingyao Wang <xingyao6@illinois.edu>
136 lines
5.0 KiB
Python
136 lines
5.0 KiB
Python
from typing import Optional
|
|
|
|
from opendevin.controller import AgentController
|
|
from opendevin.controller.agent import Agent
|
|
from opendevin.controller.state.state import State
|
|
from opendevin.core.config import AppConfig, LLMConfig
|
|
from opendevin.core.logger import opendevin_logger as logger
|
|
from opendevin.events.stream import EventStream
|
|
from opendevin.runtime import get_runtime_cls
|
|
from opendevin.runtime.runtime import Runtime
|
|
from opendevin.security import SecurityAnalyzer, options
|
|
from opendevin.storage.files import FileStore
|
|
|
|
|
|
class AgentSession:
|
|
"""Represents a session with an agent.
|
|
|
|
Attributes:
|
|
controller: The AgentController instance for controlling the agent.
|
|
"""
|
|
|
|
sid: str
|
|
event_stream: EventStream
|
|
file_store: FileStore
|
|
controller: Optional[AgentController] = None
|
|
runtime: Optional[Runtime] = None
|
|
security_analyzer: SecurityAnalyzer | None = None
|
|
_closed: bool = False
|
|
|
|
def __init__(self, sid: str, file_store: FileStore):
|
|
"""Initializes a new instance of the Session class."""
|
|
self.sid = sid
|
|
self.event_stream = EventStream(sid, file_store)
|
|
self.file_store = file_store
|
|
|
|
async def start(
|
|
self,
|
|
runtime_name: str,
|
|
config: AppConfig,
|
|
agent: Agent,
|
|
max_iterations: int,
|
|
max_budget_per_task: float | None = None,
|
|
agent_to_llm_config: dict[str, LLMConfig] | None = None,
|
|
):
|
|
"""Starts the agent session.
|
|
|
|
Args:
|
|
start_event: The start event data (optional).
|
|
"""
|
|
if self.controller or self.runtime:
|
|
raise Exception(
|
|
'Session already started. You need to close this session and start a new one.'
|
|
)
|
|
await self._create_security_analyzer(config.security.security_analyzer)
|
|
await self._create_runtime(runtime_name, config, agent)
|
|
await self._create_controller(
|
|
agent,
|
|
config.security.confirmation_mode,
|
|
max_iterations,
|
|
max_budget_per_task=max_budget_per_task,
|
|
agent_to_llm_config=agent_to_llm_config,
|
|
)
|
|
|
|
async def close(self):
|
|
if self._closed:
|
|
return
|
|
if self.controller is not None:
|
|
end_state = self.controller.get_state()
|
|
end_state.save_to_session(self.sid, self.file_store)
|
|
await self.controller.close()
|
|
if self.runtime is not None:
|
|
await self.runtime.close()
|
|
if self.security_analyzer is not None:
|
|
await self.security_analyzer.close()
|
|
self._closed = True
|
|
|
|
async def _create_security_analyzer(self, security_analyzer: str | None):
|
|
"""Creates a SecurityAnalyzer instance that will be used to analyze the agent actions."""
|
|
logger.info(f'Using security analyzer: {security_analyzer}')
|
|
if security_analyzer:
|
|
self.security_analyzer = options.SecurityAnalyzers.get(
|
|
security_analyzer, SecurityAnalyzer
|
|
)(self.event_stream)
|
|
|
|
async def _create_runtime(self, runtime_name: str, config: AppConfig, agent: Agent):
|
|
"""Creates a runtime instance."""
|
|
if self.runtime is not None:
|
|
raise Exception('Runtime already created')
|
|
|
|
logger.info(f'Using runtime: {runtime_name}')
|
|
runtime_cls = get_runtime_cls(runtime_name)
|
|
self.runtime = runtime_cls(
|
|
config=config,
|
|
event_stream=self.event_stream,
|
|
sid=self.sid,
|
|
plugins=agent.sandbox_plugins,
|
|
)
|
|
await self.runtime.ainit()
|
|
|
|
async def _create_controller(
|
|
self,
|
|
agent: Agent,
|
|
confirmation_mode: bool,
|
|
max_iterations: int,
|
|
max_budget_per_task: float | None = None,
|
|
agent_to_llm_config: dict[str, LLMConfig] | None = None,
|
|
):
|
|
"""Creates an AgentController instance."""
|
|
if self.controller is not None:
|
|
raise Exception('Controller already created')
|
|
if self.runtime is None:
|
|
raise Exception('Runtime must be initialized before the agent controller')
|
|
|
|
logger.info(f'Creating agent {agent.name} using LLM {agent.llm.config.model}')
|
|
|
|
self.controller = AgentController(
|
|
sid=self.sid,
|
|
event_stream=self.event_stream,
|
|
agent=agent,
|
|
max_iterations=int(max_iterations),
|
|
max_budget_per_task=max_budget_per_task,
|
|
agent_to_llm_config=agent_to_llm_config,
|
|
confirmation_mode=confirmation_mode,
|
|
# AgentSession is designed to communicate with the frontend, so we don't want to
|
|
# run the agent in headless mode.
|
|
headless_mode=False,
|
|
)
|
|
try:
|
|
agent_state = State.restore_from_session(self.sid, self.file_store)
|
|
self.controller.set_initial_state(
|
|
agent_state, max_iterations, confirmation_mode
|
|
)
|
|
logger.info(f'Restored agent state from session, sid: {self.sid}')
|
|
except Exception as e:
|
|
print('Error restoring state', e)
|