zerochat 9.2.0__tar.gz → 9.4.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.
- {zerochat-9.2.0/zerochat.egg-info → zerochat-9.4.0}/PKG-INFO +1 -1
- {zerochat-9.2.0 → zerochat-9.4.0}/pyproject.toml +1 -1
- {zerochat-9.2.0 → zerochat-9.4.0/zerochat.egg-info}/PKG-INFO +1 -1
- {zerochat-9.2.0 → zerochat-9.4.0}/zerochat.py +344 -4
- {zerochat-9.2.0 → zerochat-9.4.0}/LICENSE +0 -0
- {zerochat-9.2.0 → zerochat-9.4.0}/README.md +0 -0
- {zerochat-9.2.0 → zerochat-9.4.0}/setup.cfg +0 -0
- {zerochat-9.2.0 → zerochat-9.4.0}/zerochat.egg-info/SOURCES.txt +0 -0
- {zerochat-9.2.0 → zerochat-9.4.0}/zerochat.egg-info/dependency_links.txt +0 -0
- {zerochat-9.2.0 → zerochat-9.4.0}/zerochat.egg-info/entry_points.txt +0 -0
- {zerochat-9.2.0 → zerochat-9.4.0}/zerochat.egg-info/top_level.txt +0 -0
|
@@ -17,6 +17,7 @@ import base64
|
|
|
17
17
|
import datetime
|
|
18
18
|
import fnmatch
|
|
19
19
|
import functools
|
|
20
|
+
import gzip
|
|
20
21
|
import hashlib
|
|
21
22
|
import hmac
|
|
22
23
|
import importlib.metadata
|
|
@@ -36,14 +37,16 @@ import threading
|
|
|
36
37
|
import time
|
|
37
38
|
import traceback
|
|
38
39
|
import urllib.error
|
|
40
|
+
import urllib.parse
|
|
39
41
|
import urllib.request
|
|
40
42
|
from urllib.parse import urlencode
|
|
41
43
|
import venv
|
|
42
44
|
import webbrowser
|
|
45
|
+
import zlib
|
|
43
46
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
44
47
|
from pathlib import Path
|
|
45
48
|
|
|
46
|
-
SOURCE_BACKEND_VERSION = "9.
|
|
49
|
+
SOURCE_BACKEND_VERSION = "9.4.0"
|
|
47
50
|
|
|
48
51
|
def _read_source_version(filename: str) -> str | None:
|
|
49
52
|
"""Lee la versión de un archivo del repositorio cuando se ejecuta desde fuentes."""
|
|
@@ -1341,6 +1344,150 @@ def set_project_directory(path) -> dict:
|
|
|
1341
1344
|
return {"cwd": str(target), "previous_cwd": previous}
|
|
1342
1345
|
|
|
1343
1346
|
|
|
1347
|
+
def open_file_diff(path: str, tool: str | None = None, commit_fallback: bool = False) -> dict:
|
|
1348
|
+
"""Abre un visor externo de diffs (git difftool) para comparar el fichero actual frente a Git HEAD, su último commit o fichero vacío."""
|
|
1349
|
+
if not isinstance(path, str) or not path.strip():
|
|
1350
|
+
raise ValueError("La ruta del archivo está vacía.")
|
|
1351
|
+
if len(path) > MAX_PATH_LENGTH:
|
|
1352
|
+
raise ValueError("La ruta del archivo excede el tamaño máximo permitido.")
|
|
1353
|
+
if tool is not None:
|
|
1354
|
+
if not isinstance(tool, str) or not re.match(r"^[a-zA-Z0-9_-]{1,32}$", tool):
|
|
1355
|
+
raise ValueError("Nombre de herramienta de diff inválido.")
|
|
1356
|
+
|
|
1357
|
+
target = Path(path.strip()).expanduser().resolve()
|
|
1358
|
+
if not target.exists():
|
|
1359
|
+
raise ValueError(f"El archivo '{path}' no existe.")
|
|
1360
|
+
if not target.is_file():
|
|
1361
|
+
raise ValueError(f"'{path}' no es un archivo regular.")
|
|
1362
|
+
|
|
1363
|
+
git_bin = shutil.which("git")
|
|
1364
|
+
if not git_bin:
|
|
1365
|
+
raise RuntimeError("Git no está instalado o no se encuentra en el PATH del sistema.")
|
|
1366
|
+
|
|
1367
|
+
try:
|
|
1368
|
+
res = subprocess.run(
|
|
1369
|
+
[git_bin, "rev-parse", "--show-toplevel"],
|
|
1370
|
+
cwd=str(target.parent),
|
|
1371
|
+
capture_output=True,
|
|
1372
|
+
text=True,
|
|
1373
|
+
timeout=5,
|
|
1374
|
+
check=False
|
|
1375
|
+
)
|
|
1376
|
+
except Exception as err:
|
|
1377
|
+
raise RuntimeError(f"Error comprobando repositorio Git: {err}")
|
|
1378
|
+
|
|
1379
|
+
if res.returncode != 0:
|
|
1380
|
+
raise ValueError(f"El archivo '{target.name}' no pertenece a ningún repositorio Git.")
|
|
1381
|
+
|
|
1382
|
+
repo_root = Path(res.stdout.strip()).resolve()
|
|
1383
|
+
|
|
1384
|
+
has_head = subprocess.run(
|
|
1385
|
+
[git_bin, "rev-parse", "--verify", "HEAD"],
|
|
1386
|
+
cwd=str(repo_root),
|
|
1387
|
+
stdout=subprocess.DEVNULL,
|
|
1388
|
+
stderr=subprocess.DEVNULL,
|
|
1389
|
+
timeout=5,
|
|
1390
|
+
check=False
|
|
1391
|
+
).returncode == 0
|
|
1392
|
+
|
|
1393
|
+
in_head = False
|
|
1394
|
+
has_working_tree_diff = True
|
|
1395
|
+
if has_head:
|
|
1396
|
+
try:
|
|
1397
|
+
rel_path = target.relative_to(repo_root).as_posix()
|
|
1398
|
+
in_head = subprocess.run(
|
|
1399
|
+
[git_bin, "cat-file", "-e", f"HEAD:{rel_path}"],
|
|
1400
|
+
cwd=str(repo_root),
|
|
1401
|
+
stdout=subprocess.DEVNULL,
|
|
1402
|
+
stderr=subprocess.DEVNULL,
|
|
1403
|
+
timeout=5,
|
|
1404
|
+
check=False
|
|
1405
|
+
).returncode == 0
|
|
1406
|
+
if in_head:
|
|
1407
|
+
diff_check = subprocess.run(
|
|
1408
|
+
[git_bin, "diff", "--quiet", "HEAD", "--", str(target)],
|
|
1409
|
+
cwd=str(repo_root),
|
|
1410
|
+
timeout=5,
|
|
1411
|
+
check=False
|
|
1412
|
+
)
|
|
1413
|
+
has_working_tree_diff = (diff_check.returncode != 0)
|
|
1414
|
+
except ValueError:
|
|
1415
|
+
in_head = False
|
|
1416
|
+
|
|
1417
|
+
cmd = [git_bin, "difftool", "--no-prompt", "-y"]
|
|
1418
|
+
if tool:
|
|
1419
|
+
cmd.append(f"--tool={tool}")
|
|
1420
|
+
else:
|
|
1421
|
+
cmd.append("--gui")
|
|
1422
|
+
|
|
1423
|
+
mode = "working_tree"
|
|
1424
|
+
last_commit = ""
|
|
1425
|
+
if has_working_tree_diff:
|
|
1426
|
+
if in_head:
|
|
1427
|
+
cmd.extend(["HEAD", "--", str(target)])
|
|
1428
|
+
else:
|
|
1429
|
+
cmd.extend(["--no-index", "--", os.devnull, str(target)])
|
|
1430
|
+
else:
|
|
1431
|
+
# No hay cambios pendientes en el árbol de trabajo. Comprobar historial del archivo.
|
|
1432
|
+
rel_path = target.relative_to(repo_root).as_posix()
|
|
1433
|
+
log_res = subprocess.run(
|
|
1434
|
+
[git_bin, "log", "-n", "1", "--format=%H", "--", rel_path],
|
|
1435
|
+
cwd=str(repo_root),
|
|
1436
|
+
capture_output=True,
|
|
1437
|
+
text=True,
|
|
1438
|
+
timeout=5,
|
|
1439
|
+
check=False
|
|
1440
|
+
)
|
|
1441
|
+
last_commit = log_res.stdout.strip() if log_res.returncode == 0 else ""
|
|
1442
|
+
if not commit_fallback or not last_commit:
|
|
1443
|
+
return {
|
|
1444
|
+
"success": True,
|
|
1445
|
+
"has_diff": False,
|
|
1446
|
+
"in_head": in_head,
|
|
1447
|
+
"has_commit_history": bool(last_commit),
|
|
1448
|
+
"last_commit": last_commit or None,
|
|
1449
|
+
"path": str(target)
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
mode = "commit"
|
|
1453
|
+
parent_check = subprocess.run(
|
|
1454
|
+
[git_bin, "rev-parse", "--verify", f"{last_commit}~1"],
|
|
1455
|
+
cwd=str(repo_root),
|
|
1456
|
+
stdout=subprocess.DEVNULL,
|
|
1457
|
+
stderr=subprocess.DEVNULL,
|
|
1458
|
+
timeout=5,
|
|
1459
|
+
check=False
|
|
1460
|
+
)
|
|
1461
|
+
if parent_check.returncode == 0:
|
|
1462
|
+
cmd.extend([f"{last_commit}~1", last_commit, "--", str(target)])
|
|
1463
|
+
else:
|
|
1464
|
+
cmd.extend(["4b825dc642cb6eb9a060e54bf8d69288fbee4904", last_commit, "--", str(target)])
|
|
1465
|
+
|
|
1466
|
+
popen_kwargs = {
|
|
1467
|
+
"cwd": str(repo_root),
|
|
1468
|
+
"stdout": subprocess.DEVNULL,
|
|
1469
|
+
"stderr": subprocess.DEVNULL,
|
|
1470
|
+
}
|
|
1471
|
+
if os.name == "nt":
|
|
1472
|
+
popen_kwargs["creationflags"] = (
|
|
1473
|
+
getattr(subprocess, "DETACHED_PROCESS", 0) |
|
|
1474
|
+
getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
|
|
1475
|
+
)
|
|
1476
|
+
else:
|
|
1477
|
+
popen_kwargs["start_new_session"] = True
|
|
1478
|
+
|
|
1479
|
+
proc = subprocess.Popen(cmd, **popen_kwargs)
|
|
1480
|
+
return {
|
|
1481
|
+
"success": True,
|
|
1482
|
+
"has_diff": True,
|
|
1483
|
+
"mode": mode,
|
|
1484
|
+
"pid": proc.pid,
|
|
1485
|
+
"path": str(target),
|
|
1486
|
+
"in_head": in_head,
|
|
1487
|
+
"commit": last_commit or None
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
|
|
1344
1491
|
def search_files(query: str, path: str = ".", file_pattern: str = None, max_results: int = 100) -> str:
|
|
1345
1492
|
"""Recursively search for plain text or regular expressions across project files."""
|
|
1346
1493
|
try:
|
|
@@ -1350,9 +1497,11 @@ def search_files(query: str, path: str = ".", file_pattern: str = None, max_resu
|
|
|
1350
1497
|
if not target.is_dir():
|
|
1351
1498
|
return _tool_error(f"Path '{path}' is not a directory.")
|
|
1352
1499
|
|
|
1500
|
+
warning = None
|
|
1353
1501
|
try:
|
|
1354
1502
|
regex = re.compile(query, re.MULTILINE)
|
|
1355
|
-
except re.error:
|
|
1503
|
+
except re.error as err:
|
|
1504
|
+
warning = f"Query '{query}' is not a valid regular expression ({err}); treated as literal text."
|
|
1356
1505
|
regex = re.compile(re.escape(query), re.MULTILINE)
|
|
1357
1506
|
|
|
1358
1507
|
try:
|
|
@@ -1412,7 +1561,7 @@ def search_files(query: str, path: str = ".", file_pattern: str = None, max_resu
|
|
|
1412
1561
|
if truncated:
|
|
1413
1562
|
break
|
|
1414
1563
|
|
|
1415
|
-
|
|
1564
|
+
payload = {
|
|
1416
1565
|
"success": True,
|
|
1417
1566
|
"path": str(target),
|
|
1418
1567
|
"query": query,
|
|
@@ -1421,7 +1570,11 @@ def search_files(query: str, path: str = ".", file_pattern: str = None, max_resu
|
|
|
1421
1570
|
"total_matches": len(matches),
|
|
1422
1571
|
"truncated": truncated,
|
|
1423
1572
|
"matches": matches
|
|
1424
|
-
}
|
|
1573
|
+
}
|
|
1574
|
+
if warning:
|
|
1575
|
+
payload["warning"] = warning
|
|
1576
|
+
|
|
1577
|
+
return json.dumps(payload, ensure_ascii=False, indent=2)
|
|
1425
1578
|
except Exception as e:
|
|
1426
1579
|
return _tool_error(str(e))
|
|
1427
1580
|
|
|
@@ -2700,6 +2853,178 @@ DEV_CONTENT_TYPES = {
|
|
|
2700
2853
|
".map": "application/json",
|
|
2701
2854
|
}
|
|
2702
2855
|
|
|
2856
|
+
METADATA_HOSTS = frozenset({
|
|
2857
|
+
"169.254.169.254",
|
|
2858
|
+
"metadata.google.internal",
|
|
2859
|
+
"instance-data",
|
|
2860
|
+
"100.100.100.200",
|
|
2861
|
+
})
|
|
2862
|
+
DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
2863
|
+
|
|
2864
|
+
|
|
2865
|
+
def is_dangerous_host(hostname: str, allow_local: bool = False) -> bool:
|
|
2866
|
+
"""Verifica si un host representa un riesgo de SSRF (metadatos cloud, loopback o privados)."""
|
|
2867
|
+
if not hostname:
|
|
2868
|
+
return True
|
|
2869
|
+
clean_host = hostname.strip().lower().strip("[]")
|
|
2870
|
+
if clean_host in METADATA_HOSTS:
|
|
2871
|
+
return True
|
|
2872
|
+
|
|
2873
|
+
try:
|
|
2874
|
+
ip = ipaddress.ip_address(clean_host)
|
|
2875
|
+
if ip.is_link_local:
|
|
2876
|
+
return True
|
|
2877
|
+
if not allow_local and (ip.is_loopback or ip.is_private or ip.is_reserved or ip.is_unspecified):
|
|
2878
|
+
return True
|
|
2879
|
+
return False
|
|
2880
|
+
except ValueError:
|
|
2881
|
+
pass
|
|
2882
|
+
|
|
2883
|
+
if clean_host.isdigit():
|
|
2884
|
+
try:
|
|
2885
|
+
num = int(clean_host)
|
|
2886
|
+
if 0 <= num <= 4294967295:
|
|
2887
|
+
ip = ipaddress.IPv4Address(num)
|
|
2888
|
+
if ip.is_link_local:
|
|
2889
|
+
return True
|
|
2890
|
+
if not allow_local and (ip.is_loopback or ip.is_private or ip.is_reserved or ip.is_unspecified):
|
|
2891
|
+
return True
|
|
2892
|
+
return False
|
|
2893
|
+
except ValueError:
|
|
2894
|
+
pass
|
|
2895
|
+
|
|
2896
|
+
if clean_host.startswith("0x"):
|
|
2897
|
+
try:
|
|
2898
|
+
num = int(clean_host, 16)
|
|
2899
|
+
if 0 <= num <= 4294967295:
|
|
2900
|
+
ip = ipaddress.IPv4Address(num)
|
|
2901
|
+
if ip.is_link_local:
|
|
2902
|
+
return True
|
|
2903
|
+
if not allow_local and (ip.is_loopback or ip.is_private or ip.is_reserved or ip.is_unspecified):
|
|
2904
|
+
return True
|
|
2905
|
+
return False
|
|
2906
|
+
except ValueError:
|
|
2907
|
+
pass
|
|
2908
|
+
|
|
2909
|
+
if not allow_local:
|
|
2910
|
+
if clean_host in ("localhost", "0.0.0.0", "127.0.0.1", "::1"):
|
|
2911
|
+
return True
|
|
2912
|
+
if clean_host.endswith(".local") or clean_host.endswith(".internal"):
|
|
2913
|
+
return True
|
|
2914
|
+
|
|
2915
|
+
return False
|
|
2916
|
+
|
|
2917
|
+
|
|
2918
|
+
def fetch_web_resource(params: dict) -> dict:
|
|
2919
|
+
"""Descarga de forma segura un recurso web vía HTTP/HTTPS para el cliente web."""
|
|
2920
|
+
url = params.get("url")
|
|
2921
|
+
if not isinstance(url, str) or not url.strip():
|
|
2922
|
+
raise ValueError("URL no válida o ausente.")
|
|
2923
|
+
url = url.strip()
|
|
2924
|
+
parsed = urllib.parse.urlparse(url)
|
|
2925
|
+
if parsed.scheme not in ("http", "https"):
|
|
2926
|
+
raise ValueError(f"Protocolo no permitido: '{parsed.scheme}'. Solo se admite http y https.")
|
|
2927
|
+
hostname = (parsed.hostname or "").lower().strip("[]")
|
|
2928
|
+
if not hostname:
|
|
2929
|
+
raise ValueError("URL sin nombre de host válido.")
|
|
2930
|
+
|
|
2931
|
+
allow_local = bool(params.get("allow_local", False))
|
|
2932
|
+
if is_dangerous_host(hostname, allow_local):
|
|
2933
|
+
raise PermissionError("Acceso bloqueado: intento de acceso a servicios internos o metadatos de infraestructura (SSRF).")
|
|
2934
|
+
|
|
2935
|
+
timeout_s = float(params.get("timeout_s", 10.0))
|
|
2936
|
+
timeout_s = max(0.5, min(timeout_s, 30.0))
|
|
2937
|
+
max_bytes = int(params.get("max_bytes", 2 * 1024 * 1024))
|
|
2938
|
+
max_bytes = max(1024, min(max_bytes, 10 * 1024 * 1024))
|
|
2939
|
+
as_binary = bool(params.get("binary", False))
|
|
2940
|
+
|
|
2941
|
+
req_headers = {
|
|
2942
|
+
"User-Agent": DEFAULT_USER_AGENT,
|
|
2943
|
+
"Accept": "text/html,application/xhtml+xml,application/json,application/pdf,text/plain;q=0.9,*/*;q=0.8",
|
|
2944
|
+
"Accept-Encoding": "gzip, deflate",
|
|
2945
|
+
}
|
|
2946
|
+
custom_headers = params.get("headers")
|
|
2947
|
+
if isinstance(custom_headers, dict):
|
|
2948
|
+
for k, v in custom_headers.items():
|
|
2949
|
+
if isinstance(k, str) and isinstance(v, str) and k.lower() != "host":
|
|
2950
|
+
req_headers[k] = v
|
|
2951
|
+
|
|
2952
|
+
req = urllib.request.Request(url, headers=req_headers)
|
|
2953
|
+
try:
|
|
2954
|
+
with urllib.request.urlopen(req, timeout=timeout_s) as resp:
|
|
2955
|
+
status = resp.status
|
|
2956
|
+
content_type = resp.headers.get("Content-Type", "")
|
|
2957
|
+
content_encoding = resp.headers.get("Content-Encoding", "").lower()
|
|
2958
|
+
raw_data = resp.read(max_bytes + 1)
|
|
2959
|
+
truncated = len(raw_data) > max_bytes
|
|
2960
|
+
if truncated:
|
|
2961
|
+
raw_data = raw_data[:max_bytes]
|
|
2962
|
+
|
|
2963
|
+
if "gzip" in content_encoding:
|
|
2964
|
+
try:
|
|
2965
|
+
raw_data = gzip.decompress(raw_data)
|
|
2966
|
+
except (OSError, gzip.BadGzipFile):
|
|
2967
|
+
pass
|
|
2968
|
+
elif "deflate" in content_encoding:
|
|
2969
|
+
try:
|
|
2970
|
+
raw_data = zlib.decompress(raw_data)
|
|
2971
|
+
except (OSError, zlib.error):
|
|
2972
|
+
pass
|
|
2973
|
+
|
|
2974
|
+
resp_headers = {k: v for k, v in resp.getheaders()}
|
|
2975
|
+
|
|
2976
|
+
if as_binary:
|
|
2977
|
+
return {
|
|
2978
|
+
"ok": True,
|
|
2979
|
+
"status": status,
|
|
2980
|
+
"content_type": content_type,
|
|
2981
|
+
"headers": resp_headers,
|
|
2982
|
+
"base64": base64.b64encode(raw_data).decode("ascii"),
|
|
2983
|
+
"byte_size": len(raw_data),
|
|
2984
|
+
"truncated": truncated
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
charset = resp.headers.get_content_charset() or "utf-8"
|
|
2988
|
+
try:
|
|
2989
|
+
text_content = raw_data.decode(charset, errors="replace")
|
|
2990
|
+
return {
|
|
2991
|
+
"ok": True,
|
|
2992
|
+
"status": status,
|
|
2993
|
+
"content_type": content_type,
|
|
2994
|
+
"headers": resp_headers,
|
|
2995
|
+
"text": text_content,
|
|
2996
|
+
"byte_size": len(raw_data),
|
|
2997
|
+
"truncated": truncated
|
|
2998
|
+
}
|
|
2999
|
+
except (UnicodeDecodeError, LookupError):
|
|
3000
|
+
return {
|
|
3001
|
+
"ok": True,
|
|
3002
|
+
"status": status,
|
|
3003
|
+
"content_type": content_type,
|
|
3004
|
+
"headers": resp_headers,
|
|
3005
|
+
"base64": base64.b64encode(raw_data).decode("ascii"),
|
|
3006
|
+
"byte_size": len(raw_data),
|
|
3007
|
+
"truncated": truncated
|
|
3008
|
+
}
|
|
3009
|
+
except urllib.error.HTTPError as he:
|
|
3010
|
+
err_body = ""
|
|
3011
|
+
try:
|
|
3012
|
+
err_body = he.read(2048).decode("utf-8", errors="replace")
|
|
3013
|
+
except (OSError, UnicodeDecodeError):
|
|
3014
|
+
pass
|
|
3015
|
+
return {
|
|
3016
|
+
"ok": False,
|
|
3017
|
+
"status": he.code,
|
|
3018
|
+
"error": f"HTTP {he.code}: {he.reason}",
|
|
3019
|
+
"text": err_body
|
|
3020
|
+
}
|
|
3021
|
+
except Exception as exc:
|
|
3022
|
+
return {
|
|
3023
|
+
"ok": False,
|
|
3024
|
+
"status": 0,
|
|
3025
|
+
"error": str(exc)
|
|
3026
|
+
}
|
|
3027
|
+
|
|
2703
3028
|
|
|
2704
3029
|
class ZeroChatServerHandler(BaseHTTPRequestHandler):
|
|
2705
3030
|
server_version = f"ZeroChatServer/{VERSION}"
|
|
@@ -3105,6 +3430,21 @@ class ZeroChatServerHandler(BaseHTTPRequestHandler):
|
|
|
3105
3430
|
return set_project_directory(params.get("path")), None, ""
|
|
3106
3431
|
except (ValueError, OSError) as exc:
|
|
3107
3432
|
return None, {"code": -32602, "message": str(exc)}, ""
|
|
3433
|
+
if method == "zerochat/diff/open":
|
|
3434
|
+
try:
|
|
3435
|
+
commit_fallback = bool(params.get("commit_fallback") or params.get("commitFallback"))
|
|
3436
|
+
return open_file_diff(params.get("path"), params.get("tool"), commit_fallback), None, ""
|
|
3437
|
+
except (ValueError, RuntimeError) as exc:
|
|
3438
|
+
return None, {"code": -32602, "message": str(exc)}, ""
|
|
3439
|
+
except Exception as exc:
|
|
3440
|
+
return None, {"code": -32603, "message": f"Error al abrir el visor de diffs: {exc}"}, str(exc)
|
|
3441
|
+
if method == "zerochat/web/fetch":
|
|
3442
|
+
try:
|
|
3443
|
+
return fetch_web_resource(params), None, ""
|
|
3444
|
+
except (ValueError, PermissionError) as exc:
|
|
3445
|
+
return None, {"code": -32602, "message": str(exc)}, ""
|
|
3446
|
+
except Exception as exc:
|
|
3447
|
+
return None, {"code": -32603, "message": f"Error en transporte web: {exc}"}, str(exc)
|
|
3108
3448
|
if method.startswith("zerochat/jobs/"):
|
|
3109
3449
|
return self._dispatch_jobs(method, params)
|
|
3110
3450
|
if method == "zerochat/external/status":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|