zerochat 9.7.0__tar.gz → 9.9.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.
- {zerochat-9.7.0/zerochat.egg-info → zerochat-9.9.0}/PKG-INFO +1 -1
- {zerochat-9.7.0 → zerochat-9.9.0}/pyproject.toml +1 -1
- {zerochat-9.7.0 → zerochat-9.9.0/zerochat.egg-info}/PKG-INFO +1 -1
- {zerochat-9.7.0 → zerochat-9.9.0}/zerochat.py +140 -49
- {zerochat-9.7.0 → zerochat-9.9.0}/LICENSE +0 -0
- {zerochat-9.7.0 → zerochat-9.9.0}/README.md +0 -0
- {zerochat-9.7.0 → zerochat-9.9.0}/setup.cfg +0 -0
- {zerochat-9.7.0 → zerochat-9.9.0}/zerochat.egg-info/SOURCES.txt +0 -0
- {zerochat-9.7.0 → zerochat-9.9.0}/zerochat.egg-info/dependency_links.txt +0 -0
- {zerochat-9.7.0 → zerochat-9.9.0}/zerochat.egg-info/entry_points.txt +0 -0
- {zerochat-9.7.0 → zerochat-9.9.0}/zerochat.egg-info/top_level.txt +0 -0
|
@@ -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.
|
|
49
|
+
SOURCE_BACKEND_VERSION = "9.9.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."""
|
|
@@ -1344,6 +1344,92 @@ def set_project_directory(path) -> dict:
|
|
|
1344
1344
|
return {"cwd": str(target), "previous_cwd": previous}
|
|
1345
1345
|
|
|
1346
1346
|
|
|
1347
|
+
# Visores gráficos que `git difftool` ya sabe lanzar, en orden de preferencia, con el ejecutable que los delata.
|
|
1348
|
+
GUI_DIFF_TOOLS = {"vscode": "code", "meld": "meld", "kdiff3": "kdiff3", "kompare": "kompare", "gvimdiff": "gvim"}
|
|
1349
|
+
# Respaldo cuando no hay ninguno: visor web que se instala en el entorno aislado de ZeroChat.
|
|
1350
|
+
WEBDIFF_REQUIREMENT = "webdiff==1.4.0"
|
|
1351
|
+
WEBDIFF_INSTALL_TIMEOUT_SECONDS = 120
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
def _venv_webdiff() -> Path:
|
|
1355
|
+
"""Ruta de webdiff dentro de ~/zerochat/.venv, junto al intérprete del entorno."""
|
|
1356
|
+
venv_python = get_venv_python(get_venv_dir())
|
|
1357
|
+
return venv_python.with_name("webdiff" + venv_python.suffix)
|
|
1358
|
+
|
|
1359
|
+
|
|
1360
|
+
def _find_webdiff() -> str:
|
|
1361
|
+
"""webdiff del entorno de ZeroChat o, si no, del PATH; '' si no hay ninguno."""
|
|
1362
|
+
venv_webdiff = _venv_webdiff()
|
|
1363
|
+
if venv_webdiff.exists():
|
|
1364
|
+
return str(venv_webdiff)
|
|
1365
|
+
return shutil.which("webdiff") or ""
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
def _diff_tool_settings(tool: str, webdiff_bin: str) -> list:
|
|
1369
|
+
"""Pares (clave, valor) de git config que deja `tool` como visor gráfico; webdiff no es un visor integrado de git."""
|
|
1370
|
+
settings = [("diff.guitool", tool)]
|
|
1371
|
+
if tool == "webdiff":
|
|
1372
|
+
settings.insert(0, ("difftool.webdiff.cmd", f'"{webdiff_bin}" "$LOCAL" "$REMOTE"'))
|
|
1373
|
+
return settings
|
|
1374
|
+
|
|
1375
|
+
|
|
1376
|
+
def detect_gui_diff_tool() -> dict:
|
|
1377
|
+
"""Visor que proponer cuando Git no tiene ninguno: el primero instalado o, si no hay, webdiff.
|
|
1378
|
+
|
|
1379
|
+
Incluye las órdenes exactas que aplicará configure_gui_diff_tool, para mostrarlas antes de confirmar.
|
|
1380
|
+
"""
|
|
1381
|
+
tool = next((name for name, binary in GUI_DIFF_TOOLS.items() if shutil.which(binary)), "webdiff")
|
|
1382
|
+
webdiff_bin = _find_webdiff()
|
|
1383
|
+
return {
|
|
1384
|
+
"tool": tool,
|
|
1385
|
+
"install": tool == "webdiff" and not webdiff_bin,
|
|
1386
|
+
"commands": [
|
|
1387
|
+
shlex.join(["git", "config", "--global", key, value])
|
|
1388
|
+
for key, value in _diff_tool_settings(tool, webdiff_bin or str(_venv_webdiff()))
|
|
1389
|
+
],
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
|
|
1393
|
+
def _install_webdiff() -> str:
|
|
1394
|
+
"""Instala webdiff en ~/zerochat/.venv y devuelve la ruta del ejecutable."""
|
|
1395
|
+
ensure_virtual_environment()
|
|
1396
|
+
venv_python = get_venv_python(get_venv_dir())
|
|
1397
|
+
if not venv_python.exists():
|
|
1398
|
+
raise RuntimeError(f"No se pudo preparar el entorno virtual de ZeroChat: falta {venv_python}")
|
|
1399
|
+
res = subprocess.run(
|
|
1400
|
+
[str(venv_python), "-m", "pip", "install", WEBDIFF_REQUIREMENT],
|
|
1401
|
+
capture_output=True,
|
|
1402
|
+
text=True,
|
|
1403
|
+
timeout=WEBDIFF_INSTALL_TIMEOUT_SECONDS,
|
|
1404
|
+
check=False
|
|
1405
|
+
)
|
|
1406
|
+
if res.returncode != 0:
|
|
1407
|
+
raise RuntimeError(f"Error instalando webdiff: {(res.stderr or res.stdout).strip()[-500:]}")
|
|
1408
|
+
webdiff = _venv_webdiff()
|
|
1409
|
+
if not webdiff.exists():
|
|
1410
|
+
raise RuntimeError(f"webdiff se instaló pero no aparece en {webdiff}")
|
|
1411
|
+
return str(webdiff)
|
|
1412
|
+
|
|
1413
|
+
|
|
1414
|
+
def configure_gui_diff_tool(tool) -> dict:
|
|
1415
|
+
"""Deja `tool` como visor gráfico global de Git, instalando antes webdiff si es el elegido y falta."""
|
|
1416
|
+
if not isinstance(tool, str) or (tool != "webdiff" and tool not in GUI_DIFF_TOOLS):
|
|
1417
|
+
raise ValueError(f"Visor de diffs '{tool}' no reconocido.")
|
|
1418
|
+
git_bin = shutil.which("git")
|
|
1419
|
+
if not git_bin:
|
|
1420
|
+
raise RuntimeError("Git no está instalado o no se encuentra en el PATH del sistema.")
|
|
1421
|
+
|
|
1422
|
+
webdiff_bin = ""
|
|
1423
|
+
if tool == "webdiff":
|
|
1424
|
+
webdiff_bin = _find_webdiff() or _install_webdiff()
|
|
1425
|
+
elif not shutil.which(GUI_DIFF_TOOLS[tool]):
|
|
1426
|
+
raise ValueError(f"'{GUI_DIFF_TOOLS[tool]}' no está en el PATH del sistema.")
|
|
1427
|
+
|
|
1428
|
+
for key, value in _diff_tool_settings(tool, webdiff_bin):
|
|
1429
|
+
subprocess.run([git_bin, "config", "--global", key, value], capture_output=True, timeout=5, check=True)
|
|
1430
|
+
return {"tool": tool}
|
|
1431
|
+
|
|
1432
|
+
|
|
1347
1433
|
def _configured_gui_diff_tool(git_bin: str, repo_root: Path) -> str:
|
|
1348
1434
|
"""Herramienta gráfica que usaría `git difftool --gui`, o '' si no hay ninguna configurada.
|
|
1349
1435
|
|
|
@@ -1365,7 +1451,10 @@ def _configured_gui_diff_tool(git_bin: str, repo_root: Path) -> str:
|
|
|
1365
1451
|
|
|
1366
1452
|
|
|
1367
1453
|
def open_file_diff(path: str, commit_fallback: bool = False) -> dict:
|
|
1368
|
-
"""Abre un visor externo de diffs (git difftool) para comparar el fichero actual frente a Git HEAD, su último commit o fichero vacío.
|
|
1454
|
+
"""Abre un visor externo de diffs (git difftool) para comparar el fichero actual frente a Git HEAD, su último commit o fichero vacío.
|
|
1455
|
+
|
|
1456
|
+
Sin visor gráfico configurado no lanza nada y devuelve en `setup` el que propone configurar.
|
|
1457
|
+
"""
|
|
1369
1458
|
if not isinstance(path, str) or not path.strip():
|
|
1370
1459
|
raise ValueError("La ruta del archivo está vacía.")
|
|
1371
1460
|
if len(path) > MAX_PATH_LENGTH:
|
|
@@ -1398,10 +1487,7 @@ def open_file_diff(path: str, commit_fallback: bool = False) -> dict:
|
|
|
1398
1487
|
|
|
1399
1488
|
repo_root = Path(res.stdout.strip()).resolve()
|
|
1400
1489
|
if not _configured_gui_diff_tool(git_bin, repo_root):
|
|
1401
|
-
|
|
1402
|
-
"No hay un visor gráfico de diffs configurado en Git. Configura uno, por ejemplo: "
|
|
1403
|
-
"git config --global diff.guitool meld"
|
|
1404
|
-
)
|
|
1490
|
+
return {"success": False, "setup": detect_gui_diff_tool()}
|
|
1405
1491
|
|
|
1406
1492
|
has_head = subprocess.run(
|
|
1407
1493
|
[git_bin, "rev-parse", "--verify", "HEAD"],
|
|
@@ -1727,17 +1813,13 @@ class PersistentBrowserSession:
|
|
|
1727
1813
|
const readline = require('readline');
|
|
1728
1814
|
let playwright;
|
|
1729
1815
|
try {
|
|
1730
|
-
playwright = require(
|
|
1731
|
-
} catch (
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
error: "Playwright is not available. Install it with 'npm install playwright' or enable the Playwright MCP service."
|
|
1738
|
-
}));
|
|
1739
|
-
process.exit(1);
|
|
1740
|
-
}
|
|
1816
|
+
playwright = require(process.env.ZEROCHAT_PLAYWRIGHT_MODULE);
|
|
1817
|
+
} catch (e) {
|
|
1818
|
+
console.log(JSON.stringify({
|
|
1819
|
+
success: false,
|
|
1820
|
+
error: "Playwright is not available. Start the Playwright service once in Settings > MCP to install it."
|
|
1821
|
+
}));
|
|
1822
|
+
process.exit(1);
|
|
1741
1823
|
}
|
|
1742
1824
|
|
|
1743
1825
|
(async () => {
|
|
@@ -1903,8 +1985,8 @@ def browser_action_availability() -> dict:
|
|
|
1903
1985
|
"""Comprueba requisitos locales sin lanzar Node.js ni Chromium."""
|
|
1904
1986
|
if not shutil.which("node"):
|
|
1905
1987
|
return {"available": False, "error": "Node.js is not installed or not found in system PATH."}
|
|
1906
|
-
|
|
1907
|
-
if not
|
|
1988
|
+
# Solo el Playwright del servicio, no el node_modules de la carpeta desde la que arranca el servidor.
|
|
1989
|
+
if not playwright_service_cli().is_file():
|
|
1908
1990
|
return {"available": False, "error": "Playwright is not available."}
|
|
1909
1991
|
# El runner prueba las entradas de Chromium de Playwright y los canales chrome y msedge.
|
|
1910
1992
|
browsers = system_browsers()
|
|
@@ -1913,6 +1995,11 @@ def browser_action_availability() -> dict:
|
|
|
1913
1995
|
return {"available": False, "error": "Playwright Chromium is not installed."}
|
|
1914
1996
|
|
|
1915
1997
|
|
|
1998
|
+
def playwright_status() -> dict:
|
|
1999
|
+
"""Playwright del servicio y browser_action tal como están ahora, para el latido del cliente."""
|
|
2000
|
+
return {"installed": playwright_service_cli().is_file(), "browserAction": browser_action_availability()}
|
|
2001
|
+
|
|
2002
|
+
|
|
1916
2003
|
def browser_action(action: str, url: str | None = None, selector: str | None = None, value: str | None = None) -> str:
|
|
1917
2004
|
"""Control a headless browser for UI testing and visual inspection."""
|
|
1918
2005
|
try:
|
|
@@ -2217,18 +2304,22 @@ def playwright_launch_env(config: dict) -> dict[str, str]:
|
|
|
2217
2304
|
|
|
2218
2305
|
|
|
2219
2306
|
def validate_profile_browser(raw) -> dict:
|
|
2220
|
-
"""Navegador que exige un perfil: el propio con un motor
|
|
2221
|
-
|
|
2222
|
-
|
|
2307
|
+
"""Navegador que exige un perfil: el propio con un motor, y headless False si exige la ventana
|
|
2308
|
+
visible, o el del usuario con uno de sus navegadores."""
|
|
2309
|
+
if isinstance(raw, dict) and raw.get("mode") == "own" and raw.get("engine") in OWN_BROWSER_ENGINES:
|
|
2310
|
+
if set(raw) == {"mode", "engine"}:
|
|
2311
|
+
return {"mode": "own", "engine": raw["engine"]}
|
|
2312
|
+
if set(raw) == {"mode", "engine", "headless"} and raw["headless"] is False:
|
|
2313
|
+
return {"mode": "own", "engine": raw["engine"], "headless": False}
|
|
2223
2314
|
if isinstance(raw, dict) and raw.get("mode") == "user" and set(raw) == {"mode", "userBrowser"} and raw["userBrowser"] in USER_BROWSERS:
|
|
2224
2315
|
return {"mode": "user", "userBrowser": raw["userBrowser"]}
|
|
2225
|
-
raise ValueError("browser debe ser {mode: own, engine} o {mode: user, userBrowser} con un motor o navegador admitido")
|
|
2316
|
+
raise ValueError("browser debe ser {mode: own, engine[, headless: false]} o {mode: user, userBrowser} con un motor o navegador admitido")
|
|
2226
2317
|
|
|
2227
2318
|
|
|
2228
2319
|
def browser_choice(config: dict) -> dict:
|
|
2229
2320
|
"""Modo y navegador con los que arranca Playwright MCP, en la forma de validate_profile_browser."""
|
|
2230
2321
|
if config["mode"] == "own":
|
|
2231
|
-
return {"mode": "own", "engine": config["engine"]}
|
|
2322
|
+
return {"mode": "own", "engine": config["engine"], **({} if config["headless"] else {"headless": False})}
|
|
2232
2323
|
return {"mode": "user", "userBrowser": config["userBrowser"]}
|
|
2233
2324
|
|
|
2234
2325
|
|
|
@@ -2358,22 +2449,19 @@ class BrowserEngines:
|
|
|
2358
2449
|
return {name: (title, Path(location.strip())) for title, name, location in _DRY_RUN_ENTRY.findall(result.stdout)}
|
|
2359
2450
|
|
|
2360
2451
|
def list(self) -> dict:
|
|
2452
|
+
"""Navegadores del sistema y motores de Playwright; sin Playwright del servicio no hay motores."""
|
|
2361
2453
|
cli = playwright_service_cli()
|
|
2362
2454
|
result = {"systemBrowsers": system_browsers(), "extensions": user_browser_extensions(), "engines": []}
|
|
2363
2455
|
if not cli.is_file():
|
|
2364
|
-
return
|
|
2365
|
-
|
|
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)}
|
|
2456
|
+
return result
|
|
2457
|
+
locations = self._locations(cli)
|
|
2370
2458
|
with self._lock:
|
|
2371
2459
|
jobs = {engine: dict(job) for engine, job in self._jobs.items()}
|
|
2372
2460
|
for engine, entries in ENGINE_CACHE_ENTRIES.items():
|
|
2373
2461
|
title = locations.get(entries[0], (engine,))[0]
|
|
2374
2462
|
installed = all(name in locations and (locations[name][1] / "INSTALLATION_COMPLETE").is_file() for name in entries)
|
|
2375
2463
|
result["engines"].append({"id": engine, "title": title, "installed": installed, **jobs.get(engine, {})})
|
|
2376
|
-
return
|
|
2464
|
+
return result
|
|
2377
2465
|
|
|
2378
2466
|
def _check_engine(self, engine) -> Path:
|
|
2379
2467
|
if engine not in ENGINE_CACHE_ENTRIES:
|
|
@@ -2864,6 +2952,12 @@ class McpServiceManager:
|
|
|
2864
2952
|
client = self.clients.get(server_id)
|
|
2865
2953
|
state = self.states.get(server_id, "stopped")
|
|
2866
2954
|
running = bool(client and client.running() and state == "running")
|
|
2955
|
+
error = self.errors.get(server_id)
|
|
2956
|
+
# El proceso de un servicio en marcha puede morir sin que nadie lo detenga.
|
|
2957
|
+
if state == "running" and client and not running:
|
|
2958
|
+
code = client.process.poll() if client.process else None
|
|
2959
|
+
state = "error"
|
|
2960
|
+
error = f"MCP process exited with code {code}" if code is not None else "MCP process is not responding"
|
|
2867
2961
|
pref = self.preferences.get(server_id, {})
|
|
2868
2962
|
result.append({
|
|
2869
2963
|
"id": server_id,
|
|
@@ -2874,7 +2968,7 @@ class McpServiceManager:
|
|
|
2874
2968
|
"enabled": pref.get("enabled", server.get("enabledByDefault", False)),
|
|
2875
2969
|
"status": "running" if running else state,
|
|
2876
2970
|
"toolCount": len(client.tools) if running else 0,
|
|
2877
|
-
"error":
|
|
2971
|
+
"error": error,
|
|
2878
2972
|
"options": server.get("options", []),
|
|
2879
2973
|
"userOptions": pref.get("options", {}),
|
|
2880
2974
|
"oauthUrl": client.oauth_url if client else None,
|
|
@@ -3568,12 +3662,16 @@ class ZeroChatServerHandler(BaseHTTPRequestHandler):
|
|
|
3568
3662
|
if is_heartbeat:
|
|
3569
3663
|
mark_browser_active()
|
|
3570
3664
|
# 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
|
|
3665
|
+
# jobs, mostrar los comandos que siguen en marcha y avisar antes de cerrar si se detendrían;
|
|
3666
|
+
# playwright, si se puede navegar aunque se haya instalado o quitado durante la sesión;
|
|
3667
|
+
# servers, los servicios MCP externos aunque otra pestaña los cambie o su proceso muera.
|
|
3572
3668
|
self._send_json_response(200, {
|
|
3573
3669
|
"ok": True,
|
|
3574
3670
|
"cwd": str(Path.cwd().resolve()),
|
|
3575
3671
|
"jobs": [job.summary() for job in JOBS.running()],
|
|
3576
|
-
"exit_on_close": EXIT_ON_CLOSE
|
|
3672
|
+
"exit_on_close": EXIT_ON_CLOSE,
|
|
3673
|
+
"playwright": playwright_status(),
|
|
3674
|
+
"servers": GLOBAL_MCP_MANAGER.list_servers()
|
|
3577
3675
|
})
|
|
3578
3676
|
return
|
|
3579
3677
|
|
|
@@ -3597,7 +3695,6 @@ class ZeroChatServerHandler(BaseHTTPRequestHandler):
|
|
|
3597
3695
|
"version": VERSION,
|
|
3598
3696
|
"cwd": str(Path.cwd().resolve()),
|
|
3599
3697
|
"tools_count": len(LOCAL_TOOLS_DEFINITIONS),
|
|
3600
|
-
"browser_action": browser_action_availability(),
|
|
3601
3698
|
"os": DETECTED_OS
|
|
3602
3699
|
})
|
|
3603
3700
|
self._log_res(200, safe_path, (time.monotonic() - t0) * 1000)
|
|
@@ -3731,18 +3828,7 @@ class ZeroChatServerHandler(BaseHTTPRequestHandler):
|
|
|
3731
3828
|
}
|
|
3732
3829
|
return self._initialize_result(server_info), None, ""
|
|
3733
3830
|
if method == "tools/list":
|
|
3734
|
-
|
|
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, ""
|
|
3831
|
+
return {"tools": [dict(definition) for definition in LOCAL_TOOLS_DEFINITIONS]}, None, ""
|
|
3746
3832
|
if method == "tools/call":
|
|
3747
3833
|
tool_name = params["name"]
|
|
3748
3834
|
handler = LOCAL_TOOL_HANDLERS.get(tool_name)
|
|
@@ -3778,6 +3864,13 @@ class ZeroChatServerHandler(BaseHTTPRequestHandler):
|
|
|
3778
3864
|
return None, {"code": -32602, "message": str(exc)}, ""
|
|
3779
3865
|
except Exception as exc:
|
|
3780
3866
|
return None, {"code": -32603, "message": f"Error al abrir el visor de diffs: {exc}"}, str(exc)
|
|
3867
|
+
if method == "zerochat/diff/configure":
|
|
3868
|
+
try:
|
|
3869
|
+
return configure_gui_diff_tool(params.get("tool")), None, ""
|
|
3870
|
+
except (ValueError, RuntimeError) as exc:
|
|
3871
|
+
return None, {"code": -32602, "message": str(exc)}, ""
|
|
3872
|
+
except Exception as exc:
|
|
3873
|
+
return None, {"code": -32603, "message": f"Error configurando el visor de diffs: {exc}"}, str(exc)
|
|
3781
3874
|
if method == "zerochat/web/fetch":
|
|
3782
3875
|
try:
|
|
3783
3876
|
return fetch_web_resource(params), None, ""
|
|
@@ -3789,8 +3882,6 @@ class ZeroChatServerHandler(BaseHTTPRequestHandler):
|
|
|
3789
3882
|
return self._dispatch_jobs(method, params)
|
|
3790
3883
|
if method.startswith("zerochat/run/"):
|
|
3791
3884
|
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
3885
|
if method in ("zerochat/external/servers/start", "zerochat/external/servers/stop", "zerochat/external/servers/configure"):
|
|
3795
3886
|
return self._dispatch_server_control(method, params)
|
|
3796
3887
|
if method.startswith("zerochat/browser/"):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|