aiptx 2.0.7__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.
Files changed (187) hide show
  1. aipt_v2/__init__.py +110 -0
  2. aipt_v2/__main__.py +24 -0
  3. aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
  4. aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
  5. aipt_v2/agents/__init__.py +46 -0
  6. aipt_v2/agents/base.py +520 -0
  7. aipt_v2/agents/exploit_agent.py +688 -0
  8. aipt_v2/agents/ptt.py +406 -0
  9. aipt_v2/agents/state.py +168 -0
  10. aipt_v2/app.py +957 -0
  11. aipt_v2/browser/__init__.py +31 -0
  12. aipt_v2/browser/automation.py +458 -0
  13. aipt_v2/browser/crawler.py +453 -0
  14. aipt_v2/cli.py +2933 -0
  15. aipt_v2/compliance/__init__.py +71 -0
  16. aipt_v2/compliance/compliance_report.py +449 -0
  17. aipt_v2/compliance/framework_mapper.py +424 -0
  18. aipt_v2/compliance/nist_mapping.py +345 -0
  19. aipt_v2/compliance/owasp_mapping.py +330 -0
  20. aipt_v2/compliance/pci_mapping.py +297 -0
  21. aipt_v2/config.py +341 -0
  22. aipt_v2/core/__init__.py +43 -0
  23. aipt_v2/core/agent.py +630 -0
  24. aipt_v2/core/llm.py +395 -0
  25. aipt_v2/core/memory.py +305 -0
  26. aipt_v2/core/ptt.py +329 -0
  27. aipt_v2/database/__init__.py +14 -0
  28. aipt_v2/database/models.py +232 -0
  29. aipt_v2/database/repository.py +384 -0
  30. aipt_v2/docker/__init__.py +23 -0
  31. aipt_v2/docker/builder.py +260 -0
  32. aipt_v2/docker/manager.py +222 -0
  33. aipt_v2/docker/sandbox.py +371 -0
  34. aipt_v2/evasion/__init__.py +58 -0
  35. aipt_v2/evasion/request_obfuscator.py +272 -0
  36. aipt_v2/evasion/tls_fingerprint.py +285 -0
  37. aipt_v2/evasion/ua_rotator.py +301 -0
  38. aipt_v2/evasion/waf_bypass.py +439 -0
  39. aipt_v2/execution/__init__.py +23 -0
  40. aipt_v2/execution/executor.py +302 -0
  41. aipt_v2/execution/parser.py +544 -0
  42. aipt_v2/execution/terminal.py +337 -0
  43. aipt_v2/health.py +437 -0
  44. aipt_v2/intelligence/__init__.py +194 -0
  45. aipt_v2/intelligence/adaptation.py +474 -0
  46. aipt_v2/intelligence/auth.py +520 -0
  47. aipt_v2/intelligence/chaining.py +775 -0
  48. aipt_v2/intelligence/correlation.py +536 -0
  49. aipt_v2/intelligence/cve_aipt.py +334 -0
  50. aipt_v2/intelligence/cve_info.py +1111 -0
  51. aipt_v2/intelligence/knowledge_graph.py +590 -0
  52. aipt_v2/intelligence/learning.py +626 -0
  53. aipt_v2/intelligence/llm_analyzer.py +502 -0
  54. aipt_v2/intelligence/llm_tool_selector.py +518 -0
  55. aipt_v2/intelligence/payload_generator.py +562 -0
  56. aipt_v2/intelligence/rag.py +239 -0
  57. aipt_v2/intelligence/scope.py +442 -0
  58. aipt_v2/intelligence/searchers/__init__.py +5 -0
  59. aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
  60. aipt_v2/intelligence/searchers/github_searcher.py +467 -0
  61. aipt_v2/intelligence/searchers/google_searcher.py +281 -0
  62. aipt_v2/intelligence/tools.json +443 -0
  63. aipt_v2/intelligence/triage.py +670 -0
  64. aipt_v2/interactive_shell.py +559 -0
  65. aipt_v2/interface/__init__.py +5 -0
  66. aipt_v2/interface/cli.py +230 -0
  67. aipt_v2/interface/main.py +501 -0
  68. aipt_v2/interface/tui.py +1276 -0
  69. aipt_v2/interface/utils.py +583 -0
  70. aipt_v2/llm/__init__.py +39 -0
  71. aipt_v2/llm/config.py +26 -0
  72. aipt_v2/llm/llm.py +514 -0
  73. aipt_v2/llm/memory.py +214 -0
  74. aipt_v2/llm/request_queue.py +89 -0
  75. aipt_v2/llm/utils.py +89 -0
  76. aipt_v2/local_tool_installer.py +1467 -0
  77. aipt_v2/models/__init__.py +15 -0
  78. aipt_v2/models/findings.py +295 -0
  79. aipt_v2/models/phase_result.py +224 -0
  80. aipt_v2/models/scan_config.py +207 -0
  81. aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
  82. aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
  83. aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
  84. aipt_v2/monitoring/prometheus.yml +60 -0
  85. aipt_v2/orchestration/__init__.py +52 -0
  86. aipt_v2/orchestration/pipeline.py +398 -0
  87. aipt_v2/orchestration/progress.py +300 -0
  88. aipt_v2/orchestration/scheduler.py +296 -0
  89. aipt_v2/orchestrator.py +2427 -0
  90. aipt_v2/payloads/__init__.py +27 -0
  91. aipt_v2/payloads/cmdi.py +150 -0
  92. aipt_v2/payloads/sqli.py +263 -0
  93. aipt_v2/payloads/ssrf.py +204 -0
  94. aipt_v2/payloads/templates.py +222 -0
  95. aipt_v2/payloads/traversal.py +166 -0
  96. aipt_v2/payloads/xss.py +204 -0
  97. aipt_v2/prompts/__init__.py +60 -0
  98. aipt_v2/proxy/__init__.py +29 -0
  99. aipt_v2/proxy/history.py +352 -0
  100. aipt_v2/proxy/interceptor.py +452 -0
  101. aipt_v2/recon/__init__.py +44 -0
  102. aipt_v2/recon/dns.py +241 -0
  103. aipt_v2/recon/osint.py +367 -0
  104. aipt_v2/recon/subdomain.py +372 -0
  105. aipt_v2/recon/tech_detect.py +311 -0
  106. aipt_v2/reports/__init__.py +17 -0
  107. aipt_v2/reports/generator.py +313 -0
  108. aipt_v2/reports/html_report.py +378 -0
  109. aipt_v2/runtime/__init__.py +53 -0
  110. aipt_v2/runtime/base.py +30 -0
  111. aipt_v2/runtime/docker.py +401 -0
  112. aipt_v2/runtime/local.py +346 -0
  113. aipt_v2/runtime/tool_server.py +205 -0
  114. aipt_v2/runtime/vps.py +830 -0
  115. aipt_v2/scanners/__init__.py +28 -0
  116. aipt_v2/scanners/base.py +273 -0
  117. aipt_v2/scanners/nikto.py +244 -0
  118. aipt_v2/scanners/nmap.py +402 -0
  119. aipt_v2/scanners/nuclei.py +273 -0
  120. aipt_v2/scanners/web.py +454 -0
  121. aipt_v2/scripts/security_audit.py +366 -0
  122. aipt_v2/setup_wizard.py +941 -0
  123. aipt_v2/skills/__init__.py +80 -0
  124. aipt_v2/skills/agents/__init__.py +14 -0
  125. aipt_v2/skills/agents/api_tester.py +706 -0
  126. aipt_v2/skills/agents/base.py +477 -0
  127. aipt_v2/skills/agents/code_review.py +459 -0
  128. aipt_v2/skills/agents/security_agent.py +336 -0
  129. aipt_v2/skills/agents/web_pentest.py +818 -0
  130. aipt_v2/skills/prompts/__init__.py +647 -0
  131. aipt_v2/system_detector.py +539 -0
  132. aipt_v2/telemetry/__init__.py +7 -0
  133. aipt_v2/telemetry/tracer.py +347 -0
  134. aipt_v2/terminal/__init__.py +28 -0
  135. aipt_v2/terminal/executor.py +400 -0
  136. aipt_v2/terminal/sandbox.py +350 -0
  137. aipt_v2/tools/__init__.py +44 -0
  138. aipt_v2/tools/active_directory/__init__.py +78 -0
  139. aipt_v2/tools/active_directory/ad_config.py +238 -0
  140. aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
  141. aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
  142. aipt_v2/tools/active_directory/ldap_enum.py +533 -0
  143. aipt_v2/tools/active_directory/smb_attacks.py +505 -0
  144. aipt_v2/tools/agents_graph/__init__.py +19 -0
  145. aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
  146. aipt_v2/tools/api_security/__init__.py +76 -0
  147. aipt_v2/tools/api_security/api_discovery.py +608 -0
  148. aipt_v2/tools/api_security/graphql_scanner.py +622 -0
  149. aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
  150. aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
  151. aipt_v2/tools/browser/__init__.py +5 -0
  152. aipt_v2/tools/browser/browser_actions.py +238 -0
  153. aipt_v2/tools/browser/browser_instance.py +535 -0
  154. aipt_v2/tools/browser/tab_manager.py +344 -0
  155. aipt_v2/tools/cloud/__init__.py +70 -0
  156. aipt_v2/tools/cloud/cloud_config.py +273 -0
  157. aipt_v2/tools/cloud/cloud_scanner.py +639 -0
  158. aipt_v2/tools/cloud/prowler_tool.py +571 -0
  159. aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
  160. aipt_v2/tools/executor.py +307 -0
  161. aipt_v2/tools/parser.py +408 -0
  162. aipt_v2/tools/proxy/__init__.py +5 -0
  163. aipt_v2/tools/proxy/proxy_actions.py +103 -0
  164. aipt_v2/tools/proxy/proxy_manager.py +789 -0
  165. aipt_v2/tools/registry.py +196 -0
  166. aipt_v2/tools/scanners/__init__.py +343 -0
  167. aipt_v2/tools/scanners/acunetix_tool.py +712 -0
  168. aipt_v2/tools/scanners/burp_tool.py +631 -0
  169. aipt_v2/tools/scanners/config.py +156 -0
  170. aipt_v2/tools/scanners/nessus_tool.py +588 -0
  171. aipt_v2/tools/scanners/zap_tool.py +612 -0
  172. aipt_v2/tools/terminal/__init__.py +5 -0
  173. aipt_v2/tools/terminal/terminal_actions.py +37 -0
  174. aipt_v2/tools/terminal/terminal_manager.py +153 -0
  175. aipt_v2/tools/terminal/terminal_session.py +449 -0
  176. aipt_v2/tools/tool_processing.py +108 -0
  177. aipt_v2/utils/__init__.py +17 -0
  178. aipt_v2/utils/logging.py +202 -0
  179. aipt_v2/utils/model_manager.py +187 -0
  180. aipt_v2/utils/searchers/__init__.py +269 -0
  181. aipt_v2/verify_install.py +793 -0
  182. aiptx-2.0.7.dist-info/METADATA +345 -0
  183. aiptx-2.0.7.dist-info/RECORD +187 -0
  184. aiptx-2.0.7.dist-info/WHEEL +5 -0
  185. aiptx-2.0.7.dist-info/entry_points.txt +7 -0
  186. aiptx-2.0.7.dist-info/licenses/LICENSE +21 -0
  187. aiptx-2.0.7.dist-info/top_level.txt +1 -0
aipt_v2/agents/base.py ADDED
@@ -0,0 +1,520 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import contextlib
5
+ import logging
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING, Any, Optional
8
+
9
+
10
+ if TYPE_CHECKING:
11
+ from aipt_v2.telemetry import Tracer
12
+
13
+ from jinja2 import (
14
+ Environment,
15
+ FileSystemLoader,
16
+ select_autoescape,
17
+ )
18
+
19
+ from aipt_v2.llm import LLM, LLMConfig, LLMRequestFailedError
20
+ from aipt_v2.llm.utils import clean_content
21
+ from aipt_v2.tools.tool_processing import process_tool_invocations
22
+
23
+ from .state import AgentState
24
+
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class AgentMeta(type):
30
+ agent_name: str
31
+ jinja_env: Environment
32
+
33
+ def __new__(cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> type:
34
+ new_cls = super().__new__(cls, name, bases, attrs)
35
+
36
+ if name == "BaseAgent":
37
+ return new_cls
38
+
39
+ agents_dir = Path(__file__).parent
40
+ prompt_dir = agents_dir / name
41
+
42
+ new_cls.agent_name = name
43
+ new_cls.jinja_env = Environment(
44
+ loader=FileSystemLoader(prompt_dir),
45
+ autoescape=select_autoescape(enabled_extensions=(), default_for_string=False),
46
+ )
47
+
48
+ return new_cls
49
+
50
+
51
+ class BaseAgent(metaclass=AgentMeta):
52
+ max_iterations = 300
53
+ agent_name: str = ""
54
+ jinja_env: Environment
55
+ default_llm_config: LLMConfig | None = None
56
+
57
+ def __init__(self, config: dict[str, Any]):
58
+ self.config = config
59
+
60
+ self.local_sources = config.get("local_sources", [])
61
+ self.non_interactive = config.get("non_interactive", False)
62
+
63
+ if "max_iterations" in config:
64
+ self.max_iterations = config["max_iterations"]
65
+
66
+ self.llm_config_name = config.get("llm_config_name", "default")
67
+ self.llm_config = config.get("llm_config", self.default_llm_config)
68
+ if self.llm_config is None:
69
+ raise ValueError("llm_config is required but not provided")
70
+ self.llm = LLM(self.llm_config, agent_name=self.agent_name)
71
+
72
+ state_from_config = config.get("state")
73
+ if state_from_config is not None:
74
+ self.state = state_from_config
75
+ else:
76
+ self.state = AgentState(
77
+ agent_name=self.agent_name,
78
+ max_iterations=self.max_iterations,
79
+ )
80
+
81
+ with contextlib.suppress(Exception):
82
+ self.llm.set_agent_identity(self.agent_name, self.state.agent_id)
83
+ self._current_task: asyncio.Task[Any] | None = None
84
+
85
+ from aipt_v2.telemetry.tracer import get_global_tracer
86
+
87
+ tracer = get_global_tracer()
88
+ if tracer:
89
+ tracer.log_agent_creation(
90
+ agent_id=self.state.agent_id,
91
+ name=self.state.agent_name,
92
+ task=self.state.task,
93
+ parent_id=self.state.parent_id,
94
+ )
95
+ if self.state.parent_id is None:
96
+ scan_config = tracer.scan_config or {}
97
+ exec_id = tracer.log_tool_execution_start(
98
+ agent_id=self.state.agent_id,
99
+ tool_name="scan_start_info",
100
+ args=scan_config,
101
+ )
102
+ tracer.update_tool_execution(execution_id=exec_id, status="completed", result={})
103
+
104
+ else:
105
+ exec_id = tracer.log_tool_execution_start(
106
+ agent_id=self.state.agent_id,
107
+ tool_name="subagent_start_info",
108
+ args={
109
+ "name": self.state.agent_name,
110
+ "task": self.state.task,
111
+ "parent_id": self.state.parent_id,
112
+ },
113
+ )
114
+ tracer.update_tool_execution(execution_id=exec_id, status="completed", result={})
115
+
116
+ self._add_to_agents_graph()
117
+
118
+ def _add_to_agents_graph(self) -> None:
119
+ from aipt_v2.tools.agents_graph import agents_graph_actions
120
+
121
+ node = {
122
+ "id": self.state.agent_id,
123
+ "name": self.state.agent_name,
124
+ "task": self.state.task,
125
+ "status": "running",
126
+ "parent_id": self.state.parent_id,
127
+ "created_at": self.state.start_time,
128
+ "finished_at": None,
129
+ "result": None,
130
+ "llm_config": self.llm_config_name,
131
+ "agent_type": self.__class__.__name__,
132
+ "state": self.state.model_dump(),
133
+ }
134
+ agents_graph_actions._agent_graph["nodes"][self.state.agent_id] = node
135
+
136
+ agents_graph_actions._agent_instances[self.state.agent_id] = self
137
+ agents_graph_actions._agent_states[self.state.agent_id] = self.state
138
+
139
+ if self.state.parent_id:
140
+ agents_graph_actions._agent_graph["edges"].append(
141
+ {"from": self.state.parent_id, "to": self.state.agent_id, "type": "delegation"}
142
+ )
143
+
144
+ if self.state.agent_id not in agents_graph_actions._agent_messages:
145
+ agents_graph_actions._agent_messages[self.state.agent_id] = []
146
+
147
+ if self.state.parent_id is None and agents_graph_actions._root_agent_id is None:
148
+ agents_graph_actions._root_agent_id = self.state.agent_id
149
+
150
+ def cancel_current_execution(self) -> None:
151
+ if self._current_task and not self._current_task.done():
152
+ self._current_task.cancel()
153
+ self._current_task = None
154
+
155
+ async def agent_loop(self, task: str) -> dict[str, Any]: # noqa: PLR0912, PLR0915
156
+ await self._initialize_sandbox_and_state(task)
157
+
158
+ from aipt_v2.telemetry.tracer import get_global_tracer
159
+
160
+ tracer = get_global_tracer()
161
+
162
+ while True:
163
+ self._check_agent_messages(self.state)
164
+
165
+ if self.state.is_waiting_for_input():
166
+ await self._wait_for_input()
167
+ continue
168
+
169
+ if self.state.should_stop():
170
+ if self.non_interactive:
171
+ return self.state.final_result or {}
172
+ await self._enter_waiting_state(tracer)
173
+ continue
174
+
175
+ if self.state.llm_failed:
176
+ await self._wait_for_input()
177
+ continue
178
+
179
+ self.state.increment_iteration()
180
+
181
+ if (
182
+ self.state.is_approaching_max_iterations()
183
+ and not self.state.max_iterations_warning_sent
184
+ ):
185
+ self.state.max_iterations_warning_sent = True
186
+ remaining = self.state.max_iterations - self.state.iteration
187
+ warning_msg = (
188
+ f"URGENT: You are approaching the maximum iteration limit. "
189
+ f"Current: {self.state.iteration}/{self.state.max_iterations} "
190
+ f"({remaining} iterations remaining). "
191
+ f"Please prioritize completing your required task(s) and calling "
192
+ f"the appropriate finish tool (finish_scan for root agent, "
193
+ f"agent_finish for sub-agents) as soon as possible."
194
+ )
195
+ self.state.add_message("user", warning_msg)
196
+
197
+ if self.state.iteration == self.state.max_iterations - 3:
198
+ final_warning_msg = (
199
+ "CRITICAL: You have only 3 iterations left! "
200
+ "Your next message MUST be the tool call to the appropriate "
201
+ "finish tool: finish_scan if you are the root agent, or "
202
+ "agent_finish if you are a sub-agent. "
203
+ "No other actions should be taken except finishing your work "
204
+ "immediately."
205
+ )
206
+ self.state.add_message("user", final_warning_msg)
207
+
208
+ try:
209
+ should_finish = await self._process_iteration(tracer)
210
+ if should_finish:
211
+ if self.non_interactive:
212
+ self.state.set_completed({"success": True})
213
+ if tracer:
214
+ tracer.update_agent_status(self.state.agent_id, "completed")
215
+ return self.state.final_result or {}
216
+ await self._enter_waiting_state(tracer, task_completed=True)
217
+ continue
218
+
219
+ except asyncio.CancelledError:
220
+ if self.non_interactive:
221
+ raise
222
+ await self._enter_waiting_state(tracer, error_occurred=False, was_cancelled=True)
223
+ continue
224
+
225
+ except LLMRequestFailedError as e:
226
+ error_msg = str(e)
227
+ error_details = getattr(e, "details", None)
228
+ self.state.add_error(error_msg)
229
+
230
+ if self.non_interactive:
231
+ self.state.set_completed({"success": False, "error": error_msg})
232
+ if tracer:
233
+ tracer.update_agent_status(self.state.agent_id, "failed", error_msg)
234
+ if error_details:
235
+ tracer.log_tool_execution_start(
236
+ self.state.agent_id,
237
+ "llm_error_details",
238
+ {"error": error_msg, "details": error_details},
239
+ )
240
+ tracer.update_tool_execution(
241
+ tracer._next_execution_id - 1, "failed", error_details
242
+ )
243
+ return {"success": False, "error": error_msg}
244
+
245
+ self.state.enter_waiting_state(llm_failed=True)
246
+ if tracer:
247
+ tracer.update_agent_status(self.state.agent_id, "llm_failed", error_msg)
248
+ if error_details:
249
+ tracer.log_tool_execution_start(
250
+ self.state.agent_id,
251
+ "llm_error_details",
252
+ {"error": error_msg, "details": error_details},
253
+ )
254
+ tracer.update_tool_execution(
255
+ tracer._next_execution_id - 1, "failed", error_details
256
+ )
257
+ continue
258
+
259
+ except (RuntimeError, ValueError, TypeError) as e:
260
+ if not await self._handle_iteration_error(e, tracer):
261
+ if self.non_interactive:
262
+ self.state.set_completed({"success": False, "error": str(e)})
263
+ if tracer:
264
+ tracer.update_agent_status(self.state.agent_id, "failed")
265
+ raise
266
+ await self._enter_waiting_state(tracer, error_occurred=True)
267
+ continue
268
+
269
+ async def _wait_for_input(self) -> None:
270
+ import asyncio
271
+
272
+ if self.state.has_waiting_timeout():
273
+ self.state.resume_from_waiting()
274
+ self.state.add_message("assistant", "Waiting timeout reached. Resuming execution.")
275
+
276
+ from aipt_v2.telemetry.tracer import get_global_tracer
277
+
278
+ tracer = get_global_tracer()
279
+ if tracer:
280
+ tracer.update_agent_status(self.state.agent_id, "running")
281
+
282
+ try:
283
+ from aipt_v2.tools.agents_graph.agents_graph_actions import _agent_graph
284
+
285
+ if self.state.agent_id in _agent_graph["nodes"]:
286
+ _agent_graph["nodes"][self.state.agent_id]["status"] = "running"
287
+ except (ImportError, KeyError):
288
+ pass
289
+
290
+ return
291
+
292
+ await asyncio.sleep(0.5)
293
+
294
+ async def _enter_waiting_state(
295
+ self,
296
+ tracer: Optional["Tracer"],
297
+ task_completed: bool = False,
298
+ error_occurred: bool = False,
299
+ was_cancelled: bool = False,
300
+ ) -> None:
301
+ self.state.enter_waiting_state()
302
+
303
+ if tracer:
304
+ if task_completed:
305
+ tracer.update_agent_status(self.state.agent_id, "completed")
306
+ elif error_occurred:
307
+ tracer.update_agent_status(self.state.agent_id, "error")
308
+ elif was_cancelled:
309
+ tracer.update_agent_status(self.state.agent_id, "stopped")
310
+ else:
311
+ tracer.update_agent_status(self.state.agent_id, "stopped")
312
+
313
+ if task_completed:
314
+ self.state.add_message(
315
+ "assistant",
316
+ "Task completed. I'm now waiting for follow-up instructions or new tasks.",
317
+ )
318
+ elif error_occurred:
319
+ self.state.add_message(
320
+ "assistant", "An error occurred. I'm now waiting for new instructions."
321
+ )
322
+ elif was_cancelled:
323
+ self.state.add_message(
324
+ "assistant", "Execution was cancelled. I'm now waiting for new instructions."
325
+ )
326
+ else:
327
+ self.state.add_message(
328
+ "assistant",
329
+ "Execution paused. I'm now waiting for new instructions or any updates.",
330
+ )
331
+
332
+ async def _initialize_sandbox_and_state(self, task: str) -> None:
333
+ import os
334
+
335
+ sandbox_mode = os.getenv("AIPT_SANDBOX_MODE", "false").lower() == "true"
336
+ if not sandbox_mode and self.state.sandbox_id is None:
337
+ from aipt_v2.runtime import get_runtime
338
+
339
+ runtime = get_runtime()
340
+ sandbox_info = await runtime.create_sandbox(
341
+ self.state.agent_id, self.state.sandbox_token, self.local_sources
342
+ )
343
+ self.state.sandbox_id = sandbox_info["workspace_id"]
344
+ self.state.sandbox_token = sandbox_info["auth_token"]
345
+ self.state.sandbox_info = sandbox_info
346
+
347
+ if "agent_id" in sandbox_info:
348
+ self.state.sandbox_info["agent_id"] = sandbox_info["agent_id"]
349
+
350
+ if not self.state.task:
351
+ self.state.task = task
352
+
353
+ self.state.add_message("user", task)
354
+
355
+ async def _process_iteration(self, tracer: Optional["Tracer"]) -> bool:
356
+ response = await self.llm.generate(self.state.get_conversation_history())
357
+
358
+ content_stripped = (response.content or "").strip()
359
+
360
+ if not content_stripped:
361
+ corrective_message = (
362
+ "You MUST NOT respond with empty messages. "
363
+ "If you currently have nothing to do or say, use an appropriate tool instead:\n"
364
+ "- Use agents_graph_actions.wait_for_message to wait for messages "
365
+ "from user or other agents\n"
366
+ "- Use agents_graph_actions.agent_finish if you are a sub-agent "
367
+ "and your task is complete\n"
368
+ "- Use finish_actions.finish_scan if you are the root/main agent "
369
+ "and the scan is complete"
370
+ )
371
+ self.state.add_message("user", corrective_message)
372
+ return False
373
+
374
+ self.state.add_message("assistant", response.content)
375
+ if tracer:
376
+ tracer.log_chat_message(
377
+ content=clean_content(response.content),
378
+ role="assistant",
379
+ agent_id=self.state.agent_id,
380
+ )
381
+
382
+ actions = (
383
+ response.tool_invocations
384
+ if hasattr(response, "tool_invocations") and response.tool_invocations
385
+ else []
386
+ )
387
+
388
+ if actions:
389
+ return await self._execute_actions(actions, tracer)
390
+
391
+ return False
392
+
393
+ async def _execute_actions(self, actions: list[Any], tracer: Optional["Tracer"]) -> bool:
394
+ """Execute actions and return True if agent should finish."""
395
+ for action in actions:
396
+ self.state.add_action(action)
397
+
398
+ conversation_history = self.state.get_conversation_history()
399
+
400
+ tool_task = asyncio.create_task(
401
+ process_tool_invocations(actions, conversation_history, self.state)
402
+ )
403
+ self._current_task = tool_task
404
+
405
+ try:
406
+ should_agent_finish = await tool_task
407
+ self._current_task = None
408
+ except asyncio.CancelledError:
409
+ self._current_task = None
410
+ self.state.add_error("Tool execution cancelled by user")
411
+ raise
412
+
413
+ self.state.messages = conversation_history
414
+
415
+ if should_agent_finish:
416
+ self.state.set_completed({"success": True})
417
+ if tracer:
418
+ tracer.update_agent_status(self.state.agent_id, "completed")
419
+ if self.non_interactive and self.state.parent_id is None:
420
+ return True
421
+ return True
422
+
423
+ return False
424
+
425
+ async def _handle_iteration_error(
426
+ self,
427
+ error: RuntimeError | ValueError | TypeError | asyncio.CancelledError,
428
+ tracer: Optional["Tracer"],
429
+ ) -> bool:
430
+ error_msg = f"Error in iteration {self.state.iteration}: {error!s}"
431
+ logger.exception(error_msg)
432
+ self.state.add_error(error_msg)
433
+ if tracer:
434
+ tracer.update_agent_status(self.state.agent_id, "error")
435
+ return True
436
+
437
+ def _check_agent_messages(self, state: AgentState) -> None: # noqa: PLR0912
438
+ try:
439
+ from aipt_v2.tools.agents_graph.agents_graph_actions import _agent_graph, _agent_messages
440
+
441
+ agent_id = state.agent_id
442
+ if not agent_id or agent_id not in _agent_messages:
443
+ return
444
+
445
+ messages = _agent_messages[agent_id]
446
+ if messages:
447
+ has_new_messages = False
448
+ for message in messages:
449
+ if not message.get("read", False):
450
+ sender_id = message.get("from")
451
+
452
+ if state.is_waiting_for_input():
453
+ if state.llm_failed:
454
+ if sender_id == "user":
455
+ state.resume_from_waiting()
456
+ has_new_messages = True
457
+
458
+ from aipt_v2.telemetry.tracer import get_global_tracer
459
+
460
+ tracer = get_global_tracer()
461
+ if tracer:
462
+ tracer.update_agent_status(state.agent_id, "running")
463
+ else:
464
+ state.resume_from_waiting()
465
+ has_new_messages = True
466
+
467
+ from aipt_v2.telemetry.tracer import get_global_tracer
468
+
469
+ tracer = get_global_tracer()
470
+ if tracer:
471
+ tracer.update_agent_status(state.agent_id, "running")
472
+
473
+ if sender_id == "user":
474
+ sender_name = "User"
475
+ state.add_message("user", message.get("content", ""))
476
+ else:
477
+ if sender_id and sender_id in _agent_graph.get("nodes", {}):
478
+ sender_name = _agent_graph["nodes"][sender_id]["name"]
479
+
480
+ message_content = f"""<inter_agent_message>
481
+ <delivery_notice>
482
+ <important>You have received a message from another agent. You should acknowledge
483
+ this message and respond appropriately based on its content. However, DO NOT echo
484
+ back or repeat the entire message structure in your response. Simply process the
485
+ content and respond naturally as/if needed.</important>
486
+ </delivery_notice>
487
+ <sender>
488
+ <agent_name>{sender_name}</agent_name>
489
+ <agent_id>{sender_id}</agent_id>
490
+ </sender>
491
+ <message_metadata>
492
+ <type>{message.get("message_type", "information")}</type>
493
+ <priority>{message.get("priority", "normal")}</priority>
494
+ <timestamp>{message.get("timestamp", "")}</timestamp>
495
+ </message_metadata>
496
+ <content>
497
+ {message.get("content", "")}
498
+ </content>
499
+ <delivery_info>
500
+ <note>This message was delivered during your task execution.
501
+ Please acknowledge and respond if needed.</note>
502
+ </delivery_info>
503
+ </inter_agent_message>"""
504
+ state.add_message("user", message_content.strip())
505
+
506
+ message["read"] = True
507
+
508
+ if has_new_messages and not state.is_waiting_for_input():
509
+ from aipt_v2.telemetry.tracer import get_global_tracer
510
+
511
+ tracer = get_global_tracer()
512
+ if tracer:
513
+ tracer.update_agent_status(agent_id, "running")
514
+
515
+ except (AttributeError, KeyError, TypeError) as e:
516
+ import logging
517
+
518
+ logger = logging.getLogger(__name__)
519
+ logger.warning(f"Error checking agent messages: {e}")
520
+ return