Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
f21c7a2
Merge pull request #861 from 666haiwen/feat/chart-visualization
mannaandpoem Mar 22, 2025
fd5b070
sandbox init
CZH-THU Jun 27, 2025
527fd64
add utils
CZH-THU Jun 29, 2025
a6a45d7
add sandox utils
white-rm Jun 29, 2025
9513686
modify BaseTool
CZH-THU Jun 30, 2025
2a62874
add sandbox tool
CZH-THU Jul 1, 2025
d7e3c14
debug computer use tool
CZH-THU Jul 6, 2025
686ace2
add shell sandox
white-rm Jul 6, 2025
fb30c43
add daytona config
CZH-THU Jul 6, 2025
41e12eb
Merge branch 'sandbox' of https://github.com/XYDT-AI/OpenManus-sandbo…
CZH-THU Jul 6, 2025
17e1bec
test sb_browser_use_tool
CZH-THU Jul 8, 2025
904dec2
update shell sandox
white-rm Jul 8, 2025
7ce31d1
init sandbox agent
CZH-THU Jul 10, 2025
cc28354
Merge branch 'sandbox' of https://github.com/XYDT-AI/OpenManus-sandbo…
CZH-THU Jul 10, 2025
e4c4d4a
update browser sandbox tool
white-rm Jul 13, 2025
c016d56
update browser sandbox tool message
white-rm Jul 15, 2025
6fb48f4
update browser sandbox tool 2
white-rm Jul 19, 2025
5731b63
init sb_files_tool & sb_vision_tool
cyb1278588254 Jul 19, 2025
398ae43
add main for sandbox_agent
CZH-THU Jul 19, 2025
0a6207d
Merge branch 'FoundationAgents:main' into sandbox
CZH-THU Jul 20, 2025
3301d54
support use existing sandbox by id
CZH-THU Jul 20, 2025
c944227
Merge branch 'sandbox' of https://github.com/XYDT-AI/OpenManus-sandbo…
CZH-THU Jul 20, 2025
0246513
debug sb_files_tool & sb_vision_tool
cyb1278588254 Jul 20, 2025
4a28a79
add Crawl4aiTool
SNHuan Jul 22, 2025
50f56f5
eliminate sandox redundant information
white-rm Jul 22, 2025
08cd044
Format crawl4ai.py with pre-commit hooks
SNHuan Jul 23, 2025
ac66e8f
Merge pull request #1197 from SNHuan/tool/Crawl4aiTool
didiforgithub Jul 23, 2025
f99dfac
add README and delete sandbox_id
CZH-THU Jul 27, 2025
66f4ae0
feat:support 4 sandbox tools by using daytona
CZH-THU Jul 27, 2025
386aeb4
Merge branch 'FoundationAgents:main' into sandbox
CZH-THU Jul 27, 2025
a010fe7
Apply pre-commit formatting fixes
CZH-THU Jul 27, 2025
f08ed23
Merge branch 'sandbox' of https://github.com/XYDT-AI/OpenManus-sandbo…
CZH-THU Jul 27, 2025
802e212
Apply pre-commit formatting fixes
CZH-THU Jul 27, 2025
9cb3bd1
remove sb_*.py to tool/sandbox&modify requirements.txt
CZH-THU Jul 30, 2025
23f2e85
modify requirements.txt
CZH-THU Jul 30, 2025
3e604ac
Update Group Link.
didiforgithub Aug 13, 2025
5e7aba3
modify sb_browser_use tool's result
CZH-THU Aug 16, 2025
decd25a
Merge branch 'FoundationAgents:main' into sandbox
CZH-THU Aug 16, 2025
20c7207
Update README.md
didiforgithub Sep 4, 2025
52cf406
Merge branch 'FoundationAgents:main' into sandbox
CZH-THU Sep 8, 2025
c26bc92
put base64 in the currect position for sb_vision
CZH-THU Sep 14, 2025
efccc9f
Merge branch 'sandbox' of https://github.com/XYDT-AI/OpenManus-improv…
CZH-THU Sep 14, 2025
67d6c1c
Merge pull request #1206 from XYDT-AI/sandbox
didiforgithub Sep 19, 2025
37e7f9c
Merge pull request #1256 from FoundationAgents/chart-visualization
didiforgithub Oct 28, 2025
872d5bb
feat: add JiekouAI as new provider
cnJasonZ Nov 7, 2025
211a245
Merge pull request #1266 from cnJasonZ/feat/jiekouAI
didiforgithub Nov 14, 2025
52a13f2
Update README.md
didiforgithub Jan 4, 2026
f2edfc7
Update config.yml
cryptozone2030-hue Mar 30, 2026
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
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: "Join the Community Group"
- name: "ali ali"
about: Join the OpenManus community to discuss and get help from others
url: https://github.com/FoundationAgents/OpenManus?tab=readme-ov-file#community-group
28 changes: 13 additions & 15 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,22 @@ repos:
rev: v2.0.1
hooks:
- id: autoflake
args: [
--remove-all-unused-imports,
--ignore-init-module-imports,
--expand-star-imports,
--remove-duplicate-keys,
--remove-unused-variables,
--recursive,
--in-place,
--exclude=__init__.py,
]
args:
[
--remove-all-unused-imports,
--ignore-init-module-imports,
--expand-star-imports,
--remove-duplicate-keys,
--remove-unused-variables,
--recursive,
--in-place,
--exclude=__init__.py,
]
files: \.py$

- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
args: [
"--profile", "black",
"--filter-files",
"--lines-after-imports=2",
]
args:
["--profile", "black", "--filter-files", "--lines-after-imports=2"]
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,7 @@ Thanks to [PPIO](https://ppinfra.com/user/register?invited_by=OCPKCN&utm_source=

## Acknowledgement

Thanks to [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo)
and [browser-use](https://github.com/browser-use/browser-use) for providing basic support for this project!
Thanks to [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo), [browser-use](https://github.com/browser-use/browser-use) and [crawl4ai](https://github.com/unclecode/crawl4ai) for providing basic support for this project!

Additionally, we are grateful to [AAAJ](https://github.com/metauto-ai/agent-as-a-judge), [MetaGPT](https://github.com/geekan/MetaGPT), [OpenHands](https://github.com/All-Hands-AI/OpenHands) and [SWE-agent](https://github.com/SWE-agent/SWE-agent).

Expand All @@ -186,7 +185,7 @@ OpenManus is built by contributors from MetaGPT. Huge thanks to this agent commu
## Cite
```bibtex
@misc{openmanus2025,
author = {Xinbin Liang and Jinyu Xiang and Zhaoyang Yu and Jiayi Zhang and Sirui Hong and Sheng Fan and Xiao Tang},
author = {Xinbin Liang and Jinyu Xiang and Zhaoyang Yu and Jiayi Zhang and Sirui Hong and Sheng Fan and Xiao Tang and Bang Liu and Yuyu Luo and Chenglin Wu},
title = {OpenManus: An open-source framework for building general AI agents},
year = {2025},
publisher = {Zenodo},
Expand Down
5 changes: 5 additions & 0 deletions app/agent/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from app.prompt.browser import NEXT_STEP_PROMPT, SYSTEM_PROMPT
from app.schema import Message, ToolChoice
from app.tool import BrowserUseTool, Terminate, ToolCollection
from app.tool.sandbox.sb_browser_tool import SandboxBrowserTool


# Avoid circular import if BrowserAgent needs BrowserContextHelper
Expand All @@ -22,6 +23,10 @@ def __init__(self, agent: "BaseAgent"):

async def get_browser_state(self) -> Optional[dict]:
browser_tool = self.agent.available_tools.get_tool(BrowserUseTool().name)
if not browser_tool:
browser_tool = self.agent.available_tools.get_tool(
SandboxBrowserTool().name
)
if not browser_tool or not hasattr(browser_tool, "get_current_state"):
logger.warning("BrowserUseTool not found or doesn't have get_current_state")
return None
Expand Down
223 changes: 223 additions & 0 deletions app/agent/sandbox_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
from typing import Dict, List, Optional

from pydantic import Field, model_validator

from app.agent.browser import BrowserContextHelper
from app.agent.toolcall import ToolCallAgent
from app.config import config
from app.daytona.sandbox import create_sandbox, delete_sandbox
from app.daytona.tool_base import SandboxToolsBase
from app.logger import logger
from app.prompt.manus import NEXT_STEP_PROMPT, SYSTEM_PROMPT
from app.tool import Terminate, ToolCollection
from app.tool.ask_human import AskHuman
from app.tool.mcp import MCPClients, MCPClientTool
from app.tool.sandbox.sb_browser_tool import SandboxBrowserTool
from app.tool.sandbox.sb_files_tool import SandboxFilesTool
from app.tool.sandbox.sb_shell_tool import SandboxShellTool
from app.tool.sandbox.sb_vision_tool import SandboxVisionTool


class SandboxManus(ToolCallAgent):
"""A versatile general-purpose agent with support for both local and MCP tools."""

name: str = "SandboxManus"
description: str = "A versatile agent that can solve various tasks using multiple sandbox-tools including MCP-based tools"

system_prompt: str = SYSTEM_PROMPT.format(directory=config.workspace_root)
next_step_prompt: str = NEXT_STEP_PROMPT

max_observe: int = 10000
max_steps: int = 20

# MCP clients for remote tool access
mcp_clients: MCPClients = Field(default_factory=MCPClients)

# Add general-purpose tools to the tool collection
available_tools: ToolCollection = Field(
default_factory=lambda: ToolCollection(
# PythonExecute(),
# BrowserUseTool(),
# StrReplaceEditor(),
AskHuman(),
Terminate(),
)
)

special_tool_names: list[str] = Field(default_factory=lambda: [Terminate().name])
browser_context_helper: Optional[BrowserContextHelper] = None

# Track connected MCP servers
connected_servers: Dict[str, str] = Field(
default_factory=dict
) # server_id -> url/command
_initialized: bool = False
sandbox_link: Optional[dict[str, dict[str, str]]] = Field(default_factory=dict)

@model_validator(mode="after")
def initialize_helper(self) -> "SandboxManus":
"""Initialize basic components synchronously."""
self.browser_context_helper = BrowserContextHelper(self)
return self

@classmethod
async def create(cls, **kwargs) -> "SandboxManus":
"""Factory method to create and properly initialize a Manus instance."""
instance = cls(**kwargs)
await instance.initialize_mcp_servers()
await instance.initialize_sandbox_tools()
instance._initialized = True
return instance

async def initialize_sandbox_tools(
self,
password: str = config.daytona.VNC_password,
) -> None:
try:
# 创建新沙箱
if password:
sandbox = create_sandbox(password=password)
self.sandbox = sandbox
else:
raise ValueError("password must be provided")
vnc_link = sandbox.get_preview_link(6080)
website_link = sandbox.get_preview_link(8080)
vnc_url = vnc_link.url if hasattr(vnc_link, "url") else str(vnc_link)
website_url = (
website_link.url if hasattr(website_link, "url") else str(website_link)
)

# Get the actual sandbox_id from the created sandbox
actual_sandbox_id = sandbox.id if hasattr(sandbox, "id") else "new_sandbox"
if not self.sandbox_link:
self.sandbox_link = {}
self.sandbox_link[actual_sandbox_id] = {
"vnc": vnc_url,
"website": website_url,
}
logger.info(f"VNC URL: {vnc_url}")
logger.info(f"Website URL: {website_url}")
SandboxToolsBase._urls_printed = True
sb_tools = [
SandboxBrowserTool(sandbox),
SandboxFilesTool(sandbox),
SandboxShellTool(sandbox),
SandboxVisionTool(sandbox),
]
self.available_tools.add_tools(*sb_tools)

except Exception as e:
logger.error(f"Error initializing sandbox tools: {e}")
raise

async def initialize_mcp_servers(self) -> None:
"""Initialize connections to configured MCP servers."""
for server_id, server_config in config.mcp_config.servers.items():
try:
if server_config.type == "sse":
if server_config.url:
await self.connect_mcp_server(server_config.url, server_id)
logger.info(
f"Connected to MCP server {server_id} at {server_config.url}"
)
elif server_config.type == "stdio":
if server_config.command:
await self.connect_mcp_server(
server_config.command,
server_id,
use_stdio=True,
stdio_args=server_config.args,
)
logger.info(
f"Connected to MCP server {server_id} using command {server_config.command}"
)
except Exception as e:
logger.error(f"Failed to connect to MCP server {server_id}: {e}")

async def connect_mcp_server(
self,
server_url: str,
server_id: str = "",
use_stdio: bool = False,
stdio_args: List[str] = None,
) -> None:
"""Connect to an MCP server and add its tools."""
if use_stdio:
await self.mcp_clients.connect_stdio(
server_url, stdio_args or [], server_id
)
self.connected_servers[server_id or server_url] = server_url
else:
await self.mcp_clients.connect_sse(server_url, server_id)
self.connected_servers[server_id or server_url] = server_url

# Update available tools with only the new tools from this server
new_tools = [
tool for tool in self.mcp_clients.tools if tool.server_id == server_id
]
self.available_tools.add_tools(*new_tools)

async def disconnect_mcp_server(self, server_id: str = "") -> None:
"""Disconnect from an MCP server and remove its tools."""
await self.mcp_clients.disconnect(server_id)
if server_id:
self.connected_servers.pop(server_id, None)
else:
self.connected_servers.clear()

# Rebuild available tools without the disconnected server's tools
base_tools = [
tool
for tool in self.available_tools.tools
if not isinstance(tool, MCPClientTool)
]
self.available_tools = ToolCollection(*base_tools)
self.available_tools.add_tools(*self.mcp_clients.tools)

async def delete_sandbox(self, sandbox_id: str) -> None:
"""Delete a sandbox by ID."""
try:
await delete_sandbox(sandbox_id)
logger.info(f"Sandbox {sandbox_id} deleted successfully")
if sandbox_id in self.sandbox_link:
del self.sandbox_link[sandbox_id]
except Exception as e:
logger.error(f"Error deleting sandbox {sandbox_id}: {e}")
raise e

async def cleanup(self):
"""Clean up Manus agent resources."""
if self.browser_context_helper:
await self.browser_context_helper.cleanup_browser()
# Disconnect from all MCP servers only if we were initialized
if self._initialized:
await self.disconnect_mcp_server()
await self.delete_sandbox(self.sandbox.id if self.sandbox else "unknown")
self._initialized = False

async def think(self) -> bool:
"""Process current state and decide next actions with appropriate context."""
if not self._initialized:
await self.initialize_mcp_servers()
self._initialized = True

original_prompt = self.next_step_prompt
recent_messages = self.memory.messages[-3:] if self.memory.messages else []
browser_in_use = any(
tc.function.name == SandboxBrowserTool().name
for msg in recent_messages
if msg.tool_calls
for tc in msg.tool_calls
)

if browser_in_use:
self.next_step_prompt = (
await self.browser_context_helper.format_next_step_prompt()
)

result = await super().think()

# Restore original prompt
self.next_step_prompt = original_prompt

return result
32 changes: 32 additions & 0 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,25 @@ class SandboxSettings(BaseModel):
)


class DaytonaSettings(BaseModel):
daytona_api_key: str
daytona_server_url: Optional[str] = Field(
"https://app.daytona.io/api", description=""
)
daytona_target: Optional[str] = Field("us", description="enum ['eu', 'us']")
sandbox_image_name: Optional[str] = Field("whitezxj/sandbox:0.1.0", description="")
sandbox_entrypoint: Optional[str] = Field(
"/usr/bin/supervisord -n -c /etc/supervisor/conf.d/supervisord.conf",
description="",
)
# sandbox_id: Optional[str] = Field(
# None, description="ID of the daytona sandbox to use, if any"
# )
VNC_password: Optional[str] = Field(
"123456", description="VNC password for the vnc service in sandbox"
)


class MCPServerConfig(BaseModel):
"""Configuration for a single MCP server"""

Expand Down Expand Up @@ -167,6 +186,9 @@ class AppConfig(BaseModel):
run_flow_config: Optional[RunflowSettings] = Field(
None, description="Run flow configuration"
)
daytona_config: Optional[DaytonaSettings] = Field(
None, description="Daytona configuration"
)

class Config:
arbitrary_types_allowed = True
Expand Down Expand Up @@ -268,6 +290,11 @@ def _load_initial_config(self):
sandbox_settings = SandboxSettings(**sandbox_config)
else:
sandbox_settings = SandboxSettings()
daytona_config = raw_config.get("daytona", {})
if daytona_config:
daytona_settings = DaytonaSettings(**daytona_config)
else:
daytona_settings = DaytonaSettings()

mcp_config = raw_config.get("mcp", {})
mcp_settings = None
Expand Down Expand Up @@ -296,6 +323,7 @@ def _load_initial_config(self):
"search_config": search_settings,
"mcp_config": mcp_settings,
"run_flow_config": run_flow_settings,
"daytona_config": daytona_settings,
}

self._config = AppConfig(**config_dict)
Expand All @@ -308,6 +336,10 @@ def llm(self) -> Dict[str, LLMSettings]:
def sandbox(self) -> SandboxSettings:
return self._config.sandbox

@property
def daytona(self) -> DaytonaSettings:
return self._config.daytona_config

@property
def browser_config(self) -> Optional[BrowserSettings]:
return self._config.browser_config
Expand Down
Loading