omnius 1.0.31 → 1.0.33

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.
@@ -140,6 +140,26 @@ def log_message(msg: str, level: str = "INFO") -> None:
140
140
  print(f"[{ts}] [{level.upper()}] {msg}")
141
141
 
142
142
 
143
+ def _bounded_int(value, default: int, minimum: int, maximum: int) -> int:
144
+ try:
145
+ n = int(float(value))
146
+ except Exception:
147
+ n = default
148
+ return max(minimum, min(maximum, n))
149
+
150
+
151
+ def _bounded_float(value, default: float, minimum: float, maximum: float) -> float:
152
+ try:
153
+ n = float(value)
154
+ except Exception:
155
+ n = default
156
+ return max(minimum, min(maximum, n))
157
+
158
+
159
+ def _truthy(value) -> bool:
160
+ return str(value).lower() in ("1", "true", "yes", "on")
161
+
162
+
143
163
  class Tools:
144
164
  _driver: Optional[webdriver.Chrome] = None
145
165
 
@@ -163,7 +183,13 @@ class Tools:
163
183
  return None
164
184
 
165
185
  @staticmethod
166
- def open_browser(headless: bool = False, force_new: bool = False) -> str:
186
+ def open_browser(
187
+ headless: bool = False,
188
+ force_new: bool = False,
189
+ width: int = 1280,
190
+ height: int = 720,
191
+ device_scale_factor: float = 1.0,
192
+ ) -> str:
167
193
  if force_new and Tools._driver:
168
194
  try:
169
195
  Tools._driver.quit()
@@ -184,12 +210,16 @@ class Tools:
184
210
  or "/usr/bin/chromium"
185
211
  )
186
212
 
213
+ width = _bounded_int(width, 1280, 320, 3840)
214
+ height = _bounded_int(height, 720, 240, 2160)
215
+ device_scale_factor = _bounded_float(device_scale_factor, 1.0, 0.25, 3.0)
216
+
187
217
  opts = Options()
188
218
  if chrome_bin:
189
219
  opts.binary_location = chrome_bin
190
220
  if headless:
191
221
  opts.add_argument("--headless=new")
192
- opts.add_argument("--window-size=1920,1080")
222
+ opts.add_argument(f"--window-size={width},{height}")
193
223
  opts.add_argument("--disable-gpu")
194
224
  opts.add_argument("--no-sandbox")
195
225
  opts.add_argument("--disable-dev-shm-usage")
@@ -199,6 +229,7 @@ class Tools:
199
229
  try:
200
230
  log_message("[open_browser] Trying Selenium-Manager…", "DEBUG")
201
231
  Tools._driver = webdriver.Chrome(options=opts)
232
+ Tools.set_viewport(width, height, device_scale_factor)
202
233
  log_message("[open_browser] Launched via Selenium-Manager.", "SUCCESS")
203
234
  return "Browser launched (selenium-manager)"
204
235
  except WebDriverException as e:
@@ -209,6 +240,7 @@ class Tools:
209
240
  try:
210
241
  log_message(f"[open_browser] Using snap chromedriver at {snap_drv}", "DEBUG")
211
242
  Tools._driver = webdriver.Chrome(service=Service(snap_drv), options=opts)
243
+ Tools.set_viewport(width, height, device_scale_factor)
212
244
  log_message("[open_browser] Launched via snap chromedriver.", "SUCCESS")
213
245
  return "Browser launched (snap chromedriver)"
214
246
  except WebDriverException as e:
@@ -219,6 +251,7 @@ class Tools:
219
251
  try:
220
252
  log_message(f"[open_browser] Trying system chromedriver at {sys_drv}", "DEBUG")
221
253
  Tools._driver = webdriver.Chrome(service=Service(sys_drv), options=opts)
254
+ Tools.set_viewport(width, height, device_scale_factor)
222
255
  log_message("[open_browser] Launched via system chromedriver.", "SUCCESS")
223
256
  return "Browser launched (system chromedriver)"
224
257
  except WebDriverException as e:
@@ -242,6 +275,7 @@ class Tools:
242
275
  drv = shutil.which("chromedriver")
243
276
  log_message(f"[open_browser] Installed ARM64 driver at {drv}", "DEBUG")
244
277
  Tools._driver = webdriver.Chrome(service=Service(drv), options=opts)
278
+ Tools.set_viewport(width, height, device_scale_factor)
245
279
  log_message("[open_browser] Launched via downloaded ARM64 chromedriver.", "SUCCESS")
246
280
  return "Browser launched (downloaded ARM64 chromedriver)"
247
281
  except Exception as e:
@@ -257,6 +291,7 @@ class Tools:
257
291
  log_message(f"[open_browser] Installing ChromeDriver {browser_major} via webdriver-manager", "DEBUG")
258
292
  drv_path = ChromeDriverManager(driver_version=browser_major).install()
259
293
  Tools._driver = webdriver.Chrome(service=Service(drv_path), options=opts)
294
+ Tools.set_viewport(width, height, device_scale_factor)
260
295
  log_message("[open_browser] Launched via webdriver-manager.", "SUCCESS")
261
296
  return "Browser launched (webdriver-manager)"
262
297
  except Exception as e:
@@ -266,6 +301,7 @@ class Tools:
266
301
  log_message("[open_browser] Attempting `sudo snap install chromium`…", "DEBUG")
267
302
  subprocess.check_call(["sudo", "snap", "install", "chromium"])
268
303
  Tools._driver = webdriver.Chrome(service=Service(snap_drv), options=opts)
304
+ Tools.set_viewport(width, height, device_scale_factor)
269
305
  log_message("[open_browser] Launched via newly-installed snap chromium.", "SUCCESS")
270
306
  return "Browser launched (snap install fallback)"
271
307
  except Exception as e:
@@ -292,6 +328,46 @@ class Tools:
292
328
  def is_browser_open() -> bool:
293
329
  return Tools._driver is not None
294
330
 
331
+ @staticmethod
332
+ def set_viewport(width: int = 1280, height: int = 720, device_scale_factor: float = 1.0) -> Dict[str, float]:
333
+ if not Tools._driver:
334
+ raise RuntimeError("browser not open")
335
+ width = _bounded_int(width, 1280, 320, 3840)
336
+ height = _bounded_int(height, 720, 240, 2160)
337
+ device_scale_factor = _bounded_float(device_scale_factor, 1.0, 0.25, 3.0)
338
+ try:
339
+ Tools._driver.set_window_size(width, height)
340
+ except Exception as exc:
341
+ log_message(f"[viewport] set_window_size failed: {exc}", "WARNING")
342
+ try:
343
+ Tools._driver.execute_cdp_cmd("Emulation.setDeviceMetricsOverride", {
344
+ "mobile": False,
345
+ "width": width,
346
+ "height": height,
347
+ "deviceScaleFactor": device_scale_factor,
348
+ })
349
+ except Exception as exc:
350
+ log_message(f"[viewport] CDP override failed: {exc}", "DEBUG")
351
+ return Tools.viewport()
352
+
353
+ @staticmethod
354
+ def viewport() -> Dict[str, float]:
355
+ if not Tools._driver:
356
+ return {"width": 0, "height": 0, "deviceScaleFactor": 1}
357
+ try:
358
+ data = Tools._driver.execute_script(
359
+ "return {width: window.innerWidth, height: window.innerHeight, deviceScaleFactor: window.devicePixelRatio || 1};"
360
+ )
361
+ if isinstance(data, dict):
362
+ return {
363
+ "width": int(data.get("width") or 0),
364
+ "height": int(data.get("height") or 0),
365
+ "deviceScaleFactor": float(data.get("deviceScaleFactor") or 1),
366
+ }
367
+ except Exception as exc:
368
+ log_message(f"[viewport] read failed: {exc}", "DEBUG")
369
+ return {"width": 0, "height": 0, "deviceScaleFactor": 1}
370
+
295
371
  @staticmethod
296
372
  def navigate(url: str) -> str:
297
373
  if not Tools._driver:
@@ -357,9 +433,36 @@ class Tools:
357
433
  return f"Error scrolling: {exc}"
358
434
 
359
435
  @staticmethod
360
- def screenshot(filename: str = "screenshot.png") -> str:
436
+ def screenshot(filename: str = "screenshot.png", full_page: bool = False) -> str:
361
437
  if not Tools._driver:
362
438
  return "Error: browser not open"
439
+ # Prefer Chrome DevTools Protocol capture. It renders the browser
440
+ # surface directly, so this path cannot accidentally fall back to an
441
+ # OS desktop screenshot tool.
442
+ try:
443
+ drv = Tools._driver
444
+ layout = drv.execute_cdp_cmd("Page.getLayoutMetrics", {})
445
+ if full_page:
446
+ content = layout.get("contentSize") or {}
447
+ width = int(content.get("width") or 1280)
448
+ height = int(content.get("height") or 720)
449
+ clip = {"x": 0, "y": 0, "width": width, "height": height, "scale": 1}
450
+ else:
451
+ viewport = Tools.viewport()
452
+ width = int(viewport.get("width") or 1280)
453
+ height = int(viewport.get("height") or 720)
454
+ clip = {"x": 0, "y": 0, "width": width, "height": height, "scale": 1}
455
+ data = drv.execute_cdp_cmd("Page.captureScreenshot", {
456
+ "format": "png",
457
+ "fromSurface": True,
458
+ "captureBeyondViewport": bool(full_page),
459
+ "clip": clip,
460
+ })
461
+ raw = base64.b64decode(data.get("data") or "")
462
+ Path(filename).write_bytes(raw)
463
+ return filename
464
+ except Exception as exc:
465
+ log_message(f"[screenshot] CDP capture failed, falling back to webdriver screenshot: {exc}", "WARNING")
363
466
  Tools._driver.save_screenshot(filename)
364
467
  return filename
365
468
 
@@ -827,8 +930,17 @@ def session_start():
827
930
  return _error("unauthorized", 401)
828
931
  payload = request.get_json(silent=True) or {}
829
932
  headless = bool(payload.get("headless", HEADLESS_DEFAULT))
933
+ width = _bounded_int(payload.get("width"), 1280, 320, 3840)
934
+ height = _bounded_int(payload.get("height"), 720, 240, 2160)
935
+ device_scale_factor = _bounded_float(payload.get("deviceScaleFactor") or payload.get("device_scale_factor"), 1.0, 0.25, 3.0)
830
936
  with _slot():
831
- msg = Tools.open_browser(headless=headless, force_new=True)
937
+ msg = Tools.open_browser(
938
+ headless=headless,
939
+ force_new=True,
940
+ width=width,
941
+ height=height,
942
+ device_scale_factor=device_scale_factor,
943
+ )
832
944
  if not _result_ok(msg):
833
945
  return _error(msg, 500)
834
946
  sid = uuid.uuid4().hex
@@ -842,7 +954,7 @@ def session_start():
842
954
  "frames": {},
843
955
  }
844
956
  _queue_event(sid, {"type": "status", "msg": "browser_started", "detail": msg, "sid": sid, "ts": int(time.time() * 1000)})
845
- return _ok(session_id=sid, message=msg, headless=headless)
957
+ return _ok(session_id=sid, message=msg, headless=headless, viewport=Tools.viewport())
846
958
 
847
959
 
848
960
  @app.post("/session/close")
@@ -855,6 +967,29 @@ def session_close():
855
967
  return _ok(message=msg)
856
968
 
857
969
 
970
+ @app.route("/viewport", methods=["GET", "POST"])
971
+ def viewport():
972
+ if not _auth_ok(request):
973
+ return _error("unauthorized", 401)
974
+ if not Tools.is_browser_open():
975
+ return _error("browser not open", 409)
976
+ if request.method == "POST":
977
+ payload = request.get_json(silent=True) or {}
978
+ width = _bounded_int(payload.get("width"), 1280, 320, 3840)
979
+ height = _bounded_int(payload.get("height"), 720, 240, 2160)
980
+ device_scale_factor = _bounded_float(
981
+ payload.get("deviceScaleFactor") or payload.get("device_scale_factor"),
982
+ 1.0,
983
+ 0.25,
984
+ 3.0,
985
+ )
986
+ with _slot():
987
+ vp = Tools.set_viewport(width, height, device_scale_factor)
988
+ else:
989
+ vp = Tools.viewport()
990
+ return _ok(viewport=vp, width=vp.get("width"), height=vp.get("height"), deviceScaleFactor=vp.get("deviceScaleFactor"))
991
+
992
+
858
993
  @app.post("/navigate")
859
994
  def navigate():
860
995
  if not _auth_ok(request):
@@ -1200,10 +1335,20 @@ def screenshot():
1200
1335
  if not _auth_ok(request):
1201
1336
  return _error("unauthorized", 401)
1202
1337
  sid = request.args.get("sid") or next(iter(_SESSIONS), "")
1338
+ width_raw = request.args.get("width")
1339
+ height_raw = request.args.get("height")
1340
+ scale_raw = request.args.get("deviceScaleFactor") or request.args.get("device_scale_factor")
1341
+ if width_raw or height_raw or scale_raw:
1342
+ width = _bounded_int(width_raw, 1280, 320, 3840)
1343
+ height = _bounded_int(height_raw, 720, 240, 2160)
1344
+ device_scale_factor = _bounded_float(scale_raw, 1.0, 0.25, 3.0)
1345
+ with _slot():
1346
+ Tools.set_viewport(width, height, device_scale_factor)
1347
+ full_page = _truthy(request.args.get("full_page") or request.args.get("fullPage"))
1203
1348
  fname = f"{uuid.uuid4().hex}.png"
1204
1349
  fpath = OUT_DIR / fname
1205
1350
  with _slot():
1206
- msg = Tools.screenshot(str(fpath))
1351
+ msg = Tools.screenshot(str(fpath), full_page=full_page)
1207
1352
  if not _result_ok(msg):
1208
1353
  return _error(msg, 500)
1209
1354
  try:
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.31",
3
+ "version": "1.0.33",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.31",
9
+ "version": "1.0.33",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.31",
3
+ "version": "1.0.33",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",