microbots 0.0.7__tar.gz → 0.0.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {microbots-0.0.7 → microbots-0.0.8}/.vscode/launch.json +1 -1
- {microbots-0.0.7 → microbots-0.0.8}/PKG-INFO +10 -10
- {microbots-0.0.7 → microbots-0.0.8}/README.md +9 -9
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/MicroBot.py +65 -12
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/bot/LogAnalysisBot.py +4 -1
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/bot/ReadingBot.py +3 -1
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/bot/WritingBot.py +2 -1
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/environment/local_docker/LocalDockerEnvironment.py +32 -39
- microbots-0.0.8/src/microbots/extras/mount.py +73 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/utils/path.py +9 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots.egg-info/PKG-INFO +10 -10
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots.egg-info/SOURCES.txt +1 -0
- {microbots-0.0.7 → microbots-0.0.8}/test/bot/test_browsing_bot.py +15 -14
- {microbots-0.0.7 → microbots-0.0.8}/test/bot/test_log_analysis_bot.py +1 -1
- {microbots-0.0.7 → microbots-0.0.8}/test/bot/test_microbot.py +31 -11
- {microbots-0.0.7 → microbots-0.0.8}/test/environment/local_docker/test_local_docker_environment.py +15 -24
- microbots-0.0.8/test/extras/test_mount.py +37 -0
- {microbots-0.0.7 → microbots-0.0.8}/test/fixtures/fixture_test_repo.py +11 -4
- microbots-0.0.7/src/microbots/extras/mount.py +0 -27
- {microbots-0.0.7 → microbots-0.0.8}/.codecov.yml +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/.github/workflows/dockerBuildPush.yml +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/.github/workflows/publish.yml +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/.github/workflows/test.yml +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/.gitignore +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/.vscode/settings.json +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/LICENSE +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/architechture.md +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/docs/images/overall_architecture.png +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/pyproject.toml +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/pytest.ini +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/requirements.txt +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/setup.cfg +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/__init__.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/bot/BrowsingBot.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/bot/__init__.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/constants.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/environment/Environment.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/environment/local_docker/__init__.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/environment/local_docker/image_builder/Dockerfile +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/environment/local_docker/image_builder/ShellCommunicator.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/environment/local_docker/image_builder/dockerShell.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/environment/swe-rex/LocalDocker.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/extras/__init__.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/llm/__init__.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/llm/openai_api.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/tools/tool.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/tools/tool_definitions/browser-use/browser.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/tools/tool_definitions/browser-use.yaml +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/tools/tool_definitions/cscope.yaml +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/tools/tool_test.ipynb +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/utils/logger.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots/utils/network.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots.egg-info/dependency_links.txt +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots.egg-info/requires.txt +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/src/microbots.egg-info/top_level.txt +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/test/bot/calculator/calculator.log +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/test/bot/calculator/code/calculator.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/test/bot/test_reading_bot.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/test/bot/test_writing_bot.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/test/conftest.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/test/environment/swe-rex/test_local_docker.py.disabled +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/test/fixtures/fixture_issue_1.py +0 -0
- {microbots-0.0.7 → microbots-0.0.8}/test/fixtures/fixture_issue_22.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microbots
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: container-based autonomous agent framework
|
|
5
5
|
Author-email: xxx <xxx@example.com>
|
|
6
6
|
License: MIT License
|
|
@@ -48,8 +48,8 @@ Dynamic: license-file
|
|
|
48
48
|
|
|
49
49
|
# 🤖 Microbots
|
|
50
50
|
|
|
51
|
-
MicroBots is a lightweight, extensible AI agent for code comprehension and controlled file edits. It integrates cleanly
|
|
52
|
-
into automation pipelines, mounting a target directory with explicit read-only or read/write modes so LLMs can safely
|
|
51
|
+
MicroBots is a lightweight, extensible AI agent for code comprehension and controlled file edits. It integrates cleanly
|
|
52
|
+
into automation pipelines, mounting a target directory with explicit read-only or read/write modes so LLMs can safely
|
|
53
53
|
inspect, refactor, or generate files with least‑privilege access.
|
|
54
54
|
|
|
55
55
|
|
|
@@ -61,14 +61,14 @@ myWritingBot = WritingBot(
|
|
|
61
61
|
folder_to_mount=str("myReactApp"),
|
|
62
62
|
)
|
|
63
63
|
|
|
64
|
-
data = myWritingBot.run("""when doing npm run build, I get an error.
|
|
64
|
+
data = myWritingBot.run("""when doing npm run build, I get an error.
|
|
65
65
|
Fix the error and make sure the build is successful.""", timeout_in_seconds=600)
|
|
66
66
|
print(data.results)
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
## ⚠️ Project Status:
|
|
70
|
-
|
|
71
|
-
Please **use with caution** in production environments.
|
|
69
|
+
## ⚠️ Project Status: Under Fast Development
|
|
70
|
+
|
|
71
|
+
This project is currently **under active development**. Features, APIs, and internal structures are subject to change across versions without notice, and unexpected behavior may occur. Please **use with caution** in production environments.
|
|
72
72
|
|
|
73
73
|
## 🚀 How to install
|
|
74
74
|
|
|
@@ -85,7 +85,7 @@ pip install microbots
|
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
## ✨LLM Support
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
Azure OpenAI Models - Add the below environment variables in a `.env` file in the root of your application
|
|
90
90
|
|
|
91
91
|
```env
|
|
@@ -95,7 +95,7 @@ OPEN_AI_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
|
95
95
|
|
|
96
96
|
## 🤖 Bots & Usage Examples
|
|
97
97
|
|
|
98
|
-
Pre-requisite for the below example code of Bots:
|
|
98
|
+
Pre-requisite for the below example code of Bots:
|
|
99
99
|
From the root of your application, Create a folder called `code` inside which clone the repo `https://github.com/swe-agent/test-repo/`. Now run the code
|
|
100
100
|
|
|
101
101
|
|
|
@@ -120,7 +120,7 @@ The `ReadingBot` will read the files inside `code` folder and will extract infor
|
|
|
120
120
|
|
|
121
121
|
### ✍️ WritingBot
|
|
122
122
|
|
|
123
|
-
Pre-requisite for the example code:
|
|
123
|
+
Pre-requisite for the example code:
|
|
124
124
|
From the root the application, Create a folder called `code` inside which clone the repo `https://github.com/swe-agent/test-repo/`. Now run the code
|
|
125
125
|
|
|
126
126
|
```py
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# 🤖 Microbots
|
|
2
2
|
|
|
3
|
-
MicroBots is a lightweight, extensible AI agent for code comprehension and controlled file edits. It integrates cleanly
|
|
4
|
-
into automation pipelines, mounting a target directory with explicit read-only or read/write modes so LLMs can safely
|
|
3
|
+
MicroBots is a lightweight, extensible AI agent for code comprehension and controlled file edits. It integrates cleanly
|
|
4
|
+
into automation pipelines, mounting a target directory with explicit read-only or read/write modes so LLMs can safely
|
|
5
5
|
inspect, refactor, or generate files with least‑privilege access.
|
|
6
6
|
|
|
7
7
|
|
|
@@ -13,14 +13,14 @@ myWritingBot = WritingBot(
|
|
|
13
13
|
folder_to_mount=str("myReactApp"),
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
-
data = myWritingBot.run("""when doing npm run build, I get an error.
|
|
16
|
+
data = myWritingBot.run("""when doing npm run build, I get an error.
|
|
17
17
|
Fix the error and make sure the build is successful.""", timeout_in_seconds=600)
|
|
18
18
|
print(data.results)
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
## ⚠️ Project Status:
|
|
22
|
-
|
|
23
|
-
Please **use with caution** in production environments.
|
|
21
|
+
## ⚠️ Project Status: Under Fast Development
|
|
22
|
+
|
|
23
|
+
This project is currently **under active development**. Features, APIs, and internal structures are subject to change across versions without notice, and unexpected behavior may occur. Please **use with caution** in production environments.
|
|
24
24
|
|
|
25
25
|
## 🚀 How to install
|
|
26
26
|
|
|
@@ -37,7 +37,7 @@ pip install microbots
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
## ✨LLM Support
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
Azure OpenAI Models - Add the below environment variables in a `.env` file in the root of your application
|
|
42
42
|
|
|
43
43
|
```env
|
|
@@ -47,7 +47,7 @@ OPEN_AI_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
|
47
47
|
|
|
48
48
|
## 🤖 Bots & Usage Examples
|
|
49
49
|
|
|
50
|
-
Pre-requisite for the below example code of Bots:
|
|
50
|
+
Pre-requisite for the below example code of Bots:
|
|
51
51
|
From the root of your application, Create a folder called `code` inside which clone the repo `https://github.com/swe-agent/test-repo/`. Now run the code
|
|
52
52
|
|
|
53
53
|
|
|
@@ -72,7 +72,7 @@ The `ReadingBot` will read the files inside `code` folder and will extract infor
|
|
|
72
72
|
|
|
73
73
|
### ✍️ WritingBot
|
|
74
74
|
|
|
75
|
-
Pre-requisite for the example code:
|
|
75
|
+
Pre-requisite for the example code:
|
|
76
76
|
From the root the application, Create a folder called `code` inside which clone the repo `https://github.com/swe-agent/test-repo/`. Now run the code
|
|
77
77
|
|
|
78
78
|
```py
|
|
@@ -51,6 +51,33 @@ class BotRunResult:
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
class MicroBot:
|
|
54
|
+
"""
|
|
55
|
+
The core Microbot class.
|
|
56
|
+
|
|
57
|
+
MicroBot class is the core class representing the autonomous agent. Other bots are extensions of this class.
|
|
58
|
+
If you want to create a custom bot, you can directly use this class or extend it into your own bot class.
|
|
59
|
+
|
|
60
|
+
Attributes
|
|
61
|
+
----------
|
|
62
|
+
model : str
|
|
63
|
+
The model to use for the bot, in the format <provider>/<model_name>.
|
|
64
|
+
bot_type : BotType
|
|
65
|
+
The type of bot being created. It's unused. Will be removed soon.
|
|
66
|
+
system_prompt : Optional[str]
|
|
67
|
+
The system prompt to guide the bot's behavior.
|
|
68
|
+
environment : Optional[any]
|
|
69
|
+
The execution environment for the bot. If not provided, a default
|
|
70
|
+
LocalDockerEnvironment will be created.
|
|
71
|
+
additional_tools : Optional[list[Tool]]
|
|
72
|
+
A list of additional tools to install in the bot's environment.
|
|
73
|
+
folder_to_mount : Optional[Mount]
|
|
74
|
+
A folder to mount into the bot's environment. The bot will be given
|
|
75
|
+
access to this folder based on the specified permissions. This will
|
|
76
|
+
be the main code folder where the bot will work. Additional folders
|
|
77
|
+
can be mounted during the run() method. Refer to `Mount` class
|
|
78
|
+
regarding the directory structure and permission details. Defaults
|
|
79
|
+
to None.
|
|
80
|
+
"""
|
|
54
81
|
|
|
55
82
|
def __init__(
|
|
56
83
|
self,
|
|
@@ -61,6 +88,33 @@ class MicroBot:
|
|
|
61
88
|
additional_tools: Optional[list[Tool]] = [],
|
|
62
89
|
folder_to_mount: Optional[Mount] = None,
|
|
63
90
|
):
|
|
91
|
+
"""
|
|
92
|
+
Init function for MicroBot class.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
model :str
|
|
97
|
+
The model to use for the bot, in the format <provider>/<model_name>.
|
|
98
|
+
bot_type :BotType
|
|
99
|
+
The type of bot being created. It's unused. Will be removed soon.
|
|
100
|
+
system_prompt :Optional[str]
|
|
101
|
+
The system prompt to guide the bot's behavior. Defaults to None.
|
|
102
|
+
environment :Optional[any]
|
|
103
|
+
The execution environment for the bot. If not provided, a default
|
|
104
|
+
LocalDockerEnvironment will be created.
|
|
105
|
+
additional_tools :Optional[list[Tool]]
|
|
106
|
+
A list of additional tools to install in the bot's environment.
|
|
107
|
+
Defaults to [].
|
|
108
|
+
folder_to_mount :Optional[Mount]
|
|
109
|
+
A folder to mount into the bot's environment. The bot will be given
|
|
110
|
+
access to this folder based on the specified permissions. This will
|
|
111
|
+
be the main code folder where the bot will work. Additional folders
|
|
112
|
+
can be mounted using the run() method. Refer to `Mount` class
|
|
113
|
+
regarding the directory structure and permission details. Defaults
|
|
114
|
+
to None.
|
|
115
|
+
|
|
116
|
+
Note: Supports only mount type MountType.MOUNT for now.
|
|
117
|
+
"""
|
|
64
118
|
|
|
65
119
|
self.folder_to_mount = folder_to_mount
|
|
66
120
|
|
|
@@ -74,6 +128,7 @@ class MicroBot:
|
|
|
74
128
|
|
|
75
129
|
self.mounted = []
|
|
76
130
|
if folder_to_mount is not None:
|
|
131
|
+
self._validate_folder_to_mount(folder_to_mount)
|
|
77
132
|
self.mounted.append(folder_to_mount)
|
|
78
133
|
|
|
79
134
|
self.system_prompt = system_prompt
|
|
@@ -195,10 +250,7 @@ class MicroBot:
|
|
|
195
250
|
|
|
196
251
|
self.environment = LocalDockerEnvironment(
|
|
197
252
|
port=free_port,
|
|
198
|
-
folder_to_mount=
|
|
199
|
-
folder_to_mount.host_path_info.abs_path if folder_to_mount else None
|
|
200
|
-
),
|
|
201
|
-
permission=folder_to_mount.permission if folder_to_mount else None,
|
|
253
|
+
folder_to_mount=folder_to_mount,
|
|
202
254
|
)
|
|
203
255
|
|
|
204
256
|
def _create_llm(self):
|
|
@@ -215,11 +267,12 @@ class MicroBot:
|
|
|
215
267
|
if provider not in [e.value for e in ModelProvider]:
|
|
216
268
|
raise ValueError(f"Unsupported model provider: {provider}")
|
|
217
269
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
270
|
+
def _validate_folder_to_mount(self, folder_to_mount: Mount):
|
|
271
|
+
if folder_to_mount.mount_type != MountType.MOUNT:
|
|
272
|
+
logger.error(
|
|
273
|
+
"%s Only MOUNT mount type is supported for folder_to_mount",
|
|
274
|
+
LogLevelEmoji.ERROR,
|
|
275
|
+
)
|
|
276
|
+
raise ValueError(
|
|
277
|
+
"Only MOUNT mount type is supported for folder_to_mount"
|
|
278
|
+
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import os
|
|
2
3
|
from typing import Optional
|
|
3
4
|
|
|
4
5
|
from microbots.constants import DOCKER_WORKING_DIR, LOG_FILE_DIR, PermissionLabels
|
|
@@ -22,7 +23,9 @@ class LogAnalysisBot(MicroBot):
|
|
|
22
23
|
bot_type = BotType.LOG_ANALYSIS_BOT
|
|
23
24
|
|
|
24
25
|
folder_mount_info = Mount(
|
|
25
|
-
folder_to_mount,
|
|
26
|
+
folder_to_mount,
|
|
27
|
+
f"/{DOCKER_WORKING_DIR}/{os.path.basename(folder_to_mount)}",
|
|
28
|
+
PermissionLabels.READ_ONLY
|
|
26
29
|
)
|
|
27
30
|
|
|
28
31
|
system_prompt = f"""
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from typing import Optional
|
|
2
3
|
|
|
3
4
|
from microbots.constants import DOCKER_WORKING_DIR, PermissionLabels
|
|
@@ -18,8 +19,9 @@ class ReadingBot(MicroBot):
|
|
|
18
19
|
# validate init values before assigning
|
|
19
20
|
bot_type = BotType.READING_BOT
|
|
20
21
|
|
|
22
|
+
base_name = os.path.basename(folder_to_mount)
|
|
21
23
|
folder_mount_info = Mount(
|
|
22
|
-
folder_to_mount, f"/{DOCKER_WORKING_DIR}", PermissionLabels.READ_ONLY
|
|
24
|
+
folder_to_mount, f"/{DOCKER_WORKING_DIR}/{base_name}", PermissionLabels.READ_ONLY
|
|
23
25
|
)
|
|
24
26
|
|
|
25
27
|
system_prompt = f"""
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from typing import Optional
|
|
2
3
|
|
|
3
4
|
from microbots.constants import DOCKER_WORKING_DIR, PermissionLabels
|
|
@@ -19,7 +20,7 @@ class WritingBot(MicroBot):
|
|
|
19
20
|
bot_type = BotType.WRITING_BOT
|
|
20
21
|
|
|
21
22
|
folder_mount_info = Mount(
|
|
22
|
-
folder_to_mount, DOCKER_WORKING_DIR, PermissionLabels.READ_WRITE
|
|
23
|
+
folder_to_mount, f"/{DOCKER_WORKING_DIR}/{os.path.basename(folder_to_mount)}", PermissionLabels.READ_WRITE
|
|
23
24
|
)
|
|
24
25
|
|
|
25
26
|
system_prompt = f"""
|
{microbots-0.0.7 → microbots-0.0.8}/src/microbots/environment/local_docker/LocalDockerEnvironment.py
RENAMED
|
@@ -10,7 +10,8 @@ import docker
|
|
|
10
10
|
import requests
|
|
11
11
|
|
|
12
12
|
from microbots.environment.Environment import CmdReturn, Environment
|
|
13
|
-
from microbots.constants import DOCKER_WORKING_DIR, WORKING_DIR
|
|
13
|
+
from microbots.constants import DOCKER_WORKING_DIR, WORKING_DIR, PermissionLabels
|
|
14
|
+
from microbots.extras.mount import Mount
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
16
17
|
|
|
@@ -19,23 +20,13 @@ class LocalDockerEnvironment(Environment):
|
|
|
19
20
|
def __init__(
|
|
20
21
|
self,
|
|
21
22
|
port: int,
|
|
22
|
-
folder_to_mount: Optional[
|
|
23
|
-
permission: Optional[str] = None,
|
|
23
|
+
folder_to_mount: Optional[Mount] = None,
|
|
24
24
|
image: str = "kavyasree261002/shell_server:latest",
|
|
25
25
|
):
|
|
26
|
-
if folder_to_mount is None and permission is not None:
|
|
27
|
-
raise ValueError("permission provided but folder_to_mount is None")
|
|
28
|
-
elif permission is None and folder_to_mount is not None:
|
|
29
|
-
raise ValueError("folder_to_mount provided but permission is None")
|
|
30
|
-
if permission is not None and permission not in ["READ_ONLY", "READ_WRITE"]:
|
|
31
|
-
raise ValueError(
|
|
32
|
-
"permission must be 'READ_ONLY' or 'READ_WRITE' when provided"
|
|
33
|
-
)
|
|
34
26
|
|
|
35
27
|
self.image = image
|
|
36
28
|
self.folder_to_mount = folder_to_mount
|
|
37
29
|
self.overlay_mount = False
|
|
38
|
-
self.permission = permission
|
|
39
30
|
self.container = None
|
|
40
31
|
self.client = docker.from_env()
|
|
41
32
|
self.port = port # required host port
|
|
@@ -58,26 +49,26 @@ class LocalDockerEnvironment(Environment):
|
|
|
58
49
|
def start(self):
|
|
59
50
|
mode_map = {"READ_ONLY": "ro", "READ_WRITE": "rw"}
|
|
60
51
|
volumes_config = {WORKING_DIR: {"bind": DOCKER_WORKING_DIR, "mode": "rw"}}
|
|
61
|
-
if self.folder_to_mount
|
|
62
|
-
if self.permission ==
|
|
63
|
-
volumes_config[self.folder_to_mount] = {
|
|
64
|
-
"bind": f"/ro/{os.path.basename(self.folder_to_mount)}",
|
|
65
|
-
"mode": mode_map[self.permission],
|
|
52
|
+
if self.folder_to_mount:
|
|
53
|
+
if self.folder_to_mount.permission == PermissionLabels.READ_ONLY:
|
|
54
|
+
volumes_config[self.folder_to_mount.host_path_info.abs_path] = {
|
|
55
|
+
"bind": f"/ro/{os.path.basename(self.folder_to_mount.sandbox_path)}",
|
|
56
|
+
"mode": mode_map[self.folder_to_mount.permission],
|
|
66
57
|
}
|
|
67
58
|
logger.info(
|
|
68
59
|
"📦 Volume mapping: %s → /ro/%s",
|
|
69
|
-
self.folder_to_mount,
|
|
70
|
-
os.path.basename(self.folder_to_mount),
|
|
60
|
+
self.folder_to_mount.host_path_info.abs_path,
|
|
61
|
+
os.path.basename(self.folder_to_mount.sandbox_path),
|
|
71
62
|
)
|
|
72
63
|
else:
|
|
73
|
-
volumes_config[self.folder_to_mount] = {
|
|
74
|
-
"bind":
|
|
75
|
-
"mode": mode_map[self.permission],
|
|
64
|
+
volumes_config[self.folder_to_mount.host_path_info.abs_path] = {
|
|
65
|
+
"bind": self.folder_to_mount.sandbox_path,
|
|
66
|
+
"mode": mode_map[self.folder_to_mount.permission],
|
|
76
67
|
}
|
|
77
68
|
logger.debug(
|
|
78
|
-
"📦 Volume mapping: %s →
|
|
79
|
-
self.folder_to_mount,
|
|
80
|
-
|
|
69
|
+
"📦 Volume mapping: %s → %s",
|
|
70
|
+
self.folder_to_mount.host_path_info.abs_path,
|
|
71
|
+
self.folder_to_mount.sandbox_path,
|
|
81
72
|
)
|
|
82
73
|
|
|
83
74
|
# Port mapping
|
|
@@ -100,20 +91,22 @@ class LocalDockerEnvironment(Environment):
|
|
|
100
91
|
)
|
|
101
92
|
time.sleep(2) # Give some time for the server to start
|
|
102
93
|
|
|
103
|
-
if self.
|
|
104
|
-
self._setup_overlay_mount(
|
|
94
|
+
if self.folder_to_mount and self.folder_to_mount.permission == PermissionLabels.READ_ONLY:
|
|
95
|
+
self._setup_overlay_mount()
|
|
105
96
|
|
|
106
97
|
if self.folder_to_mount:
|
|
107
|
-
self.execute(f"cd
|
|
98
|
+
self.execute(f"cd {self.folder_to_mount.sandbox_path}")
|
|
108
99
|
else:
|
|
109
100
|
self.execute("cd /")
|
|
110
101
|
|
|
111
|
-
def _setup_overlay_mount(self
|
|
112
|
-
|
|
102
|
+
def _setup_overlay_mount(self):
|
|
103
|
+
# NOTE: Don't use this for any other read-only mounts except the main code folder.
|
|
104
|
+
|
|
105
|
+
path_name = os.path.basename(self.folder_to_mount.sandbox_path)
|
|
113
106
|
# Mount /ro/path_name to /{WORKING_DIR}/path_name using overlayfs
|
|
114
107
|
mount_command = (
|
|
115
|
-
f"mkdir -p
|
|
116
|
-
f"mount -t overlay overlay -o lowerdir=/ro/{path_name}/,upperdir={DOCKER_WORKING_DIR}/overlay/{path_name}/upper/,workdir={DOCKER_WORKING_DIR}/overlay/{path_name}/work/ {
|
|
108
|
+
f"mkdir -p {self.folder_to_mount.sandbox_path} /{DOCKER_WORKING_DIR}/overlay/{path_name}/upper /{DOCKER_WORKING_DIR}/overlay/{path_name}/work && sleep 5 && "
|
|
109
|
+
f"mount -t overlay overlay -o lowerdir=/ro/{path_name}/,upperdir={DOCKER_WORKING_DIR}/overlay/{path_name}/upper/,workdir={DOCKER_WORKING_DIR}/overlay/{path_name}/work/ {self.folder_to_mount.sandbox_path}"
|
|
117
110
|
)
|
|
118
111
|
self.execute(mount_command)
|
|
119
112
|
logger.info(
|
|
@@ -121,12 +114,12 @@ class LocalDockerEnvironment(Environment):
|
|
|
121
114
|
)
|
|
122
115
|
self.overlay_mount = True
|
|
123
116
|
|
|
124
|
-
def _teardown_overlay_mount(self
|
|
125
|
-
path_name = os.path.basename(os.path.abspath(folder_to_mount))
|
|
117
|
+
def _teardown_overlay_mount(self):
|
|
118
|
+
path_name = os.path.basename(os.path.abspath(self.folder_to_mount.sandbox_path))
|
|
126
119
|
|
|
127
120
|
try:
|
|
128
121
|
logger.info("🛠️ Tearing down overlay mount for %s", path_name)
|
|
129
|
-
unmount_command = f"umount -l {
|
|
122
|
+
unmount_command = f"umount -l {self.folder_to_mount.sandbox_path}"
|
|
130
123
|
ret: CmdReturn = self.execute(unmount_command)
|
|
131
124
|
if ret.return_code != 0:
|
|
132
125
|
logger.error("❌ Failed to unmount overlay: %s", ret.stderr)
|
|
@@ -134,10 +127,10 @@ class LocalDockerEnvironment(Environment):
|
|
|
134
127
|
logger.info("✅ Unmounted overlay for %s", path_name)
|
|
135
128
|
|
|
136
129
|
logger.info(
|
|
137
|
-
f"🛑 Removing overlay dirs at {
|
|
130
|
+
f"🛑 Removing overlay dirs at {self.folder_to_mount.sandbox_path} and {DOCKER_WORKING_DIR}/overlay/"
|
|
138
131
|
)
|
|
139
132
|
remove_dir_command = (
|
|
140
|
-
f"rm -rf {
|
|
133
|
+
f"rm -rf {self.folder_to_mount.sandbox_path} && "
|
|
141
134
|
f"rm -rf {DOCKER_WORKING_DIR}/overlay/"
|
|
142
135
|
)
|
|
143
136
|
ret: CmdReturn = self.execute(remove_dir_command)
|
|
@@ -156,7 +149,7 @@ class LocalDockerEnvironment(Environment):
|
|
|
156
149
|
"""Stop and remove the container"""
|
|
157
150
|
if self.container:
|
|
158
151
|
if self.overlay_mount:
|
|
159
|
-
self._teardown_overlay_mount(
|
|
152
|
+
self._teardown_overlay_mount()
|
|
160
153
|
|
|
161
154
|
self.container.stop()
|
|
162
155
|
self.container.remove()
|
|
@@ -236,7 +229,7 @@ class LocalDockerEnvironment(Environment):
|
|
|
236
229
|
e,
|
|
237
230
|
)
|
|
238
231
|
return CmdReturn(stdout="", stderr="Unexpected error", return_code=1)
|
|
239
|
-
|
|
232
|
+
|
|
240
233
|
def copy_to_container(self, src_path: str, dest_path: str) -> bool:
|
|
241
234
|
"""
|
|
242
235
|
Copy a file or folder from the host machine to the Docker container.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from microbots.constants import PermissionLabels, PermissionMapping
|
|
6
|
+
from microbots.utils.path import PathInfo, get_path_info, ends_with_separator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MountType(StrEnum):
|
|
10
|
+
"""
|
|
11
|
+
Enum representing the type of mount operation.
|
|
12
|
+
|
|
13
|
+
MOUNT : Mount the folder from host to sandbox environment.
|
|
14
|
+
COPY : Copy the folder from host to sandbox environment.
|
|
15
|
+
"""
|
|
16
|
+
MOUNT = "mount"
|
|
17
|
+
COPY = "copy"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Mount:
|
|
22
|
+
"""
|
|
23
|
+
Folder mount configuration for a microbot environment.
|
|
24
|
+
|
|
25
|
+
All the folders and files to be presented for the Bot should be
|
|
26
|
+
either mounted or copied to the Bot's sandbox environment using
|
|
27
|
+
this class.
|
|
28
|
+
|
|
29
|
+
Attributes
|
|
30
|
+
----------
|
|
31
|
+
host_path : str
|
|
32
|
+
The absolute path on the host machine to be mounted or copied.
|
|
33
|
+
sandbox_path : str
|
|
34
|
+
The absolute path inside the Bot's sandbox environment where the
|
|
35
|
+
host_path will be mounted or copied. If the host_path is a file
|
|
36
|
+
and the sandbox_path ends with a path separator ("/"), then
|
|
37
|
+
the file will be placed inside the sandbox_path directory with
|
|
38
|
+
the same base name as the host file.
|
|
39
|
+
permission : PermissionLabels
|
|
40
|
+
The permission level for the mounted/copied folder. It should
|
|
41
|
+
be one of the values from PermissionLabels enum.
|
|
42
|
+
mount_type : MountType, optional
|
|
43
|
+
The type of mount operation. Mounting and copying have are the
|
|
44
|
+
options. Possible values are MountType enum values.
|
|
45
|
+
Default is MountType.MOUNT.
|
|
46
|
+
"""
|
|
47
|
+
host_path: str
|
|
48
|
+
sandbox_path: str
|
|
49
|
+
permission: PermissionLabels
|
|
50
|
+
mount_type: MountType = MountType.MOUNT
|
|
51
|
+
|
|
52
|
+
# These will be set in __post_init__
|
|
53
|
+
permission_key: str = field(init=False)
|
|
54
|
+
host_path_info: PathInfo = field(init=False)
|
|
55
|
+
|
|
56
|
+
def __post_init__(self):
|
|
57
|
+
self.permission_key = PermissionMapping.MAPPING.get(self.permission)
|
|
58
|
+
self.host_path_info = get_path_info(self.host_path)
|
|
59
|
+
|
|
60
|
+
sandbox_path = Path(self.sandbox_path)
|
|
61
|
+
|
|
62
|
+
# Validate that sandbox_path is absolute
|
|
63
|
+
if not sandbox_path.is_absolute():
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"sandbox_path must be an absolute path. Given: {self.sandbox_path}"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if not ends_with_separator(self.host_path) and ends_with_separator(self.sandbox_path):
|
|
69
|
+
# If host_path is a file and sandbox_path ends with separator,
|
|
70
|
+
# place the file inside the sandbox_path directory with same base name
|
|
71
|
+
sandbox_path = str(sandbox_path / self.host_path_info.base_name)
|
|
72
|
+
|
|
73
|
+
self.sandbox_path = str(sandbox_path)
|
|
@@ -37,3 +37,12 @@ def get_path_info(file_or_folder: str) -> PathInfo:
|
|
|
37
37
|
abs_path=os.path.abspath(file_or_folder),
|
|
38
38
|
)
|
|
39
39
|
return PathInfo(path_valid=False, base_name="", abs_path="")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def ends_with_separator(path_str: str) -> bool:
|
|
43
|
+
# Accept both separators on Windows; only '/' on POSIX
|
|
44
|
+
seps = {os.sep}
|
|
45
|
+
if os.altsep:
|
|
46
|
+
seps.add(os.altsep)
|
|
47
|
+
return len(path_str) > 0 and path_str[-1] in seps
|
|
48
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microbots
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: container-based autonomous agent framework
|
|
5
5
|
Author-email: xxx <xxx@example.com>
|
|
6
6
|
License: MIT License
|
|
@@ -48,8 +48,8 @@ Dynamic: license-file
|
|
|
48
48
|
|
|
49
49
|
# 🤖 Microbots
|
|
50
50
|
|
|
51
|
-
MicroBots is a lightweight, extensible AI agent for code comprehension and controlled file edits. It integrates cleanly
|
|
52
|
-
into automation pipelines, mounting a target directory with explicit read-only or read/write modes so LLMs can safely
|
|
51
|
+
MicroBots is a lightweight, extensible AI agent for code comprehension and controlled file edits. It integrates cleanly
|
|
52
|
+
into automation pipelines, mounting a target directory with explicit read-only or read/write modes so LLMs can safely
|
|
53
53
|
inspect, refactor, or generate files with least‑privilege access.
|
|
54
54
|
|
|
55
55
|
|
|
@@ -61,14 +61,14 @@ myWritingBot = WritingBot(
|
|
|
61
61
|
folder_to_mount=str("myReactApp"),
|
|
62
62
|
)
|
|
63
63
|
|
|
64
|
-
data = myWritingBot.run("""when doing npm run build, I get an error.
|
|
64
|
+
data = myWritingBot.run("""when doing npm run build, I get an error.
|
|
65
65
|
Fix the error and make sure the build is successful.""", timeout_in_seconds=600)
|
|
66
66
|
print(data.results)
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
## ⚠️ Project Status:
|
|
70
|
-
|
|
71
|
-
Please **use with caution** in production environments.
|
|
69
|
+
## ⚠️ Project Status: Under Fast Development
|
|
70
|
+
|
|
71
|
+
This project is currently **under active development**. Features, APIs, and internal structures are subject to change across versions without notice, and unexpected behavior may occur. Please **use with caution** in production environments.
|
|
72
72
|
|
|
73
73
|
## 🚀 How to install
|
|
74
74
|
|
|
@@ -85,7 +85,7 @@ pip install microbots
|
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
## ✨LLM Support
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
Azure OpenAI Models - Add the below environment variables in a `.env` file in the root of your application
|
|
90
90
|
|
|
91
91
|
```env
|
|
@@ -95,7 +95,7 @@ OPEN_AI_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
|
95
95
|
|
|
96
96
|
## 🤖 Bots & Usage Examples
|
|
97
97
|
|
|
98
|
-
Pre-requisite for the below example code of Bots:
|
|
98
|
+
Pre-requisite for the below example code of Bots:
|
|
99
99
|
From the root of your application, Create a folder called `code` inside which clone the repo `https://github.com/swe-agent/test-repo/`. Now run the code
|
|
100
100
|
|
|
101
101
|
|
|
@@ -120,7 +120,7 @@ The `ReadingBot` will read the files inside `code` folder and will extract infor
|
|
|
120
120
|
|
|
121
121
|
### ✍️ WritingBot
|
|
122
122
|
|
|
123
|
-
Pre-requisite for the example code:
|
|
123
|
+
Pre-requisite for the example code:
|
|
124
124
|
From the root the application, Create a folder called `code` inside which clone the repo `https://github.com/swe-agent/test-repo/`. Now run the code
|
|
125
125
|
|
|
126
126
|
```py
|
|
@@ -54,6 +54,7 @@ test/bot/calculator/calculator.log
|
|
|
54
54
|
test/bot/calculator/code/calculator.py
|
|
55
55
|
test/environment/local_docker/test_local_docker_environment.py
|
|
56
56
|
test/environment/swe-rex/test_local_docker.py.disabled
|
|
57
|
+
test/extras/test_mount.py
|
|
57
58
|
test/fixtures/fixture_issue_1.py
|
|
58
59
|
test/fixtures/fixture_issue_22.py
|
|
59
60
|
test/fixtures/fixture_test_repo.py
|
|
@@ -19,7 +19,7 @@ from microbots import BrowsingBot, BotRunResult
|
|
|
19
19
|
@pytest.mark.slow
|
|
20
20
|
class TestBrowsingBot:
|
|
21
21
|
"""Integration tests for BrowsingBot functionality."""
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
@pytest.fixture(scope="function")
|
|
24
24
|
def browsing_bot(self):
|
|
25
25
|
"""Create a BrowsingBot instance for testing."""
|
|
@@ -31,42 +31,43 @@ class TestBrowsingBot:
|
|
|
31
31
|
bot.environment.stop()
|
|
32
32
|
except Exception as e:
|
|
33
33
|
logger.warning(f"Error stopping environment: {e}")
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
# Google search may fail due to captcha, so this test may be flaky in CI environments.
|
|
35
36
|
def test_simple_question_response(self, browsing_bot):
|
|
36
37
|
"""Test that the bot can answer a simple factual question."""
|
|
37
38
|
response: BotRunResult = browsing_bot.run(
|
|
38
|
-
"What is the capital of France?",
|
|
39
|
+
"What is the capital of France? Get this info from https://en.wikipedia.org/wiki/France",
|
|
39
40
|
timeout_in_seconds=300,
|
|
40
41
|
)
|
|
41
|
-
|
|
42
|
+
|
|
42
43
|
# Assert the response was successful
|
|
43
44
|
assert response.status == True, f"Bot failed with error: {response.error}"
|
|
44
45
|
assert response.result is not None, "Bot returned no result"
|
|
45
46
|
assert isinstance(response.result, str), "Result should be a string"
|
|
46
|
-
|
|
47
|
+
|
|
47
48
|
# Check that the result contains the expected answer
|
|
48
49
|
result_lower = response.result.lower()
|
|
49
50
|
assert "paris" in result_lower, f"Expected 'Paris' in result, got: {response.result}"
|
|
50
|
-
|
|
51
|
+
|
|
51
52
|
logger.info(f"Test passed. Bot response: {response.result}")
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Google search may fail due to captcha, so this test may be flaky in CI environments.
|
|
54
56
|
@pytest.mark.parametrize("query,expected_keywords", [
|
|
55
|
-
("
|
|
57
|
+
("Get capital of Germany from https://en.wikipedia.org/wiki/Germany", ["berlin"]),
|
|
56
58
|
("What is 2+2?", ["4", "four"]),
|
|
57
|
-
("
|
|
58
|
-
("Who is the current President of the United States?", ["Trump"]),
|
|
59
|
+
("Get the description of this CVE-2024-11738 from nvd.nist.gov website", ["Rustls"]),
|
|
59
60
|
])
|
|
60
61
|
def test_multiple_queries(self, browsing_bot, query, expected_keywords):
|
|
61
62
|
"""Test the bot with multiple different queries."""
|
|
62
63
|
response: BotRunResult = browsing_bot.run(query, timeout_in_seconds=300)
|
|
63
|
-
|
|
64
|
+
|
|
64
65
|
assert response.status == True, f"Query '{query}' failed: {response.error}"
|
|
65
66
|
assert response.result is not None, f"No result for query: {query}"
|
|
66
|
-
|
|
67
|
+
|
|
67
68
|
result_lower = response.result.lower()
|
|
68
69
|
# At least one expected keyword should be in the result
|
|
69
70
|
keyword_found = any(keyword.lower() in result_lower for keyword in expected_keywords)
|
|
70
71
|
assert keyword_found, f"None of {expected_keywords} found in result: {response.result}"
|
|
71
|
-
|
|
72
|
+
|
|
72
73
|
logger.info(f"Query '{query}' passed with result: {response.result[:100]}...")
|
|
@@ -65,7 +65,7 @@ class TestLogAnalysisBot:
|
|
|
65
65
|
log_file.write(result.stderr)
|
|
66
66
|
|
|
67
67
|
response: BotRunResult = log_analysis_bot.run(
|
|
68
|
-
log_file_path, timeout_in_seconds=300
|
|
68
|
+
str(log_file_path), timeout_in_seconds=300
|
|
69
69
|
)
|
|
70
70
|
|
|
71
71
|
print(f"Status: {response.status}, Result: {response.result}, Error: {response.error}")
|
|
@@ -47,17 +47,17 @@ You must send `task_done` as true only when you have completed the task. It mean
|
|
|
47
47
|
class TestMicroBot:
|
|
48
48
|
|
|
49
49
|
@pytest.fixture(scope="function")
|
|
50
|
-
def log_file_path(self, tmpdir):
|
|
50
|
+
def log_file_path(self, tmpdir: Path):
|
|
51
51
|
assert tmpdir.exists()
|
|
52
52
|
yield tmpdir / "error.log"
|
|
53
53
|
if tmpdir.exists():
|
|
54
54
|
subprocess.run(["sudo", "rm", "-rf", str(tmpdir)])
|
|
55
55
|
|
|
56
56
|
@pytest.fixture(scope="function")
|
|
57
|
-
def ro_mount(self, test_repo):
|
|
57
|
+
def ro_mount(self, test_repo: Path):
|
|
58
58
|
assert test_repo is not None
|
|
59
59
|
return Mount(
|
|
60
|
-
str(test_repo), DOCKER_WORKING_DIR, PermissionLabels.READ_ONLY
|
|
60
|
+
str(test_repo), f"{DOCKER_WORKING_DIR}/{test_repo.name}", PermissionLabels.READ_ONLY
|
|
61
61
|
)
|
|
62
62
|
|
|
63
63
|
@pytest.fixture(scope="function")
|
|
@@ -98,7 +98,9 @@ class TestMicroBot:
|
|
|
98
98
|
verify_function = issue_1[1]
|
|
99
99
|
|
|
100
100
|
test_repo_mount_ro = Mount(
|
|
101
|
-
str(test_repo),
|
|
101
|
+
str(test_repo),
|
|
102
|
+
f"{DOCKER_WORKING_DIR}/{test_repo.name}",
|
|
103
|
+
PermissionLabels.READ_ONLY
|
|
102
104
|
)
|
|
103
105
|
testing_bot = MicroBot(
|
|
104
106
|
model="azure-openai/mini-swe-agent-gpt5",
|
|
@@ -121,7 +123,7 @@ class TestMicroBot:
|
|
|
121
123
|
log_file.write(response.result)
|
|
122
124
|
|
|
123
125
|
test_repo_mount_rw = Mount(
|
|
124
|
-
str(test_repo), DOCKER_WORKING_DIR, PermissionLabels.READ_WRITE
|
|
126
|
+
str(test_repo), f"{DOCKER_WORKING_DIR}/{test_repo.name}", PermissionLabels.READ_WRITE
|
|
125
127
|
)
|
|
126
128
|
coding_bot = MicroBot(
|
|
127
129
|
model="azure-openai/mini-swe-agent-gpt5",
|
|
@@ -130,7 +132,7 @@ class TestMicroBot:
|
|
|
130
132
|
)
|
|
131
133
|
|
|
132
134
|
additional_mounts = Mount(
|
|
133
|
-
log_file_path,
|
|
135
|
+
str(log_file_path),
|
|
134
136
|
"/var/log",
|
|
135
137
|
PermissionLabels.READ_ONLY,
|
|
136
138
|
MountType.COPY,
|
|
@@ -148,12 +150,30 @@ class TestMicroBot:
|
|
|
148
150
|
|
|
149
151
|
verify_function(test_repo)
|
|
150
152
|
|
|
151
|
-
def
|
|
153
|
+
def test_incorrect_code_mount_type(self, log_file_path, test_repo):
|
|
152
154
|
assert test_repo is not None
|
|
153
155
|
assert log_file_path is not None
|
|
154
156
|
|
|
155
157
|
test_repo_mount_ro = Mount(
|
|
156
|
-
str(test_repo),
|
|
158
|
+
str(test_repo),
|
|
159
|
+
f"{DOCKER_WORKING_DIR}/{test_repo.name}",
|
|
160
|
+
PermissionLabels.READ_ONLY,
|
|
161
|
+
MountType.COPY,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
with pytest.raises(ValueError, match="Only MOUNT mount type is supported for folder_to_mount"):
|
|
165
|
+
testing_bot = MicroBot(
|
|
166
|
+
model="azure-openai/mini-swe-agent-gpt5",
|
|
167
|
+
system_prompt=SYSTEM_PROMPT,
|
|
168
|
+
folder_to_mount=test_repo_mount_ro,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def test_incorrect_copy_mount_type(self, log_file_path, test_repo):
|
|
172
|
+
assert test_repo is not None
|
|
173
|
+
assert log_file_path is not None
|
|
174
|
+
|
|
175
|
+
test_repo_mount_ro = Mount(
|
|
176
|
+
str(test_repo), f"{DOCKER_WORKING_DIR}/{test_repo.name}", PermissionLabels.READ_ONLY
|
|
157
177
|
)
|
|
158
178
|
testing_bot = MicroBot(
|
|
159
179
|
model="azure-openai/mini-swe-agent-gpt5",
|
|
@@ -162,7 +182,7 @@ class TestMicroBot:
|
|
|
162
182
|
)
|
|
163
183
|
|
|
164
184
|
additional_mounts = Mount(
|
|
165
|
-
log_file_path,
|
|
185
|
+
str(log_file_path),
|
|
166
186
|
"/var/log",
|
|
167
187
|
PermissionLabels.READ_ONLY,
|
|
168
188
|
MountType.MOUNT, # MOUNT is not supported yet
|
|
@@ -178,7 +198,7 @@ class TestMicroBot:
|
|
|
178
198
|
assert test_repo is not None
|
|
179
199
|
|
|
180
200
|
test_repo_mount_ro = Mount(
|
|
181
|
-
str(test_repo), DOCKER_WORKING_DIR, PermissionLabels.READ_ONLY
|
|
201
|
+
str(test_repo), f"{DOCKER_WORKING_DIR}/{test_repo.name}", PermissionLabels.READ_ONLY
|
|
182
202
|
)
|
|
183
203
|
with pytest.raises(ValueError, match="Unsupported model provider: provider"):
|
|
184
204
|
MicroBot(
|
|
@@ -191,7 +211,7 @@ class TestMicroBot:
|
|
|
191
211
|
assert test_repo is not None
|
|
192
212
|
|
|
193
213
|
test_repo_mount_ro = Mount(
|
|
194
|
-
str(test_repo), DOCKER_WORKING_DIR, PermissionLabels.READ_ONLY
|
|
214
|
+
str(test_repo), f"{DOCKER_WORKING_DIR}/{test_repo.name}", PermissionLabels.READ_ONLY
|
|
195
215
|
)
|
|
196
216
|
with pytest.raises(ValueError, match="Model should be in the format <provider>/<model_name>"):
|
|
197
217
|
MicroBot(
|
{microbots-0.0.7 → microbots-0.0.8}/test/environment/local_docker/test_local_docker_environment.py
RENAMED
|
@@ -2,19 +2,17 @@
|
|
|
2
2
|
Integration tests for LocalDockerEnvironment
|
|
3
3
|
"""
|
|
4
4
|
import pytest
|
|
5
|
-
import shutil
|
|
6
5
|
import os
|
|
7
|
-
import time
|
|
8
6
|
import socket
|
|
9
|
-
from pathlib import Path
|
|
10
7
|
import re
|
|
11
8
|
|
|
12
9
|
# Add src to path for imports
|
|
13
10
|
import sys
|
|
11
|
+
|
|
14
12
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../src")))
|
|
15
13
|
|
|
16
14
|
from microbots.environment.local_docker.LocalDockerEnvironment import LocalDockerEnvironment
|
|
17
|
-
from microbots.
|
|
15
|
+
from microbots.extras.mount import Mount
|
|
18
16
|
|
|
19
17
|
# Use the correct working directory path
|
|
20
18
|
DOCKER_WORKING_DIR = "workdir"
|
|
@@ -82,22 +80,6 @@ class TestLocalDockerEnvironmentIntegration:
|
|
|
82
80
|
assert "Hello World" in result.stdout
|
|
83
81
|
|
|
84
82
|
|
|
85
|
-
@pytest.mark.integration
|
|
86
|
-
@pytest.mark.docker
|
|
87
|
-
def test_initialization_validation_errors(self):
|
|
88
|
-
"""Test that initialization properly validates parameters"""
|
|
89
|
-
# Test permission without folder
|
|
90
|
-
with pytest.raises(ValueError, match="permission provided but folder_to_mount is None"):
|
|
91
|
-
LocalDockerEnvironment(port=8999, permission="READ_ONLY")
|
|
92
|
-
|
|
93
|
-
# Test folder without permission
|
|
94
|
-
with pytest.raises(ValueError, match="folder_to_mount provided but permission is None"):
|
|
95
|
-
LocalDockerEnvironment(port=8999, folder_to_mount="/some/path")
|
|
96
|
-
|
|
97
|
-
# Test invalid permission
|
|
98
|
-
with pytest.raises(ValueError, match="permission must be 'READ_ONLY' or 'READ_WRITE' when provided"):
|
|
99
|
-
LocalDockerEnvironment(port=8999, folder_to_mount="/some/path", permission="INVALID")
|
|
100
|
-
|
|
101
83
|
@pytest.mark.integration
|
|
102
84
|
@pytest.mark.docker
|
|
103
85
|
def test_command_execution_basic(self, shared_env):
|
|
@@ -191,11 +173,15 @@ EOF"""
|
|
|
191
173
|
mount_port = s.getsockname()[1]
|
|
192
174
|
|
|
193
175
|
env = None
|
|
176
|
+
test_dir_mount = Mount(
|
|
177
|
+
host_path=test_dir,
|
|
178
|
+
sandbox_path=f"/{DOCKER_WORKING_DIR}/{os.path.basename(test_dir)}",
|
|
179
|
+
permission="READ_WRITE"
|
|
180
|
+
)
|
|
194
181
|
try:
|
|
195
182
|
env = LocalDockerEnvironment(
|
|
196
183
|
port=mount_port,
|
|
197
|
-
folder_to_mount=
|
|
198
|
-
permission="READ_WRITE"
|
|
184
|
+
folder_to_mount=test_dir_mount,
|
|
199
185
|
)
|
|
200
186
|
|
|
201
187
|
folder_name = os.path.basename(test_dir)
|
|
@@ -246,11 +232,16 @@ EOF"""
|
|
|
246
232
|
mount_port = s.getsockname()[1]
|
|
247
233
|
|
|
248
234
|
env = None
|
|
235
|
+
test_dir_mount = Mount(
|
|
236
|
+
host_path=test_dir,
|
|
237
|
+
sandbox_path=f"/{DOCKER_WORKING_DIR}/{os.path.basename(test_dir)}",
|
|
238
|
+
permission="READ_ONLY"
|
|
239
|
+
)
|
|
240
|
+
|
|
249
241
|
try:
|
|
250
242
|
env = LocalDockerEnvironment(
|
|
251
243
|
port=mount_port,
|
|
252
|
-
folder_to_mount=
|
|
253
|
-
permission="READ_ONLY"
|
|
244
|
+
folder_to_mount=test_dir_mount,
|
|
254
245
|
)
|
|
255
246
|
|
|
256
247
|
folder_name = os.path.basename(test_dir)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
sys.path.insert(
|
|
7
|
+
0, f"{Path(__file__).resolve().parents[2].as_posix()}/src"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
logging.basicConfig(level=logging.INFO)
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
from microbots.extras.mount import Mount
|
|
15
|
+
|
|
16
|
+
@pytest.mark.integration
|
|
17
|
+
def test_non_abs_sandbox_path(tmpdir: Path):
|
|
18
|
+
with pytest.raises(ValueError, match="sandbox_path must be an absolute path"):
|
|
19
|
+
Mount(
|
|
20
|
+
host_path=str(tmpdir),
|
|
21
|
+
sandbox_path="relative/path/in/sandbox",
|
|
22
|
+
permission="READ_ONLY",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
@pytest.mark.integration
|
|
26
|
+
def test_host_file_sandbox_dir(tmpdir: Path):
|
|
27
|
+
host_file = tmpdir / "testfile.txt"
|
|
28
|
+
host_file.write_text("Sample content", encoding="utf-8")
|
|
29
|
+
|
|
30
|
+
mount = Mount(
|
|
31
|
+
host_path=str(host_file),
|
|
32
|
+
sandbox_path="/sandbox/dir/",
|
|
33
|
+
permission="READ_ONLY",
|
|
34
|
+
mount_type="copy",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
assert mount.sandbox_path == "/sandbox/dir/testfile.txt"
|
|
@@ -17,12 +17,19 @@ def test_repo(tmpdir):
|
|
|
17
17
|
capture_output=True,
|
|
18
18
|
text=True
|
|
19
19
|
)
|
|
20
|
+
|
|
21
|
+
if result.returncode != 0:
|
|
22
|
+
pytest.fail(
|
|
23
|
+
f"Failed to clone repository: {TEST_REPO}\n"
|
|
24
|
+
f"Return code: {result.returncode}\n"
|
|
25
|
+
f"Stdout: {result.stdout}\n"
|
|
26
|
+
f"Stderr: {result.stderr}"
|
|
27
|
+
)
|
|
28
|
+
|
|
20
29
|
except subprocess.CalledProcessError as e:
|
|
21
30
|
pytest.fail(
|
|
22
|
-
f"
|
|
23
|
-
f"
|
|
24
|
-
f"Stdout: {e.stdout}\n"
|
|
25
|
-
f"Stderr: {e.stderr}"
|
|
31
|
+
f"Exception while cloning repository: {TEST_REPO}\n"
|
|
32
|
+
f"Exception: {str(e)}\n"
|
|
26
33
|
)
|
|
27
34
|
|
|
28
35
|
repo_path = Path(tmpdir / os.listdir(tmpdir)[0])
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass, field
|
|
2
|
-
from enum import StrEnum
|
|
3
|
-
|
|
4
|
-
from microbots.constants import PermissionLabels, PermissionMapping
|
|
5
|
-
from microbots.utils.path import PathInfo, get_path_info
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class MountType(StrEnum):
|
|
9
|
-
MOUNT = "mount"
|
|
10
|
-
COPY = "copy"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@dataclass
|
|
14
|
-
class Mount:
|
|
15
|
-
host_path: str
|
|
16
|
-
sandbox_path: str
|
|
17
|
-
permission: PermissionLabels
|
|
18
|
-
mount_type: MountType = MountType.MOUNT
|
|
19
|
-
|
|
20
|
-
# These will be set in __post_init__
|
|
21
|
-
permission_key: str = field(init=False)
|
|
22
|
-
host_path_info: PathInfo = field(init=False)
|
|
23
|
-
|
|
24
|
-
def __post_init__(self):
|
|
25
|
-
self.permission_key = PermissionMapping.MAPPING.get(self.permission)
|
|
26
|
-
self.host_path_info = get_path_info(self.host_path)
|
|
27
|
-
self.sandbox_path = f"{self.sandbox_path}/{self.host_path_info.base_name}"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{microbots-0.0.7 → microbots-0.0.8}/src/microbots/environment/local_docker/image_builder/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{microbots-0.0.7 → microbots-0.0.8}/src/microbots/tools/tool_definitions/browser-use/browser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|