zerochat 9.7.0__tar.gz → 9.8.0__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.

Potentially problematic release.


This version of zerochat might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zerochat
3
- Version: 9.7.0
3
+ Version: 9.8.0
4
4
  Summary: Private local web client for AI models, tools and MCP services
5
5
  Author: Alberto Alday
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "zerochat"
7
- version = "9.7.0"
7
+ version = "9.8.0"
8
8
  description = "Private local web client for AI models, tools and MCP services"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zerochat
3
- Version: 9.7.0
3
+ Version: 9.8.0
4
4
  Summary: Private local web client for AI models, tools and MCP services
5
5
  Author: Alberto Alday
6
6
  License-Expression: MIT
@@ -46,7 +46,7 @@ import zlib
46
46
  from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
47
47
  from pathlib import Path
48
48
 
49
- SOURCE_BACKEND_VERSION = "9.7.0"
49
+ SOURCE_BACKEND_VERSION = "9.8.0"
50
50
 
51
51
  def _read_source_version(filename: str) -> str | None:
52
52
  """Lee la versión de un archivo del repositorio cuando se ejecuta desde fuentes."""
@@ -1727,17 +1727,13 @@ class PersistentBrowserSession:
1727
1727
  const readline = require('readline');
1728
1728
  let playwright;
1729
1729
  try {
1730
- playwright = require('playwright');
1731
- } catch (e1) {
1732
- try {
1733
- playwright = require(process.env.ZEROCHAT_PLAYWRIGHT_MODULE);
1734
- } catch (e2) {
1735
- console.log(JSON.stringify({
1736
- success: false,
1737
- error: "Playwright is not available. Install it with 'npm install playwright' or enable the Playwright MCP service."
1738
- }));
1739
- process.exit(1);
1740
- }
1730
+ playwright = require(process.env.ZEROCHAT_PLAYWRIGHT_MODULE);
1731
+ } catch (e) {
1732
+ console.log(JSON.stringify({
1733
+ success: false,
1734
+ error: "Playwright is not available. Start the Playwright service once in Settings > MCP to install it."
1735
+ }));
1736
+ process.exit(1);
1741
1737
  }
1742
1738
 
1743
1739
  (async () => {
@@ -1903,8 +1899,8 @@ def browser_action_availability() -> dict:
1903
1899
  """Comprueba requisitos locales sin lanzar Node.js ni Chromium."""
1904
1900
  if not shutil.which("node"):
1905
1901
  return {"available": False, "error": "Node.js is not installed or not found in system PATH."}
1906
- playwright_locations = (Path.cwd() / "node_modules" / "playwright", playwright_service_module())
1907
- if not any(location.is_dir() for location in playwright_locations):
1902
+ # Solo el Playwright del servicio, no el node_modules de la carpeta desde la que arranca el servidor.
1903
+ if not playwright_service_cli().is_file():
1908
1904
  return {"available": False, "error": "Playwright is not available."}
1909
1905
  # El runner prueba las entradas de Chromium de Playwright y los canales chrome y msedge.
1910
1906
  browsers = system_browsers()
@@ -1913,6 +1909,11 @@ def browser_action_availability() -> dict:
1913
1909
  return {"available": False, "error": "Playwright Chromium is not installed."}
1914
1910
 
1915
1911
 
1912
+ def playwright_status() -> dict:
1913
+ """Playwright del servicio y browser_action tal como están ahora, para el latido del cliente."""
1914
+ return {"installed": playwright_service_cli().is_file(), "browserAction": browser_action_availability()}
1915
+
1916
+
1916
1917
  def browser_action(action: str, url: str | None = None, selector: str | None = None, value: str | None = None) -> str:
1917
1918
  """Control a headless browser for UI testing and visual inspection."""
1918
1919
  try:
@@ -2358,22 +2359,19 @@ class BrowserEngines:
2358
2359
  return {name: (title, Path(location.strip())) for title, name, location in _DRY_RUN_ENTRY.findall(result.stdout)}
2359
2360
 
2360
2361
  def list(self) -> dict:
2362
+ """Navegadores del sistema y motores de Playwright; sin Playwright del servicio no hay motores."""
2361
2363
  cli = playwright_service_cli()
2362
2364
  result = {"systemBrowsers": system_browsers(), "extensions": user_browser_extensions(), "engines": []}
2363
2365
  if not cli.is_file():
2364
- return {**result, "available": False, "error": "Playwright MCP is not installed."}
2365
- try:
2366
- version = json.loads((cli.parent / "package.json").read_text(encoding="utf-8")).get("version")
2367
- locations = self._locations(cli)
2368
- except (OSError, json.JSONDecodeError, RuntimeError, subprocess.SubprocessError) as err:
2369
- return {**result, "available": False, "error": str(err)}
2366
+ return result
2367
+ locations = self._locations(cli)
2370
2368
  with self._lock:
2371
2369
  jobs = {engine: dict(job) for engine, job in self._jobs.items()}
2372
2370
  for engine, entries in ENGINE_CACHE_ENTRIES.items():
2373
2371
  title = locations.get(entries[0], (engine,))[0]
2374
2372
  installed = all(name in locations and (locations[name][1] / "INSTALLATION_COMPLETE").is_file() for name in entries)
2375
2373
  result["engines"].append({"id": engine, "title": title, "installed": installed, **jobs.get(engine, {})})
2376
- return {**result, "available": True, "playwrightVersion": version}
2374
+ return result
2377
2375
 
2378
2376
  def _check_engine(self, engine) -> Path:
2379
2377
  if engine not in ENGINE_CACHE_ENTRIES:
@@ -2864,6 +2862,12 @@ class McpServiceManager:
2864
2862
  client = self.clients.get(server_id)
2865
2863
  state = self.states.get(server_id, "stopped")
2866
2864
  running = bool(client and client.running() and state == "running")
2865
+ error = self.errors.get(server_id)
2866
+ # El proceso de un servicio en marcha puede morir sin que nadie lo detenga.
2867
+ if state == "running" and client and not running:
2868
+ code = client.process.poll() if client.process else None
2869
+ state = "error"
2870
+ error = f"MCP process exited with code {code}" if code is not None else "MCP process is not responding"
2867
2871
  pref = self.preferences.get(server_id, {})
2868
2872
  result.append({
2869
2873
  "id": server_id,
@@ -2874,7 +2878,7 @@ class McpServiceManager:
2874
2878
  "enabled": pref.get("enabled", server.get("enabledByDefault", False)),
2875
2879
  "status": "running" if running else state,
2876
2880
  "toolCount": len(client.tools) if running else 0,
2877
- "error": self.errors.get(server_id),
2881
+ "error": error,
2878
2882
  "options": server.get("options", []),
2879
2883
  "userOptions": pref.get("options", {}),
2880
2884
  "oauthUrl": client.oauth_url if client else None,
@@ -3568,12 +3572,16 @@ class ZeroChatServerHandler(BaseHTTPRequestHandler):
3568
3572
  if is_heartbeat:
3569
3573
  mark_browser_active()
3570
3574
  # cwd permite a cada pestaña notar que otra ha cambiado el directorio del proyecto;
3571
- # jobs, mostrar los comandos que siguen en marcha y avisar antes de cerrar si se detendrían.
3575
+ # jobs, mostrar los comandos que siguen en marcha y avisar antes de cerrar si se detendrían;
3576
+ # playwright, si se puede navegar aunque se haya instalado o quitado durante la sesión;
3577
+ # servers, los servicios MCP externos aunque otra pestaña los cambie o su proceso muera.
3572
3578
  self._send_json_response(200, {
3573
3579
  "ok": True,
3574
3580
  "cwd": str(Path.cwd().resolve()),
3575
3581
  "jobs": [job.summary() for job in JOBS.running()],
3576
- "exit_on_close": EXIT_ON_CLOSE
3582
+ "exit_on_close": EXIT_ON_CLOSE,
3583
+ "playwright": playwright_status(),
3584
+ "servers": GLOBAL_MCP_MANAGER.list_servers()
3577
3585
  })
3578
3586
  return
3579
3587
 
@@ -3597,7 +3605,6 @@ class ZeroChatServerHandler(BaseHTTPRequestHandler):
3597
3605
  "version": VERSION,
3598
3606
  "cwd": str(Path.cwd().resolve()),
3599
3607
  "tools_count": len(LOCAL_TOOLS_DEFINITIONS),
3600
- "browser_action": browser_action_availability(),
3601
3608
  "os": DETECTED_OS
3602
3609
  })
3603
3610
  self._log_res(200, safe_path, (time.monotonic() - t0) * 1000)
@@ -3731,18 +3738,7 @@ class ZeroChatServerHandler(BaseHTTPRequestHandler):
3731
3738
  }
3732
3739
  return self._initialize_result(server_info), None, ""
3733
3740
  if method == "tools/list":
3734
- browser_availability = browser_action_availability()
3735
- tools = []
3736
- for definition in LOCAL_TOOLS_DEFINITIONS:
3737
- item = dict(definition)
3738
- if item.get("name") == "browser_action":
3739
- item["availability"] = browser_availability
3740
- tools.append(item)
3741
- return {"tools": tools}, None, ""
3742
- if method == "tools/availability":
3743
- if params.get("name", "") != "browser_action":
3744
- return None, {"code": -32602, "message": "Herramienta no compatible con comprobación de disponibilidad."}, ""
3745
- return browser_action_availability(), None, ""
3741
+ return {"tools": [dict(definition) for definition in LOCAL_TOOLS_DEFINITIONS]}, None, ""
3746
3742
  if method == "tools/call":
3747
3743
  tool_name = params["name"]
3748
3744
  handler = LOCAL_TOOL_HANDLERS.get(tool_name)
@@ -3789,8 +3785,6 @@ class ZeroChatServerHandler(BaseHTTPRequestHandler):
3789
3785
  return self._dispatch_jobs(method, params)
3790
3786
  if method.startswith("zerochat/run/"):
3791
3787
  return dispatch_run(method, params)
3792
- if method == "zerochat/external/status":
3793
- return {"host": "running", "version": VERSION, "servers": GLOBAL_MCP_MANAGER.list_servers()}, None, ""
3794
3788
  if method in ("zerochat/external/servers/start", "zerochat/external/servers/stop", "zerochat/external/servers/configure"):
3795
3789
  return self._dispatch_server_control(method, params)
3796
3790
  if method.startswith("zerochat/browser/"):
File without changes
File without changes
File without changes