Compare commits

...

63 Commits

Author SHA1 Message Date
openhands
93287ef9ac Fix microagent test filenames to match expected names
- Change test filenames from 'test.md' to match expected microagent names
- Use 'default.md' for tests expecting 'default' name
- Use 'custom_name.md' for test expecting 'custom_name' name
- Use 'test_agent.md' for test expecting 'test_agent' name
- This properly tests the filename-based naming behavior
2025-06-24 14:20:34 +00:00
openhands
e70595f46f Fix microagent tests and remove debug prints
- Update test assertions to expect filename as microagent name instead of 'default'
- Remove debug print statements from microagent.py
- Revert pytest-asyncio dependency addition as requested
- All tests now pass with the new filename-based naming behavior
2025-06-24 14:16:20 +00:00
openhands
1d3ff66987 Fix failing tests: add missing newlines and pytest-asyncio dependency
- Add missing newlines at end of microagent files (fixed by pre-commit)
- Add pytest-asyncio dependency to fix async test execution
- All non-Docker tests now pass
2025-06-24 14:01:12 +00:00
Xingyao Wang
1a95f86802 fix all remaining issue' 2025-06-23 17:49:02 -04:00
Xingyao Wang
eee12bfd94 fix test 2025-06-23 16:09:32 -04:00
Xingyao Wang
8c2d4dbe8b Merge branch 'main' into update-microagent-docs 2025-06-23 14:22:56 -04:00
Xingyao Wang
0ca3188afa Merge branch 'main' into update-microagent-docs 2025-06-18 14:23:58 -04:00
openhands
283f503870 Exclude name field in MicroagentMetadata as it's deprecated 2025-06-08 22:07:33 +00:00
openhands
0691e5c0d0 Remove type: field from all microagent markdown files 2025-06-08 19:48:01 +00:00
openhands
fc16da8fd2 Update microagent documentation to clarify that type field is optional 2025-06-08 19:39:17 +00:00
openhands
bd3ff43c67 Remove name field from microagent files 2025-06-08 19:35:06 +00:00
openhands
0fe5b808af Update microagent code to use filename as name when not specified 2025-06-08 19:34:59 +00:00
openhands
6c49686ff0 Add MCP tools documentation and update microagent field requirements 2025-06-08 19:30:21 +00:00
openhands
17212bb2f2 Remove unused fields from microagent code and update all microagent files 2025-06-08 19:26:56 +00:00
openhands
9d9f931e95 Remove unused fields from microagent documentation and example 2025-06-08 19:23:47 +00:00
openhands
6fe9680474 Consolidate task microagent documentation into keyword-triggered microagents 2025-06-08 19:19:44 +00:00
Xingyao Wang
53c80d1c92 Merge branch 'main' into update-microagent-docs 2025-06-08 15:17:37 -04:00
openhands
401262f353 Update documentation for task microagents with user input support 2025-06-08 19:15:31 +00:00
Xingyao Wang
58845b01a3 rename more files 2025-06-08 14:30:37 -04:00
Xingyao Wang
469d184157 address engel comment 2025-06-08 14:28:22 -04:00
Xingyao Wang
4837c4dc74 Update microagents/get_test_to_pass.md
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
2025-06-09 02:24:23 +08:00
Xingyao Wang
6763f21cc3 Merge branch 'main' into add-back-microagents 2025-06-07 16:47:00 -04:00
Xingyao Wang
32e610ac1d revert unnecessary change 2025-06-07 16:30:55 -04:00
Xingyao Wang
85c65391ca revert changes 2025-06-03 13:53:27 -04:00
Xingyao Wang
c444dbfbbf remove fe changes 2025-06-03 12:04:37 -04:00
Xingyao Wang
dd988d0f14 revert fe 2025-06-03 12:03:00 -04:00
Xingyao Wang
6f1a74e286 merge main 2025-06-03 11:37:51 -04:00
Xingyao Wang
7b956b6103 revert docs to look like main 2025-06-03 11:35:57 -04:00
openhands
34b097115d Fix linting issues in frontend and Python code 2025-05-19 01:39:48 +00:00
openhands
3e4ab4f379 Fix docstring formatting in KnowledgeMicroagent class 2025-05-19 01:29:23 +00:00
openhands
54cd9f7e44 Fix unlocalized strings in microagent-dropdown.tsx 2025-05-19 01:26:33 +00:00
openhands
802b765f98 Add microagent button and dropdown to trajectory actions 2025-05-17 12:05:13 +00:00
openhands
18c88f99ff Merge from main to resolve conflicts 2025-05-17 06:56:11 +00:00
openhands
f3934be07b Fix microagent suggestions using tippy.js for better popup handling 2025-05-12 12:55:00 +00:00
openhands
6ce9f49d1e Fix linting issues in TipTap editor component 2025-05-12 11:06:15 +00:00
openhands
fc07622b20 Implement microagent suggestions using TipTap 2025-05-12 11:00:08 +00:00
Xingyao Wang
da935f9d8f Merge branch 'main' into add-back-microagents 2025-05-03 00:04:17 +08:00
openhands
642cc52a1a Fix linting issues in handlers.ts 2025-05-02 13:06:21 +00:00
openhands
4c361ab9e5 Add mock handler for microagents endpoint 2025-05-02 09:23:25 +00:00
openhands
5dfa1bb6eb Fix microagent suggestions UI and TypeScript errors 2025-05-02 09:21:15 +00:00
Xingyao Wang
a07cf972a5 Merge commit '6032d2620d6ec252d3c80695a6de1fc88da9c87a' into add-back-microagents 2025-05-02 09:03:17 +00:00
openhands
f2e3bc3254 Fix microagent suggestions feature 2025-05-02 08:52:19 +00:00
openhands
3790ec7d60 Add tests for microagent suggestions component 2025-05-02 03:31:41 +00:00
openhands
3c0719309e Add microagent suggestions feature to chat input 2025-05-02 02:57:57 +00:00
Xingyao Wang
0236e0943e fix test 2025-05-02 02:09:27 +00:00
Xingyao Wang
cd464c0022 rename files 2025-05-01 10:38:04 +08:00
Xingyao Wang
4519a7f4f3 fix test 2025-05-01 02:29:52 +00:00
Xingyao Wang
fdc591330b add remain 2025-05-01 02:25:38 +00:00
Xingyao Wang
98e454e82c fix lint and missing imports 2025-05-01 02:25:24 +00:00
Xingyao Wang
e088d2d24a simplify microagent 2025-05-01 02:13:46 +00:00
Xingyao Wang
58c574af1e revert changes 2025-05-01 02:13:00 +00:00
Xingyao Wang
405f0069f8 revert some changes 2025-05-01 02:03:06 +00:00
Xingyao Wang
f26d770d03 remove hardcoded last line 2025-05-01 02:01:51 +00:00
Xingyao Wang
bf2c3de219 cleanup tests 2025-04-30 11:11:23 +08:00
Xingyao Wang
7c35ce16e5 Merge branch 'main' into add-back-microagents 2025-04-30 11:07:17 +08:00
Xingyao Wang
f4024ccd94 Update microagents/update_pr_description.md
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
2025-04-30 10:43:37 +08:00
Xingyao Wang
b55bfed831 Update microagents/address_pr_comments.md
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
2025-04-30 10:38:22 +08:00
OpenHands Bot
cb0994027f 🤖 Auto-fix Python linting issues 2025-04-29 16:02:04 +00:00
openhands
bcc9bd0b9a Move task microagent tests to test_microagent_task.py 2025-04-29 02:12:14 +00:00
openhands
6c144e6b5a Add back microagent files with special handling for user inputs 2025-04-29 02:06:42 +00:00
openhands
e90b841b0d Update microagent files to match original ones with added triggers and variable prompts 2025-04-29 01:48:10 +00:00
openhands
a1e6ed4dff Add special handling for microagents that require user input 2025-04-29 01:47:18 +00:00
openhands
ad6311d3cd Add back microagent files and add special handling for user input variables 2025-04-29 01:33:23 +00:00
25 changed files with 334 additions and 257 deletions

View File

@@ -5,26 +5,111 @@ description: Keyword-triggered microagents provide OpenHands with specific instr
## Usage
These microagents are only loaded when a prompt includes one of the trigger words.
Keyword-triggered microagents are only loaded when a prompt includes one of the trigger words. There are two types of keyword-triggered microagents:
1. **Standard Keyword Microagents**: Triggered by keywords embedded in text
2. **Command-Style Microagents**: Triggered by command-style inputs (e.g., `/fix_test`) that can prompt for user input
Additionally, there's a special type of microagent that's always active:
3. **Repository Microagents**: Always active for a specific repository, providing repository-specific context and tools
## Frontmatter Syntax
Frontmatter is required for keyword-triggered microagents. It must be placed at the top of the file,
above the guidelines.
above the guidelines. Enclose the frontmatter in triple dashes (---).
Enclose the frontmatter in triple dashes (---) and include the following fields:
### Standard Keyword Microagents
For standard keyword microagents, include the following fields:
| Field | Description | Required | Default |
|------------|--------------------------------------------------|----------|------------------|
| `name` | The name of the microagent | No | Filename |
| `type` | The type of microagent (`knowledge`) | No | Inferred |
| `triggers` | A list of keywords that activate the microagent. | Yes | None |
| `agent` | The agent this microagent applies to. | No | 'CodeActAgent' |
### Command-Style Microagents
## Example
For command-style microagents that require user input, include the following fields:
Keyword-triggered microagent file example located at `.openhands/microagents/yummy.md`:
```
| Field | Description | Required | Default |
|------------|------------------------------------------------------------|----------|------------------|
| `name` | The name of the microagent | No | Filename |
| `type` | The type of microagent (`task`) | No | Inferred |
| `triggers` | A list of command triggers (e.g., `/fix_test`) | No | `/[name]` |
| `inputs` | A list of input variables the microagent requires | Yes | None |
### Repository Microagents
Repository microagents are always active for a specific repository. They provide repository-specific context and tools.
| Field | Description | Required | Default |
|------------|------------------------------------------------------------|----------|------------------|
| `name` | The name of the microagent | No | Filename |
| `type` | The type of microagent (`repo`) | No | Inferred |
#### Repository Microagent Example
Here's an example of a repository microagent:
```yaml
---
# The type field is optional and will be inferred as 'repo' when no triggers are present
---
# Repository Guidelines
This repository follows these coding standards:
1. Use PEP 8 for Python code
2. Use ESLint for JavaScript code
3. Write unit tests for all new features
```
This microagent is always active when working with the repository and provides repository-specific guidelines.
### MCP Tools Support
Microagents can also provide additional MCP (Model-Code-Prompt) tools to the agent. This is useful for extending the agent's capabilities with custom tools.
| Field | Description | Required | Default |
|--------------|-----------------------------------------------------------|----------|------------------|
| `mcp_tools` | Configuration for additional MCP tools | No | None |
#### MCP Tools Example
Here's an example of a microagent that provides an additional MCP tool (the `fetch` tool for accessing web content):
```yaml
---
# The type field is optional and will be inferred as 'repo' when no triggers are present
mcp_tools:
stdio_servers:
- name: "fetch"
command: uvx
args:
- mcp-server-fetch
---
```
This microagent is a repository microagent (always active) that adds the `fetch` tool to the agent's capabilities.
Each input in the `inputs` list requires:
| Field | Description | Required |
|---------------|--------------------------------------------------|----------|
| `name` | The name of the input variable | Yes |
| `description` | A description of what the input should contain | Yes |
## Examples
### Standard Keyword Microagent Example
Standard keyword microagent file example located at `.openhands/microagents/yummy.md`:
```yaml
---
# The type field is optional and will be inferred as 'knowledge' when triggers are present
triggers:
- yummyhappy
- happyyummy
@@ -33,4 +118,58 @@ triggers:
The user has said the magic word. Respond with "That was delicious!"
```
[See examples of microagents triggered by keywords in the official OpenHands repository](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents)
### Command-Style Microagent Example
Command-style microagent file example located at `.openhands/microagents/fix_test.md`:
```yaml
---
# The type field is optional and will be inferred as 'task' when inputs are present
triggers:
- /fix_test
inputs:
- name: BRANCH_NAME
description: "Branch for the agent to work on"
- name: TEST_COMMAND_TO_RUN
description: "The test command you want the agent to work on. For example, `pytest tests/unit/test_bash_parsing.py`"
- name: FUNCTION_TO_FIX
description: "The name of function to fix"
- name: FILE_FOR_FUNCTION
description: "The path of the file that contains the function"
---
Can you check out branch "{{ BRANCH_NAME }}", and run {{ TEST_COMMAND_TO_RUN }}.
Help me fix these tests to pass by fixing the {{ FUNCTION_TO_FIX }} function in file {{ FILE_FOR_FUNCTION }}.
PLEASE DO NOT modify the tests by yourself -- Let me know if you think some of the tests are incorrect.
```
## Using Command-Style Microagents
Command-style microagents are designed to streamline common development tasks by providing structured templates for specific operations. They are triggered using a command-style format and will prompt the user for any required inputs.
### How to Use
1. Type `/` in the chat input to see available command-style microagents
2. Select a microagent from the dropdown or type its name (e.g., `/fix_test`)
3. The agent will prompt you for any required inputs
4. Provide the requested information
5. The agent will execute the task with your inputs
### Template Variables
In the body of a command-style microagent, you can reference input variables using the `{{ VARIABLE_NAME }}` syntax. These will be replaced with the user-provided values when the microagent is triggered.
### Available Command-Style Microagents
OpenHands includes several built-in command-style microagents:
| Command | Description |
|----------------------|-------------------------------------------------------|
| `/fix_test` | Fix failing tests by modifying a specific function |
| `/update_test` | Update tests for a new implementation |
| `/update_pr` | Update a pull request description |
| `/address_pr_comments` | Address comments on a pull request |
| `/add_repo_instruction` | Add instructions to the repository microagent |
[See examples of microagents in the official OpenHands repository](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents)

View File

@@ -8,7 +8,7 @@ description: Microagents are specialized prompts that enhance OpenHands with dom
Currently OpenHands supports the following types of microagents:
- [General Microagents](./microagents-repo): General guidelines for OpenHands about the repository.
- [Keyword-Triggered Microagents](./microagents-keyword): Guidelines activated by specific keywords in prompts.
- [Keyword-Triggered Microagents](./microagents-keyword): Guidelines activated by specific keywords in prompts, including command-style microagents that prompt for user inputs.
To customize OpenHands' behavior, create a .openhands/microagents/ directory in the root of your repository and
add `<microagent_name>.md` files inside. For repository-specific guidelines, you can ask OpenHands to analyze your repository and create a comprehensive `repo.md` file (see [General Microagents](./microagents-repo) for details).
@@ -34,7 +34,7 @@ some-repository/
Each microagent file may include frontmatter that provides additional information. In some cases, this frontmatter
is required:
| Microagent Type | Required |
|---------------------------------|----------|
| `General Microagents` | No |
| `Keyword-Triggered Microagents` | Yes |
| Microagent Type | Required |
|------------------------------------------------|----------|
| `General Microagents` | No |
| `Keyword-Triggered Microagents (all types)` | Yes |

View File

@@ -1,20 +1,16 @@
---
name: add_agent
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers:
- new agent
- new microagent
- create agent
- create an agent
- create microagent
- create a microagent
- add agent
- add an agent
- add microagent
- add a microagent
- microagent template
- new agent
- new microagent
- create agent
- create an agent
- create microagent
- create a microagent
- add agent
- add an agent
- add microagent
- add a microagent
- microagent template
---
This agent helps create new microagents in the `.openhands/microagents` directory by providing guidance and templates.

View File

@@ -1,13 +1,9 @@
---
name: add_repo_inst
version: 1.0.0
author: openhands
agent: CodeActAgent
inputs:
- description: Branch for the agent to work on
name: REPO_FOLDER_NAME
triggers:
- /add_repo_inst
inputs:
- name: REPO_FOLDER_NAME
description: "Branch for the agent to work on"
---
Please browse the current repository under /workspace/{{ REPO_FOLDER_NAME }}, look at the documentation and relevant code, and understand the purpose of this repository.
@@ -18,7 +14,6 @@ Here's an example:
```markdown
---
name: repo
type: repo
agent: CodeActAgent
---

View File

@@ -1,15 +1,11 @@
---
name: address_pr_comments
version: 1.0.0
author: openhands
agent: CodeActAgent
inputs:
- description: URL of the pull request
name: PR_URL
- description: Branch name corresponds to the pull request
name: BRANCH_NAME
triggers:
- /address_pr_comments
inputs:
- name: PR_URL
description: "URL of the pull request"
- name: BRANCH_NAME
description: "Branch name corresponds to the pull request"
---
First, check the branch {{ BRANCH_NAME }} and read the diff against the main branch to understand the purpose.

View File

@@ -1,8 +1,4 @@
---
name: agent_memory
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers:
- /remember
---

View File

@@ -1,15 +1,8 @@
---
# This is a repo microagent that is always activated
# to include necessary default tools implemented with MCP
name: default-tools
type: repo
version: 1.0.0
agent: CodeActAgent
mcp_tools:
stdio_servers:
- name: "fetch"
command: "uvx"
args: ["mcp-server-fetch"]
# We leave the body empty because MCP tools will automatically add the
# tool description for LLMs in tool calls, so there's no need to add extra descriptions.
- args:
- mcp-server-fetch
command: uvx
name: fetch
---

View File

@@ -1,8 +1,4 @@
---
name: docker
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers:
- docker
- container

View File

@@ -1,19 +1,16 @@
---
name: fix_test
version: 1.0.0
author: openhands
agent: CodeActAgent
inputs:
- description: Branch for the agent to work on
name: BRANCH_NAME
- description: The test command you want the agent to work on. For example, `pytest
tests/unit/test_bash_parsing.py`
name: TEST_COMMAND_TO_RUN
- description: The name of function to fix
name: FUNCTION_TO_FIX
- description: The path of the file that contains the function
name: FILE_FOR_FUNCTION
triggers:
- /fix_test
inputs:
- name: BRANCH_NAME
description: "Branch for the agent to work on"
- name: TEST_COMMAND_TO_RUN
description: "The test command you want the agent to work on. For example, `pytest tests/unit/test_bash_parsing.py`"
- name: FUNCTION_TO_FIX
description: "The name of function to fix"
- name: FILE_FOR_FUNCTION
description: "The path of the file that contains the function"
---
Can you check out branch "{{ BRANCH_NAME }}", and run {{ TEST_COMMAND_TO_RUN }}.

View File

@@ -1,8 +1,4 @@
---
name: flarglebargle
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers:
- flarglebargle
---

View File

@@ -1,8 +1,4 @@
---
name: github
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers:
- github
- git

View File

@@ -1,8 +1,4 @@
---
name: gitlab
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers:
- gitlab
- git

View File

@@ -1,8 +1,4 @@
---
name: kubernetes
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers:
- kubernetes
- k8s

View File

@@ -1,8 +1,4 @@
---
name: npm
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers:
- npm
---

View File

@@ -1,8 +1,4 @@
---
name: pdflatex
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers:
- pdflatex
---

View File

@@ -1,15 +1,12 @@
---
name: security
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers:
- security
- vulnerability
- authentication
- authorization
- permissions
- security
- vulnerability
- authentication
- authorization
- permissions
---
This document provides guidance on security best practices
You should always be considering security implications when developing.

View File

@@ -1,16 +1,12 @@
---
name: SSH Microagent
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers:
- ssh
- remote server
- remote machine
- remote host
- remote connection
- secure shell
- ssh keys
- ssh
- remote server
- remote machine
- remote host
- remote connection
- secure shell
- ssh keys
---
# SSH Microagent

View File

@@ -1,12 +1,8 @@
---
name: swift-linux
type: knowledge
agent: CodeActAgent
version: 1.0.0
triggers:
- swift-linux
- swift-debian
- swift-installation
triggers:
- swift-linux
- swift-debian
- swift-installation
---
# Swift Installation Guide for Debian Linux

View File

@@ -1,19 +1,15 @@
---
name: update_pr_description
version: 1.0.0
author: openhands
agent: CodeActAgent
inputs:
- description: URL of the pull request
name: PR_URL
type: string
validation:
pattern: ^https://github.com/.+/.+/pull/[0-9]+$
- description: Branch name corresponds to the pull request
name: BRANCH_NAME
type: string
triggers:
- /update_pr_description
inputs:
- name: PR_URL
description: "URL of the pull request"
type: string
validation:
pattern: "^https://github.com/.+/.+/pull/[0-9]+$"
- name: BRANCH_NAME
description: "Branch name corresponds to the pull request"
type: string
---
Please check the branch "{{ BRANCH_NAME }}" and look at the diff against the main branch. This branch belongs to this PR "{{ PR_URL }}".

View File

@@ -1,15 +1,12 @@
---
name: update_test
version: 1.0.0
author: openhands
agent: CodeActAgent
inputs:
- description: Branch for the agent to work on
name: BRANCH_NAME
- description: The test command you want the agent to work on. For example, `pytest
tests/unit/test_bash_parsing.py`
name: TEST_COMMAND_TO_RUN
triggers:
- /update_test
inputs:
- name: BRANCH_NAME
description: "Branch for the agent to work on"
- name: TEST_COMMAND_TO_RUN
description: "The test command you want the agent to work on. For example, `pytest tests/unit/test_bash_parsing.py`"
---
Can you check out branch "{{ BRANCH_NAME }}", and run {{ TEST_COMMAND_TO_RUN }}.

View File

@@ -40,6 +40,11 @@ class BaseMicroagent(BaseModel):
derived_name = None
if microagent_dir is not None:
derived_name = str(path.relative_to(microagent_dir).with_suffix(''))
else:
derived_name = path.with_suffix('').name
logger.warning(
f'No microagent_dir provided. Microagent name will be the file name: {derived_name}'
)
# Only load directly from path if file_content is not provided
if file_content is None:
@@ -95,6 +100,16 @@ class BaseMicroagent(BaseModel):
MicroagentType.TASK: TaskMicroagent,
}
# We will always use derived_name if available
assert derived_name is not None
agent_name = derived_name
if metadata.name is not None:
logger.warning(
f'Detected `name:` field in frontmatter for microagent {metadata.name}. '
"This is deprecated. Microagent's name will use the file name "
f'({derived_name}) instead.'
)
# Infer the agent type:
# 1. If inputs exist -> TASK
# 2. If triggers exist -> KNOWLEDGE
@@ -102,8 +117,7 @@ class BaseMicroagent(BaseModel):
inferred_type: MicroagentType
if metadata.inputs:
inferred_type = MicroagentType.TASK
# Add a trigger for the agent name if not already present
trigger = f'/{metadata.name}'
trigger = f'/{agent_name}'
if not metadata.triggers or trigger not in metadata.triggers:
if not metadata.triggers:
metadata.triggers = [trigger]
@@ -120,9 +134,6 @@ class BaseMicroagent(BaseModel):
# This should theoretically not happen with the logic above
raise ValueError(f'Could not determine microagent type for: {path}')
# Use derived_name if available (from relative path), otherwise fallback to metadata.name
agent_name = derived_name if derived_name is not None else metadata.name
agent_class = subclass_map[inferred_type]
return agent_class(
name=agent_name,

View File

@@ -25,10 +25,12 @@ class InputMetadata(BaseModel):
class MicroagentMetadata(BaseModel):
"""Metadata for all microagents."""
name: str = 'default'
name: str = Field(default='default', exclude=True)
type: MicroagentType = Field(default=MicroagentType.REPO_KNOWLEDGE)
version: str = Field(default='1.0.0')
agent: str = Field(default='CodeActAgent')
# Keep these fields for backward compatibility but they're not used
version: str = Field(default='1.0.0', exclude=True)
agent: str = Field(default='CodeActAgent', exclude=True)
author: str = Field(default='', exclude=True)
triggers: list[str] = [] # optional, only exists for knowledge microagents
inputs: list[InputMetadata] = [] # optional, only exists for task microagents
mcp_tools: MCPConfig | None = (

19
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
[[package]]
name = "aioboto3"
@@ -462,7 +462,7 @@ description = "LTS Port of Python audioop"
optional = false
python-versions = ">=3.13"
groups = ["main"]
markers = "python_version >= \"3.13\""
markers = "python_version == \"3.13\""
files = [
{file = "audioop_lts-0.2.1-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd1345ae99e17e6910f47ce7d52673c6a1a70820d78b67de1b7abb3af29c426a"},
{file = "audioop_lts-0.2.1-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:e175350da05d2087e12cea8e72a70a1a8b14a17e92ed2022952a4419689ede5e"},
@@ -1644,7 +1644,7 @@ files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\" or os_name == \"nt\"", dev = "os_name == \"nt\" or sys_platform == \"win32\"", runtime = "sys_platform == \"win32\"", test = "platform_system == \"Windows\" or sys_platform == \"win32\""}
markers = {main = "platform_system == \"Windows\" or os_name == \"nt\" or sys_platform == \"win32\"", dev = "os_name == \"nt\" or sys_platform == \"win32\"", runtime = "sys_platform == \"win32\"", test = "platform_system == \"Windows\" or sys_platform == \"win32\""}
[[package]]
name = "comm"
@@ -3053,8 +3053,8 @@ files = [
google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]}
google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev"
proto-plus = [
{version = ">=1.22.3,<2.0.0dev"},
{version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""},
{version = ">=1.22.3,<2.0.0dev"},
]
protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev"
@@ -3076,8 +3076,8 @@ googleapis-common-protos = ">=1.56.2,<2.0.0"
grpcio = {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}
grpcio-status = {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}
proto-plus = [
{version = ">=1.22.3,<2.0.0"},
{version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""},
{version = ">=1.22.3,<2.0.0"},
]
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0"
requests = ">=2.18.0,<3.0.0"
@@ -3295,8 +3295,8 @@ google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0", extras
google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0"
grpc-google-iam-v1 = ">=0.14.0,<1.0.0"
proto-plus = [
{version = ">=1.22.3,<2.0.0"},
{version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""},
{version = ">=1.22.3,<2.0.0"},
]
protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0"
@@ -6586,8 +6586,8 @@ files = [
[package.dependencies]
googleapis-common-protos = ">=1.52,<2.0"
grpcio = [
{version = ">=1.63.2,<2.0.0", markers = "python_version < \"3.13\""},
{version = ">=1.66.2,<2.0.0", markers = "python_version >= \"3.13\""},
{version = ">=1.63.2,<2.0.0", markers = "python_version < \"3.13\""},
]
opentelemetry-api = ">=1.15,<2.0"
opentelemetry-exporter-otlp-proto-common = "1.34.1"
@@ -9350,7 +9350,6 @@ files = [
{file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"},
{file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"},
]
markers = {evaluation = "platform_system == \"Linux\" and platform_machine == \"x86_64\""}
[package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""]
@@ -9593,7 +9592,7 @@ description = "Standard library aifc redistribution. \"dead battery\"."
optional = false
python-versions = "*"
groups = ["main"]
markers = "python_version >= \"3.13\""
markers = "python_version == \"3.13\""
files = [
{file = "standard_aifc-3.13.0-py3-none-any.whl", hash = "sha256:f7ae09cc57de1224a0dd8e3eb8f73830be7c3d0bc485de4c1f82b4a7f645ac66"},
{file = "standard_aifc-3.13.0.tar.gz", hash = "sha256:64e249c7cb4b3daf2fdba4e95721f811bde8bdfc43ad9f936589b7bb2fae2e43"},
@@ -9610,7 +9609,7 @@ description = "Standard library chunk redistribution. \"dead battery\"."
optional = false
python-versions = "*"
groups = ["main"]
markers = "python_version >= \"3.13\""
markers = "python_version == \"3.13\""
files = [
{file = "standard_chunk-3.13.0-py3-none-any.whl", hash = "sha256:17880a26c285189c644bd5bd8f8ed2bdb795d216e3293e6dbe55bbd848e2982c"},
{file = "standard_chunk-3.13.0.tar.gz", hash = "sha256:4ac345d37d7e686d2755e01836b8d98eda0d1a3ee90375e597ae43aaf064d654"},

View File

@@ -1,7 +1,6 @@
"""Tests for microagent loading in runtime."""
import os
import tempfile
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
@@ -20,7 +19,7 @@ from openhands.microagent.microagent import (
RepoMicroagent,
TaskMicroagent,
)
from openhands.microagent.types import MicroagentType
from openhands.microagent.types import InputMetadata, MicroagentType
def _create_test_microagents(test_dir: str):
@@ -32,10 +31,6 @@ def _create_test_microagents(test_dir: str):
knowledge_dir = microagents_dir / 'knowledge'
knowledge_dir.mkdir(exist_ok=True)
knowledge_agent = """---
name: test_knowledge_agent
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers:
- test
- pytest
@@ -45,17 +40,10 @@ triggers:
Testing best practices and guidelines.
"""
(knowledge_dir / 'knowledge.md').write_text(knowledge_agent)
(knowledge_dir / 'test_knowledge_agent.md').write_text(knowledge_agent)
# Create test repo agent
repo_agent = """---
name: test_repo_agent
type: repo
version: 1.0.0
agent: CodeActAgent
---
# Test Repository Agent
repo_agent = """# Test Repository Agent
Repository-specific test instructions.
"""
@@ -89,7 +77,7 @@ def test_load_microagents_with_trailing_slashes(
# Check knowledge agents
assert len(knowledge_agents) == 1
agent = knowledge_agents[0]
assert agent.name == 'knowledge/knowledge'
assert agent.name == 'knowledge/test_knowledge_agent'
assert 'test' in agent.triggers
assert 'pytest' in agent.triggers
@@ -126,7 +114,7 @@ def test_load_microagents_with_selected_repo(temp_dir, runtime_cls, run_as_openh
# Check knowledge agents
assert len(knowledge_agents) == 1
agent = knowledge_agents[0]
assert agent.name == 'knowledge/knowledge'
assert agent.name == 'knowledge/test_knowledge_agent'
assert 'test' in agent.triggers
assert 'pytest' in agent.triggers
@@ -180,7 +168,7 @@ Repository-specific test instructions.
_close_test_runtime(runtime)
def test_task_microagent_creation():
def test_task_microagent_creation(temp_dir):
"""Test that a TaskMicroagent is created correctly."""
content = """---
name: test_task
@@ -196,21 +184,43 @@ inputs:
This is a test task microagent with a variable: ${test_var}.
"""
with open(os.path.join(temp_dir, 'test_task.md'), 'w') as f:
f.write(content)
with tempfile.NamedTemporaryFile(suffix='.md') as f:
f.write(content.encode())
f.flush()
agent = BaseMicroagent.load(os.path.join(temp_dir, 'test_task.md'))
agent = BaseMicroagent.load(f.name)
assert isinstance(agent, TaskMicroagent)
assert agent.type == MicroagentType.TASK
assert agent.name == 'test_task'
assert '/test_task' in agent.triggers
assert "If the user didn't provide any of these variables" in agent.content
assert agent.inputs == [InputMetadata(name='TEST_VAR', description='Test variable')]
simplified_content = """---
triggers:
- /test_task
inputs:
- name: TEST_VAR
description: "Test variable"
---
assert isinstance(agent, TaskMicroagent)
assert agent.type == MicroagentType.TASK
assert agent.name == 'test_task'
assert '/test_task' in agent.triggers
assert "If the user didn't provide any of these variables" in agent.content
This is a test task microagent with a variable: ${test_var}.
"""
with open(os.path.join(temp_dir, 'test_task.md'), 'w') as f:
f.write(simplified_content)
simplified_agent = BaseMicroagent.load(os.path.join(temp_dir, 'test_task.md'))
assert isinstance(simplified_agent, TaskMicroagent)
assert simplified_agent.type == MicroagentType.TASK
assert simplified_agent.name == 'test_task'
assert '/test_task' in simplified_agent.triggers
assert (
"If the user didn't provide any of these variables" in simplified_agent.content
)
def test_task_microagent_variable_extraction():
def test_task_microagent_variable_extraction(temp_dir):
"""Test that variables are correctly extracted from the content."""
content = """---
name: test_task
@@ -227,19 +237,18 @@ inputs:
This is a test with variables: ${var1}, ${var2}, and ${var3}.
"""
with tempfile.NamedTemporaryFile(suffix='.md') as f:
f.write(content.encode())
f.flush()
with open(os.path.join(temp_dir, 'test_task.md'), 'w') as f:
f.write(content)
agent = BaseMicroagent.load(f.name)
agent = BaseMicroagent.load(os.path.join(temp_dir, 'test_task.md'))
assert isinstance(agent, TaskMicroagent)
variables = agent.extract_variables(agent.content)
assert set(variables) == {'var1', 'var2', 'var3'}
assert agent.requires_user_input()
assert isinstance(agent, TaskMicroagent)
variables = agent.extract_variables(agent.content)
assert set(variables) == {'var1', 'var2', 'var3'}
assert agent.requires_user_input()
def test_knowledge_microagent_no_prompt():
def test_knowledge_microagent_no_prompt(temp_dir):
"""Test that a regular KnowledgeMicroagent doesn't get the prompt."""
content = """---
name: test_knowledge
@@ -252,19 +261,17 @@ triggers:
This is a test knowledge microagent.
"""
with open(os.path.join(temp_dir, 'test_knowledge.md'), 'w') as f:
f.write(content)
with tempfile.NamedTemporaryFile(suffix='.md') as f:
f.write(content.encode())
f.flush()
agent = BaseMicroagent.load(os.path.join(temp_dir, 'test_knowledge.md'))
agent = BaseMicroagent.load(f.name)
assert isinstance(agent, KnowledgeMicroagent)
assert agent.type == MicroagentType.KNOWLEDGE
assert "If the user didn't provide any of these variables" not in agent.content
assert isinstance(agent, KnowledgeMicroagent)
assert agent.type == MicroagentType.KNOWLEDGE
assert "If the user didn't provide any of these variables" not in agent.content
def test_task_microagent_trigger_addition():
def test_task_microagent_trigger_addition(temp_dir):
"""Test that a trigger is added if not present."""
content = """---
name: test_task
@@ -278,18 +285,16 @@ inputs:
This is a test task microagent.
"""
with open(os.path.join(temp_dir, 'test_task.md'), 'w') as f:
f.write(content)
with tempfile.NamedTemporaryFile(suffix='.md') as f:
f.write(content.encode())
f.flush()
agent = BaseMicroagent.load(os.path.join(temp_dir, 'test_task.md'))
agent = BaseMicroagent.load(f.name)
assert isinstance(agent, TaskMicroagent)
assert '/test_task' in agent.triggers
assert isinstance(agent, TaskMicroagent)
assert '/test_task' in agent.triggers
def test_task_microagent_no_duplicate_trigger():
def test_task_microagent_no_duplicate_trigger(temp_dir):
"""Test that a trigger is not duplicated if already present."""
content = """---
name: test_task
@@ -306,21 +311,19 @@ inputs:
This is a test task microagent.
"""
with open(os.path.join(temp_dir, 'test_task.md'), 'w') as f:
f.write(content)
with tempfile.NamedTemporaryFile(suffix='.md') as f:
f.write(content.encode())
f.flush()
agent = BaseMicroagent.load(os.path.join(temp_dir, 'test_task.md'))
agent = BaseMicroagent.load(f.name)
assert isinstance(agent, TaskMicroagent)
assert agent.triggers.count('/test_task') == 1 # No duplicates
assert len(agent.triggers) == 2
assert 'another_trigger' in agent.triggers
assert '/test_task' in agent.triggers
assert isinstance(agent, TaskMicroagent)
assert agent.triggers.count('/test_task') == 1 # No duplicates
assert len(agent.triggers) == 2
assert 'another_trigger' in agent.triggers
assert '/test_task' in agent.triggers
def test_task_microagent_match_trigger():
def test_task_microagent_match_trigger(temp_dir):
"""Test that a task microagent matches its trigger correctly."""
content = """---
name: test_task
@@ -337,17 +340,16 @@ inputs:
This is a test task microagent.
"""
with tempfile.NamedTemporaryFile(suffix='.md') as f:
f.write(content.encode())
f.flush()
with open(os.path.join(temp_dir, 'test_task.md'), 'w') as f:
f.write(content)
agent = BaseMicroagent.load(f.name)
agent = BaseMicroagent.load(os.path.join(temp_dir, 'test_task.md'))
assert isinstance(agent, TaskMicroagent)
assert agent.match_trigger('/test_task') == '/test_task'
assert agent.match_trigger(' /test_task ') == '/test_task'
assert agent.match_trigger('This contains /test_task') == '/test_task'
assert agent.match_trigger('/other_task') is None
assert isinstance(agent, TaskMicroagent)
assert agent.match_trigger('/test_task') == '/test_task'
assert agent.match_trigger(' /test_task ') == '/test_task'
assert agent.match_trigger('This contains /test_task') == '/test_task'
assert agent.match_trigger('/other_task') is None
def test_default_tools_microagent_exists():
@@ -369,15 +371,12 @@ def test_default_tools_microagent_exists():
with open(default_tools_path, 'r') as f:
content = f.read()
# Verify it's a repo microagent (always activated)
assert 'type: repo' in content, 'default-tools.md should be a repo microagent'
assert 'command: uvx' in content, 'default-tools.md should use uvx command'
assert 'mcp-server-fetch' in content, 'default-tools.md should use mcp-server-fetch'
# Verify it has the fetch tool configured
assert 'name: "fetch"' in content, 'default-tools.md should have a fetch tool'
assert 'command: "uvx"' in content, 'default-tools.md should use uvx command'
assert 'args: ["mcp-server-fetch"]' in content, (
'default-tools.md should use mcp-server-fetch'
)
agent = BaseMicroagent.load(default_tools_path)
assert isinstance(agent, RepoMicroagent)
@pytest.mark.asyncio

View File

@@ -7,7 +7,7 @@ from openhands.microagent.types import MicroagentType
def test_load_markdown_without_frontmatter():
"""Test loading a markdown file without frontmatter."""
content = '# Test Content\nThis is a test markdown file without frontmatter.'
path = Path('test.md')
path = Path('default.md')
# Load the agent from content using keyword argument
agent = BaseMicroagent.load(path=path, file_content=content)
@@ -26,7 +26,7 @@ def test_load_markdown_with_empty_frontmatter():
content = (
'---\n---\n# Test Content\nThis is a test markdown file with empty frontmatter.'
)
path = Path('test.md')
path = Path('default.md')
# Load the agent from content using keyword argument
agent = BaseMicroagent.load(path=path, file_content=content)
@@ -50,12 +50,12 @@ name: custom_name
---
# Test Content
This is a test markdown file with partial frontmatter."""
path = Path('test.md')
path = Path('custom_name.md')
# Load the agent from content using keyword argument
agent = BaseMicroagent.load(path=path, file_content=content)
# Verify it uses provided name but default values for other fields
# Verify it uses filename instead of provided name (filename takes precedence)
assert isinstance(agent, RepoMicroagent)
assert agent.name == 'custom_name'
assert (
@@ -77,12 +77,12 @@ version: 2.0.0
---
# Test Content
This is a test markdown file with full frontmatter."""
path = Path('test.md')
path = Path('test_agent.md')
# Load the agent from content using keyword argument
agent = BaseMicroagent.load(path=path, file_content=content)
# Verify all provided values are used
# Verify filename is used for name but other metadata values are preserved
assert isinstance(agent, RepoMicroagent)
assert agent.name == 'test_agent'
assert (