strix-agent 0.1.9__py3-none-any.whl → 0.1.11__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.
- strix/agents/StrixAgent/strix_agent.py +18 -6
- strix/agents/StrixAgent/system_prompt.jinja +26 -7
- strix/agents/base_agent.py +3 -0
- strix/cli/app.py +3 -1
- strix/cli/main.py +85 -1
- strix/cli/tool_components/terminal_renderer.py +92 -60
- strix/llm/llm.py +3 -3
- strix/runtime/docker_runtime.py +204 -160
- strix/runtime/runtime.py +3 -2
- strix/runtime/tool_server.py +136 -28
- strix/tools/agents_graph/agents_graph_actions.py +4 -4
- strix/tools/agents_graph/agents_graph_actions_schema.xml +17 -1
- strix/tools/argument_parser.py +2 -1
- strix/tools/executor.py +3 -0
- strix/tools/terminal/__init__.py +2 -2
- strix/tools/terminal/terminal_actions.py +22 -40
- strix/tools/terminal/terminal_actions_schema.xml +113 -88
- strix/tools/terminal/terminal_manager.py +83 -123
- strix/tools/terminal/terminal_session.py +447 -0
- {strix_agent-0.1.9.dist-info → strix_agent-0.1.11.dist-info}/METADATA +4 -15
- {strix_agent-0.1.9.dist-info → strix_agent-0.1.11.dist-info}/RECORD +24 -24
- strix/tools/terminal/terminal_instance.py +0 -231
- {strix_agent-0.1.9.dist-info → strix_agent-0.1.11.dist-info}/LICENSE +0 -0
- {strix_agent-0.1.9.dist-info → strix_agent-0.1.11.dist-info}/WHEEL +0 -0
- {strix_agent-0.1.9.dist-info → strix_agent-0.1.11.dist-info}/entry_points.txt +0 -0
@@ -5,173 +5,133 @@ import sys
|
|
5
5
|
import threading
|
6
6
|
from typing import Any
|
7
7
|
|
8
|
-
from .
|
8
|
+
from .terminal_session import TerminalSession
|
9
9
|
|
10
10
|
|
11
11
|
class TerminalManager:
|
12
12
|
def __init__(self) -> None:
|
13
|
-
self.
|
13
|
+
self.sessions: dict[str, TerminalSession] = {}
|
14
14
|
self._lock = threading.Lock()
|
15
15
|
self.default_terminal_id = "default"
|
16
|
+
self.default_timeout = 30.0
|
16
17
|
|
17
18
|
self._register_cleanup_handlers()
|
18
19
|
|
19
|
-
def
|
20
|
-
self,
|
20
|
+
def execute_command(
|
21
|
+
self,
|
22
|
+
command: str,
|
23
|
+
is_input: bool = False,
|
24
|
+
timeout: float | None = None,
|
25
|
+
terminal_id: str | None = None,
|
26
|
+
no_enter: bool = False,
|
21
27
|
) -> dict[str, Any]:
|
22
28
|
if terminal_id is None:
|
23
29
|
terminal_id = self.default_terminal_id
|
24
30
|
|
25
|
-
|
26
|
-
if terminal_id in self.terminals:
|
27
|
-
raise ValueError(f"Terminal '{terminal_id}' already exists")
|
28
|
-
|
29
|
-
initial_command = None
|
30
|
-
if inputs:
|
31
|
-
command_parts: list[str] = []
|
32
|
-
for input_item in inputs:
|
33
|
-
if input_item == "Enter":
|
34
|
-
initial_command = " ".join(command_parts) + "\n"
|
35
|
-
break
|
36
|
-
if input_item.startswith("literal:"):
|
37
|
-
command_parts.append(input_item[8:])
|
38
|
-
elif input_item not in [
|
39
|
-
"Space",
|
40
|
-
"Tab",
|
41
|
-
"Backspace",
|
42
|
-
]:
|
43
|
-
command_parts.append(input_item)
|
44
|
-
|
45
|
-
try:
|
46
|
-
terminal = TerminalInstance(terminal_id, initial_command)
|
47
|
-
self.terminals[terminal_id] = terminal
|
48
|
-
|
49
|
-
if inputs and not initial_command:
|
50
|
-
terminal.send_input(inputs)
|
51
|
-
result = terminal.wait(2.0)
|
52
|
-
else:
|
53
|
-
result = terminal.wait(1.0)
|
54
|
-
|
55
|
-
result["message"] = f"Terminal '{terminal_id}' created successfully"
|
56
|
-
|
57
|
-
except (OSError, ValueError, RuntimeError) as e:
|
58
|
-
raise RuntimeError(f"Failed to create terminal '{terminal_id}': {e}") from e
|
59
|
-
else:
|
60
|
-
return result
|
61
|
-
|
62
|
-
def send_input(
|
63
|
-
self, terminal_id: str | None = None, inputs: list[str] | None = None
|
64
|
-
) -> dict[str, Any]:
|
65
|
-
if terminal_id is None:
|
66
|
-
terminal_id = self.default_terminal_id
|
67
|
-
|
68
|
-
if not inputs:
|
69
|
-
raise ValueError("No inputs provided")
|
70
|
-
|
71
|
-
with self._lock:
|
72
|
-
if terminal_id not in self.terminals:
|
73
|
-
raise ValueError(f"Terminal '{terminal_id}' not found")
|
74
|
-
|
75
|
-
terminal = self.terminals[terminal_id]
|
31
|
+
session = self._get_or_create_session(terminal_id)
|
76
32
|
|
77
33
|
try:
|
78
|
-
|
79
|
-
result = terminal.wait(2.0)
|
80
|
-
result["message"] = f"Input sent to terminal '{terminal_id}'"
|
81
|
-
except (OSError, ValueError, RuntimeError) as e:
|
82
|
-
raise RuntimeError(f"Failed to send input to terminal '{terminal_id}': {e}") from e
|
83
|
-
else:
|
84
|
-
return result
|
34
|
+
result = session.execute(command, is_input, timeout or self.default_timeout, no_enter)
|
85
35
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
raise ValueError(f"Terminal '{terminal_id}' not found")
|
36
|
+
return {
|
37
|
+
"content": result["content"],
|
38
|
+
"command": command,
|
39
|
+
"terminal_id": terminal_id,
|
40
|
+
"status": result["status"],
|
41
|
+
"exit_code": result.get("exit_code"),
|
42
|
+
"working_dir": result.get("working_dir"),
|
43
|
+
}
|
95
44
|
|
96
|
-
|
45
|
+
except RuntimeError as e:
|
46
|
+
return {
|
47
|
+
"error": str(e),
|
48
|
+
"command": command,
|
49
|
+
"terminal_id": terminal_id,
|
50
|
+
"content": "",
|
51
|
+
"status": "error",
|
52
|
+
"exit_code": None,
|
53
|
+
"working_dir": None,
|
54
|
+
}
|
55
|
+
except OSError as e:
|
56
|
+
return {
|
57
|
+
"error": f"System error: {e}",
|
58
|
+
"command": command,
|
59
|
+
"terminal_id": terminal_id,
|
60
|
+
"content": "",
|
61
|
+
"status": "error",
|
62
|
+
"exit_code": None,
|
63
|
+
"working_dir": None,
|
64
|
+
}
|
97
65
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
else:
|
104
|
-
return result
|
66
|
+
def _get_or_create_session(self, terminal_id: str) -> TerminalSession:
|
67
|
+
with self._lock:
|
68
|
+
if terminal_id not in self.sessions:
|
69
|
+
self.sessions[terminal_id] = TerminalSession(terminal_id)
|
70
|
+
return self.sessions[terminal_id]
|
105
71
|
|
106
|
-
def
|
72
|
+
def close_session(self, terminal_id: str | None = None) -> dict[str, Any]:
|
107
73
|
if terminal_id is None:
|
108
74
|
terminal_id = self.default_terminal_id
|
109
75
|
|
110
76
|
with self._lock:
|
111
|
-
if terminal_id not in self.
|
112
|
-
|
77
|
+
if terminal_id not in self.sessions:
|
78
|
+
return {
|
79
|
+
"terminal_id": terminal_id,
|
80
|
+
"message": f"Terminal '{terminal_id}' not found",
|
81
|
+
"status": "not_found",
|
82
|
+
}
|
113
83
|
|
114
|
-
|
84
|
+
session = self.sessions.pop(terminal_id)
|
115
85
|
|
116
86
|
try:
|
117
|
-
|
118
|
-
except (
|
119
|
-
|
87
|
+
session.close()
|
88
|
+
except (RuntimeError, OSError) as e:
|
89
|
+
return {
|
90
|
+
"terminal_id": terminal_id,
|
91
|
+
"error": f"Failed to close terminal '{terminal_id}': {e}",
|
92
|
+
"status": "error",
|
93
|
+
}
|
120
94
|
else:
|
121
95
|
return {
|
122
96
|
"terminal_id": terminal_id,
|
123
97
|
"message": f"Terminal '{terminal_id}' closed successfully",
|
124
|
-
"
|
125
|
-
"is_running": False,
|
98
|
+
"status": "closed",
|
126
99
|
}
|
127
100
|
|
128
|
-
def
|
129
|
-
if terminal_id is None:
|
130
|
-
terminal_id = self.default_terminal_id
|
131
|
-
|
132
|
-
with self._lock:
|
133
|
-
if terminal_id not in self.terminals:
|
134
|
-
raise ValueError(f"Terminal '{terminal_id}' not found")
|
135
|
-
|
136
|
-
terminal = self.terminals[terminal_id]
|
137
|
-
|
138
|
-
return terminal.get_snapshot()
|
139
|
-
|
140
|
-
def list_terminals(self) -> dict[str, Any]:
|
101
|
+
def list_sessions(self) -> dict[str, Any]:
|
141
102
|
with self._lock:
|
142
|
-
|
143
|
-
for tid,
|
144
|
-
|
145
|
-
"is_running":
|
146
|
-
"
|
147
|
-
"process_id": terminal.process.pid if terminal.process else None,
|
103
|
+
session_info: dict[str, dict[str, Any]] = {}
|
104
|
+
for tid, session in self.sessions.items():
|
105
|
+
session_info[tid] = {
|
106
|
+
"is_running": session.is_running(),
|
107
|
+
"working_dir": session.get_working_dir(),
|
148
108
|
}
|
149
109
|
|
150
|
-
return {"
|
110
|
+
return {"sessions": session_info, "total_count": len(session_info)}
|
151
111
|
|
152
|
-
def
|
112
|
+
def cleanup_dead_sessions(self) -> None:
|
153
113
|
with self._lock:
|
154
|
-
|
155
|
-
for tid,
|
156
|
-
if not
|
157
|
-
|
114
|
+
dead_sessions: list[str] = []
|
115
|
+
for tid, session in self.sessions.items():
|
116
|
+
if not session.is_running():
|
117
|
+
dead_sessions.append(tid)
|
158
118
|
|
159
|
-
for tid in
|
160
|
-
|
119
|
+
for tid in dead_sessions:
|
120
|
+
session = self.sessions.pop(tid)
|
161
121
|
with contextlib.suppress(Exception):
|
162
|
-
|
122
|
+
session.close()
|
163
123
|
|
164
|
-
def
|
124
|
+
def close_all_sessions(self) -> None:
|
165
125
|
with self._lock:
|
166
|
-
|
167
|
-
self.
|
126
|
+
sessions_to_close = list(self.sessions.values())
|
127
|
+
self.sessions.clear()
|
168
128
|
|
169
|
-
for
|
129
|
+
for session in sessions_to_close:
|
170
130
|
with contextlib.suppress(Exception):
|
171
|
-
|
131
|
+
session.close()
|
172
132
|
|
173
133
|
def _register_cleanup_handlers(self) -> None:
|
174
|
-
atexit.register(self.
|
134
|
+
atexit.register(self.close_all_sessions)
|
175
135
|
|
176
136
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
177
137
|
signal.signal(signal.SIGINT, self._signal_handler)
|
@@ -180,7 +140,7 @@ class TerminalManager:
|
|
180
140
|
signal.signal(signal.SIGHUP, self._signal_handler)
|
181
141
|
|
182
142
|
def _signal_handler(self, _signum: int, _frame: Any) -> None:
|
183
|
-
self.
|
143
|
+
self.close_all_sessions()
|
184
144
|
sys.exit(0)
|
185
145
|
|
186
146
|
|