strix-agent 0.4.0__py3-none-any.whl → 0.6.2__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 +3 -3
- strix/agents/StrixAgent/system_prompt.jinja +30 -26
- strix/agents/base_agent.py +159 -75
- strix/agents/state.py +5 -2
- strix/config/__init__.py +12 -0
- strix/config/config.py +172 -0
- strix/interface/assets/tui_styles.tcss +195 -230
- strix/interface/cli.py +16 -41
- strix/interface/main.py +151 -74
- strix/interface/streaming_parser.py +119 -0
- strix/interface/tool_components/__init__.py +4 -0
- strix/interface/tool_components/agent_message_renderer.py +190 -0
- strix/interface/tool_components/agents_graph_renderer.py +54 -38
- strix/interface/tool_components/base_renderer.py +68 -36
- strix/interface/tool_components/browser_renderer.py +106 -91
- strix/interface/tool_components/file_edit_renderer.py +117 -36
- strix/interface/tool_components/finish_renderer.py +43 -10
- strix/interface/tool_components/notes_renderer.py +63 -38
- strix/interface/tool_components/proxy_renderer.py +133 -92
- strix/interface/tool_components/python_renderer.py +121 -8
- strix/interface/tool_components/registry.py +19 -12
- strix/interface/tool_components/reporting_renderer.py +196 -28
- strix/interface/tool_components/scan_info_renderer.py +22 -19
- strix/interface/tool_components/terminal_renderer.py +270 -90
- strix/interface/tool_components/thinking_renderer.py +8 -6
- strix/interface/tool_components/todo_renderer.py +225 -0
- strix/interface/tool_components/user_message_renderer.py +26 -19
- strix/interface/tool_components/web_search_renderer.py +7 -6
- strix/interface/tui.py +907 -262
- strix/interface/utils.py +236 -4
- strix/llm/__init__.py +6 -2
- strix/llm/config.py +8 -5
- strix/llm/dedupe.py +217 -0
- strix/llm/llm.py +209 -356
- strix/llm/memory_compressor.py +6 -5
- strix/llm/utils.py +17 -8
- strix/runtime/__init__.py +12 -3
- strix/runtime/docker_runtime.py +121 -202
- strix/runtime/tool_server.py +55 -95
- strix/skills/README.md +64 -0
- strix/skills/__init__.py +110 -0
- strix/{prompts → skills}/frameworks/nextjs.jinja +26 -0
- strix/skills/scan_modes/deep.jinja +145 -0
- strix/skills/scan_modes/quick.jinja +63 -0
- strix/skills/scan_modes/standard.jinja +91 -0
- strix/telemetry/README.md +38 -0
- strix/telemetry/__init__.py +7 -1
- strix/telemetry/posthog.py +137 -0
- strix/telemetry/tracer.py +194 -54
- strix/tools/__init__.py +11 -4
- strix/tools/agents_graph/agents_graph_actions.py +20 -21
- strix/tools/agents_graph/agents_graph_actions_schema.xml +8 -8
- strix/tools/browser/browser_actions.py +10 -6
- strix/tools/browser/browser_actions_schema.xml +6 -1
- strix/tools/browser/browser_instance.py +96 -48
- strix/tools/browser/tab_manager.py +121 -102
- strix/tools/context.py +12 -0
- strix/tools/executor.py +63 -4
- strix/tools/file_edit/file_edit_actions.py +6 -3
- strix/tools/file_edit/file_edit_actions_schema.xml +45 -3
- strix/tools/finish/finish_actions.py +80 -105
- strix/tools/finish/finish_actions_schema.xml +121 -14
- strix/tools/notes/notes_actions.py +6 -33
- strix/tools/notes/notes_actions_schema.xml +50 -46
- strix/tools/proxy/proxy_actions.py +14 -2
- strix/tools/proxy/proxy_actions_schema.xml +0 -1
- strix/tools/proxy/proxy_manager.py +28 -16
- strix/tools/python/python_actions.py +2 -2
- strix/tools/python/python_actions_schema.xml +9 -1
- strix/tools/python/python_instance.py +39 -37
- strix/tools/python/python_manager.py +43 -31
- strix/tools/registry.py +73 -12
- strix/tools/reporting/reporting_actions.py +218 -31
- strix/tools/reporting/reporting_actions_schema.xml +256 -8
- strix/tools/terminal/terminal_actions.py +2 -2
- strix/tools/terminal/terminal_actions_schema.xml +6 -0
- strix/tools/terminal/terminal_manager.py +41 -30
- strix/tools/thinking/thinking_actions_schema.xml +27 -25
- strix/tools/todo/__init__.py +18 -0
- strix/tools/todo/todo_actions.py +568 -0
- strix/tools/todo/todo_actions_schema.xml +225 -0
- strix/utils/__init__.py +0 -0
- strix/utils/resource_paths.py +13 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/METADATA +90 -65
- strix_agent-0.6.2.dist-info/RECORD +134 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/WHEEL +1 -1
- strix/llm/request_queue.py +0 -87
- strix/prompts/README.md +0 -64
- strix/prompts/__init__.py +0 -109
- strix_agent-0.4.0.dist-info/RECORD +0 -118
- /strix/{prompts → skills}/cloud/.gitkeep +0 -0
- /strix/{prompts → skills}/coordination/root_agent.jinja +0 -0
- /strix/{prompts → skills}/custom/.gitkeep +0 -0
- /strix/{prompts → skills}/frameworks/fastapi.jinja +0 -0
- /strix/{prompts → skills}/protocols/graphql.jinja +0 -0
- /strix/{prompts → skills}/reconnaissance/.gitkeep +0 -0
- /strix/{prompts → skills}/technologies/firebase_firestore.jinja +0 -0
- /strix/{prompts → skills}/technologies/supabase.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/authentication_jwt.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/broken_function_level_authorization.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/business_logic.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/csrf.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/idor.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/information_disclosure.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/insecure_file_uploads.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/mass_assignment.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/open_redirect.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/path_traversal_lfi_rfi.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/race_conditions.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/rce.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/sql_injection.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/ssrf.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/subdomain_takeover.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/xss.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/xxe.jinja +0 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/entry_points.txt +0 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info/licenses}/LICENSE +0 -0
|
@@ -8,13 +8,13 @@ class StrixAgent(BaseAgent):
|
|
|
8
8
|
max_iterations = 300
|
|
9
9
|
|
|
10
10
|
def __init__(self, config: dict[str, Any]):
|
|
11
|
-
|
|
11
|
+
default_skills = []
|
|
12
12
|
|
|
13
13
|
state = config.get("state")
|
|
14
14
|
if state is None or (hasattr(state, "parent_id") and state.parent_id is None):
|
|
15
|
-
|
|
15
|
+
default_skills = ["root_agent"]
|
|
16
16
|
|
|
17
|
-
self.default_llm_config = LLMConfig(
|
|
17
|
+
self.default_llm_config = LLMConfig(skills=default_skills)
|
|
18
18
|
|
|
19
19
|
super().__init__(config)
|
|
20
20
|
|
|
@@ -10,15 +10,15 @@ You follow all instructions and rules provided to you exactly as written in the
|
|
|
10
10
|
|
|
11
11
|
<communication_rules>
|
|
12
12
|
CLI OUTPUT:
|
|
13
|
-
-
|
|
14
|
-
-
|
|
13
|
+
- You may use simple markdown: **bold**, *italic*, `code`, ~~strikethrough~~, [links](url), and # headers
|
|
14
|
+
- Do NOT use complex markdown like bullet lists, numbered lists, or tables
|
|
15
15
|
- Use line breaks and indentation for structure
|
|
16
16
|
- NEVER use "Strix" or any identifiable names/markers in HTTP requests, payloads, user-agents, or any inputs
|
|
17
17
|
|
|
18
18
|
INTER-AGENT MESSAGES:
|
|
19
|
-
- NEVER echo inter_agent_message or agent_completion_report
|
|
20
|
-
- Process these internally without displaying
|
|
21
|
-
- NEVER echo agent_identity
|
|
19
|
+
- NEVER echo inter_agent_message or agent_completion_report blocks that are sent to you in your output.
|
|
20
|
+
- Process these internally without displaying them
|
|
21
|
+
- NEVER echo agent_identity blocks; treat them as internal metadata for identity only. Do not include them in outputs or tool calls.
|
|
22
22
|
- Minimize inter-agent messaging: only message when essential for coordination or assistance; avoid routine status updates; batch non-urgent information; prefer parent/child completion flows and shared artifacts over messaging
|
|
23
23
|
|
|
24
24
|
AUTONOMOUS BEHAVIOR:
|
|
@@ -134,6 +134,7 @@ VALIDATION REQUIREMENTS:
|
|
|
134
134
|
- Keep going until you find something that matters
|
|
135
135
|
- A vulnerability is ONLY considered reported when a reporting agent uses create_vulnerability_report with full details. Mentions in agent_finish, finish_scan, or generic messages are NOT sufficient
|
|
136
136
|
- Do NOT patch/fix before reporting: first create the vulnerability report via create_vulnerability_report (by the reporting agent). Only after reporting is completed should fixing/patching proceed
|
|
137
|
+
- DEDUPLICATION: The create_vulnerability_report tool uses LLM-based deduplication. If it rejects your report as a duplicate, DO NOT attempt to re-submit the same vulnerability. Accept the rejection and move on to testing other areas. The vulnerability has already been reported by another agent
|
|
137
138
|
</execution_guidelines>
|
|
138
139
|
|
|
139
140
|
<vulnerability_focus>
|
|
@@ -263,25 +264,25 @@ CRITICAL RULES:
|
|
|
263
264
|
- **ONE AGENT = ONE TASK** - Don't let agents do multiple unrelated jobs
|
|
264
265
|
- **SPAWN REACTIVELY** - Create new agents based on what you discover
|
|
265
266
|
- **ONLY REPORTING AGENTS** can use create_vulnerability_report tool
|
|
266
|
-
- **AGENT SPECIALIZATION MANDATORY** - Each agent must be highly specialized; prefer 1–3
|
|
267
|
+
- **AGENT SPECIALIZATION MANDATORY** - Each agent must be highly specialized; prefer 1–3 skills, up to 5 for complex contexts
|
|
267
268
|
- **NO GENERIC AGENTS** - Avoid creating broad, multi-purpose agents that dilute focus
|
|
268
269
|
|
|
269
270
|
AGENT SPECIALIZATION EXAMPLES:
|
|
270
271
|
|
|
271
272
|
GOOD SPECIALIZATION:
|
|
272
|
-
- "SQLi Validation Agent" with
|
|
273
|
-
- "XSS Discovery Agent" with
|
|
274
|
-
- "Auth Testing Agent" with
|
|
275
|
-
- "SSRF + XXE Agent" with
|
|
273
|
+
- "SQLi Validation Agent" with skills: sql_injection
|
|
274
|
+
- "XSS Discovery Agent" with skills: xss
|
|
275
|
+
- "Auth Testing Agent" with skills: authentication_jwt, business_logic
|
|
276
|
+
- "SSRF + XXE Agent" with skills: ssrf, xxe, rce (related attack vectors)
|
|
276
277
|
|
|
277
278
|
BAD SPECIALIZATION:
|
|
278
|
-
- "General Web Testing Agent" with
|
|
279
|
-
- "Everything Agent" with
|
|
280
|
-
- Any agent with more than 5
|
|
279
|
+
- "General Web Testing Agent" with skills: sql_injection, xss, csrf, ssrf, authentication_jwt (too broad)
|
|
280
|
+
- "Everything Agent" with skills: all available skills (completely unfocused)
|
|
281
|
+
- Any agent with more than 5 skills (violates constraints)
|
|
281
282
|
|
|
282
283
|
FOCUS PRINCIPLES:
|
|
283
284
|
- Each agent should have deep expertise in 1-3 related vulnerability types
|
|
284
|
-
- Agents with single
|
|
285
|
+
- Agents with single skills have the deepest specialization
|
|
285
286
|
- Related vulnerabilities (like SSRF+XXE or Auth+Business Logic) can be combined
|
|
286
287
|
- Never create "kitchen sink" agents that try to do everything
|
|
287
288
|
|
|
@@ -300,36 +301,39 @@ PERSISTENCE IS MANDATORY:
|
|
|
300
301
|
</multi_agent_system>
|
|
301
302
|
|
|
302
303
|
<tool_usage>
|
|
303
|
-
Tool
|
|
304
|
+
Tool call format:
|
|
304
305
|
<function=tool_name>
|
|
305
306
|
<parameter=param_name>value</parameter>
|
|
306
307
|
</function>
|
|
307
308
|
|
|
308
309
|
CRITICAL RULES:
|
|
309
310
|
0. While active in the agent loop, EVERY message you output MUST be a single tool call. Do not send plain text-only responses.
|
|
310
|
-
1.
|
|
311
|
+
1. Exactly one tool call per message — never include more than one <function>...</function> block in a single LLM message.
|
|
311
312
|
2. Tool call must be last in message
|
|
312
|
-
3.
|
|
313
|
-
4. Use ONLY the exact
|
|
314
|
-
5.
|
|
313
|
+
3. EVERY tool call MUST end with </function>. This is MANDATORY. Never omit the closing tag. End your response immediately after </function>.
|
|
314
|
+
4. Use ONLY the exact format shown above. NEVER use JSON/YAML/INI or any other syntax for tools or parameters.
|
|
315
|
+
5. When sending ANY multi-line content in tool parameters, use real newlines (actual line breaks). Do NOT emit literal "\n" sequences. Literal "\n" instead of real line breaks will cause tools to fail.
|
|
316
|
+
6. Tool names must match exactly the tool "name" defined (no module prefixes, dots, or variants).
|
|
315
317
|
- Correct: <function=think> ... </function>
|
|
316
318
|
- Incorrect: <thinking_tools.think> ... </function>
|
|
317
319
|
- Incorrect: <think> ... </think>
|
|
318
320
|
- Incorrect: {"think": {...}}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
+
7. Parameters must use <parameter=param_name>value</parameter> exactly. Do NOT pass parameters as JSON or key:value lines. Do NOT add quotes/braces around values.
|
|
322
|
+
8. Do NOT wrap tool calls in markdown/code fences or add any text before or after the tool block.
|
|
321
323
|
|
|
322
324
|
Example (agent creation tool):
|
|
323
325
|
<function=create_agent>
|
|
324
326
|
<parameter=task>Perform targeted XSS testing on the search endpoint</parameter>
|
|
325
327
|
<parameter=name>XSS Discovery Agent</parameter>
|
|
326
|
-
<parameter=
|
|
328
|
+
<parameter=skills>xss</parameter>
|
|
327
329
|
</function>
|
|
328
330
|
|
|
329
331
|
SPRAYING EXECUTION NOTE:
|
|
330
332
|
- When performing large payload sprays or fuzzing, encapsulate the entire spraying loop inside a single python or terminal tool call (e.g., a Python script using asyncio/aiohttp). Do not issue one tool call per payload.
|
|
331
333
|
- Favor batch-mode CLI tools (sqlmap, ffuf, nuclei, zaproxy, arjun) where appropriate and check traffic via the proxy when beneficial
|
|
332
334
|
|
|
335
|
+
REMINDER: Always close each tool call with </function> before going into the next. Incomplete tool calls will fail.
|
|
336
|
+
|
|
333
337
|
{{ get_tools_prompt() }}
|
|
334
338
|
</tool_usage>
|
|
335
339
|
|
|
@@ -392,12 +396,12 @@ Directories:
|
|
|
392
396
|
Default user: pentester (sudo available)
|
|
393
397
|
</environment>
|
|
394
398
|
|
|
395
|
-
{% if
|
|
399
|
+
{% if loaded_skill_names %}
|
|
396
400
|
<specialized_knowledge>
|
|
397
|
-
{# Dynamic
|
|
401
|
+
{# Dynamic skills loaded based on agent specialization #}
|
|
398
402
|
|
|
399
|
-
{% for
|
|
400
|
-
{{
|
|
403
|
+
{% for skill_name in loaded_skill_names %}
|
|
404
|
+
{{ get_skill(skill_name) }}
|
|
401
405
|
|
|
402
406
|
{% endfor %}
|
|
403
407
|
</specialized_knowledge>
|
strix/agents/base_agent.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import contextlib
|
|
3
3
|
import logging
|
|
4
|
-
from pathlib import Path
|
|
5
4
|
from typing import TYPE_CHECKING, Any, Optional
|
|
6
5
|
|
|
7
6
|
|
|
@@ -16,7 +15,9 @@ from jinja2 import (
|
|
|
16
15
|
|
|
17
16
|
from strix.llm import LLM, LLMConfig, LLMRequestFailedError
|
|
18
17
|
from strix.llm.utils import clean_content
|
|
18
|
+
from strix.runtime import SandboxInitializationError
|
|
19
19
|
from strix.tools import process_tool_invocations
|
|
20
|
+
from strix.utils.resource_paths import get_strix_resource_path
|
|
20
21
|
|
|
21
22
|
from .state import AgentState
|
|
22
23
|
|
|
@@ -34,8 +35,7 @@ class AgentMeta(type):
|
|
|
34
35
|
if name == "BaseAgent":
|
|
35
36
|
return new_cls
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
prompt_dir = agents_dir / name
|
|
38
|
+
prompt_dir = get_strix_resource_path("agents", name)
|
|
39
39
|
|
|
40
40
|
new_cls.agent_name = name
|
|
41
41
|
new_cls.jinja_env = Environment(
|
|
@@ -65,20 +65,21 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
65
65
|
self.llm_config = config.get("llm_config", self.default_llm_config)
|
|
66
66
|
if self.llm_config is None:
|
|
67
67
|
raise ValueError("llm_config is required but not provided")
|
|
68
|
-
self.llm = LLM(self.llm_config, agent_name=self.agent_name)
|
|
69
|
-
|
|
70
68
|
state_from_config = config.get("state")
|
|
71
69
|
if state_from_config is not None:
|
|
72
70
|
self.state = state_from_config
|
|
73
71
|
else:
|
|
74
72
|
self.state = AgentState(
|
|
75
|
-
agent_name=
|
|
73
|
+
agent_name="Root Agent",
|
|
76
74
|
max_iterations=self.max_iterations,
|
|
77
75
|
)
|
|
78
76
|
|
|
77
|
+
self.llm = LLM(self.llm_config, agent_name=self.agent_name)
|
|
78
|
+
|
|
79
79
|
with contextlib.suppress(Exception):
|
|
80
|
-
self.llm.set_agent_identity(self.agent_name, self.state.agent_id)
|
|
80
|
+
self.llm.set_agent_identity(self.state.agent_name, self.state.agent_id)
|
|
81
81
|
self._current_task: asyncio.Task[Any] | None = None
|
|
82
|
+
self._force_stop = False
|
|
82
83
|
|
|
83
84
|
from strix.telemetry.tracer import get_global_tracer
|
|
84
85
|
|
|
@@ -145,19 +146,22 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
145
146
|
if self.state.parent_id is None and agents_graph_actions._root_agent_id is None:
|
|
146
147
|
agents_graph_actions._root_agent_id = self.state.agent_id
|
|
147
148
|
|
|
148
|
-
def cancel_current_execution(self) -> None:
|
|
149
|
-
if self._current_task and not self._current_task.done():
|
|
150
|
-
self._current_task.cancel()
|
|
151
|
-
self._current_task = None
|
|
152
|
-
|
|
153
149
|
async def agent_loop(self, task: str) -> dict[str, Any]: # noqa: PLR0912, PLR0915
|
|
154
|
-
await self._initialize_sandbox_and_state(task)
|
|
155
|
-
|
|
156
150
|
from strix.telemetry.tracer import get_global_tracer
|
|
157
151
|
|
|
158
152
|
tracer = get_global_tracer()
|
|
159
153
|
|
|
154
|
+
try:
|
|
155
|
+
await self._initialize_sandbox_and_state(task)
|
|
156
|
+
except SandboxInitializationError as e:
|
|
157
|
+
return self._handle_sandbox_error(e, tracer)
|
|
158
|
+
|
|
160
159
|
while True:
|
|
160
|
+
if self._force_stop:
|
|
161
|
+
self._force_stop = False
|
|
162
|
+
await self._enter_waiting_state(tracer, was_cancelled=True)
|
|
163
|
+
continue
|
|
164
|
+
|
|
161
165
|
self._check_agent_messages(self.state)
|
|
162
166
|
|
|
163
167
|
if self.state.is_waiting_for_input():
|
|
@@ -204,7 +208,11 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
204
208
|
self.state.add_message("user", final_warning_msg)
|
|
205
209
|
|
|
206
210
|
try:
|
|
207
|
-
|
|
211
|
+
iteration_task = asyncio.create_task(self._process_iteration(tracer))
|
|
212
|
+
self._current_task = iteration_task
|
|
213
|
+
should_finish = await iteration_task
|
|
214
|
+
self._current_task = None
|
|
215
|
+
|
|
208
216
|
if should_finish:
|
|
209
217
|
if self.non_interactive:
|
|
210
218
|
self.state.set_completed({"success": True})
|
|
@@ -215,43 +223,22 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
215
223
|
continue
|
|
216
224
|
|
|
217
225
|
except asyncio.CancelledError:
|
|
226
|
+
self._current_task = None
|
|
227
|
+
if tracer:
|
|
228
|
+
partial_content = tracer.finalize_streaming_as_interrupted(self.state.agent_id)
|
|
229
|
+
if partial_content and partial_content.strip():
|
|
230
|
+
self.state.add_message(
|
|
231
|
+
"assistant", f"{partial_content}\n\n[ABORTED BY USER]"
|
|
232
|
+
)
|
|
218
233
|
if self.non_interactive:
|
|
219
234
|
raise
|
|
220
235
|
await self._enter_waiting_state(tracer, error_occurred=False, was_cancelled=True)
|
|
221
236
|
continue
|
|
222
237
|
|
|
223
238
|
except LLMRequestFailedError as e:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if self.non_interactive:
|
|
229
|
-
self.state.set_completed({"success": False, "error": error_msg})
|
|
230
|
-
if tracer:
|
|
231
|
-
tracer.update_agent_status(self.state.agent_id, "failed", error_msg)
|
|
232
|
-
if error_details:
|
|
233
|
-
tracer.log_tool_execution_start(
|
|
234
|
-
self.state.agent_id,
|
|
235
|
-
"llm_error_details",
|
|
236
|
-
{"error": error_msg, "details": error_details},
|
|
237
|
-
)
|
|
238
|
-
tracer.update_tool_execution(
|
|
239
|
-
tracer._next_execution_id - 1, "failed", error_details
|
|
240
|
-
)
|
|
241
|
-
return {"success": False, "error": error_msg}
|
|
242
|
-
|
|
243
|
-
self.state.enter_waiting_state(llm_failed=True)
|
|
244
|
-
if tracer:
|
|
245
|
-
tracer.update_agent_status(self.state.agent_id, "llm_failed", error_msg)
|
|
246
|
-
if error_details:
|
|
247
|
-
tracer.log_tool_execution_start(
|
|
248
|
-
self.state.agent_id,
|
|
249
|
-
"llm_error_details",
|
|
250
|
-
{"error": error_msg, "details": error_details},
|
|
251
|
-
)
|
|
252
|
-
tracer.update_tool_execution(
|
|
253
|
-
tracer._next_execution_id - 1, "failed", error_details
|
|
254
|
-
)
|
|
239
|
+
result = self._handle_llm_error(e, tracer)
|
|
240
|
+
if result is not None:
|
|
241
|
+
return result
|
|
255
242
|
continue
|
|
256
243
|
|
|
257
244
|
except (RuntimeError, ValueError, TypeError) as e:
|
|
@@ -265,11 +252,12 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
265
252
|
continue
|
|
266
253
|
|
|
267
254
|
async def _wait_for_input(self) -> None:
|
|
268
|
-
|
|
255
|
+
if self._force_stop:
|
|
256
|
+
return
|
|
269
257
|
|
|
270
258
|
if self.state.has_waiting_timeout():
|
|
271
259
|
self.state.resume_from_waiting()
|
|
272
|
-
self.state.add_message("
|
|
260
|
+
self.state.add_message("user", "Waiting timeout reached. Resuming execution.")
|
|
273
261
|
|
|
274
262
|
from strix.telemetry.tracer import get_global_tracer
|
|
275
263
|
|
|
@@ -334,16 +322,22 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
334
322
|
if not sandbox_mode and self.state.sandbox_id is None:
|
|
335
323
|
from strix.runtime import get_runtime
|
|
336
324
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
325
|
+
try:
|
|
326
|
+
runtime = get_runtime()
|
|
327
|
+
sandbox_info = await runtime.create_sandbox(
|
|
328
|
+
self.state.agent_id, self.state.sandbox_token, self.local_sources
|
|
329
|
+
)
|
|
330
|
+
self.state.sandbox_id = sandbox_info["workspace_id"]
|
|
331
|
+
self.state.sandbox_token = sandbox_info["auth_token"]
|
|
332
|
+
self.state.sandbox_info = sandbox_info
|
|
333
|
+
|
|
334
|
+
if "agent_id" in sandbox_info:
|
|
335
|
+
self.state.sandbox_info["agent_id"] = sandbox_info["agent_id"]
|
|
336
|
+
except Exception as e:
|
|
337
|
+
from strix.telemetry import posthog
|
|
344
338
|
|
|
345
|
-
|
|
346
|
-
|
|
339
|
+
posthog.error("sandbox_init_error", str(e))
|
|
340
|
+
raise
|
|
347
341
|
|
|
348
342
|
if not self.state.task:
|
|
349
343
|
self.state.task = task
|
|
@@ -351,9 +345,17 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
351
345
|
self.state.add_message("user", task)
|
|
352
346
|
|
|
353
347
|
async def _process_iteration(self, tracer: Optional["Tracer"]) -> bool:
|
|
354
|
-
|
|
348
|
+
final_response = None
|
|
349
|
+
|
|
350
|
+
async for response in self.llm.generate(self.state.get_conversation_history()):
|
|
351
|
+
final_response = response
|
|
352
|
+
if tracer and response.content:
|
|
353
|
+
tracer.update_streaming_content(self.state.agent_id, response.content)
|
|
354
|
+
|
|
355
|
+
if final_response is None:
|
|
356
|
+
return False
|
|
355
357
|
|
|
356
|
-
content_stripped = (
|
|
358
|
+
content_stripped = (final_response.content or "").strip()
|
|
357
359
|
|
|
358
360
|
if not content_stripped:
|
|
359
361
|
corrective_message = (
|
|
@@ -369,17 +371,19 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
369
371
|
self.state.add_message("user", corrective_message)
|
|
370
372
|
return False
|
|
371
373
|
|
|
372
|
-
|
|
374
|
+
thinking_blocks = getattr(final_response, "thinking_blocks", None)
|
|
375
|
+
self.state.add_message("assistant", final_response.content, thinking_blocks=thinking_blocks)
|
|
373
376
|
if tracer:
|
|
377
|
+
tracer.clear_streaming_content(self.state.agent_id)
|
|
374
378
|
tracer.log_chat_message(
|
|
375
|
-
content=clean_content(
|
|
379
|
+
content=clean_content(final_response.content),
|
|
376
380
|
role="assistant",
|
|
377
381
|
agent_id=self.state.agent_id,
|
|
378
382
|
)
|
|
379
383
|
|
|
380
384
|
actions = (
|
|
381
|
-
|
|
382
|
-
if hasattr(
|
|
385
|
+
final_response.tool_invocations
|
|
386
|
+
if hasattr(final_response, "tool_invocations") and final_response.tool_invocations
|
|
383
387
|
else []
|
|
384
388
|
)
|
|
385
389
|
|
|
@@ -420,18 +424,6 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
420
424
|
|
|
421
425
|
return False
|
|
422
426
|
|
|
423
|
-
async def _handle_iteration_error(
|
|
424
|
-
self,
|
|
425
|
-
error: RuntimeError | ValueError | TypeError | asyncio.CancelledError,
|
|
426
|
-
tracer: Optional["Tracer"],
|
|
427
|
-
) -> bool:
|
|
428
|
-
error_msg = f"Error in iteration {self.state.iteration}: {error!s}"
|
|
429
|
-
logger.exception(error_msg)
|
|
430
|
-
self.state.add_error(error_msg)
|
|
431
|
-
if tracer:
|
|
432
|
-
tracer.update_agent_status(self.state.agent_id, "error")
|
|
433
|
-
return True
|
|
434
|
-
|
|
435
427
|
def _check_agent_messages(self, state: AgentState) -> None: # noqa: PLR0912
|
|
436
428
|
try:
|
|
437
429
|
from strix.tools.agents_graph.agents_graph_actions import _agent_graph, _agent_messages
|
|
@@ -516,3 +508,95 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
516
508
|
logger = logging.getLogger(__name__)
|
|
517
509
|
logger.warning(f"Error checking agent messages: {e}")
|
|
518
510
|
return
|
|
511
|
+
|
|
512
|
+
def _handle_sandbox_error(
|
|
513
|
+
self,
|
|
514
|
+
error: SandboxInitializationError,
|
|
515
|
+
tracer: Optional["Tracer"],
|
|
516
|
+
) -> dict[str, Any]:
|
|
517
|
+
error_msg = str(error.message)
|
|
518
|
+
error_details = error.details
|
|
519
|
+
self.state.add_error(error_msg)
|
|
520
|
+
|
|
521
|
+
if self.non_interactive:
|
|
522
|
+
self.state.set_completed({"success": False, "error": error_msg})
|
|
523
|
+
if tracer:
|
|
524
|
+
tracer.update_agent_status(self.state.agent_id, "failed", error_msg)
|
|
525
|
+
if error_details:
|
|
526
|
+
exec_id = tracer.log_tool_execution_start(
|
|
527
|
+
self.state.agent_id,
|
|
528
|
+
"sandbox_error_details",
|
|
529
|
+
{"error": error_msg, "details": error_details},
|
|
530
|
+
)
|
|
531
|
+
tracer.update_tool_execution(exec_id, "failed", {"details": error_details})
|
|
532
|
+
return {"success": False, "error": error_msg, "details": error_details}
|
|
533
|
+
|
|
534
|
+
self.state.enter_waiting_state()
|
|
535
|
+
if tracer:
|
|
536
|
+
tracer.update_agent_status(self.state.agent_id, "sandbox_failed", error_msg)
|
|
537
|
+
if error_details:
|
|
538
|
+
exec_id = tracer.log_tool_execution_start(
|
|
539
|
+
self.state.agent_id,
|
|
540
|
+
"sandbox_error_details",
|
|
541
|
+
{"error": error_msg, "details": error_details},
|
|
542
|
+
)
|
|
543
|
+
tracer.update_tool_execution(exec_id, "failed", {"details": error_details})
|
|
544
|
+
|
|
545
|
+
return {"success": False, "error": error_msg, "details": error_details}
|
|
546
|
+
|
|
547
|
+
def _handle_llm_error(
|
|
548
|
+
self,
|
|
549
|
+
error: LLMRequestFailedError,
|
|
550
|
+
tracer: Optional["Tracer"],
|
|
551
|
+
) -> dict[str, Any] | None:
|
|
552
|
+
error_msg = str(error)
|
|
553
|
+
error_details = getattr(error, "details", None)
|
|
554
|
+
self.state.add_error(error_msg)
|
|
555
|
+
|
|
556
|
+
if self.non_interactive:
|
|
557
|
+
self.state.set_completed({"success": False, "error": error_msg})
|
|
558
|
+
if tracer:
|
|
559
|
+
tracer.update_agent_status(self.state.agent_id, "failed", error_msg)
|
|
560
|
+
if error_details:
|
|
561
|
+
exec_id = tracer.log_tool_execution_start(
|
|
562
|
+
self.state.agent_id,
|
|
563
|
+
"llm_error_details",
|
|
564
|
+
{"error": error_msg, "details": error_details},
|
|
565
|
+
)
|
|
566
|
+
tracer.update_tool_execution(exec_id, "failed", {"details": error_details})
|
|
567
|
+
return {"success": False, "error": error_msg}
|
|
568
|
+
|
|
569
|
+
self.state.enter_waiting_state(llm_failed=True)
|
|
570
|
+
if tracer:
|
|
571
|
+
tracer.update_agent_status(self.state.agent_id, "llm_failed", error_msg)
|
|
572
|
+
if error_details:
|
|
573
|
+
exec_id = tracer.log_tool_execution_start(
|
|
574
|
+
self.state.agent_id,
|
|
575
|
+
"llm_error_details",
|
|
576
|
+
{"error": error_msg, "details": error_details},
|
|
577
|
+
)
|
|
578
|
+
tracer.update_tool_execution(exec_id, "failed", {"details": error_details})
|
|
579
|
+
|
|
580
|
+
return None
|
|
581
|
+
|
|
582
|
+
async def _handle_iteration_error(
|
|
583
|
+
self,
|
|
584
|
+
error: RuntimeError | ValueError | TypeError | asyncio.CancelledError,
|
|
585
|
+
tracer: Optional["Tracer"],
|
|
586
|
+
) -> bool:
|
|
587
|
+
error_msg = f"Error in iteration {self.state.iteration}: {error!s}"
|
|
588
|
+
logger.exception(error_msg)
|
|
589
|
+
self.state.add_error(error_msg)
|
|
590
|
+
if tracer:
|
|
591
|
+
tracer.update_agent_status(self.state.agent_id, "error")
|
|
592
|
+
return True
|
|
593
|
+
|
|
594
|
+
def cancel_current_execution(self) -> None:
|
|
595
|
+
self._force_stop = True
|
|
596
|
+
if self._current_task and not self._current_task.done():
|
|
597
|
+
try:
|
|
598
|
+
loop = self._current_task.get_loop()
|
|
599
|
+
loop.call_soon_threadsafe(self._current_task.cancel)
|
|
600
|
+
except RuntimeError:
|
|
601
|
+
self._current_task.cancel()
|
|
602
|
+
self._current_task = None
|
strix/agents/state.py
CHANGED
|
@@ -43,8 +43,11 @@ class AgentState(BaseModel):
|
|
|
43
43
|
self.iteration += 1
|
|
44
44
|
self.last_updated = datetime.now(UTC).isoformat()
|
|
45
45
|
|
|
46
|
-
def add_message(self, role: str, content: Any) -> None:
|
|
47
|
-
|
|
46
|
+
def add_message(self, role: str, content: Any, thinking_blocks: list[dict[str, Any]] | None = None) -> None:
|
|
47
|
+
message = {"role": role, "content": content}
|
|
48
|
+
if thinking_blocks:
|
|
49
|
+
message["thinking_blocks"] = thinking_blocks
|
|
50
|
+
self.messages.append(message)
|
|
48
51
|
self.last_updated = datetime.now(UTC).isoformat()
|
|
49
52
|
|
|
50
53
|
def add_action(self, action: dict[str, Any]) -> None:
|