agent-portal-2 0.1.0
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.
- package/.continue/agents/new-config.yaml +22 -0
- package/AGENT_STEERING.md +36 -0
- package/ARCHITECTURE.md +13 -0
- package/CHANGELOG.md +97 -0
- package/CLI.md +38 -0
- package/CONTRIBUTING.md +55 -0
- package/INSTALLATION.md +58 -0
- package/LICENSE +60 -0
- package/PLUGIN_SYSTEM.md +33 -0
- package/PYTHON_SDK.md +22 -0
- package/QUICKSTART.md +19 -0
- package/README.md +385 -0
- package/RELEASE_NOTES_v0.1.0.md +281 -0
- package/ROADMAP.md +3 -0
- package/RUNTIME.md +44 -0
- package/SAFETY_MODEL.md +24 -0
- package/TESTING.md +35 -0
- package/TROUBLESHOOTING.md +30 -0
- package/UPGRADE_GUIDE.md +288 -0
- package/VS_CODE_EXTENSION.md +47 -0
- package/agent-portal.config.json +20 -0
- package/apps/desktop/agent-portal-desktop.zip +0 -0
- package/apps/desktop/fixtures/local-workflow.html +151 -0
- package/apps/desktop/package.json +18 -0
- package/apps/desktop/src/main.ts +117 -0
- package/apps/desktop/tsconfig.json +8 -0
- package/apps/vscode-extension/LICENSE +60 -0
- package/apps/vscode-extension/README.md +20 -0
- package/apps/vscode-extension/media/agent-portal-logo.png +0 -0
- package/apps/vscode-extension/package.json +149 -0
- package/apps/vscode-extension/src/extension.ts +614 -0
- package/apps/vscode-extension/tsconfig.json +12 -0
- package/assets/branding/agent-portal-logo.png +0 -0
- package/connectors/chatgpt-tools/README.md +9 -0
- package/connectors/claude-mcp-server/README.md +9 -0
- package/connectors/gemini-connector/README.md +9 -0
- package/connectors/rest-websocket-api/README.md +9 -0
- package/docs/MCP_SERVER.md +68 -0
- package/docs/architecture.md +214 -0
- package/docs/roadmap.md +125 -0
- package/package.json +21 -0
- package/packages/agent-portal-mcp/README.md +12 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/__init__.py +3 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/bridge/__init__.py +1 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/bridge/runtime_client.py +180 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/cli.py +32 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/doctor.py +71 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/schemas/__init__.py +1 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/schemas/actions.py +17 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/schemas/results.py +24 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/schemas/risk.py +20 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/security/__init__.py +1 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/security/policy.py +27 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/server.py +148 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/tool_registry.py +58 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/tools/__init__.py +1 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/tools/browser.py +89 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/tools/common.py +98 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/tools/inspection.py +93 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/tools/navigation.py +93 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/tools/reports.py +34 -0
- package/packages/agent-portal-mcp/agent_portal_mcp/tools/steering.py +93 -0
- package/packages/agent-portal-mcp/pyproject.toml +20 -0
- package/packages/agent-portal-mcp/tests/test_doctor.py +20 -0
- package/packages/agent-portal-mcp/tests/test_mcp_server.py +161 -0
- package/packages/core/package.json +15 -0
- package/packages/core/src/index.ts +1842 -0
- package/packages/core/tsconfig.json +8 -0
- package/packages/mcp-server/package.json +15 -0
- package/packages/mcp-server/src/index.ts +73 -0
- package/packages/mcp-server/tsconfig.json +8 -0
- package/packages/sdk/package.json +15 -0
- package/packages/sdk/src/index.ts +544 -0
- package/packages/sdk/tsconfig.json +8 -0
- package/plugins/README.md +16 -0
- package/plugins/agent-portal-browser/plugin.json +19 -0
- package/plugins/agent-portal-python/plugin.json +16 -0
- package/plugins/agent-portal-skills/plugin.json +19 -0
- package/plugins/agent-portal-vscode/plugin.json +27 -0
- package/plugins/example-runtime-plugin/README.md +3 -0
- package/plugins/example-runtime-plugin/plugin.json +20 -0
- package/plugins/plugin.schema.json +53 -0
- package/python/README.md +18 -0
- package/python/agent_portal/__init__.py +5 -0
- package/python/agent_portal/__main__.py +5 -0
- package/python/agent_portal/browser.py +393 -0
- package/python/agent_portal/cli.py +164 -0
- package/python/agent_portal/config.py +31 -0
- package/python/agent_portal/doctor.py +165 -0
- package/python/agent_portal/exceptions.py +39 -0
- package/python/agent_portal/logging_utils.py +33 -0
- package/python/agent_portal/metrics.py +309 -0
- package/python/agent_portal/models.py +160 -0
- package/python/agent_portal/plugin_system.py +42 -0
- package/python/agent_portal/rate_limit.py +253 -0
- package/python/agent_portal/runtime.py +739 -0
- package/python/agent_portal/server.py +351 -0
- package/python/agent_portal/validation.py +299 -0
- package/python/pyproject.toml +29 -0
- package/python/tests/test_config.py +24 -0
- package/python/tests/test_doctor.py +19 -0
- package/python/tests/test_metrics.py +180 -0
- package/python/tests/test_rate_limit.py +237 -0
- package/python/tests/test_runtime.py +122 -0
- package/python/tests/test_server.py +53 -0
- package/python/tests/test_validation.py +170 -0
- package/releases/desktop/agent-portal-desktop/README.md +378 -0
- package/releases/desktop/agent-portal-desktop/RELEASE_NOTES.md +14 -0
- package/releases/desktop/agent-portal-desktop/assets/branding/agent-portal-logo.png +0 -0
- package/releases/desktop/agent-portal-desktop/fixtures/local-workflow.html +151 -0
- package/releases/desktop/agent-portal-desktop/launch-agent-portal.bat +4 -0
- package/releases/desktop/agent-portal-desktop.zip +0 -0
- package/releases/python/agent_portal-0.0.2-py3-none-any.whl +0 -0
- package/releases/python/agent_portal-0.0.2.tar.gz +0 -0
- package/scripts/package_desktop.mjs +117 -0
- package/scripts/release_python.py +46 -0
- package/tests/plugin-manifest.test.mjs +26 -0
- package/tests/runtime.test.mjs +41 -0
- package/tests/vscode-extension.test.mjs +22 -0
- package/tsconfig.base.json +16 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from dataclasses import asdict
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import sys
|
|
8
|
+
from urllib.error import HTTPError, URLError
|
|
9
|
+
from urllib.request import Request, urlopen
|
|
10
|
+
|
|
11
|
+
from .config import load_config, save_default_config
|
|
12
|
+
from .doctor import run_doctor
|
|
13
|
+
from .plugin_system import discover_plugins, validate_plugin_manifest
|
|
14
|
+
from .runtime import PortalRuntime
|
|
15
|
+
from .server import serve
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main() -> None:
|
|
19
|
+
parser = build_parser()
|
|
20
|
+
args = parser.parse_args()
|
|
21
|
+
workspace = Path.cwd()
|
|
22
|
+
save_default_config(workspace)
|
|
23
|
+
config = load_config(workspace)
|
|
24
|
+
host = args.host or config.runtime_host
|
|
25
|
+
port = args.port or config.runtime_port
|
|
26
|
+
runtime_url = f"http://{host}:{port}"
|
|
27
|
+
|
|
28
|
+
config.runtime_host = host
|
|
29
|
+
config.runtime_port = port
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
if args.command == "start":
|
|
33
|
+
serve(PortalRuntime(workspace, config))
|
|
34
|
+
return
|
|
35
|
+
if args.command == "stop":
|
|
36
|
+
print_json_or_text(post_json(f"{runtime_url}/control/stop"), args.json)
|
|
37
|
+
return
|
|
38
|
+
if args.command == "status":
|
|
39
|
+
print_json_or_text(get_json(f"{runtime_url}/status"), args.json)
|
|
40
|
+
return
|
|
41
|
+
if args.command == "doctor":
|
|
42
|
+
report = run_doctor(workspace)
|
|
43
|
+
payload = {"checks": [asdict(check) for check in report.checks]}
|
|
44
|
+
print_json_or_text(payload, args.json)
|
|
45
|
+
return
|
|
46
|
+
if args.command == "open":
|
|
47
|
+
payload = {"url": args.url}
|
|
48
|
+
print_json_or_text(post_json(f"{runtime_url}/browser/open", payload), args.json)
|
|
49
|
+
return
|
|
50
|
+
if args.command == "screenshot":
|
|
51
|
+
payload = {"label": args.label}
|
|
52
|
+
print_json_or_text(post_json(f"{runtime_url}/browser/screenshot", payload), args.json)
|
|
53
|
+
return
|
|
54
|
+
if args.command == "report":
|
|
55
|
+
print_json_or_text(post_json(f"{runtime_url}/report/generate"), args.json)
|
|
56
|
+
return
|
|
57
|
+
if args.command == "plugins":
|
|
58
|
+
if args.plugins_command == "list":
|
|
59
|
+
payload = [str(path) for path in discover_plugins(workspace)]
|
|
60
|
+
print_json_or_text(payload, args.json)
|
|
61
|
+
return
|
|
62
|
+
if args.plugins_command == "validate":
|
|
63
|
+
results = {
|
|
64
|
+
str(path): validate_plugin_manifest(path)
|
|
65
|
+
for path in discover_plugins(workspace)
|
|
66
|
+
}
|
|
67
|
+
print_json_or_text(results, args.json)
|
|
68
|
+
return
|
|
69
|
+
if args.command == "mcp":
|
|
70
|
+
mcp_cli = load_mcp_cli_module()
|
|
71
|
+
argv = [args.mcp_command]
|
|
72
|
+
if args.host or args.port:
|
|
73
|
+
argv.extend(["--runtime-url", runtime_url])
|
|
74
|
+
if args.json:
|
|
75
|
+
argv.append("--json")
|
|
76
|
+
old_argv = sys.argv[:]
|
|
77
|
+
try:
|
|
78
|
+
sys.argv = ["agent-portal-mcp", *argv]
|
|
79
|
+
mcp_cli.main()
|
|
80
|
+
finally:
|
|
81
|
+
sys.argv = old_argv
|
|
82
|
+
return
|
|
83
|
+
except (HTTPError, URLError) as exc:
|
|
84
|
+
print_json_or_text(
|
|
85
|
+
{
|
|
86
|
+
"error": "Runtime request failed",
|
|
87
|
+
"details": str(exc),
|
|
88
|
+
"suggestedFix": f"Start the runtime with `agent-portal --host {host} --port {port} start`.",
|
|
89
|
+
},
|
|
90
|
+
True if args.json else False,
|
|
91
|
+
)
|
|
92
|
+
raise SystemExit(1) from exc
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
96
|
+
parser = argparse.ArgumentParser(prog="agent-portal")
|
|
97
|
+
parser.add_argument("--json", action="store_true")
|
|
98
|
+
parser.add_argument("--verbose", action="store_true")
|
|
99
|
+
parser.add_argument("--debug", action="store_true")
|
|
100
|
+
parser.add_argument("--host")
|
|
101
|
+
parser.add_argument("--port", type=int)
|
|
102
|
+
parser.add_argument("--profile")
|
|
103
|
+
|
|
104
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
105
|
+
subparsers.add_parser("start")
|
|
106
|
+
subparsers.add_parser("stop")
|
|
107
|
+
subparsers.add_parser("status")
|
|
108
|
+
subparsers.add_parser("doctor")
|
|
109
|
+
|
|
110
|
+
open_parser = subparsers.add_parser("open")
|
|
111
|
+
open_parser.add_argument("url")
|
|
112
|
+
|
|
113
|
+
screenshot_parser = subparsers.add_parser("screenshot")
|
|
114
|
+
screenshot_parser.add_argument("--label", default="manual")
|
|
115
|
+
|
|
116
|
+
subparsers.add_parser("report")
|
|
117
|
+
|
|
118
|
+
plugins_parser = subparsers.add_parser("plugins")
|
|
119
|
+
plugins_subparsers = plugins_parser.add_subparsers(dest="plugins_command", required=True)
|
|
120
|
+
plugins_subparsers.add_parser("list")
|
|
121
|
+
plugins_subparsers.add_parser("validate")
|
|
122
|
+
|
|
123
|
+
mcp_parser = subparsers.add_parser("mcp")
|
|
124
|
+
mcp_subparsers = mcp_parser.add_subparsers(dest="mcp_command", required=True)
|
|
125
|
+
mcp_subparsers.add_parser("start")
|
|
126
|
+
mcp_subparsers.add_parser("doctor")
|
|
127
|
+
return parser
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_json(url: str) -> object:
|
|
131
|
+
with urlopen(url) as response:
|
|
132
|
+
return json.loads(response.read().decode("utf8"))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def post_json(url: str, payload: dict[str, object] | None = None) -> object:
|
|
136
|
+
data = json.dumps(payload or {}).encode("utf8")
|
|
137
|
+
request = Request(url, data=data, headers={"Content-Type": "application/json"})
|
|
138
|
+
with urlopen(request) as response:
|
|
139
|
+
return json.loads(response.read().decode("utf8"))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def print_json_or_text(payload: object, json_output: bool) -> None:
|
|
143
|
+
if json_output:
|
|
144
|
+
print(json.dumps(payload, indent=2))
|
|
145
|
+
return
|
|
146
|
+
if isinstance(payload, dict):
|
|
147
|
+
for key, value in payload.items():
|
|
148
|
+
print(f"{key}: {value}")
|
|
149
|
+
return
|
|
150
|
+
if isinstance(payload, list):
|
|
151
|
+
for entry in payload:
|
|
152
|
+
print(f"- {entry}")
|
|
153
|
+
return
|
|
154
|
+
print(payload)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def load_mcp_cli_module():
|
|
158
|
+
repo_root = Path(__file__).resolve().parents[2]
|
|
159
|
+
mcp_src = repo_root / "packages" / "agent-portal-mcp"
|
|
160
|
+
if str(mcp_src) not in sys.path:
|
|
161
|
+
sys.path.insert(0, str(mcp_src))
|
|
162
|
+
from agent_portal_mcp import cli as mcp_cli # type: ignore
|
|
163
|
+
|
|
164
|
+
return mcp_cli
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .models import RuntimeConfigModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
DEFAULT_CONFIG_PATH = "agent-portal.config.json"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_config(base_path: Path | None = None) -> RuntimeConfigModel:
|
|
14
|
+
root = base_path or Path.cwd()
|
|
15
|
+
config_path = root / DEFAULT_CONFIG_PATH
|
|
16
|
+
if not config_path.exists():
|
|
17
|
+
return RuntimeConfigModel()
|
|
18
|
+
|
|
19
|
+
raw = json.loads(config_path.read_text(encoding="utf8"))
|
|
20
|
+
return RuntimeConfigModel(**raw)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def save_default_config(base_path: Path | None = None) -> Path:
|
|
24
|
+
root = base_path or Path.cwd()
|
|
25
|
+
config_path = root / DEFAULT_CONFIG_PATH
|
|
26
|
+
if not config_path.exists():
|
|
27
|
+
config_path.write_text(
|
|
28
|
+
json.dumps(asdict(RuntimeConfigModel()), indent=2),
|
|
29
|
+
encoding="utf8",
|
|
30
|
+
)
|
|
31
|
+
return config_path
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import socket
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .config import DEFAULT_CONFIG_PATH, load_config
|
|
11
|
+
from .models import DoctorReport, HealthCheckResult
|
|
12
|
+
from .plugin_system import discover_plugins, validate_plugin_manifest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run_doctor(base_path: Path | None = None) -> DoctorReport:
|
|
16
|
+
root = base_path or Path.cwd()
|
|
17
|
+
config = load_config(root)
|
|
18
|
+
report = DoctorReport()
|
|
19
|
+
|
|
20
|
+
report.checks.append(check_python_version())
|
|
21
|
+
report.checks.append(check_dependency("playwright", "Install with `pip install -e ./python`."))
|
|
22
|
+
report.checks.append(check_config_file(root))
|
|
23
|
+
report.checks.append(check_runtime_port(config.runtime_host, config.runtime_port))
|
|
24
|
+
report.checks.append(check_directory_writable(root / config.screenshot_directory, "Screenshot directory"))
|
|
25
|
+
report.checks.append(check_directory_writable(root / config.report_directory, "Report directory"))
|
|
26
|
+
report.checks.extend(check_plugin_manifests(root))
|
|
27
|
+
report.checks.append(check_vscode_extension(root))
|
|
28
|
+
report.checks.append(check_os_support())
|
|
29
|
+
report.checks.append(check_chromium_presence())
|
|
30
|
+
return report
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def check_python_version() -> HealthCheckResult:
|
|
34
|
+
if sys.version_info >= (3, 10):
|
|
35
|
+
return HealthCheckResult("python-version", "passed", sys.version.split()[0])
|
|
36
|
+
return HealthCheckResult(
|
|
37
|
+
"python-version",
|
|
38
|
+
"failed",
|
|
39
|
+
sys.version.split()[0],
|
|
40
|
+
"Install Python 3.10 or newer.",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def check_dependency(module_name: str, suggested_fix: str) -> HealthCheckResult:
|
|
45
|
+
found = importlib.util.find_spec(module_name) is not None
|
|
46
|
+
if found:
|
|
47
|
+
return HealthCheckResult(module_name, "passed", "Installed")
|
|
48
|
+
return HealthCheckResult(module_name, "failed", "Missing", suggested_fix)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def check_config_file(root: Path) -> HealthCheckResult:
|
|
52
|
+
config_path = root / DEFAULT_CONFIG_PATH
|
|
53
|
+
if not config_path.exists():
|
|
54
|
+
return HealthCheckResult(
|
|
55
|
+
"config-file",
|
|
56
|
+
"warning",
|
|
57
|
+
f"{config_path.name} not found",
|
|
58
|
+
"Start the runtime once or create the default config with `agent-portal start`.",
|
|
59
|
+
)
|
|
60
|
+
try:
|
|
61
|
+
json.loads(config_path.read_text(encoding="utf8"))
|
|
62
|
+
return HealthCheckResult("config-file", "passed", str(config_path))
|
|
63
|
+
except json.JSONDecodeError as exc:
|
|
64
|
+
return HealthCheckResult(
|
|
65
|
+
"config-file",
|
|
66
|
+
"failed",
|
|
67
|
+
str(exc),
|
|
68
|
+
"Fix the JSON syntax in agent-portal.config.json.",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def check_runtime_port(host: str, port: int) -> HealthCheckResult:
|
|
73
|
+
sock = socket.socket()
|
|
74
|
+
try:
|
|
75
|
+
sock.bind((host, port))
|
|
76
|
+
return HealthCheckResult("runtime-port", "passed", f"{host}:{port} is available")
|
|
77
|
+
except OSError:
|
|
78
|
+
return HealthCheckResult(
|
|
79
|
+
"runtime-port",
|
|
80
|
+
"warning",
|
|
81
|
+
f"{host}:{port} is already in use",
|
|
82
|
+
"Stop the other runtime or choose a different port.",
|
|
83
|
+
)
|
|
84
|
+
finally:
|
|
85
|
+
sock.close()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def check_directory_writable(directory: Path, label: str) -> HealthCheckResult:
|
|
89
|
+
try:
|
|
90
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
test_file = directory / ".write-test"
|
|
92
|
+
test_file.write_text("ok", encoding="utf8")
|
|
93
|
+
test_file.unlink()
|
|
94
|
+
return HealthCheckResult(label, "passed", str(directory))
|
|
95
|
+
except OSError as exc:
|
|
96
|
+
return HealthCheckResult(
|
|
97
|
+
label,
|
|
98
|
+
"failed",
|
|
99
|
+
str(exc),
|
|
100
|
+
f"Fix filesystem permissions for {directory}.",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def check_plugin_manifests(root: Path) -> list[HealthCheckResult]:
|
|
105
|
+
results: list[HealthCheckResult] = []
|
|
106
|
+
for plugin_manifest in discover_plugins(root):
|
|
107
|
+
errors = validate_plugin_manifest(plugin_manifest)
|
|
108
|
+
if errors:
|
|
109
|
+
results.append(
|
|
110
|
+
HealthCheckResult(
|
|
111
|
+
f"plugin:{plugin_manifest.parent.name}",
|
|
112
|
+
"failed",
|
|
113
|
+
"; ".join(errors),
|
|
114
|
+
"Fix the manifest fields and retry.",
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
results.append(
|
|
119
|
+
HealthCheckResult(
|
|
120
|
+
f"plugin:{plugin_manifest.parent.name}",
|
|
121
|
+
"passed",
|
|
122
|
+
"Valid"
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
return results
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def check_vscode_extension(root: Path) -> HealthCheckResult:
|
|
129
|
+
extension_dir = root / "apps" / "vscode-extension"
|
|
130
|
+
package_json = extension_dir / "package.json"
|
|
131
|
+
if package_json.exists():
|
|
132
|
+
return HealthCheckResult("vscode-extension", "passed", str(package_json))
|
|
133
|
+
return HealthCheckResult(
|
|
134
|
+
"vscode-extension",
|
|
135
|
+
"warning",
|
|
136
|
+
str(package_json),
|
|
137
|
+
"Restore the VS Code extension package if editor integration is required.",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def check_os_support() -> HealthCheckResult:
|
|
142
|
+
if os.name == "nt":
|
|
143
|
+
return HealthCheckResult("os-support", "passed", "Windows supported")
|
|
144
|
+
return HealthCheckResult(
|
|
145
|
+
"os-support",
|
|
146
|
+
"warning",
|
|
147
|
+
os.name,
|
|
148
|
+
"Validate browser paths and runtime behavior on this operating system.",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def check_chromium_presence() -> HealthCheckResult:
|
|
153
|
+
locations = [
|
|
154
|
+
Path.home() / "AppData" / "Local" / "ms-playwright",
|
|
155
|
+
Path.home() / ".cache" / "ms-playwright",
|
|
156
|
+
]
|
|
157
|
+
for playwright_cache in locations:
|
|
158
|
+
if playwright_cache.exists():
|
|
159
|
+
return HealthCheckResult("chromium-installed", "passed", str(playwright_cache))
|
|
160
|
+
return HealthCheckResult(
|
|
161
|
+
"chromium-installed",
|
|
162
|
+
"warning",
|
|
163
|
+
"Chromium cache not found",
|
|
164
|
+
"Run `npx playwright install chromium` and `python -m playwright install chromium` if needed.",
|
|
165
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AgentPortalError(Exception):
|
|
5
|
+
def __init__(
|
|
6
|
+
self,
|
|
7
|
+
message: str,
|
|
8
|
+
*,
|
|
9
|
+
module: str,
|
|
10
|
+
likely_cause: str,
|
|
11
|
+
suggested_fix: str,
|
|
12
|
+
can_continue: bool,
|
|
13
|
+
) -> None:
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
self.module = module
|
|
16
|
+
self.likely_cause = likely_cause
|
|
17
|
+
self.suggested_fix = suggested_fix
|
|
18
|
+
self.can_continue = can_continue
|
|
19
|
+
|
|
20
|
+
def to_dict(self) -> dict[str, object]:
|
|
21
|
+
return {
|
|
22
|
+
"message": str(self),
|
|
23
|
+
"module": self.module,
|
|
24
|
+
"likelyCause": self.likely_cause,
|
|
25
|
+
"suggestedFix": self.suggested_fix,
|
|
26
|
+
"canContinue": self.can_continue,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RuntimeStartupError(AgentPortalError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BrowserOperationError(AgentPortalError):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class PolicyBlockedError(AgentPortalError):
|
|
39
|
+
pass
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class JsonFormatter(logging.Formatter):
|
|
10
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
11
|
+
payload = {
|
|
12
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
13
|
+
"level": record.levelname,
|
|
14
|
+
"logger": record.name,
|
|
15
|
+
"message": record.getMessage(),
|
|
16
|
+
}
|
|
17
|
+
if hasattr(record, "context"):
|
|
18
|
+
payload["context"] = getattr(record, "context")
|
|
19
|
+
return json.dumps(payload)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def build_logger(name: str, level: str = "INFO") -> logging.Logger:
|
|
23
|
+
logger = logging.getLogger(name)
|
|
24
|
+
if logger.handlers:
|
|
25
|
+
logger.setLevel(level.upper())
|
|
26
|
+
return logger
|
|
27
|
+
|
|
28
|
+
logger.setLevel(level.upper())
|
|
29
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
30
|
+
handler.setFormatter(JsonFormatter())
|
|
31
|
+
logger.addHandler(handler)
|
|
32
|
+
logger.propagate = False
|
|
33
|
+
return logger
|