mini-swe-agent 1.3.0__tar.gz → 1.4.0__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.
Files changed (52) hide show
  1. {mini_swe_agent-1.3.0/src/mini_swe_agent.egg-info → mini_swe_agent-1.4.0}/PKG-INFO +1 -1
  2. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0/src/mini_swe_agent.egg-info}/PKG-INFO +1 -1
  3. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/__init__.py +1 -1
  4. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/agents/interactive_textual.py +168 -82
  5. mini_swe_agent-1.4.0/src/minisweagent/config/mini.tcss +85 -0
  6. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/environments/docker.py +3 -1
  7. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/run/mini.py +3 -2
  8. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/run/utils/save.py +1 -0
  9. mini_swe_agent-1.3.0/src/minisweagent/config/mini.tcss +0 -128
  10. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/LICENSE.md +0 -0
  11. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/README.md +0 -0
  12. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/pyproject.toml +0 -0
  13. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/setup.cfg +0 -0
  14. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/mini_swe_agent.egg-info/SOURCES.txt +0 -0
  15. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/mini_swe_agent.egg-info/dependency_links.txt +0 -0
  16. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/mini_swe_agent.egg-info/entry_points.txt +0 -0
  17. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/mini_swe_agent.egg-info/requires.txt +0 -0
  18. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/mini_swe_agent.egg-info/top_level.txt +0 -0
  19. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/__main__.py +0 -0
  20. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/agents/__init__.py +0 -0
  21. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/agents/default.py +0 -0
  22. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/agents/interactive.py +0 -0
  23. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/config/README.md +0 -0
  24. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/config/__init__.py +0 -0
  25. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/config/default.yaml +0 -0
  26. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/config/extra/__init__.py +0 -0
  27. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/config/extra/swebench.yaml +0 -0
  28. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/config/github_issue.yaml +0 -0
  29. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/config/mini.yaml +0 -0
  30. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/environments/__init__.py +0 -0
  31. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/environments/extra/__init__.py +0 -0
  32. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/environments/extra/swerex_docker.py +0 -0
  33. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/environments/local.py +0 -0
  34. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/environments/singularity.py +0 -0
  35. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/models/__init__.py +0 -0
  36. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/models/anthropic.py +0 -0
  37. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/models/litellm_model.py +0 -0
  38. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/models/test_models.py +0 -0
  39. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/models/utils/cache_control.py +0 -0
  40. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/models/utils/key_per_thread.py +0 -0
  41. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/py.typed +0 -0
  42. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/run/__init__.py +0 -0
  43. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/run/extra/__init__.py +0 -0
  44. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/run/extra/config.py +0 -0
  45. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/run/extra/swebench.py +0 -0
  46. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/run/extra/swebench_single.py +0 -0
  47. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/run/extra/utils/batch_progress.py +0 -0
  48. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/run/github_issue.py +0 -0
  49. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/run/hello_world.py +0 -0
  50. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/run/inspector.py +0 -0
  51. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/run/mini_extra.py +0 -0
  52. {mini_swe_agent-1.3.0 → mini_swe_agent-1.4.0}/src/minisweagent/run/utils/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-swe-agent
3
- Version: 1.3.0
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-swe-agent
3
- Version: 1.3.0
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
@@ -8,7 +8,7 @@ This file provides:
8
8
  unless you want the static type checking.
9
9
  """
10
10
 
11
- __version__ = "1.3.0"
11
+ __version__ = "1.4.0"
12
12
 
13
13
  import os
14
14
  from pathlib import Path
@@ -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 == "confirm" and not any(
59
- re.match(r, action["action"]) for r in self.config.whitelist_actions
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
- if result := self.app.confirmation_container.request_confirmation(action["action"]):
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 ConfirmationPromptContainer(Container):
122
+ class SmartInputContainer(Container):
91
123
  def __init__(self, app: "AgentApp"):
92
- """This class is responsible for handling the action execution confirmation."""
93
- super().__init__(id="confirmation-container")
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.rejecting = False
127
+ self._multiline_mode = False
96
128
  self.can_focus = True
97
129
  self.display = False
98
130
 
99
- self._pending_action: str | None = None
100
- self._confirmation_event = threading.Event()
101
- self._confirmation_result: str | None = None
131
+ self.pending_prompt: str | None = None
132
+ self._input_event = threading.Event()
133
+ self._input_result: str | None = None
102
134
 
103
- def compose(self) -> ComposeResult:
104
- yield Static(
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
- yield TextArea(id="rejection-input")
109
- rejection_help = Static(
110
- "Press [bold]Ctrl+D[/bold] to submit rejection message",
111
- id="rejection-help",
112
- classes="rejection-help",
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 _complete_confirmation(self, rejection_message: str | None):
127
- """Internal method to complete the confirmation process."""
128
- self._confirmation_result = rejection_message
129
- self._pending_action = None
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.rejecting = False
132
- rejection_input = self.query_one("#rejection-input", TextArea)
133
- rejection_input.display = False
134
- rejection_input.text = ""
135
- rejection_help = self.query_one("#rejection-help", Static)
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
- if self.rejecting and event.key == "ctrl+d":
223
+ """Handle key events."""
224
+ if event.key == "ctrl+t" and not self._multiline_mode:
145
225
  event.prevent_default()
146
- rejection_input = self.query_one("#rejection-input", TextArea)
147
- self._complete_confirmation(rejection_input.text)
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.confirmation_container = ConfirmationPromptContainer(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.query_one(VerticalScroll).scroll_to(y=0, animate=False)
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 VerticalScroll():
211
- yield Vertical(id="content")
212
- yield self.confirmation_container
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
- vs = self.query_one(VerticalScroll)
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.confirmation_container._pending_action is not None:
271
- self.agent_state = "AWAITING_CONFIRMATION"
272
- self.confirmation_container.display = (
273
- self.confirmation_container._pending_action is not None and self.i_step == len(items) - 1
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.confirmation_container._complete_confirmation(None)
298
- self.notify("YOLO mode enabled - actions will execute immediately")
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 - actions will require confirmation")
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
- vs = self.query_one(VerticalScroll)
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
- vs = self.query_one(VerticalScroll)
322
- vs.scroll_to(y=vs.scroll_target_y - 15)
408
+ self._vscroll.scroll_to(y=self._vscroll.scroll_target_y - 15)
@@ -0,0 +1,85 @@
1
+ Screen {
2
+ layout: grid;
3
+ grid-size: 1;
4
+ grid-rows: auto 1fr auto;
5
+ }
6
+
7
+ #main {
8
+ height: 100%;
9
+ padding: 1;
10
+ layout: vertical;
11
+ }
12
+
13
+ Footer {
14
+ dock: bottom;
15
+ content-align: center middle;
16
+ }
17
+
18
+ #content {
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;
46
+ }
47
+
48
+ .message-container {
49
+ margin: 1;
50
+ padding: 1;
51
+ background: $surface;
52
+ height: auto;
53
+ width: 100%;
54
+ }
55
+
56
+ .message-header {
57
+ text-align: left;
58
+ color: $primary;
59
+ padding: 0 1;
60
+ text-style: bold;
61
+ }
62
+
63
+ .input-request-header {
64
+ color: $warning;
65
+ }
66
+
67
+ .message-content {
68
+ margin-top: 1;
69
+ padding: 0 1;
70
+ }
71
+
72
+ Header.running {
73
+ background: $error;
74
+ }
75
+
76
+ .button-container {
77
+ layout: horizontal;
78
+ align-horizontal: center;
79
+ margin-top: 1;
80
+ }
81
+
82
+ .button-container Button {
83
+ margin: 0 1;
84
+ min-width: 10;
85
+ }
@@ -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
- "infinity", # Keep container running
54
+ self.config.container_timeout,
53
55
  ]
54
56
  print(f"Starting container with command: {shlex.join(cmd)}")
55
57
  result = subprocess.run(
@@ -24,6 +24,7 @@ from minisweagent.run.extra.config import configure_if_first_time
24
24
  from minisweagent.run.utils.save import save_traj
25
25
 
26
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"
27
28
  console = Console(highlight=False)
28
29
  app = typer.Typer(rich_markup_mode="rich")
29
30
  prompt_session = PromptSession(history=FileHistory(global_config_dir / "mini_task_history.txt"))
@@ -83,7 +84,7 @@ def main(
83
84
  yolo: bool = typer.Option(False, "-y", "--yolo", help="Run without confirmation"),
84
85
  cost_limit: float | None = typer.Option(None, "-l", "--cost-limit", help="Cost limit. Set to 0 to disable."),
85
86
  config_spec: Path = typer.Option(DEFAULT_CONFIG, "-c", "--config", help="Path to config file"),
86
- output: Path | None = typer.Option(None, "-o", "--output", help="Output file"),
87
+ output: Path | None = typer.Option(DEFAULT_OUTPUT, "-o", "--output", help="Output trajectory file"),
87
88
  exit_immediately: bool = typer.Option(
88
89
  False, "--exit-immediately", help="Exit immediately when the agent wants to finish instead of prompting."
89
90
  ),
@@ -107,7 +108,7 @@ def main(
107
108
  config["agent"]["mode"] = "confirm" if not yolo else "yolo"
108
109
  if cost_limit:
109
110
  config["agent"]["cost_limit"] = cost_limit
110
- if not visual and exit_immediately:
111
+ if exit_immediately:
111
112
  config["agent"]["confirm_exit"] = False
112
113
  model = get_model(model_name, config.get("model", {}))
113
114
  env = LocalEnvironment(**config.get("env", {}))
@@ -23,6 +23,7 @@ def save_traj(
23
23
  },
24
24
  },
25
25
  "messages": [],
26
+ "trajectory_format": "mini-swe-agent-1",
26
27
  } | kwargs
27
28
  if agent is not None:
28
29
  data["info"]["model_stats"]["instance_cost"] = agent.model.cost
@@ -1,128 +0,0 @@
1
- Screen {
2
- layout: grid;
3
- grid-size: 1;
4
- grid-rows: 1fr 8 1fr;
5
- }
6
-
7
- #main {
8
- height: 100%;
9
- padding: 1;
10
- }
11
-
12
- Footer {
13
- dock: bottom;
14
- content-align: center middle;
15
- }
16
-
17
- #content {
18
- height: auto;
19
- }
20
-
21
- .message-container {
22
- margin: 1;
23
- padding: 1;
24
- background: $surface;
25
- height: auto;
26
- width: 100%;
27
- }
28
-
29
- .message-header {
30
- text-align: left;
31
- color: $primary;
32
- padding: 0 1;
33
- text-style: bold;
34
- }
35
-
36
- .message-content {
37
- margin-top: 1;
38
- padding: 0 1;
39
- }
40
-
41
- Header.running {
42
- background: $error;
43
- }
44
-
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
- .button-container {
69
- layout: horizontal;
70
- align-horizontal: center;
71
- margin-top: 1;
72
- }
73
-
74
- .button-container Button {
75
- margin: 0 1;
76
- 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
- }
File without changes
File without changes