diff --git a/openhands/agenthub/codeact_agent/prompts/system_prompt.j2 b/openhands/agenthub/codeact_agent/prompts/system_prompt.j2
index adae439544a7..b6dfcd9bda75 100644
--- a/openhands/agenthub/codeact_agent/prompts/system_prompt.j2
+++ b/openhands/agenthub/codeact_agent/prompts/system_prompt.j2
@@ -5,9 +5,14 @@ You are OpenHands agent, a helpful AI assistant that can interact with a compute
* The assistant MUST NOT include comments in the code unless they are necessary to describe non-obvious behavior.
{{ runtime_info }}
-{% if repo_instructions -%}
+{% if repository_info %}
+
+At the user's request, repository {{ repository_info.repo_name }} has been cloned to directory {{ repository_info.repo_directory }}.
+
+{% endif %}
+{% if repository_instructions -%}
-{{ repo_instructions }}
+{{ repository_instructions }}
{% endif %}
{% if runtime_info and runtime_info.available_hosts -%}
diff --git a/openhands/runtime/base.py b/openhands/runtime/base.py
index 27643ac3bc8d..4d747e616a7b 100644
--- a/openhands/runtime/base.py
+++ b/openhands/runtime/base.py
@@ -206,7 +206,7 @@ async def _handle_action(self, event: Action) -> None:
source = event.source if event.source else EventSource.AGENT
self.event_stream.add_event(observation, source) # type: ignore[arg-type]
- def clone_repo(self, github_token: str, selected_repository: str):
+ def clone_repo(self, github_token: str, selected_repository: str) -> str:
if not github_token or not selected_repository:
raise ValueError(
'github_token and selected_repository must be provided to clone a repository'
@@ -223,6 +223,7 @@ def clone_repo(self, github_token: str, selected_repository: str):
)
self.log('info', f'Cloning repo: {selected_repository}')
self.run_action(action)
+ return dir_name
def get_microagents_from_selected_repo(
self, selected_repository: str | None
diff --git a/openhands/server/session/agent_session.py b/openhands/server/session/agent_session.py
index 0e7bf554a814..70bf6eeca6bb 100644
--- a/openhands/server/session/agent_session.py
+++ b/openhands/server/session/agent_session.py
@@ -212,8 +212,9 @@ async def _create_runtime(
)
return
+ repo_directory = None
if selected_repository:
- await call_sync_from_async(
+ repo_directory = await call_sync_from_async(
self.runtime.clone_repo, github_token, selected_repository
)
@@ -223,6 +224,10 @@ async def _create_runtime(
self.runtime.get_microagents_from_selected_repo, selected_repository
)
agent.prompt_manager.load_microagents(microagents)
+ if selected_repository and repo_directory:
+ agent.prompt_manager.set_repository_info(
+ selected_repository, repo_directory
+ )
logger.debug(
f'Runtime initialized with plugins: {[plugin.name for plugin in self.runtime.plugins]}'
diff --git a/openhands/utils/prompt.py b/openhands/utils/prompt.py
index 899e625f159b..8d81cbbdf9d6 100644
--- a/openhands/utils/prompt.py
+++ b/openhands/utils/prompt.py
@@ -20,6 +20,14 @@ class RuntimeInfo:
available_hosts: dict[str, int]
+@dataclass
+class RepositoryInfo:
+ """Information about a GitHub repository that has been cloned."""
+
+ repo_name: str | None = None
+ repo_directory: str | None = None
+
+
class PromptManager:
"""
Manages prompt templates and micro-agents for AI interactions.
@@ -42,7 +50,7 @@ def __init__(
):
self.disabled_microagents: list[str] = disabled_microagents or []
self.prompt_dir: str = prompt_dir
-
+ self.repository_info: RepositoryInfo | None = None
self.system_template: Template = self._load_template('system_prompt')
self.user_template: Template = self._load_template('user_prompt')
self.runtime_info = RuntimeInfo(available_hosts={})
@@ -80,9 +88,6 @@ def load_microagents(self, microagents: list[BaseMicroAgent]):
elif isinstance(microagent, RepoMicroAgent):
self.repo_microagents[microagent.name] = microagent
- def set_runtime_info(self, runtime: Runtime):
- self.runtime_info.available_hosts = runtime.web_hosts
-
def _load_template(self, template_name: str) -> Template:
if self.prompt_dir is None:
raise ValueError('Prompt directory is not set')
@@ -102,10 +107,31 @@ def get_system_message(self) -> str:
if repo_instructions:
repo_instructions += '\n\n'
repo_instructions += microagent.content
+
return self.system_template.render(
- runtime_info=self.runtime_info, repo_instructions=repo_instructions
+ repository_instructions=repo_instructions,
+ repository_info=self.repository_info,
+ runtime_info=self.runtime_info,
).strip()
+ def set_runtime_info(self, runtime: Runtime):
+ self.runtime_info.available_hosts = runtime.web_hosts
+
+ def set_repository_info(
+ self,
+ repo_name: str,
+ repo_directory: str,
+ ) -> None:
+ """Sets information about the GitHub repository that has been cloned.
+
+ Args:
+ repo_name: The name of the GitHub repository (e.g. 'owner/repo')
+ repo_directory: The directory where the repository has been cloned
+ """
+ self.repository_info = RepositoryInfo(
+ repo_name=repo_name, repo_directory=repo_directory
+ )
+
def get_example_user_message(self) -> str:
"""This is the initial user message provided to the agent
before *actual* user instructions are provided.
diff --git a/tests/unit/test_prompt_manager.py b/tests/unit/test_prompt_manager.py
index 6d5cf3a983c7..4f2a69f7f0d5 100644
--- a/tests/unit/test_prompt_manager.py
+++ b/tests/unit/test_prompt_manager.py
@@ -5,7 +5,7 @@
from openhands.core.message import Message, TextContent
from openhands.microagent import BaseMicroAgent
-from openhands.utils.prompt import PromptManager
+from openhands.utils.prompt import PromptManager, RepositoryInfo
@pytest.fixture
@@ -39,6 +39,7 @@ def test_prompt_manager_with_microagent(prompt_dir):
with open(os.path.join(prompt_dir, 'micro', f'{microagent_name}.md'), 'w') as f:
f.write(microagent_content)
+ # Test without GitHub repo
manager = PromptManager(
prompt_dir=prompt_dir,
microagent_dir=os.path.join(prompt_dir, 'micro'),
@@ -53,6 +54,14 @@ def test_prompt_manager_with_microagent(prompt_dir):
'You are OpenHands agent, a helpful AI assistant that can interact with a computer to solve tasks.'
in manager.get_system_message()
)
+ assert '' not in manager.get_system_message()
+
+ # Test with GitHub repo
+ manager.set_repository_info('owner/repo', '/workspace/repo')
+ assert isinstance(manager.get_system_message(), str)
+ assert '' in manager.get_system_message()
+ assert 'owner/repo' in manager.get_system_message()
+ assert '/workspace/repo' in manager.get_system_message()
assert isinstance(manager.get_example_user_message(), str)
@@ -76,20 +85,56 @@ def test_prompt_manager_file_not_found(prompt_dir):
def test_prompt_manager_template_rendering(prompt_dir):
# Create temporary template files
with open(os.path.join(prompt_dir, 'system_prompt.j2'), 'w') as f:
- f.write('System prompt: bar')
+ f.write("""System prompt: bar
+{% if repository_info %}
+
+At the user's request, repository {{ repository_info.repo_name }} has been cloned to directory {{ repository_info.repo_directory }}.
+
+{% endif %}
+{{ repo_instructions }}""")
with open(os.path.join(prompt_dir, 'user_prompt.j2'), 'w') as f:
f.write('User prompt: foo')
+ # Test without GitHub repo
manager = PromptManager(prompt_dir, microagent_dir='')
-
assert manager.get_system_message() == 'System prompt: bar'
assert manager.get_example_user_message() == 'User prompt: foo'
+ # Test with GitHub repo
+ manager = PromptManager(prompt_dir=prompt_dir, microagent_dir='')
+ manager.set_repository_info('owner/repo', '/workspace/repo')
+ assert manager.repository_info.repo_name == 'owner/repo'
+ system_msg = manager.get_system_message()
+ assert 'System prompt: bar' in system_msg
+ assert '' in system_msg
+ assert (
+ "At the user's request, repository owner/repo has been cloned to directory /workspace/repo."
+ in system_msg
+ )
+ assert '' in system_msg
+ assert manager.get_example_user_message() == 'User prompt: foo'
+
# Clean up temporary files
os.remove(os.path.join(prompt_dir, 'system_prompt.j2'))
os.remove(os.path.join(prompt_dir, 'user_prompt.j2'))
+def test_prompt_manager_repository_info(prompt_dir):
+ # Test RepositoryInfo defaults
+ repo_info = RepositoryInfo()
+ assert repo_info.repo_name is None
+ assert repo_info.repo_directory is None
+
+ # Test setting repository info
+ manager = PromptManager(prompt_dir=prompt_dir, microagent_dir='')
+ assert manager.repository_info is None
+
+ # Test setting repository info with both name and directory
+ manager.set_repository_info('owner/repo2', '/workspace/repo2')
+ assert manager.repository_info.repo_name == 'owner/repo2'
+ assert manager.repository_info.repo_directory == '/workspace/repo2'
+
+
def test_prompt_manager_disabled_microagents(prompt_dir):
# Create test microagent files
microagent1_name = 'test_microagent1'