Snapshot assembled only from the signed corresponding-source archive. Source archive SHA-256: db91a270cdefc4be79056ed38a0b4a16747e1ab130592645eac40fa3809809df
376 lines
16 KiB
Python
376 lines
16 KiB
Python
"""Original Fleet providers and generic confirmed-node routing."""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import socket
|
|
import sys
|
|
import threading
|
|
import time
|
|
import uuid
|
|
from typing import Any
|
|
|
|
_BOOTSTRAP_LOCK = threading.RLock()
|
|
|
|
JOB_RE = re.compile(r"^[A-Za-z0-9._-]{1,80}$")
|
|
READ_CAPABILITIES = frozenset({
|
|
"system.status", "hardware.summary", "storage.inspect",
|
|
"network.inspect", "service.status",
|
|
})
|
|
|
|
|
|
class FleetError(ValueError):
|
|
"""Stable public error codes only."""
|
|
|
|
|
|
def _require(condition: object, code: str) -> None:
|
|
if not condition:
|
|
raise FleetError(code)
|
|
|
|
|
|
def original_paths() -> tuple[Path, Path]:
|
|
root = Path(__file__).resolve().parents[2]
|
|
choices = [
|
|
("FRAMEBRIDGE_BROKER_SOURCE", "FrameBridge-Broker-RC1/src"),
|
|
("FRAMEBRIDGE_FLEET_SOURCE", "FrameBridge-Fleet-Hub/app"),
|
|
]
|
|
result = []
|
|
for variable, suffix in choices:
|
|
configured = os.environ.get(variable)
|
|
candidates = [Path(configured)] if configured else [
|
|
root / "original/geranas" / suffix,
|
|
root / "sources/original-geranas" / suffix,
|
|
]
|
|
normalized = []
|
|
for candidate in candidates:
|
|
nested = candidate / ("src" if variable == "FRAMEBRIDGE_BROKER_SOURCE" else "app")
|
|
if (candidate / "framebridge_linux_chatgpt").is_dir():
|
|
normalized.append(candidate)
|
|
elif (nested / "framebridge_linux_chatgpt").is_dir():
|
|
normalized.append(nested)
|
|
path = next(iter(normalized), None)
|
|
_require(path is not None, "ORIGINAL_MODULES_UNAVAILABLE")
|
|
result.append(path)
|
|
return result[0], result[1]
|
|
|
|
|
|
def bootstrap_original() -> None:
|
|
"""Extend the original package search path without copying or merging it."""
|
|
# Registration now dispatches in workers; protect shared import paths.
|
|
with _BOOTSTRAP_LOCK:
|
|
broker_source, fleet_source = original_paths()
|
|
for path in (fleet_source, broker_source):
|
|
if str(path) in sys.path:
|
|
sys.path.remove(str(path))
|
|
sys.path.insert(0, str(path))
|
|
import framebridge_linux_chatgpt
|
|
package_file = Path(framebridge_linux_chatgpt.__file__).resolve()
|
|
_require(package_file.is_relative_to(broker_source.resolve()), "ORIGINAL_PACKAGE_SHADOWED")
|
|
for root in (broker_source, fleet_source):
|
|
path = str(root / "framebridge_linux_chatgpt")
|
|
if path not in framebridge_linux_chatgpt.__path__:
|
|
framebridge_linux_chatgpt.__path__.append(path)
|
|
|
|
|
|
def state_directory() -> Path:
|
|
raw = os.environ.get("FRAMEBRIDGE_EXPANDED_STATE") or os.environ.get("FRAMEBRIDGE_STATE_DIR")
|
|
_require(bool(raw), "EXPANDED_STATE_REQUIRED")
|
|
return Path(raw).expanduser()
|
|
|
|
|
|
def local_config() -> dict[str, Any]:
|
|
from .config import load_config
|
|
config = load_config(state_directory())
|
|
_require(type(config) is dict, "PROFILE_CONFIG_INVALID")
|
|
_require(config.get("role") in ("standalone", "central", "node"), "PROFILE_ROLE_INVALID")
|
|
_require(type(config.get("name")) is str and JOB_RE.fullmatch(config["name"]), "PROFILE_NAME_INVALID")
|
|
try:
|
|
uuid.UUID(str(config["node_id"]))
|
|
except (KeyError, ValueError, TypeError):
|
|
raise FleetError("PROFILE_NODE_ID_INVALID") from None
|
|
return config
|
|
|
|
|
|
def _local_descriptor(config: dict[str, Any]) -> dict[str, Any]:
|
|
bootstrap_original()
|
|
from framebridge_linux_chatgpt.seed_capabilities import execute_capability
|
|
_require(callable(execute_capability), "LOCAL_READ_PROVIDER_UNAVAILABLE")
|
|
return {
|
|
"alias": config["name"], "node_id": str(config["node_id"]),
|
|
"hostname": socket.gethostname(), "transport": "local",
|
|
"target": "LOCAL", "capability_profile": "original-readonly",
|
|
"source": "local", "operational_transport_ready": True,
|
|
}
|
|
|
|
|
|
def _remote_descriptors(config: dict[str, Any]) -> list[dict[str, Any]]:
|
|
if config["role"] != "central":
|
|
return []
|
|
from . import broker
|
|
items = broker.list_confirmed_nodes()
|
|
_require(type(items) is list, "BROKER_NODE_LIST_INVALID")
|
|
result = []
|
|
for item in items:
|
|
_require(type(item) is dict, "BROKER_NODE_INVALID")
|
|
alias = item.get("alias")
|
|
node_id = item.get("node_id")
|
|
_require(type(alias) is str and JOB_RE.fullmatch(alias), "BROKER_NODE_ALIAS_INVALID")
|
|
try:
|
|
uuid.UUID(str(node_id))
|
|
except (ValueError, TypeError):
|
|
raise FleetError("BROKER_NODE_ID_INVALID") from None
|
|
result.append({
|
|
"alias": alias, "node_id": str(node_id),
|
|
"hostname": str(item.get("hostname") or alias),
|
|
"transport": "broker_outbound", "target": alias,
|
|
"capability_profile": "original-readonly",
|
|
"source": "confirmed_pairing",
|
|
"operational_transport_ready": item.get("ready") is True,
|
|
})
|
|
return result
|
|
|
|
|
|
def descriptors() -> list[dict[str, Any]]:
|
|
config = local_config()
|
|
result = [_local_descriptor(config), *_remote_descriptors(config)]
|
|
namespace: set[str] = set()
|
|
for item in result:
|
|
for value in (item["alias"], item["node_id"]):
|
|
_require(value not in namespace and (value != "LOCAL" or item["transport"] == "local"),
|
|
"FLEET_IDENTITY_COLLISION")
|
|
namespace.add(value)
|
|
return result
|
|
|
|
|
|
def discover_nodes() -> dict[str, Any]:
|
|
try:
|
|
nodes = descriptors()
|
|
return {"ok": True, "nodes": sorted(nodes, key=lambda item: item["alias"]),
|
|
"sources": {"local": 1, "confirmed_pairing": len(nodes) - 1}}
|
|
except Exception as exc:
|
|
return {"ok": False, "error": str(exc) if isinstance(exc, FleetError) else "FLEET_DISCOVERY_FAILED"}
|
|
|
|
|
|
def resolve_node(alias: str) -> dict[str, Any]:
|
|
_require(type(alias) is str and JOB_RE.fullmatch(alias), "NODE_ALIAS_INVALID")
|
|
nodes = descriptors()
|
|
if alias == "LOCAL":
|
|
return next(item for item in nodes if item["transport"] == "local")
|
|
matches = [item for item in nodes if alias in (item["alias"], item["node_id"])]
|
|
_require(len(matches) == 1, "NODE_NOT_FOUND")
|
|
return matches[0]
|
|
|
|
|
|
def _target_identity(requested: str, node: dict[str, Any]) -> dict[str, Any]:
|
|
"""Describe the confirmed route, never identity claimed by a node response."""
|
|
return {
|
|
"requested_target": requested,
|
|
"resolved_target": node["alias"],
|
|
"node": node["alias"],
|
|
"target": "LOCAL" if node["transport"] == "local" else node["alias"],
|
|
"node_id": node["node_id"],
|
|
"hostname": node.get("hostname"),
|
|
"transport": node["transport"],
|
|
}
|
|
|
|
|
|
def _normalize_read(capability: str, args: dict[str, Any]) -> dict[str, Any]:
|
|
bootstrap_original()
|
|
from framebridge_linux_chatgpt.fleet_routing import ReadCapability, normalize_read_args
|
|
_require(capability in READ_CAPABILITIES, "FLEET_READ_CAPABILITY_DENIED")
|
|
try:
|
|
return normalize_read_args(ReadCapability(capability), args)
|
|
except (TypeError, ValueError):
|
|
raise FleetError("FLEET_READ_ARGUMENTS_INVALID") from None
|
|
|
|
|
|
def read_local(capability: str, args: dict[str, Any]) -> dict[str, Any]:
|
|
normalized = _normalize_read(capability, args)
|
|
from framebridge_linux_chatgpt.seed_capabilities import execute_capability
|
|
data = execute_capability(capability, normalized)
|
|
_require(type(data) is dict, "LOCAL_READ_RESPONSE_INVALID")
|
|
if data.get("ok") is False:
|
|
return data
|
|
return {"ok": True, "capability": capability, "result": data}
|
|
|
|
|
|
def node_read(alias: str, capability: str, args: dict[str, Any]) -> dict[str, Any]:
|
|
node = None
|
|
try:
|
|
normalized = _normalize_read(capability, args)
|
|
node = resolve_node(alias)
|
|
if node["transport"] == "local":
|
|
result = read_local(capability, normalized)
|
|
else:
|
|
_require(node["operational_transport_ready"], "NODE_OFFLINE")
|
|
from . import broker
|
|
result = broker.request(node["node_id"], capability, normalized, timeout_s=30)
|
|
_require(type(result) is dict, "NODE_READ_RESPONSE_INVALID")
|
|
response = dict(result)
|
|
response.update(_target_identity(alias, node))
|
|
return response
|
|
except Exception as exc:
|
|
response = {"ok": False, "error": str(exc) if isinstance(exc, FleetError) else "NODE_READ_FAILED"}
|
|
if node is not None:
|
|
response.update(_target_identity(alias, node))
|
|
return response
|
|
|
|
|
|
def _exec_payload(script: str, authorized: bool, timeout_s: int, job_id: str,
|
|
sha256: str, include_stdout: bool) -> dict[str, Any]:
|
|
_require(authorized is True, "AUTHORIZED_TRUE_REQUIRED")
|
|
_require(type(script) is str and bool(script), "SCRIPT_REQUIRED")
|
|
data = script.encode("utf-8")
|
|
_require(len(data) <= 262144, "SCRIPT_TOO_LARGE")
|
|
digest = hashlib.sha256(data).hexdigest()
|
|
_require(not sha256 or (type(sha256) is str and sha256.lower() == digest), "SCRIPT_SHA256_MISMATCH")
|
|
try:
|
|
timeout = int(timeout_s)
|
|
except (TypeError, ValueError):
|
|
raise FleetError("INVALID_TIMEOUT") from None
|
|
_require(1 <= timeout <= 600, "INVALID_TIMEOUT")
|
|
job = job_id or "node-mcp-" + time.strftime("%Y%m%d-%H%M%S") + "-" + uuid.uuid4().hex[:12]
|
|
_require(type(job) is str and JOB_RE.fullmatch(job), "INVALID_JOB_ID")
|
|
return {"version": 1, "mode": "EXEC", "authorized": True, "script": script,
|
|
"timeout_s": timeout, "job_id": job, "sha256": digest,
|
|
"include_stdout": include_stdout is True}
|
|
|
|
|
|
def execute_node(alias: str, script: str, authorized: bool, timeout_s: int = 120,
|
|
job_id: str = "", sha256: str = "", include_stdout: bool = False) -> dict[str, Any]:
|
|
node = None
|
|
try:
|
|
payload = _exec_payload(script, authorized, timeout_s, job_id, sha256, include_stdout)
|
|
node = resolve_node(alias)
|
|
if node["transport"] == "local":
|
|
from .runtime import adapter_request
|
|
result = adapter_request(payload)
|
|
else:
|
|
_require(node["operational_transport_ready"], "NODE_OFFLINE")
|
|
from . import broker
|
|
result = broker.request(node["node_id"], "admin.exec", payload, timeout_s=payload["timeout_s"])
|
|
_require(type(result) is dict, "NODE_EXEC_RESPONSE_INVALID")
|
|
response = dict(result)
|
|
if response.get("ok") is True:
|
|
_require(response.get("mcp_job_id", response.get("job_id")) == payload["job_id"],
|
|
"NODE_EXEC_JOB_BINDING_INVALID")
|
|
_require(response.get("mcp_script_sha256", response.get("command_sha256")) == payload["sha256"],
|
|
"NODE_EXEC_HASH_BINDING_INVALID")
|
|
response.update(_target_identity(alias, node))
|
|
response.update(mcp_job_id=payload["job_id"],
|
|
mcp_script_sha256=payload["sha256"])
|
|
if include_stdout is not True:
|
|
response.pop("stdout", None)
|
|
return response
|
|
except Exception as exc:
|
|
response = {"ok": False, "error": str(exc) if isinstance(exc, FleetError) else "NODE_EXEC_FAILED"}
|
|
if node is not None:
|
|
response.update(_target_identity(alias, node))
|
|
return response
|
|
|
|
|
|
def execute_screen_node(alias: str, script: str, authorized: bool, timeout_s: int = 30,
|
|
job_id: str = "", sha256: str = "", include_stdout: bool = False) -> dict[str, Any]:
|
|
"""Use the same confirmed-node authority with an explicit non-shell screen operation."""
|
|
from .real_screen.screen_transport import REQUEST_TYPE, validate_payload, ScreenTransportError
|
|
node = None
|
|
try:
|
|
_require(type(timeout_s) is int and 1 <= timeout_s <= 30, "INVALID_SCREEN_TIMEOUT")
|
|
payload = _exec_payload(script, authorized, timeout_s, job_id, sha256, include_stdout)
|
|
payload["request_type"] = REQUEST_TYPE
|
|
validate_payload(payload)
|
|
node = resolve_node(alias)
|
|
if node["transport"] == "local":
|
|
from .runtime import adapter_request
|
|
result = adapter_request(payload)
|
|
else:
|
|
_require(node["operational_transport_ready"], "NODE_OFFLINE")
|
|
from . import broker
|
|
result = broker.request(node["node_id"], "admin.exec", payload, timeout_s=payload["timeout_s"])
|
|
_require(type(result) is dict, "NODE_EXEC_RESPONSE_INVALID")
|
|
response = dict(result)
|
|
if response.get("ok") is True:
|
|
_require(response.get("mcp_job_id", response.get("job_id")) == payload["job_id"],
|
|
"NODE_EXEC_JOB_BINDING_INVALID")
|
|
_require(response.get("mcp_script_sha256", response.get("command_sha256")) == payload["sha256"],
|
|
"NODE_EXEC_HASH_BINDING_INVALID")
|
|
if response.get("adapter_error") == "ValueError:UNSUPPORTED_REQUEST_TYPE":
|
|
# An old adapter rejects this type before EXEC. Never retry it as shell code.
|
|
from .real_screen.screen_rpc import encode
|
|
response.update(ok=False, exit_code="1", stdout_truncated=False,
|
|
stdout=encode({"ok": False, "error": {"code": "SCREEN_RPC_UNSUPPORTED_ON_NODE"}}).decode("ascii") + "\n")
|
|
response.update(_target_identity(alias, node))
|
|
response.update(mcp_job_id=payload["job_id"],
|
|
mcp_script_sha256=payload["sha256"])
|
|
return response
|
|
except Exception as exc:
|
|
response = {"ok": False, "error": str(exc) if isinstance(exc, (FleetError, ScreenTransportError)) else "SCREEN_RPC_FAILED"}
|
|
if node is not None:
|
|
response.update(_target_identity(alias, node))
|
|
return response
|
|
|
|
|
|
def cua_node(alias: str, payload: dict[str, Any], authorized: bool,
|
|
timeout_s: int = 60, job_id: str = "") -> dict[str, Any]:
|
|
node = None
|
|
try:
|
|
_require(authorized is True, "AUTHORIZED_TRUE_REQUIRED")
|
|
_require(type(payload) is dict, "CUA_PAYLOAD_REQUIRED")
|
|
_require(payload.get("action") in {"OBSERVE", "CLICK", "DOUBLE_CLICK", "TYPE", "KEY", "SCROLL", "DRAG"},
|
|
"CUA_ACTION_UNSUPPORTED")
|
|
try:
|
|
timeout = int(timeout_s)
|
|
except (TypeError, ValueError):
|
|
raise FleetError("INVALID_CUA_TIMEOUT") from None
|
|
_require(1 <= timeout <= 180, "INVALID_CUA_TIMEOUT")
|
|
job = job_id or "cua-node-" + uuid.uuid4().hex
|
|
_require(type(job) is str and JOB_RE.fullmatch(job), "INVALID_JOB_ID")
|
|
request = {"version": 1, "mode": "EXEC", "request_type": "cua",
|
|
"authorized": True, "timeout_s": timeout, "job_id": job, "cua": dict(payload)}
|
|
node = resolve_node(alias)
|
|
if node["transport"] == "local":
|
|
from .runtime import adapter_request
|
|
result = adapter_request(request)
|
|
else:
|
|
_require(node["operational_transport_ready"], "NODE_OFFLINE")
|
|
from . import broker
|
|
result = broker.request(node["node_id"], "admin.cua", request, timeout_s=timeout)
|
|
_require(type(result) is dict, "NODE_CUA_RESPONSE_INVALID")
|
|
response = dict(result)
|
|
if response.get("ok") is True:
|
|
_require(response.get("job_id") == job, "NODE_CUA_JOB_BINDING_INVALID")
|
|
response.update(_target_identity(alias, node))
|
|
return response
|
|
except Exception as exc:
|
|
response = {"ok": False, "error": str(exc) if isinstance(exc, FleetError) else "NODE_CUA_FAILED"}
|
|
if node is not None:
|
|
response.update(_target_identity(alias, node))
|
|
return response
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Original six-tool Fleet READ Hub")
|
|
parser.add_argument("command", choices=("serve",), nargs="?", default="serve")
|
|
parser.parse_args()
|
|
bootstrap_original()
|
|
from mcp.server.fastmcp import FastMCP
|
|
from framebridge_linux_chatgpt.fleet_hub import FleetHub
|
|
from framebridge_linux_chatgpt.fleet_mcp_surface import register_fleet_read_tools
|
|
host = os.environ.get("FRAMEBRIDGE_FLEET_HUB_HOST", "127.0.0.1")
|
|
_require(host in ("127.0.0.1", "::1"), "FLEET_HUB_HOST_INVALID")
|
|
port = int(os.environ.get("FRAMEBRIDGE_FLEET_HUB_PORT", "20877"))
|
|
_require(1024 <= port <= 65535, "FLEET_HUB_PORT_INVALID")
|
|
server = FastMCP("FrameBridge Original Expanded Fleet", host=host, port=port,
|
|
streamable_http_path="/mcp")
|
|
hub = FleetHub(discover_nodes, node_read)
|
|
register_fleet_read_tools(server, hub)
|
|
from .http_server import run_streamable_mcp
|
|
run_streamable_mcp(server)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|