mini-swe-agent 1.2.0__py3-none-any.whl → 1.4.0__py3-none-any.whl
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.
- {mini_swe_agent-1.2.0.dist-info → mini_swe_agent-1.4.0.dist-info}/METADATA +1 -1
- {mini_swe_agent-1.2.0.dist-info → mini_swe_agent-1.4.0.dist-info}/RECORD +15 -15
- minisweagent/__init__.py +1 -1
- minisweagent/agents/interactive_textual.py +168 -82
- minisweagent/config/mini.tcss +32 -75
- minisweagent/config/mini.yaml +12 -7
- minisweagent/environments/docker.py +3 -1
- minisweagent/models/litellm_model.py +5 -0
- minisweagent/run/extra/swebench.py +10 -8
- minisweagent/run/mini.py +17 -14
- minisweagent/run/utils/save.py +1 -0
- {mini_swe_agent-1.2.0.dist-info → mini_swe_agent-1.4.0.dist-info}/WHEEL +0 -0
- {mini_swe_agent-1.2.0.dist-info → mini_swe_agent-1.4.0.dist-info}/entry_points.txt +0 -0
- {mini_swe_agent-1.2.0.dist-info → mini_swe_agent-1.4.0.dist-info}/licenses/LICENSE.md +0 -0
- {mini_swe_agent-1.2.0.dist-info → mini_swe_agent-1.4.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mini-swe-agent
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Nano SWE Agent - A simple AI software engineering agent
|
|
5
5
|
Author-email: Kilian Lieret <kilian.lieret@posteo.de>, "Carlos E. Jimenez" <carlosej@princeton.edu>
|
|
6
6
|
License: MIT License
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
mini_swe_agent-1.
|
|
2
|
-
minisweagent/__init__.py,sha256=
|
|
1
|
+
mini_swe_agent-1.4.0.dist-info/licenses/LICENSE.md,sha256=D3luWPkdHAe7LBsdD4vzqDAXw6Xewb3G-uczss0uh1s,1094
|
|
2
|
+
minisweagent/__init__.py,sha256=3w6Aygz0dJetlfY8zRfpQ4rZecN_3XvpkNLJubSeGjA,1787
|
|
3
3
|
minisweagent/__main__.py,sha256=FIyAOiw--c3FQ2g240FOM1FdL0lk_PxSpixu0pQ7WFo,194
|
|
4
4
|
minisweagent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
minisweagent/agents/__init__.py,sha256=cpjJLzg1IGxLM-tZpoMJV9S33ye13XtdBO0x7DU_Lrk,48
|
|
6
6
|
minisweagent/agents/default.py,sha256=6TZUfKch6e_7m05BXmjqUjV-k068s6ldg7T6zJBbQW8,5438
|
|
7
7
|
minisweagent/agents/interactive.py,sha256=_DCBabdwdIR4gAojT_TaQW2MSFtmBq997mwmiDGYdRA,7327
|
|
8
|
-
minisweagent/agents/interactive_textual.py,sha256
|
|
8
|
+
minisweagent/agents/interactive_textual.py,sha256=-GrhQT3nh_bn4qyWFRMf6F7xEqMsRnQ-Ry5n3JJBjsc,16077
|
|
9
9
|
minisweagent/config/README.md,sha256=tPruhnQDhZ8ugc1FNPKk9tVMRltmmIjdYgvHCmN-3Hs,354
|
|
10
10
|
minisweagent/config/__init__.py,sha256=UfORdQID1Ek_dduZlybUsIKJjihImkSqNU5tIjpw0hk,694
|
|
11
11
|
minisweagent/config/default.yaml,sha256=AGhcIq6X6n5Fs71ufO3B6CtZ4PS877tCxkPkrWR5Ylg,4497
|
|
12
12
|
minisweagent/config/github_issue.yaml,sha256=evvu3AJ52tXYSdami9_B8zfazOAE2r2XXkzVmScBoKc,4539
|
|
13
|
-
minisweagent/config/mini.tcss,sha256=
|
|
14
|
-
minisweagent/config/mini.yaml,sha256=
|
|
13
|
+
minisweagent/config/mini.tcss,sha256=VgdZZqWElA5_nn4DJUFMpz8C7Gmi5s5XOtm7pfyM83Q,1122
|
|
14
|
+
minisweagent/config/mini.yaml,sha256=WluQAx4AII9MFk3xDSzsJTosNJfgZti02niCYZWYq_A,5346
|
|
15
15
|
minisweagent/config/extra/__init__.py,sha256=e1MoAlDn_wc9HnXNoncf1P-B4DQ-iRf6n7Q_txjZGRI,52
|
|
16
16
|
minisweagent/config/extra/swebench.yaml,sha256=LNpTahpul6HL0HozgAAz-C6kpX3wZA7Tg8uE-ZmgrF4,7577
|
|
17
17
|
minisweagent/environments/__init__.py,sha256=g5mKac1YgVOZVKvmiAiuyPSevRYpI69V4vYrbCH3gsI,54
|
|
18
|
-
minisweagent/environments/docker.py,sha256=
|
|
18
|
+
minisweagent/environments/docker.py,sha256=VYk7i0T0IgUF_s-N-DqYkHsBWbfgaIMpJZIIdEtetTw,3871
|
|
19
19
|
minisweagent/environments/local.py,sha256=-2EV3RqZSB8WEjJE7BHLhRjocPMLpoJ3HbM8QB1WXUU,1060
|
|
20
20
|
minisweagent/environments/singularity.py,sha256=j7ptRVF8GwDLd-5IjhT5j7fNxEJz9amuLTmVxotaMlI,1796
|
|
21
21
|
minisweagent/environments/extra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
minisweagent/environments/extra/swerex_docker.py,sha256=MOhhFdX1sAk_U0g-GOxohfjrldzO4YfrUnHq8qJff7c,1502
|
|
23
23
|
minisweagent/models/__init__.py,sha256=J4bnvfMByTVG0cL_6p51sm8gdargXhARfbG5c0UZ8Z4,2890
|
|
24
24
|
minisweagent/models/anthropic.py,sha256=D8nHvvbgzPjla0He8p0O9kaXASPWg1Sai0pHsAj_Yn8,855
|
|
25
|
-
minisweagent/models/litellm_model.py,sha256=
|
|
25
|
+
minisweagent/models/litellm_model.py,sha256=jMWsFYPPoXgBaN6Ypa5MR6A9hOpvacnYEXZkzuAkgNc,2283
|
|
26
26
|
minisweagent/models/test_models.py,sha256=oB3jmZUire5TkVT8ebUCD3jLuLhPIbcTiTqdIix85Yw,1174
|
|
27
27
|
minisweagent/models/utils/cache_control.py,sha256=mG9cE56HQaUwXfoqvXoH6LcbMV_G1vlEE1aBBpikXYg,1608
|
|
28
28
|
minisweagent/models/utils/key_per_thread.py,sha256=Vlxt--rapNNYCgIHrMCu1WVAkuiVIhC_awbarkbnkZQ,644
|
|
@@ -30,17 +30,17 @@ minisweagent/run/__init__.py,sha256=WIoYgHVl7iZF2YncrfV3IttupG6P5KogroKHKECka3A,
|
|
|
30
30
|
minisweagent/run/github_issue.py,sha256=GWOkGM09jOYV93p6xIM_kKWmC1yP_d5lprafWlqoBN0,2748
|
|
31
31
|
minisweagent/run/hello_world.py,sha256=erLnEwNmPFLxq3-8zyv66Vy1kIqMqQf97vISX7LrQXg,959
|
|
32
32
|
minisweagent/run/inspector.py,sha256=QnY3oYzm-yq3w9Jzs112Lco2Rg84vSocAWrQRVz_1lc,7127
|
|
33
|
-
minisweagent/run/mini.py,sha256=
|
|
33
|
+
minisweagent/run/mini.py,sha256=Q-B5LDFQtEoqxMC3cHpbQr8IW1qNpiTh_eaQpYHz954,4589
|
|
34
34
|
minisweagent/run/mini_extra.py,sha256=ecA1PnTWElpO60G9RktvVLtUOf3bZ_ESmnSttS6izhQ,1465
|
|
35
35
|
minisweagent/run/extra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
36
|
minisweagent/run/extra/config.py,sha256=paMHfplhKsqNmzhCmozxhXWHvBzBCUlwUWD8N7ytCPc,3277
|
|
37
|
-
minisweagent/run/extra/swebench.py,sha256=
|
|
37
|
+
minisweagent/run/extra/swebench.py,sha256=m5_PZI4ojkUyCxzkkMtel_vlnYmjziWrXu73yHoZGFs,9688
|
|
38
38
|
minisweagent/run/extra/swebench_single.py,sha256=L3Kk4G65o3MCPLMEwGNIs77-AFf6Lfc8o1oxrbN-ZWM,1991
|
|
39
39
|
minisweagent/run/extra/utils/batch_progress.py,sha256=u__khJ-fipZLxTJu43LamGAtPUCqEZYEi8J7SfH7X6A,6211
|
|
40
40
|
minisweagent/run/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
minisweagent/run/utils/save.py,sha256=
|
|
42
|
-
mini_swe_agent-1.
|
|
43
|
-
mini_swe_agent-1.
|
|
44
|
-
mini_swe_agent-1.
|
|
45
|
-
mini_swe_agent-1.
|
|
46
|
-
mini_swe_agent-1.
|
|
41
|
+
minisweagent/run/utils/save.py,sha256=q7omf7zYHg73k8-Iyp9w5YVSYvDAacRrh4X9L_4VhNM,942
|
|
42
|
+
mini_swe_agent-1.4.0.dist-info/METADATA,sha256=tM-cskbK7S2xIRUegGm39qXgPT3tOfpfZOlOZfk-lcg,13459
|
|
43
|
+
mini_swe_agent-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
44
|
+
mini_swe_agent-1.4.0.dist-info/entry_points.txt,sha256=d1_yRbTaGjs1UXHa6JQK0sKDGBIVGm8oeW0k2kfbJgQ,182
|
|
45
|
+
mini_swe_agent-1.4.0.dist-info/top_level.txt,sha256=zKF4t8bFpV87fdVABZt2Da-vnb4Vkh_CxkwQx5YT4Ew,13
|
|
46
|
+
mini_swe_agent-1.4.0.dist-info/RECORD,,
|
minisweagent/__init__.py
CHANGED
|
@@ -8,6 +8,7 @@ import os
|
|
|
8
8
|
import re
|
|
9
9
|
import threading
|
|
10
10
|
import time
|
|
11
|
+
import traceback
|
|
11
12
|
from dataclasses import dataclass, field
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
from typing import Literal
|
|
@@ -19,9 +20,9 @@ from textual.binding import Binding
|
|
|
19
20
|
from textual.containers import Container, Vertical, VerticalScroll
|
|
20
21
|
from textual.css.query import NoMatches
|
|
21
22
|
from textual.events import Key
|
|
22
|
-
from textual.widgets import Footer, Header, Static, TextArea
|
|
23
|
+
from textual.widgets import Footer, Header, Input, Static, TextArea
|
|
23
24
|
|
|
24
|
-
from minisweagent.agents.default import AgentConfig, DefaultAgent, NonTerminatingException
|
|
25
|
+
from minisweagent.agents.default import AgentConfig, DefaultAgent, NonTerminatingException, Submitted
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
@dataclass
|
|
@@ -30,6 +31,8 @@ class TextualAgentConfig(AgentConfig):
|
|
|
30
31
|
"""Mode for action execution: 'confirm' requires user confirmation, 'yolo' executes immediately."""
|
|
31
32
|
whitelist_actions: list[str] = field(default_factory=list)
|
|
32
33
|
"""Never confirm actions that match these regular expressions."""
|
|
34
|
+
confirm_exit: bool = True
|
|
35
|
+
"""If the agent wants to finish, do we ask for confirmation from user?"""
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
class TextualAgent(DefaultAgent):
|
|
@@ -37,17 +40,29 @@ class TextualAgent(DefaultAgent):
|
|
|
37
40
|
"""Connects the DefaultAgent to the TextualApp."""
|
|
38
41
|
self.app = app
|
|
39
42
|
super().__init__(*args, config_class=TextualAgentConfig, **kwargs)
|
|
43
|
+
self._current_action_from_human = False
|
|
40
44
|
|
|
41
45
|
def add_message(self, role: str, content: str):
|
|
42
46
|
super().add_message(role, content)
|
|
43
47
|
if self.app.agent_state != "UNINITIALIZED":
|
|
44
48
|
self.app.call_from_thread(self.app.on_message_added)
|
|
45
49
|
|
|
50
|
+
def query(self) -> dict:
|
|
51
|
+
if self.config.mode == "human":
|
|
52
|
+
human_input = self.app.input_container.request_input("Enter your command:")
|
|
53
|
+
self._current_action_from_human = True
|
|
54
|
+
msg = {"content": f"\n```bash\n{human_input}\n```"}
|
|
55
|
+
self.add_message("assistant", msg["content"])
|
|
56
|
+
return msg
|
|
57
|
+
self._current_action_from_human = False
|
|
58
|
+
return super().query()
|
|
59
|
+
|
|
46
60
|
def run(self, task: str) -> tuple[str, str]:
|
|
47
61
|
try:
|
|
48
62
|
exit_status, result = super().run(task)
|
|
49
63
|
except Exception as e:
|
|
50
64
|
result = str(e)
|
|
65
|
+
print(traceback.format_exc())
|
|
51
66
|
self.app.call_from_thread(self.app.on_agent_finished, "ERROR", result)
|
|
52
67
|
return "ERROR", result
|
|
53
68
|
else:
|
|
@@ -55,13 +70,30 @@ class TextualAgent(DefaultAgent):
|
|
|
55
70
|
return exit_status, result
|
|
56
71
|
|
|
57
72
|
def execute_action(self, action: dict) -> dict:
|
|
58
|
-
if self.config.mode == "
|
|
59
|
-
|
|
73
|
+
if self.config.mode == "human" and not self._current_action_from_human: # threading, grrrrr
|
|
74
|
+
raise NonTerminatingException("Command not executed because user switched to manual mode.")
|
|
75
|
+
if (
|
|
76
|
+
self.config.mode == "confirm"
|
|
77
|
+
and action["action"].strip()
|
|
78
|
+
and not any(re.match(r, action["action"]) for r in self.config.whitelist_actions)
|
|
60
79
|
):
|
|
61
|
-
|
|
80
|
+
result = self.app.input_container.request_input("Press ENTER to confirm or provide rejection reason")
|
|
81
|
+
if result: # Non-empty string means rejection
|
|
62
82
|
raise NonTerminatingException(f"Command not executed: {result}")
|
|
63
83
|
return super().execute_action(action)
|
|
64
84
|
|
|
85
|
+
def has_finished(self, output: dict[str, str]):
|
|
86
|
+
try:
|
|
87
|
+
return super().has_finished(output)
|
|
88
|
+
except Submitted as e:
|
|
89
|
+
if self.config.confirm_exit:
|
|
90
|
+
if new_task := self.app.input_container.request_input(
|
|
91
|
+
"[bold green]Agent wants to finish.[/bold green] "
|
|
92
|
+
"[green]Type a comment to give it a new task or press enter to quit.\n"
|
|
93
|
+
).strip():
|
|
94
|
+
raise NonTerminatingException(f"The user added a new task: {new_task}")
|
|
95
|
+
raise e
|
|
96
|
+
|
|
65
97
|
|
|
66
98
|
class AddLogEmitCallback(logging.Handler):
|
|
67
99
|
def __init__(self, callback):
|
|
@@ -87,77 +119,123 @@ def _messages_to_steps(messages: list[dict]) -> list[list[dict]]:
|
|
|
87
119
|
return steps
|
|
88
120
|
|
|
89
121
|
|
|
90
|
-
class
|
|
122
|
+
class SmartInputContainer(Container):
|
|
91
123
|
def __init__(self, app: "AgentApp"):
|
|
92
|
-
"""
|
|
93
|
-
super().__init__(
|
|
124
|
+
"""Smart input container supporting single-line and multi-line input modes."""
|
|
125
|
+
super().__init__(classes="smart-input-container")
|
|
94
126
|
self._app = app
|
|
95
|
-
self.
|
|
127
|
+
self._multiline_mode = False
|
|
96
128
|
self.can_focus = True
|
|
97
129
|
self.display = False
|
|
98
130
|
|
|
99
|
-
self.
|
|
100
|
-
self.
|
|
101
|
-
self.
|
|
131
|
+
self.pending_prompt: str | None = None
|
|
132
|
+
self._input_event = threading.Event()
|
|
133
|
+
self._input_result: str | None = None
|
|
102
134
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
"Press [bold]ENTER[/bold] to confirm action or [bold]BACKSPACE[/bold] to reject (or [bold]y[/bold] to toggle YOLO mode)",
|
|
106
|
-
classes="confirmation-prompt",
|
|
135
|
+
self._header_display = Static(
|
|
136
|
+
"USER INPUT REQUESTED", id="input-header-display", classes="message-header input-request-header"
|
|
107
137
|
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
"
|
|
111
|
-
|
|
112
|
-
|
|
138
|
+
self._hint_text = Static(
|
|
139
|
+
"[bold]Enter[/bold] to submit, [bold]Ctrl+T[/bold] to switch to multi-line input, [bold]Tab[/bold] to switch focus with other controls",
|
|
140
|
+
classes="hint-text",
|
|
141
|
+
)
|
|
142
|
+
self._single_input = Input(placeholder="Type your input...")
|
|
143
|
+
self._multi_input = TextArea("", show_line_numbers=False, classes="multi-input")
|
|
144
|
+
|
|
145
|
+
self._input_elements_container = Vertical(
|
|
146
|
+
self._header_display,
|
|
147
|
+
self._hint_text,
|
|
148
|
+
self._single_input,
|
|
149
|
+
self._multi_input,
|
|
150
|
+
classes="message-container",
|
|
113
151
|
)
|
|
114
|
-
rejection_help.display = False
|
|
115
|
-
yield rejection_help
|
|
116
|
-
|
|
117
|
-
def request_confirmation(self, action: str) -> str | None:
|
|
118
|
-
"""Request confirmation for an action. Returns rejection message or None."""
|
|
119
|
-
self._confirmation_event.clear()
|
|
120
|
-
self._confirmation_result = None
|
|
121
|
-
self._pending_action = action
|
|
122
|
-
self._app.call_from_thread(self._app.update_content)
|
|
123
|
-
self._confirmation_event.wait()
|
|
124
|
-
return self._confirmation_result
|
|
125
152
|
|
|
126
|
-
def
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
153
|
+
def compose(self) -> ComposeResult:
|
|
154
|
+
yield self._input_elements_container
|
|
155
|
+
|
|
156
|
+
def on_mount(self) -> None:
|
|
157
|
+
"""Initialize the widget state."""
|
|
158
|
+
self._multi_input.display = False
|
|
159
|
+
self._update_mode_display()
|
|
160
|
+
|
|
161
|
+
def on_focus(self) -> None:
|
|
162
|
+
"""Called when the container gains focus."""
|
|
163
|
+
if self._multiline_mode:
|
|
164
|
+
self._multi_input.focus()
|
|
165
|
+
else:
|
|
166
|
+
self._single_input.focus()
|
|
167
|
+
|
|
168
|
+
def request_input(self, prompt: str) -> str:
|
|
169
|
+
"""Request input from user. Returns input text (empty string if confirmed without reason)."""
|
|
170
|
+
self._input_event.clear()
|
|
171
|
+
self._input_result = None
|
|
172
|
+
self.pending_prompt = prompt
|
|
173
|
+
self._header_display.update(prompt)
|
|
174
|
+
self._update_mode_display()
|
|
175
|
+
self._app.call_from_thread(self._app.update_content)
|
|
176
|
+
self._input_event.wait()
|
|
177
|
+
return self._input_result or ""
|
|
178
|
+
|
|
179
|
+
def _complete_input(self, input_text: str):
|
|
180
|
+
"""Internal method to complete the input process."""
|
|
181
|
+
self._input_result = input_text
|
|
182
|
+
self.pending_prompt = None
|
|
183
|
+
self._header_display.update("USER INPUT REQUESTED")
|
|
130
184
|
self.display = False
|
|
131
|
-
self.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
rejection_help.display = False
|
|
137
|
-
# Reset agent state to RUNNING after confirmation is completed
|
|
138
|
-
if rejection_message is None:
|
|
139
|
-
self._app.agent_state = "RUNNING"
|
|
140
|
-
self._confirmation_event.set()
|
|
185
|
+
self._single_input.value = ""
|
|
186
|
+
self._multi_input.text = ""
|
|
187
|
+
self._multiline_mode = False
|
|
188
|
+
self._update_mode_display()
|
|
189
|
+
self._app.agent_state = "RUNNING"
|
|
141
190
|
self._app.update_content()
|
|
191
|
+
# Reset scroll position to bottom since input container disappearing changes layout
|
|
192
|
+
# somehow scroll_to doesn't work.
|
|
193
|
+
self._app._vscroll.scroll_y = 0
|
|
194
|
+
self._input_event.set()
|
|
195
|
+
|
|
196
|
+
def action_toggle_mode(self) -> None:
|
|
197
|
+
"""Switch from single-line to multi-line mode (one-way only)."""
|
|
198
|
+
if self.pending_prompt is None or self._multiline_mode:
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
self._multiline_mode = True
|
|
202
|
+
self._update_mode_display()
|
|
203
|
+
self.on_focus()
|
|
204
|
+
|
|
205
|
+
def _update_mode_display(self) -> None:
|
|
206
|
+
"""Update the display based on current mode."""
|
|
207
|
+
if self._multiline_mode:
|
|
208
|
+
self._multi_input.text = self._single_input.value
|
|
209
|
+
self._single_input.display = False
|
|
210
|
+
self._multi_input.display = True
|
|
211
|
+
|
|
212
|
+
else:
|
|
213
|
+
self._multi_input.display = False
|
|
214
|
+
self._single_input.display = True
|
|
215
|
+
|
|
216
|
+
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
217
|
+
"""Handle single-line input submission."""
|
|
218
|
+
if not self._multiline_mode:
|
|
219
|
+
text = event.input.value.strip()
|
|
220
|
+
self._complete_input(text)
|
|
142
221
|
|
|
143
222
|
def on_key(self, event: Key) -> None:
|
|
144
|
-
|
|
223
|
+
"""Handle key events."""
|
|
224
|
+
if event.key == "ctrl+t" and not self._multiline_mode:
|
|
145
225
|
event.prevent_default()
|
|
146
|
-
|
|
147
|
-
|
|
226
|
+
self.action_toggle_mode()
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
if self._multiline_mode and event.key == "ctrl+d":
|
|
230
|
+
event.prevent_default()
|
|
231
|
+
self._complete_input(self._multi_input.text.strip())
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
if event.key == "escape":
|
|
235
|
+
event.prevent_default()
|
|
236
|
+
self.can_focus = False
|
|
237
|
+
self._app.set_focus(None)
|
|
148
238
|
return
|
|
149
|
-
if not self.rejecting:
|
|
150
|
-
if event.key == "enter":
|
|
151
|
-
event.prevent_default()
|
|
152
|
-
self._complete_confirmation(None)
|
|
153
|
-
elif event.key == "backspace":
|
|
154
|
-
event.prevent_default()
|
|
155
|
-
self.rejecting = True
|
|
156
|
-
rejection_input = self.query_one("#rejection-input", TextArea)
|
|
157
|
-
rejection_input.display = True
|
|
158
|
-
rejection_input.focus()
|
|
159
|
-
rejection_help = self.query_one("#rejection-help", Static)
|
|
160
|
-
rejection_help.display = True
|
|
161
239
|
|
|
162
240
|
|
|
163
241
|
class AgentApp(App):
|
|
@@ -171,6 +249,7 @@ class AgentApp(App):
|
|
|
171
249
|
Binding("q", "quit", "Quit"),
|
|
172
250
|
Binding("y", "yolo", "Switch to YOLO Mode"),
|
|
173
251
|
Binding("c", "confirm", "Switch to Confirm Mode"),
|
|
252
|
+
Binding("u", "human", "Switch to Human Mode"),
|
|
174
253
|
]
|
|
175
254
|
|
|
176
255
|
def __init__(self, model, env, task: str, **kwargs):
|
|
@@ -182,13 +261,15 @@ class AgentApp(App):
|
|
|
182
261
|
self.agent = TextualAgent(self, model=model, env=env, **kwargs)
|
|
183
262
|
self._i_step = 0
|
|
184
263
|
self.n_steps = 1
|
|
185
|
-
self.
|
|
264
|
+
self.input_container = SmartInputContainer(self)
|
|
186
265
|
self.log_handler = AddLogEmitCallback(lambda record: self.call_from_thread(self.on_log_message_emitted, record))
|
|
187
266
|
logging.getLogger().addHandler(self.log_handler)
|
|
188
267
|
self._spinner = Spinner("dots")
|
|
189
268
|
self.exit_status: str | None = None
|
|
190
269
|
self.result: str | None = None
|
|
191
270
|
|
|
271
|
+
self._vscroll = VerticalScroll()
|
|
272
|
+
|
|
192
273
|
# --- Basics ---
|
|
193
274
|
|
|
194
275
|
@property
|
|
@@ -201,15 +282,16 @@ class AgentApp(App):
|
|
|
201
282
|
"""Set current step index, automatically clamping to valid bounds."""
|
|
202
283
|
if value != self._i_step:
|
|
203
284
|
self._i_step = max(0, min(value, self.n_steps - 1))
|
|
204
|
-
self.
|
|
285
|
+
self._vscroll.scroll_to(y=0, animate=False)
|
|
205
286
|
self.update_content()
|
|
206
287
|
|
|
207
288
|
def compose(self) -> ComposeResult:
|
|
208
289
|
yield Header()
|
|
209
290
|
with Container(id="main"):
|
|
210
|
-
with
|
|
211
|
-
|
|
212
|
-
|
|
291
|
+
with self._vscroll:
|
|
292
|
+
with Vertical(id="content"):
|
|
293
|
+
pass
|
|
294
|
+
yield self.input_container
|
|
213
295
|
yield Footer()
|
|
214
296
|
|
|
215
297
|
def on_mount(self) -> None:
|
|
@@ -221,8 +303,7 @@ class AgentApp(App):
|
|
|
221
303
|
# --- Reacting to events ---
|
|
222
304
|
|
|
223
305
|
def on_message_added(self) -> None:
|
|
224
|
-
|
|
225
|
-
auto_follow = self.i_step == self.n_steps - 1 and vs.scroll_target_y <= 1
|
|
306
|
+
auto_follow = self.i_step == self.n_steps - 1 and self._vscroll.scroll_y <= 1
|
|
226
307
|
self.n_steps = len(_messages_to_steps(self.agent.messages))
|
|
227
308
|
self.update_content()
|
|
228
309
|
if auto_follow:
|
|
@@ -267,13 +348,11 @@ class AgentApp(App):
|
|
|
267
348
|
message_container.mount(Static(role.upper(), classes="message-header"))
|
|
268
349
|
message_container.mount(Static(Text(content_str, no_wrap=False), classes="message-content"))
|
|
269
350
|
|
|
270
|
-
if self.
|
|
271
|
-
self.agent_state = "
|
|
272
|
-
self.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if self.confirmation_container.display:
|
|
276
|
-
self.confirmation_container.focus()
|
|
351
|
+
if self.input_container.pending_prompt is not None:
|
|
352
|
+
self.agent_state = "AWAITING_INPUT"
|
|
353
|
+
self.input_container.display = self.input_container.pending_prompt is not None and self.i_step == len(items) - 1
|
|
354
|
+
if self.input_container.display:
|
|
355
|
+
self.input_container.on_focus()
|
|
277
356
|
|
|
278
357
|
self._update_headers()
|
|
279
358
|
self.refresh()
|
|
@@ -294,12 +373,21 @@ class AgentApp(App):
|
|
|
294
373
|
|
|
295
374
|
def action_yolo(self):
|
|
296
375
|
self.agent.config.mode = "yolo"
|
|
297
|
-
self.
|
|
298
|
-
|
|
376
|
+
if self.input_container.pending_prompt is not None:
|
|
377
|
+
self.input_container._complete_input("") # accept
|
|
378
|
+
self.notify("YOLO mode enabled - LM actions will execute immediately")
|
|
379
|
+
|
|
380
|
+
def action_human(self):
|
|
381
|
+
if self.agent.config.mode == "confirm" and self.input_container.pending_prompt is not None:
|
|
382
|
+
self.input_container._complete_input("User switched to manual mode, this command will be ignored")
|
|
383
|
+
self.agent.config.mode = "human"
|
|
384
|
+
self.notify("Human mode enabled - you can now type commands directly")
|
|
299
385
|
|
|
300
386
|
def action_confirm(self):
|
|
387
|
+
if self.agent.config.mode == "human" and self.input_container.pending_prompt is not None:
|
|
388
|
+
self.input_container._complete_input("") # just submit blank action
|
|
301
389
|
self.agent.config.mode = "confirm"
|
|
302
|
-
self.notify("Confirm mode enabled -
|
|
390
|
+
self.notify("Confirm mode enabled - LM proposes commands and you confirm/reject them")
|
|
303
391
|
|
|
304
392
|
def action_next_step(self) -> None:
|
|
305
393
|
self.i_step += 1
|
|
@@ -314,9 +402,7 @@ class AgentApp(App):
|
|
|
314
402
|
self.i_step = self.n_steps - 1
|
|
315
403
|
|
|
316
404
|
def action_scroll_down(self) -> None:
|
|
317
|
-
|
|
318
|
-
vs.scroll_to(y=vs.scroll_target_y + 15)
|
|
405
|
+
self._vscroll.scroll_to(y=self._vscroll.scroll_target_y + 15)
|
|
319
406
|
|
|
320
407
|
def action_scroll_up(self) -> None:
|
|
321
|
-
|
|
322
|
-
vs.scroll_to(y=vs.scroll_target_y - 15)
|
|
408
|
+
self._vscroll.scroll_to(y=self._vscroll.scroll_target_y - 15)
|
minisweagent/config/mini.tcss
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Screen {
|
|
2
2
|
layout: grid;
|
|
3
3
|
grid-size: 1;
|
|
4
|
-
grid-rows: 1fr
|
|
4
|
+
grid-rows: auto 1fr auto;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
#main {
|
|
8
8
|
height: 100%;
|
|
9
9
|
padding: 1;
|
|
10
|
+
layout: vertical;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
Footer {
|
|
@@ -16,6 +17,32 @@ Footer {
|
|
|
16
17
|
|
|
17
18
|
#content {
|
|
18
19
|
height: auto;
|
|
20
|
+
min-height: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.smart-input-container {
|
|
24
|
+
height: auto;
|
|
25
|
+
margin-top: 0;
|
|
26
|
+
padding: 0;
|
|
27
|
+
min-height: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.multi-input {
|
|
31
|
+
height: auto;
|
|
32
|
+
max-height: 20;
|
|
33
|
+
min-height: 3;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.prompt-display {
|
|
37
|
+
margin-bottom: 1;
|
|
38
|
+
padding: 0 1;
|
|
39
|
+
text-style: bold;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.hint-text{
|
|
43
|
+
margin-bottom: 1;
|
|
44
|
+
padding: 0 1;
|
|
45
|
+
color: white;
|
|
19
46
|
}
|
|
20
47
|
|
|
21
48
|
.message-container {
|
|
@@ -33,6 +60,10 @@ Footer {
|
|
|
33
60
|
text-style: bold;
|
|
34
61
|
}
|
|
35
62
|
|
|
63
|
+
.input-request-header {
|
|
64
|
+
color: $warning;
|
|
65
|
+
}
|
|
66
|
+
|
|
36
67
|
.message-content {
|
|
37
68
|
margin-top: 1;
|
|
38
69
|
padding: 0 1;
|
|
@@ -42,29 +73,6 @@ Header.running {
|
|
|
42
73
|
background: $error;
|
|
43
74
|
}
|
|
44
75
|
|
|
45
|
-
.confirmation-modal {
|
|
46
|
-
layout: vertical;
|
|
47
|
-
background: $surface;
|
|
48
|
-
margin: 1 4;
|
|
49
|
-
min-width: 40;
|
|
50
|
-
padding: 1;
|
|
51
|
-
border: tall $primary;
|
|
52
|
-
height: auto;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.modal-title {
|
|
56
|
-
text-align: center;
|
|
57
|
-
text-style: bold;
|
|
58
|
-
margin-bottom: 1;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.modal-content {
|
|
62
|
-
margin: 1 2;
|
|
63
|
-
min-height: 1;
|
|
64
|
-
max-height: 10;
|
|
65
|
-
overflow-y: auto;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
76
|
.button-container {
|
|
69
77
|
layout: horizontal;
|
|
70
78
|
align-horizontal: center;
|
|
@@ -74,55 +82,4 @@ Header.running {
|
|
|
74
82
|
.button-container Button {
|
|
75
83
|
margin: 0 1;
|
|
76
84
|
min-width: 10;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.confirmation-container {
|
|
80
|
-
background: $boost;
|
|
81
|
-
border: heavy $primary;
|
|
82
|
-
padding: 1;
|
|
83
|
-
margin: 1;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.confirmation-header {
|
|
87
|
-
color: $warning;
|
|
88
|
-
text-style: bold;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.command-to-confirm {
|
|
92
|
-
background: $surface;
|
|
93
|
-
margin: 1 0;
|
|
94
|
-
padding: 1;
|
|
95
|
-
color: $text;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
#confirmation-input {
|
|
99
|
-
margin-top: 1;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.confirmation-prompt {
|
|
103
|
-
background: $boost;
|
|
104
|
-
border: heavy $warning;
|
|
105
|
-
padding: 1;
|
|
106
|
-
margin: 1;
|
|
107
|
-
text-align: center;
|
|
108
|
-
color: $warning;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.confirmation-prompt:focus {
|
|
112
|
-
border: heavy $accent;
|
|
113
|
-
background: $panel;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
#rejection-input {
|
|
117
|
-
display: none;
|
|
118
|
-
margin: 1;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.rejection-help {
|
|
122
|
-
background: $boost;
|
|
123
|
-
border: heavy $warning;
|
|
124
|
-
padding: 1;
|
|
125
|
-
margin: 1;
|
|
126
|
-
text-align: center;
|
|
127
|
-
color: $warning;
|
|
128
85
|
}
|
minisweagent/config/mini.yaml
CHANGED
|
@@ -23,13 +23,15 @@ agent:
|
|
|
23
23
|
You can execute bash commands and edit files to implement the necessary changes.
|
|
24
24
|
|
|
25
25
|
## Recommended Workflow
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
for your work: CLAUDE.md, .
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
|
|
27
|
+
1. If present, you might want to take a look at the following files that set additional guidelines
|
|
28
|
+
for your work: CLAUDE.md, .github/copilot-instructions.md. Use a find command to locate all of them at once.
|
|
29
|
+
If files corresponding to multiple AI systems are present, it's enough to read the ones for one of them.
|
|
30
|
+
2. Analyze the codebase by finding and reading relevant files.
|
|
31
|
+
3. Create a script to reproduce the issue
|
|
32
|
+
4. Edit the source code to resolve the issue
|
|
33
|
+
5. Verify your fix works by running your script again
|
|
34
|
+
6. Test edge cases to ensure your fix is robust
|
|
33
35
|
|
|
34
36
|
## Important Rules
|
|
35
37
|
|
|
@@ -140,6 +142,9 @@ agent:
|
|
|
140
142
|
<action>
|
|
141
143
|
```
|
|
142
144
|
</response_example>
|
|
145
|
+
|
|
146
|
+
Note: In rare cases, if you need to reference a similar format in your command, you might have
|
|
147
|
+
to proceed in two steps, first writing TRIPLEBACKTICKSBASH, then replacing them with ```bash.
|
|
143
148
|
step_limit: 0.
|
|
144
149
|
cost_limit: 3.
|
|
145
150
|
mode: confirm
|
|
@@ -24,6 +24,8 @@ class DockerEnvironmentConfig:
|
|
|
24
24
|
"""Path to the docker/container executable."""
|
|
25
25
|
run_args: list[str] = field(default_factory=list)
|
|
26
26
|
"""Additional arguments to pass to the docker/container executable."""
|
|
27
|
+
container_timeout: str = "2h"
|
|
28
|
+
"""Max duration to keep container running. Uses the same format as the sleep command."""
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
class DockerEnvironment:
|
|
@@ -49,7 +51,7 @@ class DockerEnvironment:
|
|
|
49
51
|
*self.config.run_args,
|
|
50
52
|
self.config.image,
|
|
51
53
|
"sleep",
|
|
52
|
-
|
|
54
|
+
self.config.container_timeout,
|
|
53
55
|
]
|
|
54
56
|
print(f"Starting container with command: {shlex.join(cmd)}")
|
|
55
57
|
result = subprocess.run(
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import logging
|
|
2
3
|
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
3
5
|
from typing import Any
|
|
4
6
|
|
|
5
7
|
import litellm
|
|
@@ -20,6 +22,7 @@ logger = logging.getLogger("litellm_model")
|
|
|
20
22
|
class LitellmModelConfig:
|
|
21
23
|
model_name: str
|
|
22
24
|
model_kwargs: dict[str, Any] = field(default_factory=dict)
|
|
25
|
+
litellm_model_registry: Path | None = None
|
|
23
26
|
|
|
24
27
|
|
|
25
28
|
class LitellmModel:
|
|
@@ -27,6 +30,8 @@ class LitellmModel:
|
|
|
27
30
|
self.config = LitellmModelConfig(**kwargs)
|
|
28
31
|
self.cost = 0.0
|
|
29
32
|
self.n_calls = 0
|
|
33
|
+
if self.config.litellm_model_registry is not None:
|
|
34
|
+
litellm.utils.register_model(json.loads(self.config.litellm_model_registry.read_text()))
|
|
30
35
|
|
|
31
36
|
@retry(
|
|
32
37
|
stop=stop_after_attempt(10),
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
|
-
"""Run mini-SWE-agent on
|
|
4
|
-
|
|
5
|
-
[not dim]
|
|
6
|
-
More information about the usage: [bold green]https://mini-swe-agent.com/latest/usage/swebench/[/bold green]
|
|
7
|
-
[/not dim]
|
|
8
|
-
"""
|
|
3
|
+
"""Run mini-SWE-agent on SWE-bench instances in batch mode."""
|
|
4
|
+
# Read this first: https://mini-swe-agent.com/latest/usage/swebench/ (usage docs)
|
|
9
5
|
|
|
10
6
|
import concurrent.futures
|
|
11
7
|
import json
|
|
@@ -28,6 +24,13 @@ from minisweagent.models import get_model
|
|
|
28
24
|
from minisweagent.run.extra.utils.batch_progress import RunBatchProgressManager
|
|
29
25
|
from minisweagent.run.utils.save import save_traj
|
|
30
26
|
|
|
27
|
+
_HELP_TEXT = """Run mini-SWE-agent on SWEBench instances.
|
|
28
|
+
|
|
29
|
+
[not dim]
|
|
30
|
+
More information about the usage: [bold green]https://mini-swe-agent.com/latest/usage/swebench/[/bold green]
|
|
31
|
+
[/not dim]
|
|
32
|
+
"""
|
|
33
|
+
|
|
31
34
|
app = typer.Typer(rich_markup_mode="rich", add_completion=False)
|
|
32
35
|
|
|
33
36
|
DATASET_MAPPING = {
|
|
@@ -168,7 +171,7 @@ def filter_instances(
|
|
|
168
171
|
return instances
|
|
169
172
|
|
|
170
173
|
|
|
171
|
-
@app.command()
|
|
174
|
+
@app.command(help=_HELP_TEXT)
|
|
172
175
|
def main(
|
|
173
176
|
subset: str = typer.Option("lite", "--subset", help="SWEBench subset to use or path to a dataset"),
|
|
174
177
|
split: str = typer.Option("dev", "--split", help="Dataset split"),
|
|
@@ -183,7 +186,6 @@ def main(
|
|
|
183
186
|
builtin_config_dir / "extra" / "swebench.yaml", "-c", "--config", help="Path to a config file"
|
|
184
187
|
),
|
|
185
188
|
) -> None:
|
|
186
|
-
"""Run mini-SWE-agent on SWEBench instances"""
|
|
187
189
|
dataset_path = DATASET_MAPPING.get(subset, subset)
|
|
188
190
|
print(f"Loading dataset {dataset_path}, split {split}...")
|
|
189
191
|
instances = list(load_dataset(dataset_path, split=split))
|
minisweagent/run/mini.py
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
|
-
"""Run mini-SWE-agent in your local environment.
|
|
4
|
-
|
|
5
|
-
[not dim]
|
|
6
|
-
There are two different user interfaces:
|
|
7
|
-
|
|
8
|
-
[bold green]mini[/bold green] Simple REPL-style interface
|
|
9
|
-
[bold green]mini -v[/bold green] Pager-style interface (Textual)
|
|
10
|
-
|
|
11
|
-
More information about the usage: [bold green]https://mini-swe-agent.com/latest/usage/mini/[/bold green]
|
|
12
|
-
[/not dim]
|
|
13
|
-
"""
|
|
3
|
+
"""Run mini-SWE-agent in your local environment. This is the default executable `mini`."""
|
|
4
|
+
# Read this first: https://mini-swe-agent.com/latest/usage/mini/ (usage)
|
|
14
5
|
|
|
15
6
|
import os
|
|
16
7
|
from pathlib import Path
|
|
@@ -33,9 +24,21 @@ from minisweagent.run.extra.config import configure_if_first_time
|
|
|
33
24
|
from minisweagent.run.utils.save import save_traj
|
|
34
25
|
|
|
35
26
|
DEFAULT_CONFIG = Path(os.getenv("MSWEA_MINI_CONFIG_PATH", builtin_config_dir / "mini.yaml"))
|
|
27
|
+
DEFAULT_OUTPUT = global_config_dir / "last_mini_run.traj.json"
|
|
36
28
|
console = Console(highlight=False)
|
|
37
29
|
app = typer.Typer(rich_markup_mode="rich")
|
|
38
30
|
prompt_session = PromptSession(history=FileHistory(global_config_dir / "mini_task_history.txt"))
|
|
31
|
+
_HELP_TEXT = """Run mini-SWE-agent in your local environment.
|
|
32
|
+
|
|
33
|
+
[not dim]
|
|
34
|
+
There are two different user interfaces:
|
|
35
|
+
|
|
36
|
+
[bold green]mini[/bold green] Simple REPL-style interface
|
|
37
|
+
[bold green]mini -v[/bold green] Pager-style interface (Textual)
|
|
38
|
+
|
|
39
|
+
More information about the usage: [bold green]https://mini-swe-agent.com/latest/usage/mini/[/bold green]
|
|
40
|
+
[/not dim]
|
|
41
|
+
"""
|
|
39
42
|
|
|
40
43
|
|
|
41
44
|
def run_interactive(model: Model, env: Environment, agent_config: dict, task: str, output: Path | None = None) -> Any:
|
|
@@ -68,7 +71,7 @@ def run_textual(model: Model, env: Environment, agent_config: dict, task: str, o
|
|
|
68
71
|
save_traj(agent_app.agent, output, exit_status=agent_app.exit_status, result=agent_app.result)
|
|
69
72
|
|
|
70
73
|
|
|
71
|
-
@app.command(help=
|
|
74
|
+
@app.command(help=_HELP_TEXT)
|
|
72
75
|
def main(
|
|
73
76
|
visual: bool = typer.Option(False, "-v", "--visual", help="Use visual (pager-style) UI (Textual)"),
|
|
74
77
|
model_name: str | None = typer.Option(
|
|
@@ -81,7 +84,7 @@ def main(
|
|
|
81
84
|
yolo: bool = typer.Option(False, "-y", "--yolo", help="Run without confirmation"),
|
|
82
85
|
cost_limit: float | None = typer.Option(None, "-l", "--cost-limit", help="Cost limit. Set to 0 to disable."),
|
|
83
86
|
config_spec: Path = typer.Option(DEFAULT_CONFIG, "-c", "--config", help="Path to config file"),
|
|
84
|
-
output: Path | None = typer.Option(
|
|
87
|
+
output: Path | None = typer.Option(DEFAULT_OUTPUT, "-o", "--output", help="Output trajectory file"),
|
|
85
88
|
exit_immediately: bool = typer.Option(
|
|
86
89
|
False, "--exit-immediately", help="Exit immediately when the agent wants to finish instead of prompting."
|
|
87
90
|
),
|
|
@@ -105,7 +108,7 @@ def main(
|
|
|
105
108
|
config["agent"]["mode"] = "confirm" if not yolo else "yolo"
|
|
106
109
|
if cost_limit:
|
|
107
110
|
config["agent"]["cost_limit"] = cost_limit
|
|
108
|
-
if
|
|
111
|
+
if exit_immediately:
|
|
109
112
|
config["agent"]["confirm_exit"] = False
|
|
110
113
|
model = get_model(model_name, config.get("model", {}))
|
|
111
114
|
env = LocalEnvironment(**config.get("env", {}))
|
minisweagent/run/utils/save.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|