webpilot-engine 2.0.0__tar.gz → 2.0.2__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 (61) hide show
  1. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/PKG-INFO +1 -1
  2. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/adapters/browser_adapter.py +48 -5
  3. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/adapters/live_session_manager.py +17 -13
  4. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/cli.py +41 -8
  5. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/pyproject.toml +1 -1
  6. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/supervisor/client.py +11 -6
  7. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/supervisor/master_daemon.py +17 -10
  8. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/tests/test_live_session.py +2 -0
  9. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/webpilot_engine.egg-info/PKG-INFO +1 -1
  10. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/README.md +0 -0
  11. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/adapters/__init__.py +0 -0
  12. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/adapters/base.py +0 -0
  13. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/adapters/dom_scripts.py +0 -0
  14. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/adapters/envelope_builder.py +0 -0
  15. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/adapters/upload_adapter.py +0 -0
  16. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/builder.py +0 -0
  17. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/config/__init__.py +0 -0
  18. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/config/settings.py +0 -0
  19. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/constants/__init__.py +0 -0
  20. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/constants/contracts.py +0 -0
  21. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/constants/selectors.py +0 -0
  22. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/constants/timeouts.py +0 -0
  23. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/core/__init__.py +0 -0
  24. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/core/exceptions.py +0 -0
  25. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/core/models.py +0 -0
  26. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/engine.py +0 -0
  27. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/exceptions.py +0 -0
  28. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/mcp_server.py +0 -0
  29. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/models.py +0 -0
  30. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/orchestration/__init__.py +0 -0
  31. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/orchestration/context.py +0 -0
  32. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/orchestration/flow_orchestrator.py +0 -0
  33. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/orchestration/session_coordinator.py +0 -0
  34. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/services/__init__.py +0 -0
  35. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/services/auth_navigation_service.py +0 -0
  36. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/services/field_interaction_service.py +0 -0
  37. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/services/form_filler_service.py +0 -0
  38. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/services/inspection_formatter_service.py +0 -0
  39. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/services/inspection_service.py +0 -0
  40. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/services/reactive_interaction_service.py +0 -0
  41. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/setup.cfg +0 -0
  42. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/supervisor/__init__.py +0 -0
  43. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/supervisor/contracts.py +0 -0
  44. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/supervisor/shell.py +0 -0
  45. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/supervisor/worker_process.py +0 -0
  46. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/tests/test_config_and_builder.py +0 -0
  47. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/tests/test_contracts.py +0 -0
  48. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/tests/test_engine_facade.py +0 -0
  49. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/tests/test_mcp_server.py +0 -0
  50. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/tests/test_orchestration.py +0 -0
  51. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/tests/test_redaction.py +0 -0
  52. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/tests/test_services.py +0 -0
  53. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/tests/test_supervisor.py +0 -0
  54. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/validators/__init__.py +0 -0
  55. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/validators/form_validator.py +0 -0
  56. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/validators/payload_validator.py +0 -0
  57. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/webpilot_engine.egg-info/SOURCES.txt +0 -0
  58. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/webpilot_engine.egg-info/dependency_links.txt +0 -0
  59. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/webpilot_engine.egg-info/entry_points.txt +0 -0
  60. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/webpilot_engine.egg-info/requires.txt +0 -0
  61. {webpilot_engine-2.0.0 → webpilot_engine-2.0.2}/webpilot_engine.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webpilot-engine
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: WebPilot: Enterprise Autonomous Browser Engine, Universal Form Inspector & AI Agent Web Runtime
5
5
  Author: Antigravity Engineering
6
6
  License: MIT
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
+ import subprocess
6
7
  import sys
7
8
  from typing import Any, Dict, List, Optional
8
9
  from playwright.sync_api import sync_playwright, Browser, BrowserContext, Page, Playwright
@@ -135,6 +136,43 @@ class BrowserAdapter(BrowserAdapterProtocol):
135
136
  else:
136
137
  raise IndexError(f"Tab index #{index} out of range (Total open tabs: {len(pages)})")
137
138
 
139
+ def _ensure_browser_binaries(self) -> None:
140
+ """Verifies Chromium is installed; if not, automatically downloads it or shows a clear error."""
141
+ needs_install = False
142
+ try:
143
+ exec_path = self._pw.chromium.executable_path
144
+ if not exec_path or not os.path.exists(exec_path):
145
+ needs_install = True
146
+ except Exception:
147
+ needs_install = True
148
+
149
+ if needs_install:
150
+ print("\n" + "═" * 70)
151
+ print(" 🚀 WEBPILOT | INITIAL BROWSER SETUP")
152
+ print(" [*] Playwright Chromium binaries not found on this system.")
153
+ print(" [*] Automatically downloading Chromium browser (one-time setup)...")
154
+ print("═" * 70)
155
+ try:
156
+ cmd = [sys.executable, "-m", "playwright", "install", "chromium"]
157
+ subprocess.run(cmd, check=True)
158
+ print("[✓] Chromium installed successfully!")
159
+ except Exception as exc:
160
+ err_banner = (
161
+ "\n" + "═" * 70 + "\n"
162
+ " ❌ WEBPILOT ERROR: CHROMIUM BROWSER NOT INSTALLED\n"
163
+ " " + "═" * 35 + "\n"
164
+ " WebPilot requires Chromium to operate websites.\n"
165
+ " Automatic installation failed (offline or network restriction).\n\n"
166
+ " Please run the following command manually to install the browser:\n\n"
167
+ " playwright install chromium\n"
168
+ " or\n"
169
+ " wp install\n\n"
170
+ f" Details: {exc}\n"
171
+ "═" * 70 + "\n"
172
+ )
173
+ print(err_banner, file=sys.stderr)
174
+ raise RuntimeError("Chromium browser is not installed. Please run: wp install") from None
175
+
138
176
  def start(self, cookies_path: Optional[str] = None) -> None:
139
177
  """Starts browser context or connects to existing persistent live session."""
140
178
  if self.close_session:
@@ -146,6 +184,7 @@ class BrowserAdapter(BrowserAdapterProtocol):
146
184
  return
147
185
 
148
186
  self._pw = sync_playwright().start()
187
+ self._ensure_browser_binaries()
149
188
 
150
189
  existing_session = LiveSessionManager.load_session_state()
151
190
  should_use_persistent = self.keep_alive or (existing_session is not None)
@@ -214,11 +253,15 @@ class BrowserAdapter(BrowserAdapterProtocol):
214
253
  args=["--disable-blink-features=AutomationControlled"],
215
254
  )
216
255
  try:
217
- import subprocess
218
- ps_c = f'(Get-CimInstance Win32_Process | Where-Object {{ $_.ParentProcessId -eq {os.getpid()} }}).ProcessId | Select-Object -First 1'
219
- out = subprocess.check_output(["powershell", "-NoProfile", "-Command", ps_c], text=True).strip()
220
- if out.isdigit():
221
- self.browser_pid = int(out)
256
+ if sys.platform == "win32":
257
+ ps_c = f'(Get-CimInstance Win32_Process | Where-Object {{ $_.ParentProcessId -eq {os.getpid()} }}).ProcessId | Select-Object -First 1'
258
+ out = subprocess.check_output(["powershell", "-NoProfile", "-Command", ps_c], text=True).strip()
259
+ if out.isdigit():
260
+ self.browser_pid = int(out)
261
+ else:
262
+ out = subprocess.check_output(["pgrep", "-P", str(os.getpid())], text=True).strip().split()
263
+ if out and out[0].isdigit():
264
+ self.browser_pid = int(out[0])
222
265
  except Exception:
223
266
  pass
224
267
 
@@ -188,15 +188,16 @@ class LiveSessionManager:
188
188
  proc_pid = cls._launch_wmi_process(cmd_str)
189
189
 
190
190
  if proc_pid is None:
191
- flags = 0
191
+ popen_kwargs: Dict[str, Any] = {
192
+ "stdout": subprocess.DEVNULL,
193
+ "stderr": subprocess.DEVNULL,
194
+ }
192
195
  if sys.platform == "win32":
193
- flags = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
194
- proc = subprocess.Popen(
195
- args,
196
- stdout=subprocess.DEVNULL,
197
- stderr=subprocess.DEVNULL,
198
- creationflags=flags,
199
- )
196
+ popen_kwargs["creationflags"] = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
197
+ else:
198
+ popen_kwargs["start_new_session"] = True
199
+
200
+ proc = subprocess.Popen(args, **popen_kwargs)
200
201
  proc_pid = proc.pid
201
202
 
202
203
  # Wait up to 5 seconds for CDP port to become active
@@ -303,16 +304,19 @@ if ($proc.ReturnValue -eq 0) {{
303
304
  if wmi_pid is not None:
304
305
  return
305
306
 
306
- flags = 0
307
+ popen_kwargs: Dict[str, Any] = {
308
+ "stdout": subprocess.DEVNULL,
309
+ "stderr": subprocess.DEVNULL,
310
+ }
307
311
  if sys.platform == "win32":
308
- flags = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
312
+ popen_kwargs["creationflags"] = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
313
+ else:
314
+ popen_kwargs["start_new_session"] = True
309
315
 
310
316
  try:
311
317
  subprocess.Popen(
312
318
  [sys.executable, "-m", "adapters.live_session_manager", str(browser_pid), str(port), str(timeout_seconds)],
313
- stdout=subprocess.DEVNULL,
314
- stderr=subprocess.DEVNULL,
315
- creationflags=flags,
319
+ **popen_kwargs,
316
320
  )
317
321
  except Exception:
318
322
  pass
@@ -326,15 +326,48 @@ def main():
326
326
  p_service.add_argument("--host", default="127.0.0.1", help="Supervisor host address")
327
327
  p_service.add_argument("--port", type=int, default=9333, help="Supervisor port")
328
328
 
329
+ # 5. Browser Binaries Installation Subcommand
330
+ p_install = subparsers.add_parser("install", help="Download and configure required Playwright Chromium browser")
331
+ p_install.add_argument("--all", action="store_true", help="Download all browsers (Chromium, Firefox, WebKit)")
332
+
329
333
  args = parser.parse_args()
330
- if args.command == "inspect":
331
- inspect_cmd(args)
332
- elif args.command == "apply":
333
- apply_cmd(args)
334
- elif args.command == "shell":
335
- shell_cmd(args)
336
- elif args.command == "service":
337
- service_cmd(args)
334
+ try:
335
+ if args.command == "inspect":
336
+ inspect_cmd(args)
337
+ elif args.command == "apply":
338
+ apply_cmd(args)
339
+ elif args.command == "shell":
340
+ shell_cmd(args)
341
+ elif args.command == "service":
342
+ service_cmd(args)
343
+ elif args.command == "install":
344
+ install_cmd(args)
345
+ except KeyboardInterrupt:
346
+ print("\n[!] Operation cancelled by user.")
347
+ sys.exit(130)
348
+ except Exception as exc:
349
+ print(f"\n[!] Error: {exc}", file=sys.stderr)
350
+ sys.exit(1)
351
+
352
+
353
+ def install_cmd(args: argparse.Namespace) -> None:
354
+ """Installs required Playwright browser binaries."""
355
+ import subprocess
356
+ print("\n" + "═" * 70)
357
+ print(" 🚀 WEBPILOT | BROWSER BINARY INSTALLER")
358
+ print("═" * 70)
359
+ target = "all" if getattr(args, "all", False) else "chromium"
360
+ cmd = [sys.executable, "-m", "playwright", "install"]
361
+ if target != "all":
362
+ cmd.append("chromium")
363
+ print(f"[*] Downloading and configuring Playwright {target} browser binaries...")
364
+ try:
365
+ subprocess.run(cmd, check=True)
366
+ print("\n[✓] Browser binaries downloaded and configured successfully!")
367
+ except Exception as exc:
368
+ print(f"\n[!] Failed to install browser binaries: {exc}", file=sys.stderr)
369
+ print("Please check your internet connection or run 'playwright install chromium' manually.", file=sys.stderr)
370
+ sys.exit(1)
338
371
 
339
372
 
340
373
  if __name__ == "__main__":
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "webpilot-engine"
7
- version = "2.0.0"
7
+ version = "2.0.2"
8
8
  description = "WebPilot: Enterprise Autonomous Browser Engine, Universal Form Inspector & AI Agent Web Runtime"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -49,15 +49,12 @@ class SupervisorClient:
49
49
  script_path = os.path.join(PROJECT_ROOT, "supervisor", "master_daemon.py")
50
50
  py_exe = sys.executable
51
51
 
52
- # Launch detached via WMI outside any parent terminal Job Object
52
+ # Launch detached via WMI outside any parent terminal Job Object on Windows
53
53
  if sys.platform == "win32":
54
54
  if getattr(sys, "frozen", False):
55
- py_exe = sys.executable
56
55
  cwd = os.path.dirname(py_exe)
57
56
  cmd_line = f'`"{py_exe}`" --run-daemon'
58
57
  else:
59
- script_path = os.path.join(PROJECT_ROOT, "supervisor", "master_daemon.py")
60
- py_exe = sys.executable
61
58
  cwd = PROJECT_ROOT
62
59
  cmd_line = f'`"{py_exe}`" `"{script_path}`"'
63
60
 
@@ -79,12 +76,20 @@ if ($proc.ReturnValue -eq 0) {{
79
76
  timeout=5,
80
77
  )
81
78
  else:
79
+ # POSIX / Linux / macOS
80
+ if getattr(sys, "frozen", False):
81
+ cmd = [py_exe, "--run-daemon"]
82
+ cwd = os.path.dirname(py_exe)
83
+ else:
84
+ cmd = [py_exe, script_path]
85
+ cwd = PROJECT_ROOT
86
+
82
87
  subprocess.Popen(
83
- [sys.executable, script_path],
88
+ cmd,
84
89
  stdout=subprocess.DEVNULL,
85
90
  stderr=subprocess.DEVNULL,
86
91
  start_new_session=True,
87
- cwd=PROJECT_ROOT,
92
+ cwd=cwd,
88
93
  )
89
94
 
90
95
  # Wait up to 4 seconds for supervisor to become active
@@ -87,15 +87,18 @@ class MasterSupervisor:
87
87
  worker_cmd = [sys.executable, "-m", "supervisor.worker_process"]
88
88
  worker_cwd = PROJECT_ROOT
89
89
 
90
- self.worker_process = subprocess.Popen(
91
- worker_cmd,
92
- stdin=subprocess.PIPE,
93
- stdout=subprocess.PIPE,
94
- stderr=subprocess.PIPE,
95
- text=True,
96
- bufsize=1,
97
- cwd=worker_cwd,
98
- )
90
+ popen_kwargs: Dict[str, Any] = {
91
+ "stdin": subprocess.PIPE,
92
+ "stdout": subprocess.PIPE,
93
+ "stderr": subprocess.PIPE,
94
+ "text": True,
95
+ "bufsize": 1,
96
+ "cwd": worker_cwd,
97
+ }
98
+ if sys.platform != "win32":
99
+ popen_kwargs["start_new_session"] = True
100
+
101
+ self.worker_process = subprocess.Popen(worker_cmd, **popen_kwargs)
99
102
  return self.worker_process
100
103
 
101
104
  def execute_action(self, req: SupervisorActionRequest) -> SupervisorActionResponse:
@@ -193,7 +196,11 @@ class MasterSupervisor:
193
196
  stderr=subprocess.DEVNULL,
194
197
  )
195
198
  else:
196
- proc.kill()
199
+ try:
200
+ import signal
201
+ os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
202
+ except Exception:
203
+ proc.kill()
197
204
  except Exception:
198
205
  pass
199
206
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import os
4
4
  import subprocess
5
+ import sys
5
6
  import time
6
7
  import urllib.request
7
8
  import pytest
@@ -10,6 +11,7 @@ from playwright.sync_api import sync_playwright
10
11
  from adapters.live_session_manager import LiveSessionManager, DEFAULT_CDP_PORT
11
12
 
12
13
 
14
+ @pytest.mark.skipif(sys.platform != "win32", reason="WMI is only available on Windows")
13
15
  def test_wmi_launch():
14
16
  """Verify _launch_wmi_process returns a real PID."""
15
17
  p = sync_playwright().start()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webpilot-engine
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: WebPilot: Enterprise Autonomous Browser Engine, Universal Form Inspector & AI Agent Web Runtime
5
5
  Author: Antigravity Engineering
6
6
  License: MIT