mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
5 Commits
1.2.1
...
feat/plann
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e5a709b52 | ||
|
|
d1ba74f639 | ||
|
|
135c127f50 | ||
|
|
affba28554 | ||
|
|
7d1829451b |
@@ -1,4 +1,6 @@
|
||||
from openhands.agenthub.readonly_agent.readonly_agent import ReadOnlyAgent
|
||||
from openhands.agenthub.readonly_agent.readonly_agent import ReadOnlyPlanningAgent
|
||||
from openhands.controller.agent import Agent
|
||||
|
||||
Agent.register('ReadOnlyAgent', ReadOnlyAgent)
|
||||
Agent.register('ReadOnlyPlanningAgent', ReadOnlyPlanningAgent)
|
||||
# Backward-compat alias for tests and existing references
|
||||
Agent.register('ReadOnlyAgent', ReadOnlyPlanningAgent)
|
||||
|
||||
@@ -22,6 +22,7 @@ from openhands.agenthub.readonly_agent.tools import (
|
||||
GlobTool,
|
||||
GrepTool,
|
||||
ViewTool,
|
||||
create_plan_md_tool,
|
||||
)
|
||||
from openhands.core.exceptions import (
|
||||
FunctionCallNotExistsError,
|
||||
@@ -33,11 +34,12 @@ from openhands.events.action import (
|
||||
AgentFinishAction,
|
||||
AgentThinkAction,
|
||||
CmdRunAction,
|
||||
FileEditAction,
|
||||
FileReadAction,
|
||||
MCPAction,
|
||||
MessageAction,
|
||||
)
|
||||
from openhands.events.event import FileReadSource
|
||||
from openhands.events.event import FileEditSource, FileReadSource
|
||||
from openhands.events.tool import ToolCallMetadata
|
||||
|
||||
|
||||
@@ -193,6 +195,42 @@ def response_to_actions(
|
||||
|
||||
glob_cmd = glob_to_cmdrun(pattern, path)
|
||||
action = CmdRunAction(command=glob_cmd, is_input=False)
|
||||
# ================================================
|
||||
# Plan MD Tool (ACI: FileReadAction/FileEditAction)
|
||||
# ================================================
|
||||
elif tool_call.function.name == 'plan_md':
|
||||
cmd = arguments.get('command')
|
||||
if cmd not in ['view', 'create', 'edit']:
|
||||
raise FunctionCallValidationError(
|
||||
f'Invalid command for plan_md: {cmd}'
|
||||
)
|
||||
plan_path = '/workspace/PLAN.md'
|
||||
if cmd == 'view':
|
||||
action = FileReadAction(
|
||||
path=plan_path, impl_source=FileReadSource.OH_ACI
|
||||
)
|
||||
elif cmd == 'create':
|
||||
file_text = arguments.get('file_text', '')
|
||||
action = FileEditAction(
|
||||
path=plan_path,
|
||||
command='create',
|
||||
file_text=file_text,
|
||||
impl_source=FileEditSource.OH_ACI,
|
||||
)
|
||||
else: # edit
|
||||
old_str = arguments.get('old_str')
|
||||
new_str = arguments.get('new_str')
|
||||
if old_str is None or new_str is None:
|
||||
raise FunctionCallValidationError(
|
||||
'plan_md.edit requires both old_str and new_str (replace full content). Use plan_md.view first to get exact old_str.'
|
||||
)
|
||||
action = FileEditAction(
|
||||
path=plan_path,
|
||||
command='str_replace',
|
||||
old_str=old_str,
|
||||
new_str=new_str,
|
||||
impl_source=FileEditSource.OH_ACI,
|
||||
)
|
||||
|
||||
# ================================================
|
||||
# MCPAction (MCP)
|
||||
@@ -245,4 +283,5 @@ def get_tools() -> list[ChatCompletionToolParam]:
|
||||
GrepTool,
|
||||
GlobTool,
|
||||
ViewTool,
|
||||
create_plan_md_tool(),
|
||||
]
|
||||
|
||||
@@ -1,34 +1,29 @@
|
||||
You are OpenHands ReadOnlyAgent, a helpful AI assistant focused on code analysis and exploration. You can:
|
||||
- Explore and analyze codebases
|
||||
- Browse the web for relevant information
|
||||
- Plan potential changes
|
||||
- Answer questions about code
|
||||
You are the ReadOnlyPlanningAgent, a planning-focused assistant.
|
||||
Your SOLE GOAL is to produce and iterate on a single planning document for the current task: /workspace/PLAN.md.
|
||||
You must not perform implementation work; when the plan is ready, finish the session.
|
||||
|
||||
<CAPABILITIES>
|
||||
✓ READ-ONLY TOOLS:
|
||||
- view: Read file contents
|
||||
- grep: Search for patterns
|
||||
- glob: List matching files
|
||||
- think: Analyze information
|
||||
- web_read: Access web resources
|
||||
- finish: Complete current task
|
||||
CAPABILITIES
|
||||
- grep: search patterns in files to understand the codebase
|
||||
- glob: list matching files
|
||||
- view: read files to gather context
|
||||
- plan_md: manage the planning doc PLAN.md (only commands: view, create, edit)
|
||||
- think: reason about findings
|
||||
- finish: end the planning session when ready to execute
|
||||
|
||||
✗ RESTRICTIONS:
|
||||
- Cannot modify any files
|
||||
- Cannot execute state-changing commands
|
||||
</CAPABILITIES>
|
||||
STRICT RESTRICTIONS
|
||||
- Do NOT run shell commands or execute code
|
||||
- Do NOT modify any files except /workspace/PLAN.md via plan_md
|
||||
- Do NOT create or edit any other files
|
||||
|
||||
<GUIDELINES>
|
||||
1. When analyzing code or answering questions:
|
||||
- Be thorough and methodical
|
||||
- Prioritize accuracy over speed
|
||||
- Provide detailed explanations
|
||||
PLANNING GUIDELINES
|
||||
- Produce a concise, numbered plan with rationale and assumptions
|
||||
- Capture open questions and risks
|
||||
- Use plan_md.view to inspect current content, plan_md.create to create the file, and plan_md.edit to replace content
|
||||
- For plan_md.edit, first view the file to obtain its current exact contents; then replace the entire file by setting old_str to the exact current contents and new_str to the updated full plan
|
||||
- Iterate as needed until the plan is solid
|
||||
- When done, use finish to end the planning session with a short summary of the plan
|
||||
|
||||
2. For file operations:
|
||||
- Always verify file locations before accessing
|
||||
- Don't assume paths are relative to current directory
|
||||
|
||||
3. If asked to make changes:
|
||||
- Explain you are read-only
|
||||
- Recommend using CodeActAgent instead
|
||||
</GUIDELINES>
|
||||
OUTPUT STYLE
|
||||
- Keep the plan readable and structured
|
||||
- Prefer bullets and numbered lists
|
||||
- Defer implementation details and code changes to the execution phase
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
"""ReadOnlyAgent - A specialized version of CodeActAgent that only uses read-only tools."""
|
||||
"""ReadOnlyPlanningAgent - A specialized planning agent for read-only research plus maintaining PLAN.md.
|
||||
|
||||
This agent is derived from CodeActAgent and constrained to:
|
||||
- Use read-only research tools (grep, glob, view) to explore the repository
|
||||
- Maintain a single planning document at /workspace/PLAN.md via a dedicated plan editor tool
|
||||
- Avoid any other code execution or edits outside PLAN.md
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
@@ -19,20 +25,14 @@ from openhands.llm.llm import LLM
|
||||
from openhands.utils.prompt import PromptManager
|
||||
|
||||
|
||||
class ReadOnlyAgent(CodeActAgent):
|
||||
class ReadOnlyPlanningAgent(CodeActAgent):
|
||||
VERSION = '1.0'
|
||||
"""
|
||||
The ReadOnlyAgent is a specialized version of CodeActAgent that only uses read-only tools.
|
||||
|
||||
This agent is designed for safely exploring codebases without making any changes.
|
||||
It only has access to tools that don't modify the system: grep, glob, view, think, finish, web_read.
|
||||
|
||||
Use this agent when you want to:
|
||||
1. Explore a codebase to understand its structure
|
||||
2. Search for specific patterns or code
|
||||
3. Research without making any changes
|
||||
|
||||
When you're ready to make changes, switch to the regular CodeActAgent.
|
||||
The ReadOnlyPlanningAgent is designed for planning large features safely.
|
||||
It provides:
|
||||
- Read-only repo exploration: grep, glob, view
|
||||
- PLAN.md maintenance via a specialized plan editor tool (create/view/edit only)
|
||||
- No other file modifications or command execution
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -50,7 +50,7 @@ class ReadOnlyAgent(CodeActAgent):
|
||||
super().__init__(llm, config)
|
||||
|
||||
logger.debug(
|
||||
f'TOOLS loaded for ReadOnlyAgent: {", ".join([tool.get("function").get("name") for tool in self.tools])}'
|
||||
f'TOOLS loaded for ReadOnlyPlanningAgent: {", ".join([tool.get("function").get("name") for tool in self.tools])}'
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -74,7 +74,7 @@ class ReadOnlyAgent(CodeActAgent):
|
||||
- mcp_tools (list[dict]): The list of MCP tools.
|
||||
"""
|
||||
logger.warning(
|
||||
'ReadOnlyAgent does not support MCP tools. MCP tools will be ignored by the agent.'
|
||||
'ReadOnlyPlanningAgent does not support MCP tools. MCP tools will be ignored by the agent.'
|
||||
)
|
||||
|
||||
def response_to_actions(self, response: 'ModelResponse') -> list['Action']:
|
||||
|
||||
@@ -5,17 +5,20 @@ This module defines the read-only tools for the ReadOnlyAgent.
|
||||
|
||||
from .glob import GlobTool
|
||||
from .grep import GrepTool
|
||||
from .plan_md import create_plan_md_tool
|
||||
from .view import ViewTool
|
||||
|
||||
__all__ = [
|
||||
'ViewTool',
|
||||
'GrepTool',
|
||||
'GlobTool',
|
||||
'create_plan_md_tool',
|
||||
]
|
||||
|
||||
# Define the list of read-only tools
|
||||
# Define the list of read-only tools for exploration and planning
|
||||
READ_ONLY_TOOLS = [
|
||||
ViewTool,
|
||||
GrepTool,
|
||||
GlobTool,
|
||||
# plan_md tool is exported as a factory; included via function_calling.get_tools
|
||||
]
|
||||
|
||||
47
openhands/agenthub/readonly_agent/tools/plan_md.py
Normal file
47
openhands/agenthub/readonly_agent/tools/plan_md.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from litellm import ChatCompletionToolParam, ChatCompletionToolParamFunctionChunk
|
||||
|
||||
PLAN_MD_TOOL_NAME = 'plan_md'
|
||||
|
||||
_PLAN_MD_DESCRIPTION = """Maintain the planning document PLAN.md in the repository root.
|
||||
|
||||
This tool is ONLY for planning. It can:
|
||||
- view: Show the current contents of PLAN.md
|
||||
- create: Create PLAN.md with provided text (fails if file already exists)
|
||||
- edit: Replace PLAN.md content with provided text
|
||||
|
||||
It MUST NOT be used to modify any other file.
|
||||
"""
|
||||
|
||||
|
||||
def create_plan_md_tool() -> ChatCompletionToolParam:
|
||||
return ChatCompletionToolParam(
|
||||
type='function',
|
||||
function=ChatCompletionToolParamFunctionChunk(
|
||||
name=PLAN_MD_TOOL_NAME,
|
||||
description=_PLAN_MD_DESCRIPTION,
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'command': {
|
||||
'type': 'string',
|
||||
'enum': ['view', 'create', 'edit'],
|
||||
'description': 'Operation to perform on PLAN.md',
|
||||
},
|
||||
'file_text': {
|
||||
'type': 'string',
|
||||
'description': 'Required for create: Initial content of PLAN.md',
|
||||
},
|
||||
'old_str': {
|
||||
'type': 'string',
|
||||
'description': 'Required for edit: EXACT current content of PLAN.md (unique match). Use plan_md.view first.',
|
||||
},
|
||||
'new_str': {
|
||||
'type': 'string',
|
||||
'description': 'Required for edit: New full content of PLAN.md (replaces file)',
|
||||
},
|
||||
},
|
||||
'required': ['command'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -46,6 +46,8 @@ from openhands.core.schema import AgentState
|
||||
from openhands.core.schema.exit_reason import ExitReason
|
||||
from openhands.events import EventSource
|
||||
from openhands.events.action import (
|
||||
AgentDelegateAction,
|
||||
AgentFinishAction,
|
||||
ChangeAgentStateAction,
|
||||
MessageAction,
|
||||
)
|
||||
@@ -165,6 +167,10 @@ async def handle_commands(
|
||||
)
|
||||
elif command == '/mcp':
|
||||
await handle_mcp_command(config)
|
||||
elif command == '/plan':
|
||||
await handle_plan_command(config, event_stream, agent_state)
|
||||
elif command == '/execute':
|
||||
await handle_execute_command(event_stream)
|
||||
else:
|
||||
close_repl = True
|
||||
action = MessageAction(content=command)
|
||||
@@ -173,6 +179,38 @@ async def handle_commands(
|
||||
return close_repl, reload_microagents, new_session_requested, exit_reason
|
||||
|
||||
|
||||
async def handle_plan_command(
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
agent_state: str,
|
||||
) -> None:
|
||||
# Prompt for planning objective and delegate to ReadOnlyPlanningAgent
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<ansigreen>Planning mode activated. Please provide your planning objective:</ansigreen>'
|
||||
)
|
||||
)
|
||||
objective = await read_prompt_input(config, agent_state)
|
||||
if objective == '/exit':
|
||||
return
|
||||
event_stream.add_event(
|
||||
AgentDelegateAction(agent='ReadOnlyPlanningAgent', inputs={'task': objective}),
|
||||
EventSource.USER,
|
||||
)
|
||||
|
||||
|
||||
async def handle_execute_command(event_stream):
|
||||
event_stream.add_event(
|
||||
AgentFinishAction(
|
||||
final_thought='Switching to execution mode',
|
||||
outputs={
|
||||
'content': 'Read-Only planning agent finished its discussion with the user. You may find the discussed plan at PLAN.md.'
|
||||
},
|
||||
),
|
||||
EventSource.USER,
|
||||
)
|
||||
|
||||
|
||||
def handle_exit_command(
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
|
||||
@@ -86,6 +86,8 @@ COMMANDS = {
|
||||
'/settings': 'Display and modify current settings',
|
||||
'/resume': 'Resume the agent when paused',
|
||||
'/mcp': 'Manage MCP server configuration and view errors',
|
||||
'/plan': 'Delegate to planning agent (ReadOnlyPlanningAgent) to draft/iterate PLAN.md',
|
||||
'/execute': 'End planning delegate and resume execution by parent agent',
|
||||
}
|
||||
|
||||
print_lock = threading.Lock()
|
||||
|
||||
Reference in New Issue
Block a user