Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
### Project-specific ###
# Config with secrets
config/config.toml
config/config.daytona.toml

# Logs
logs/

Expand Down
66 changes: 63 additions & 3 deletions app/agent/manus.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os
from datetime import datetime
from typing import Dict, List, Optional

from pydantic import Field, model_validator
Expand All @@ -24,6 +26,36 @@ class Manus(ToolCallAgent):
system_prompt: str = SYSTEM_PROMPT.format(directory=config.workspace_root)
next_step_prompt: str = NEXT_STEP_PROMPT

def get_system_prompt_with_session(self) -> str:
"""Get system prompt with session folder information."""
base_prompt = self.system_prompt
if self.session_folder:
session_info = (
f"\n\nSESSION OUTPUT FOLDER: All research outputs should be saved to: {self.session_folder}\n"
f"Absolute paths for file operations:\n"
f"- Guides/documentation: {os.path.join(self.session_folder, 'guides')}/\n"
f"- Code files: {os.path.join(self.session_folder, 'code')}/\n"
f"- Data files: {os.path.join(self.session_folder, 'data')}/\n"
)
return base_prompt + session_info
return base_prompt

def get_session_path(self, subfolder: str = "") -> str:
"""Get the full path for saving files in the session folder.

Args:
subfolder: One of 'guides', 'code', 'data', or empty string for session root

Returns:
Full path to the requested subfolder
"""
if not self.session_folder:
return ""

if subfolder:
return os.path.join(self.session_folder, subfolder)
return self.session_folder

max_observe: int = 10000
max_steps: int = 20

Expand All @@ -50,6 +82,29 @@ class Manus(ToolCallAgent):
) # server_id -> url/command
_initialized: bool = False

# Session output folder for organizing research outputs
session_folder: Optional[str] = None

@model_validator(mode="after")
def initialize_session_folder(self) -> "Manus":
"""Initialize session output folder with unique timestamp."""
if not self.session_folder:
# Generate unique session folder with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
slug = "session" # Could be customized based on task type
session_path = os.path.join(config.workspace_root, "output", f"{slug}_{timestamp}")

# Create the session folder and subfolders
os.makedirs(session_path, exist_ok=True)
os.makedirs(os.path.join(session_path, "guides"), exist_ok=True)
os.makedirs(os.path.join(session_path, "code"), exist_ok=True)
os.makedirs(os.path.join(session_path, "data"), exist_ok=True)

self.session_folder = session_path
logger.info(f"📁 Created session output folder: {session_path}")

return self

@model_validator(mode="after")
def initialize_helper(self) -> "Manus":
"""Initialize basic components synchronously."""
Expand Down Expand Up @@ -143,7 +198,11 @@ async def think(self) -> bool:
await self.initialize_mcp_servers()
self._initialized = True

original_prompt = self.next_step_prompt
# Update system prompt with session folder information
original_system_prompt = self.system_prompt
self.system_prompt = self.get_system_prompt_with_session()

original_next_step_prompt = self.next_step_prompt
recent_messages = self.memory.messages[-3:] if self.memory.messages else []
browser_in_use = any(
tc.function.name == BrowserUseTool().name
Expand All @@ -159,7 +218,8 @@ async def think(self) -> bool:

result = await super().think()

# Restore original prompt
self.next_step_prompt = original_prompt
# Restore original prompts
self.system_prompt = original_system_prompt
self.next_step_prompt = original_next_step_prompt

return result
10 changes: 7 additions & 3 deletions app/agent/toolcall.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,14 @@ async def execute_tool(self, command: ToolCall) -> str:
)

return observation
except json.JSONDecodeError:
error_msg = f"Error parsing arguments for {name}: Invalid JSON format"
except json.JSONDecodeError as je:
error_msg = (
f"Invalid JSON format for tool '{name}'. The tool arguments could not be parsed. "
f"Make sure all required parameters are provided and properly formatted as JSON. "
f"Error details: {str(je)}"
)
logger.error(
f"📝 Oops! The arguments for '{name}' don't make sense - invalid JSON, arguments:{command.function.arguments}"
f"📝 JSON parsing error for '{name}': {str(je)}\nArguments: {command.function.arguments}"
)
return f"Error: {error_msg}"
except Exception as e:
Expand Down
2 changes: 1 addition & 1 deletion app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ def _load_initial_config(self):
if daytona_config:
daytona_settings = DaytonaSettings(**daytona_config)
else:
daytona_settings = DaytonaSettings()
daytona_settings = None

mcp_config = raw_config.get("mcp", {})
mcp_settings = None
Expand Down
17 changes: 15 additions & 2 deletions app/prompt/manus.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
SYSTEM_PROMPT = (
"You are OpenManus, an all-capable AI assistant, aimed at solving any task presented by the user. You have various tools at your disposal that you can call upon to efficiently complete complex requests. Whether it's programming, information retrieval, file processing, web browsing, or human interaction (only for extreme cases), you can handle it all."
"The initial directory is: {directory}"
"You are OpenManus, an all-capable AI assistant, aimed at solving any task presented by the user. You have various tools at your disposal that you can call upon to efficiently complete complex requests. Whether it's programming, information retrieval, file processing, web browsing, or human interaction (only for extreme cases), you can handle it all.\n"
"\n"
"IMPORTANT GUIDELINES:\n"
"1. Only use tools when truly necessary. Answer straightforward questions directly without tools.\n"
"2. python_execute: ALWAYS provide the 'code' parameter with complete, runnable Python code.\n"
"3. browser_use_tool: Use for web browsing, research, and information gathering from the internet.\n"
"4. str_replace_editor:\n"
" - For 'create': MUST provide 'command', 'path', AND 'file_text' parameters. Never omit file_text.\n"
" - For 'view': Provide 'command' and 'path' to view file/directory contents.\n"
" - For 'str_replace'/'insert': Provide all required parameters (command, path, old_str, new_str, etc.)\n"
"5. ask_human: Use ONLY in extreme cases when user interaction is absolutely required.\n"
"6. CRITICAL: When calling ANY tool, provide ALL required parameters. Incomplete tool calls will fail.\n"
"7. Use the session folder for saving outputs: {directory}/output/\n"
"\n"
"The initial workspace directory is: {directory}"
)

NEXT_STEP_PROMPT = """
Expand Down
14 changes: 9 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
pydantic~=2.10.6
openai~=1.66.3
pydantic~=2.11.5
openai~=1.108.1
tenacity~=9.0.0
pyyaml~=6.0.2
loguru~=0.7.3
structlog~=25.1.0
numpy
datasets~=3.4.1
fastapi~=0.115.11
Expand All @@ -20,7 +21,6 @@ baidusearch~=1.0.3
duckduckgo_search~=7.5.3

aiofiles~=24.1.0
pydantic_core~=2.27.2
colorama~=0.4.6
playwright~=1.51.0

Expand All @@ -33,10 +33,14 @@ httpx>=0.27.0
tomli>=2.0.0

boto3~=1.37.18
daytona>=0.161.0

requests~=2.32.3
beautifulsoup4~=4.13.3
crawl4ai~=0.6.3
crawl4ai~=0.7.4

huggingface-hub~=0.29.2
setuptools~=75.8.0
setuptools~=80.9.0

# Dependency conflict resolution
orjson>=3.11.5