superbrowser-sdk 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. super_browser/__init__.py +17 -0
  2. super_browser/agent/__init__.py +37 -0
  3. super_browser/agent/config.py +18 -0
  4. super_browser/agent/debug.py +201 -0
  5. super_browser/agent/delegator.py +145 -0
  6. super_browser/agent/facade.py +1216 -0
  7. super_browser/agent/llm/__init__.py +7 -0
  8. super_browser/agent/llm/anthropic_client.py +326 -0
  9. super_browser/agent/llm/browser_transport.py +352 -0
  10. super_browser/agent/llm/budget_aware.py +195 -0
  11. super_browser/agent/llm/factory.py +89 -0
  12. super_browser/agent/llm/openai_client.py +409 -0
  13. super_browser/agent/llm/protocol.py +73 -0
  14. super_browser/agent/loop.py +647 -0
  15. super_browser/agent/loop_detector.py +75 -0
  16. super_browser/agent/plugins.py +38 -0
  17. super_browser/agent/registry.py +218 -0
  18. super_browser/agent/router.py +99 -0
  19. super_browser/agent/structured_logging.py +76 -0
  20. super_browser/agent/types.py +184 -0
  21. super_browser/behavioral/__init__.py +36 -0
  22. super_browser/behavioral/bezier.py +64 -0
  23. super_browser/behavioral/dwell.py +162 -0
  24. super_browser/behavioral/fitts.py +35 -0
  25. super_browser/behavioral/gauss.py +100 -0
  26. super_browser/behavioral/keyboard.py +183 -0
  27. super_browser/behavioral/mouse.py +198 -0
  28. super_browser/behavioral/navigation.py +144 -0
  29. super_browser/behavioral/orchestrator.py +221 -0
  30. super_browser/behavioral/prng.py +27 -0
  31. super_browser/behavioral/qwerty.py +163 -0
  32. super_browser/behavioral/scroll.py +113 -0
  33. super_browser/behavioral/session_seed.py +85 -0
  34. super_browser/behavioral/types.py +62 -0
  35. super_browser/browser/__init__.py +15 -0
  36. super_browser/browser/backends/__init__.py +43 -0
  37. super_browser/browser/backends/cdp_backend.py +613 -0
  38. super_browser/browser/backends/patchright_backend.py +351 -0
  39. super_browser/browser/backends/playwright_backend.py +368 -0
  40. super_browser/browser/backends/selenium_backend.py +567 -0
  41. super_browser/browser/cdp.py +241 -0
  42. super_browser/browser/cloak_backend.py +162 -0
  43. super_browser/browser/cloud.py +265 -0
  44. super_browser/browser/config.py +48 -0
  45. super_browser/browser/discovery.py +101 -0
  46. super_browser/browser/engine.py +326 -0
  47. super_browser/browser/fetch.py +384 -0
  48. super_browser/browser/injectors/__init__.py +46 -0
  49. super_browser/browser/injectors/bidi_injector.py +39 -0
  50. super_browser/browser/injectors/cdp_injector.py +83 -0
  51. super_browser/browser/injectors/page_injector.py +71 -0
  52. super_browser/browser/page.py +96 -0
  53. super_browser/browser/session.py +295 -0
  54. super_browser/browser/shutdown.py +75 -0
  55. super_browser/browser/tabs.py +140 -0
  56. super_browser/budget/__init__.py +40 -0
  57. super_browser/budget/cascade.py +142 -0
  58. super_browser/budget/client.py +132 -0
  59. super_browser/budget/compressor.py +218 -0
  60. super_browser/budget/cost_estimator.py +67 -0
  61. super_browser/budget/credential_pool.py +282 -0
  62. super_browser/budget/governor.py +279 -0
  63. super_browser/budget/types.py +227 -0
  64. super_browser/cli/__init__.py +214 -0
  65. super_browser/cli/commands.py +235 -0
  66. super_browser/cli/interactive.py +85 -0
  67. super_browser/cli/script.py +266 -0
  68. super_browser/cli.py +279 -0
  69. super_browser/config.py +479 -0
  70. super_browser/events/__init__.py +31 -0
  71. super_browser/events/bus.py +110 -0
  72. super_browser/events/types.py +42 -0
  73. super_browser/interaction/__init__.py +32 -0
  74. super_browser/interaction/cache.py +180 -0
  75. super_browser/interaction/controller.py +648 -0
  76. super_browser/interaction/decorator.py +41 -0
  77. super_browser/interaction/presets.py +117 -0
  78. super_browser/interaction/recovery.py +48 -0
  79. super_browser/interaction/snapshot.py +153 -0
  80. super_browser/interaction/types.py +135 -0
  81. super_browser/interaction/vision.py +56 -0
  82. super_browser/memory/__init__.py +6 -0
  83. super_browser/memory/integration.py +85 -0
  84. super_browser/memory/store.py +241 -0
  85. super_browser/memory/types.py +57 -0
  86. super_browser/plugins/__init__.py +10 -0
  87. super_browser/plugins/decorators.py +33 -0
  88. super_browser/plugins/hooks.py +28 -0
  89. super_browser/py.typed +0 -0
  90. super_browser/recording/__init__.py +19 -0
  91. super_browser/recording/persistence.py +49 -0
  92. super_browser/recording/recorder.py +189 -0
  93. super_browser/recording/replayer.py +218 -0
  94. super_browser/recording/report.py +123 -0
  95. super_browser/recording/types.py +124 -0
  96. super_browser/recovery/__init__.py +67 -0
  97. super_browser/recovery/checkpoint.py +317 -0
  98. super_browser/recovery/classifier.py +235 -0
  99. super_browser/recovery/coordinator.py +240 -0
  100. super_browser/recovery/event_bus.py +67 -0
  101. super_browser/recovery/format_validator.py +172 -0
  102. super_browser/recovery/reflection.py +109 -0
  103. super_browser/recovery/retry_tracker.py +81 -0
  104. super_browser/recovery/session_recovery.py +248 -0
  105. super_browser/recovery/types.py +178 -0
  106. super_browser/recovery/watchdogs.py +251 -0
  107. super_browser/results/__init__.py +54 -0
  108. super_browser/results/output.py +154 -0
  109. super_browser/results/typed.py +165 -0
  110. super_browser/results/types.py +361 -0
  111. super_browser/results/validation.py +126 -0
  112. super_browser/security/__init__.py +75 -0
  113. super_browser/security/action_redaction.py +127 -0
  114. super_browser/security/approval.py +130 -0
  115. super_browser/security/credential_vault.py +203 -0
  116. super_browser/security/domain_filter.py +56 -0
  117. super_browser/security/gate.py +114 -0
  118. super_browser/security/injection.py +162 -0
  119. super_browser/security/manager.py +185 -0
  120. super_browser/security/policy.py +69 -0
  121. super_browser/security/redactor.py +151 -0
  122. super_browser/security/types.py +215 -0
  123. super_browser/session/__init__.py +1 -0
  124. super_browser/session/proxy.py +153 -0
  125. super_browser/skills/__init__.py +31 -0
  126. super_browser/skills/activation.py +38 -0
  127. super_browser/skills/markdown.py +109 -0
  128. super_browser/skills/registry.py +335 -0
  129. super_browser/skills/types.py +123 -0
  130. super_browser/stealth/__init__.py +94 -0
  131. super_browser/stealth/action_policy.py +88 -0
  132. super_browser/stealth/captcha.py +318 -0
  133. super_browser/stealth/challenges/__init__.py +41 -0
  134. super_browser/stealth/challenges/cache.py +293 -0
  135. super_browser/stealth/challenges/pow.py +242 -0
  136. super_browser/stealth/challenges/turnstile.py +259 -0
  137. super_browser/stealth/consistency/__init__.py +28 -0
  138. super_browser/stealth/consistency/dag.py +142 -0
  139. super_browser/stealth/consistency/derive.py +282 -0
  140. super_browser/stealth/consistency/errors.py +40 -0
  141. super_browser/stealth/consistency/inject.py +429 -0
  142. super_browser/stealth/consistency/inject_delivery.py +283 -0
  143. super_browser/stealth/consistency/matrix.py +106 -0
  144. super_browser/stealth/consistency/prng.py +115 -0
  145. super_browser/stealth/consistency/rule.py +64 -0
  146. super_browser/stealth/consistency/rules/__init__.py +40 -0
  147. super_browser/stealth/consistency/rules/audio.py +37 -0
  148. super_browser/stealth/consistency/rules/behavior.py +142 -0
  149. super_browser/stealth/consistency/rules/fonts.py +49 -0
  150. super_browser/stealth/consistency/rules/gpu.py +116 -0
  151. super_browser/stealth/consistency/rules/locale.py +66 -0
  152. super_browser/stealth/consistency/rules/navigator.py +70 -0
  153. super_browser/stealth/consistency/rules/screen.py +87 -0
  154. super_browser/stealth/consistency/rules/user_agent.py +121 -0
  155. super_browser/stealth/diagnostics.py +204 -0
  156. super_browser/stealth/ejecta/__init__.py +20 -0
  157. super_browser/stealth/ejecta/audio.py +182 -0
  158. super_browser/stealth/ejecta/browser_apis.py +225 -0
  159. super_browser/stealth/ejecta/canvas.py +210 -0
  160. super_browser/stealth/ejecta/config.py +48 -0
  161. super_browser/stealth/ejecta/registry.py +55 -0
  162. super_browser/stealth/ejecta/timing.py +159 -0
  163. super_browser/stealth/ejecta/types.py +30 -0
  164. super_browser/stealth/ejecta/webrtc.py +120 -0
  165. super_browser/stealth/fingerprint_scanner.py +187 -0
  166. super_browser/stealth/fingerprint_score.py +122 -0
  167. super_browser/stealth/headers.py +115 -0
  168. super_browser/stealth/human.py +419 -0
  169. super_browser/stealth/human_config.py +158 -0
  170. super_browser/stealth/ip_reputation.py +330 -0
  171. super_browser/stealth/manager.py +463 -0
  172. super_browser/stealth/profiles/__init__.py +145 -0
  173. super_browser/stealth/profiles/data/linux-chrome-stable.json +98 -0
  174. super_browser/stealth/profiles/data/macos-chrome-stable.json +130 -0
  175. super_browser/stealth/profiles/data/macos-m4-chrome-stable.json +132 -0
  176. super_browser/stealth/profiles/data/windows-chrome-stable.json +103 -0
  177. super_browser/stealth/profiles/host_detect.py +32 -0
  178. super_browser/stealth/profiles/schema.py +165 -0
  179. super_browser/stealth/proxy.py +86 -0
  180. super_browser/stealth/proxy_pool.py +490 -0
  181. super_browser/stealth/report.py +111 -0
  182. super_browser/stealth/scoring.py +54 -0
  183. super_browser/stealth/tls_baselines.json +36 -0
  184. super_browser/stealth/tls_fingerprint.py +494 -0
  185. super_browser/stealth/types.py +179 -0
  186. super_browser/stealth/user_agent_pool.py +148 -0
  187. super_browser/stealth/validation/__init__.py +13 -0
  188. super_browser/stealth/validation/checks.py +334 -0
  189. super_browser/stealth/validation/harness.py +161 -0
  190. super_browser/stealth/validation/report.py +31 -0
  191. super_browser/stealth/validation/suite.py +49 -0
  192. super_browser/testing.py +422 -0
  193. super_browser/tracing/__init__.py +29 -0
  194. super_browser/tracing/cost_analytics.py +49 -0
  195. super_browser/tracing/flow_logger.py +283 -0
  196. super_browser/tracing/middleware.py +42 -0
  197. super_browser/tracing/session_db.py +243 -0
  198. super_browser/tracing/sinks.py +166 -0
  199. super_browser/tracing/types.py +175 -0
  200. super_browser/verification/__init__.py +39 -0
  201. super_browser/verification/ax_diff.py +65 -0
  202. super_browser/verification/hasher.py +154 -0
  203. super_browser/verification/types.py +153 -0
  204. super_browser/verification/verifier.py +331 -0
  205. super_browser/vision/__init__.py +49 -0
  206. super_browser/vision/cache.py +178 -0
  207. super_browser/vision/controller.py +373 -0
  208. super_browser/vision/coords.py +57 -0
  209. super_browser/vision/factory.py +116 -0
  210. super_browser/vision/ocr.py +140 -0
  211. super_browser/vision/providers.py +348 -0
  212. super_browser/vision/types.py +110 -0
  213. superbrowser_sdk-2.0.0.dist-info/METADATA +562 -0
  214. superbrowser_sdk-2.0.0.dist-info/RECORD +217 -0
  215. superbrowser_sdk-2.0.0.dist-info/WHEEL +4 -0
  216. superbrowser_sdk-2.0.0.dist-info/entry_points.txt +2 -0
  217. superbrowser_sdk-2.0.0.dist-info/licenses/LICENSE +190 -0
@@ -0,0 +1,17 @@
1
+ """Super Browser — Comprehensive browser control for AI agents."""
2
+
3
+ from super_browser.agent.facade import SuperBrowser as SuperBrowser # noqa: F401
4
+ from super_browser.agent.llm import create_llm as create_llm # noqa: F401
5
+ from super_browser.agent.types import StreamEvent as StreamEvent # noqa: F401
6
+ from super_browser.config import Config as Config # noqa: F401
7
+ from super_browser.results.types import ActionResult as ActionResult # noqa: F401
8
+
9
+ __version__ = "2.0.0"
10
+
11
+ __all__ = [
12
+ "SuperBrowser",
13
+ "Config",
14
+ "ActionResult",
15
+ "create_llm",
16
+ "StreamEvent",
17
+ ]
@@ -0,0 +1,37 @@
1
+ """GAP-07: Agent Orchestration & Facade."""
2
+
3
+ from super_browser.agent.delegator import SubagentDelegator
4
+ from super_browser.agent.facade import SuperBrowser
5
+ from super_browser.agent.llm import LLMClient, create_llm
6
+ from super_browser.agent.loop import AgentLoop
7
+ from super_browser.agent.loop_detector import ActionLoopDetector
8
+ from super_browser.agent.plugins import PluginRegistry, PluginSlot
9
+ from super_browser.agent.registry import ToolDefinition, ToolParameter, ToolRegistry, Toolset
10
+ from super_browser.agent.types import (
11
+ ChildTask,
12
+ DelegationResult,
13
+ DelegationStatus,
14
+ LoopNudge,
15
+ LoopResult,
16
+ PlanItem,
17
+ PlanStatus,
18
+ PluginSlotKey,
19
+ StepEvent,
20
+ StepResult,
21
+ )
22
+ from super_browser.config import AgentConfig
23
+
24
+ __all__ = [
25
+ "AgentConfig",
26
+ "ChildTask", "DelegationResult", "DelegationStatus",
27
+ "LoopNudge", "LoopResult",
28
+ "PlanItem", "PlanStatus", "PluginSlotKey",
29
+ "StepEvent", "StepResult",
30
+ "ActionLoopDetector",
31
+ "ToolDefinition", "ToolParameter", "ToolRegistry", "Toolset",
32
+ "AgentLoop",
33
+ "SuperBrowser",
34
+ "SubagentDelegator",
35
+ "PluginRegistry", "PluginSlot",
36
+ "LLMClient", "create_llm",
37
+ ]
@@ -0,0 +1,18 @@
1
+ """Agent configuration types.
2
+
3
+ ``SuperBrowserConfig`` was removed in v2.0. Its fields are now flattened
4
+ onto :class:`AgentConfig` in ``config.py``.
5
+
6
+ This module provides ``AgentConfig`` via lazy import to avoid circular
7
+ dependency issues.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+
13
+ def __getattr__(name: str):
14
+ """Lazy re-export for backward-compatible import path."""
15
+ if name == "AgentConfig":
16
+ from super_browser.config import AgentConfig
17
+ return AgentConfig
18
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,201 @@
1
+ """Interactive debug session for agent failures.
2
+
3
+ When debug mode is enabled, the agent can pause on failure to allow
4
+ interactive inspection of browser state, capture error artifacts
5
+ (screenshot + DOM snapshot), and resume or abort execution.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import logging
12
+ import time
13
+ from dataclasses import dataclass, field
14
+ from pathlib import Path
15
+ from typing import Any, Optional
16
+
17
+ from super_browser.agent.types import DebugConfig
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ @dataclass
23
+ class DebugSnapshot:
24
+ """Captured debug state at point of failure."""
25
+
26
+ url: str = ""
27
+ title: str = ""
28
+ screenshot_path: str = ""
29
+ dom_path: str = ""
30
+ visible_text_summary: str = ""
31
+ error_message: str = ""
32
+ timestamp: float = field(default_factory=time.time)
33
+
34
+
35
+ class InteractiveDebugSession:
36
+ """Manages interactive debugging when an agent step fails.
37
+
38
+ In interactive mode (default), the session pauses and waits for user
39
+ input before continuing. In non-interactive environments the session
40
+ logs state and auto-continues.
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ config: DebugConfig,
46
+ *,
47
+ interactive: bool = True,
48
+ input_reader: Optional[Any] = None,
49
+ output_writer: Optional[Any] = None,
50
+ ) -> None:
51
+ self._config = config
52
+ self._interactive = interactive
53
+ self._input_reader = input_reader
54
+ self._output_writer = output_writer
55
+ self._snapshots: list[DebugSnapshot] = []
56
+
57
+ # -- Public API --
58
+
59
+ async def pause_on_failure(
60
+ self,
61
+ page: Any,
62
+ error: Exception,
63
+ *,
64
+ step_number: int = 0,
65
+ ) -> str:
66
+ """Pause execution on failure for inspection.
67
+
68
+ Returns user command: ``"continue"``, ``"abort"``, or ``"inspect"``.
69
+ In non-interactive mode, always returns ``"continue"``.
70
+ """
71
+ snapshot = await self._capture_snapshot(page, error)
72
+ self._snapshots.append(snapshot)
73
+
74
+ logger.warning(
75
+ "Debug pause on step %d: %s url=%s",
76
+ step_number,
77
+ str(error),
78
+ snapshot.url,
79
+ )
80
+
81
+ if not self._interactive:
82
+ return "continue"
83
+
84
+ # Interactive: prompt user
85
+ self._write(f"\n🔴 STEP {step_number} FAILED: {error}")
86
+ self._write(f" URL: {snapshot.url}")
87
+ self._write(" Commands: [c]ontinue, [a]bort, [i]nspect → ")
88
+
89
+ command = await self._read_input()
90
+ if command is None:
91
+ return "continue"
92
+
93
+ cmd = command.strip().lower()
94
+ if cmd in ("a", "abort"):
95
+ return "abort"
96
+ if cmd in ("i", "inspect"):
97
+ state = await self.inspect_state(page)
98
+ self._write(f" Title: {state['title']}")
99
+ self._write(f" URL: {state['url']}")
100
+ self._write(f" Visible text (first 200 chars): {state['visible_text_summary'][:200]}")
101
+ return "continue"
102
+ return "continue"
103
+
104
+ async def capture_error_artifacts(
105
+ self,
106
+ page: Any,
107
+ error: Exception,
108
+ config: DebugConfig,
109
+ ) -> DebugSnapshot:
110
+ """Capture screenshot and optional DOM snapshot for an error."""
111
+ snapshot = await self._capture_snapshot(page, error)
112
+
113
+ screenshot_dir = Path(config.screenshot_dir)
114
+ screenshot_dir.mkdir(parents=True, exist_ok=True)
115
+
116
+ ts = int(time.time() * 1000)
117
+
118
+ # Screenshot
119
+ try:
120
+ screenshot_path = screenshot_dir / f"error_{ts}.png"
121
+ if page and hasattr(page, "screenshot"):
122
+ await page.screenshot(path=str(screenshot_path))
123
+ snapshot.screenshot_path = str(screenshot_path)
124
+ logger.info("Debug screenshot saved: %s", screenshot_path)
125
+ except Exception as exc:
126
+ logger.warning("Failed to capture screenshot: %s", exc)
127
+
128
+ # DOM snapshot
129
+ if config.capture_dom:
130
+ try:
131
+ dom_path = screenshot_dir / f"error_{ts}.dom.html"
132
+ if page and hasattr(page, "content"):
133
+ content = await page.content()
134
+ dom_path.write_text(content, encoding="utf-8")
135
+ snapshot.dom_path = str(dom_path)
136
+ logger.info("Debug DOM snapshot saved: %s", dom_path)
137
+ except Exception as exc:
138
+ logger.warning("Failed to capture DOM snapshot: %s", exc)
139
+
140
+ self._snapshots.append(snapshot)
141
+ return snapshot
142
+
143
+ async def inspect_state(self, page: Any) -> dict[str, str]:
144
+ """Return current page state: URL, title, visible text summary."""
145
+ state: dict[str, str] = {
146
+ "url": "",
147
+ "title": "",
148
+ "visible_text_summary": "",
149
+ }
150
+ if page is None:
151
+ return state
152
+ try:
153
+ state["url"] = page.url if hasattr(page, "url") else ""
154
+ except Exception:
155
+ pass
156
+ try:
157
+ if hasattr(page, "title"):
158
+ title_result = page.title()
159
+ if asyncio.iscoroutine(title_result):
160
+ title_result = await title_result
161
+ state["title"] = title_result
162
+ except Exception:
163
+ pass
164
+ try:
165
+ if hasattr(page, "evaluate"):
166
+ text = await page.evaluate("() => document.body?.innerText?.substring(0, 500) || ''")
167
+ state["visible_text_summary"] = text or ""
168
+ except Exception:
169
+ pass
170
+ return state
171
+
172
+ @property
173
+ def snapshots(self) -> list[DebugSnapshot]:
174
+ return list(self._snapshots)
175
+
176
+ # -- Internals --
177
+
178
+ async def _capture_snapshot(self, page: Any, error: Exception) -> DebugSnapshot:
179
+ state = await self.inspect_state(page)
180
+ return DebugSnapshot(
181
+ url=state["url"],
182
+ title=state["title"],
183
+ visible_text_summary=state["visible_text_summary"],
184
+ error_message=str(error),
185
+ timestamp=time.time(),
186
+ )
187
+
188
+ def _write(self, message: str) -> None:
189
+ if self._output_writer:
190
+ self._output_writer(message)
191
+ else:
192
+ # Default: just log
193
+ logger.info("Debug: %s", message)
194
+
195
+ async def _read_input(self) -> Optional[str]:
196
+ if self._input_reader:
197
+ result = self._input_reader()
198
+ if asyncio.iscoroutine(result):
199
+ return await result
200
+ return result
201
+ return None
@@ -0,0 +1,145 @@
1
+ """SubagentDelegator — parallel child agents with isolated browser contexts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import time
8
+ from typing import Any, Optional
9
+
10
+ from super_browser.agent.loop import AgentLoop
11
+ from super_browser.agent.registry import ToolRegistry
12
+ from super_browser.agent.types import ChildTask, DelegationResult, DelegationStatus
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class SubagentDelegator:
18
+
19
+ def __init__(
20
+ self,
21
+ browser_session: Any,
22
+ registry: ToolRegistry,
23
+ llm_client: Any,
24
+ *,
25
+ max_concurrency: int = 4,
26
+ max_steps_per_child: int = 50,
27
+ recovery_coordinator: Optional[Any] = None,
28
+ budget_client: Optional[Any] = None,
29
+ flow_logger: Optional[Any] = None,
30
+ security_manager: Optional[Any] = None,
31
+ stealth_manager: Optional[Any] = None,
32
+ ) -> None:
33
+ self._session = browser_session
34
+ self._registry = registry
35
+ self._llm = llm_client
36
+ self._max_concurrency = max_concurrency
37
+ self._max_steps = max_steps_per_child
38
+ self._recovery_coordinator = recovery_coordinator
39
+ self._budget_client = budget_client
40
+ self._flow_logger = flow_logger
41
+ self._security_manager = security_manager
42
+ self._stealth_manager = stealth_manager
43
+ self._open_tabs = 0
44
+
45
+ @property
46
+ def open_tabs(self) -> int:
47
+ """Current number of open child tabs (read-only)."""
48
+ return self._open_tabs
49
+
50
+ async def delegate(
51
+ self,
52
+ tasks: list[str],
53
+ *,
54
+ max_concurrency: Optional[int] = None,
55
+ abort_signal: Optional[asyncio.Event] = None,
56
+ ) -> DelegationResult:
57
+ start = time.monotonic()
58
+ concurrency = max_concurrency or self._max_concurrency
59
+ semaphore = asyncio.Semaphore(concurrency)
60
+
61
+ children = [ChildTask(instruction=instr) for instr in tasks]
62
+
63
+ async def _run_with_semaphore(task: ChildTask) -> ChildTask:
64
+ async with semaphore:
65
+ if abort_signal and abort_signal.is_set():
66
+ task.status = DelegationStatus.CANCELLED
67
+ return task
68
+ return await self._run_child(task)
69
+
70
+ results = await asyncio.gather(
71
+ *[_run_with_semaphore(c) for c in children],
72
+ return_exceptions=True,
73
+ )
74
+
75
+ final_tasks: list[ChildTask] = []
76
+ for r in results:
77
+ if isinstance(r, Exception):
78
+ failed = ChildTask(instruction="error", status=DelegationStatus.FAILED, result=str(r))
79
+ final_tasks.append(failed)
80
+ else:
81
+ final_tasks.append(r)
82
+
83
+ duration = (time.monotonic() - start) * 1000
84
+ completed = sum(1 for t in final_tasks if t.status == DelegationStatus.COMPLETED)
85
+ failed = sum(1 for t in final_tasks if t.status == DelegationStatus.FAILED)
86
+ cancelled = sum(1 for t in final_tasks if t.status == DelegationStatus.CANCELLED)
87
+
88
+ # Sanity check — should never happen, but catch programming errors.
89
+ assert self._open_tabs <= concurrency, (
90
+ f"Tab cap violated: {self._open_tabs} open tabs > {concurrency} max_concurrency"
91
+ )
92
+
93
+ return DelegationResult(
94
+ tasks=final_tasks,
95
+ total_duration_ms=duration,
96
+ completed_count=completed,
97
+ failed_count=failed,
98
+ cancelled_count=cancelled,
99
+ )
100
+
101
+ async def _run_child(self, task: ChildTask) -> ChildTask:
102
+ task.status = DelegationStatus.RUNNING
103
+ task.started_at = time.monotonic()
104
+
105
+ page = None
106
+ try:
107
+ self._open_tabs += 1
108
+ assert self._open_tabs <= self._max_concurrency, (
109
+ f"Hard tab cap violated: {self._open_tabs} > {self._max_concurrency}"
110
+ )
111
+ page = await self._session.new_page()
112
+
113
+ from super_browser.interaction.controller import MultimodalController
114
+ controller = MultimodalController(page, page.cdp)
115
+
116
+ child_loop = AgentLoop(
117
+ controller=controller,
118
+ registry=self._registry,
119
+ llm_client=self._llm,
120
+ max_steps=self._max_steps,
121
+ recovery_coordinator=self._recovery_coordinator,
122
+ budget_client=self._budget_client,
123
+ flow_logger=self._flow_logger,
124
+ security_manager=self._security_manager,
125
+ stealth_manager=self._stealth_manager,
126
+ )
127
+ loop_result = await child_loop.run(task.instruction)
128
+
129
+ task.result = loop_result
130
+ task.status = DelegationStatus.COMPLETED
131
+
132
+ except Exception as exc:
133
+ logger.warning("Child task %s failed: %s", task.task_id[:8], exc)
134
+ task.result = str(exc)
135
+ task.status = DelegationStatus.FAILED
136
+ finally:
137
+ if page:
138
+ try:
139
+ await page.close()
140
+ except Exception:
141
+ pass
142
+ self._open_tabs -= 1
143
+
144
+ task.completed_at = time.monotonic()
145
+ return task