FrameBridge 0.3.1 community source release

This commit is contained in:
FrameBridge Development committed 2026-10-26 22:09:18 -04:00
commit b23819fff1
1354 files changed
+343716

No files matched your search

@@ -0,0 +1,202 @@
"""Explicit lifecycle scopes shared by human and agent command interfaces."""
from __future__ import annotations
import hashlib
import os
import pwd
import time
from . import autostart, connection_background, runtime
from .config import load_config, state_path
def _owned_setup(state):
state, directory, unit = autostart._paths(state)
record = autostart._manifest(directory)
if record and (record.get("unit") != unit.name or hashlib.sha256(autostart._read(unit)).hexdigest() != record.get("unit_sha256")):
raise RuntimeError("The managed service changed outside FrameBridge; no lifecycle change was made.")
return state, directory, unit, record
def _runtime_stopped(result):
if type(result) is not dict or result.get("ok") is False or result.get("status_unavailable") or result.get("running") is not False:
return False
processes = result.get("processes", [])
return type(processes) is list and all(type(item) is dict and item.get("alive") is False for item in processes)
def _settled_runtime_stop(state, result):
"""Observe bounded asynchronous cleanup without treating query-ok as stopped."""
if _runtime_stopped(result):
return result
deadline = time.monotonic() + 2
while time.monotonic() < deadline:
time.sleep(0.05)
result = runtime.status(state)
if _runtime_stopped(result):
break
return result
def status(state):
"""One observation model: actual tools/transport, intended transport and boot."""
state = state_path(state)
configured = (state / "config.json").is_file()
config = load_config(state) if configured else {}
local = autostart._safe_runtime_status(runtime, state) if configured else {"running": False, "ready": False, "configured": False}
connection = connection_background.status(state)
directory = state / "autostart"
if directory.exists() or directory.is_symlink():
startup = autostart.status(state)
else:
startup = {"configured": False, "enabled": False, "service_active": False, "service_state": "not_configured", "linger": None, "encrypted_credential_present": False}
boot = {key: startup.get(key) for key in ("configured", "enabled", "service_active", "service_state", "linger", "encrypted_credential_present", "unit")}
boot["desired_enabled"] = startup.get("boot_desired_enabled")
if config.get("role") == "node":
desired = "not_applicable"
elif not boot["configured"]:
desired = "not_configured"
else:
desired = "connected" if autostart.connection_requested(state) else "disconnected"
return {"ok": True, "configured": configured, "role": config.get("role"), "name": config.get("name"),
"runtime": local, "connection": connection, "boot": boot,
"desired_connection": desired,
"disconnect_scope": "until_explicit_connect",
"next_boot_connection": "paused" if desired == "disconnected" else ("requested" if desired == "connected" else "not_applicable"),
"worker": startup.get("worker", {"state": "not_configured"})}
def service_start(state):
"""Start the configured stack now without changing its boot enablement."""
def action(value):
state, directory, unit, record = _owned_setup(value)
if not record and load_config(state)["role"] == "node":
autostart._enable(state, start_now=True, confirm=False)
return {"ok": True, "state": "start_requested", "scope": "managed_stack",
"initial_configuration": True, "boot_policy_changed": True,
"boot_default_enabled": True, "status": status(state)}
if not record:
result = runtime.start(state)
return {"ok": bool(result.get("running") and result.get("ready")), "state": "started" if result.get("running") and result.get("ready") else "not_ready",
"scope": "local_tools", "boot_policy_changed": False, "runtime": result}
autostart._clear_manual_stop(state)
autostart._systemctl("start", unit.name)
return {"ok": True, "state": "start_requested", "scope": "managed_stack", "boot_policy_changed": False, "status": status(state)}
return autostart._configuration_action(state, action)
def service_stop(state):
"""Stop current tools and transport; retain desired transport and boot policy."""
def action(value):
state, directory, unit, record = _owned_setup(value)
if record:
autostart.mark_manual_stop(state)
autostart._systemctl("stop", unit.name)
connection = connection_background.disconnect(state)
local = _settled_runtime_stop(state, runtime.stop(state))
success = connection.get("ok") is not False and not connection.get("alive") and _runtime_stopped(local)
return {"ok": success, "state": "stopped" if success else "stop_incomplete",
"scope": "managed_stack" if record else "local_tools", "boot_policy_changed": False, "saved_material_preserved": True,
"connection_intent_preserved": True, "connection": connection, "runtime": local}
return autostart._configuration_action(state, action)
def _boot(state, enabled):
def action(value):
state, directory, unit, record = _owned_setup(value)
initialized = False
if not record:
if enabled and load_config(state)["role"] == "node":
autostart._enable(state, start_now=False, confirm=False)
state, directory, unit, record = _owned_setup(state)
initialized = True
else:
raise RuntimeError("Managed startup is not configured. Use fb connect, or fb service start for a node profile.")
if enabled and not autostart._linger(pwd.getpwuid(os.getuid()).pw_name):
raise RuntimeError("OWNER_AUTHENTICATION_REQUIRED: run fb connect in a normal terminal, or fb autostart enable for a node, to enable startup after logout.")
policy = directory / "boot-policy.json"
before = autostart._optional_read(policy)
autostart._set_boot_requested(state, enabled)
installed = autostart._read(policy)
try:
autostart._systemctl("enable" if enabled else "disable", unit.name)
except BaseException:
if autostart._optional_read(policy) == installed:
if before is None:
policy.unlink()
else:
autostart._write(policy, before, replace=True)
raise
return {"ok": True, "state": "boot_enabled" if enabled else "boot_disabled",
"boot_policy_changed": True, "current_execution_unchanged": True, "initial_configuration": initialized,
"saved_material_preserved": True, "status": status(state)}
return autostart._configuration_action(state, action)
def boot_enable(state):
return _boot(state, True)
def boot_disable(state):
return _boot(state, False)
def disconnect(state):
"""Persist a pause until explicit connect, including worker recovery/reboot."""
def action(value):
def pause(inner):
autostart._set_connection_requested(inner, False)
result = connection_background.disconnect(inner)
return {"ok": result.get("ok") is not False and not result.get("alive"),
"state": "disconnected" if result.get("ok") is not False and not result.get("alive") else "disconnect_incomplete",
"desired_connection": "disconnected",
"scope": "until_explicit_connect", "local_tools_preserved": True,
"saved_material_preserved": True, "boot_policy_changed": False,
"connection": result}
return autostart._connection_action(value, pause)
return autostart._configuration_action(state, action)
def forget_credentials(state):
"""Remove the managed connection setup/material; retain profile and linger.
The command front end requires explicit confirmation before invoking this
destructive operation. Legacy autostart disable retains its old contract.
"""
def action(value):
state, directory, unit, record = _owned_setup(value)
if not record:
return {"ok": True, "state": "already_forgotten", "profile_preserved": True}
if record.get("node"):
raise RuntimeError("This node profile has no ChatGPT credential to forget.")
blob = directory / "credential.cred"
if hashlib.sha256(autostart._read(blob)).hexdigest() != record.get("credential_sha256"):
raise RuntimeError("The encrypted credential changed outside FrameBridge; nothing was forgotten.")
backup = directory / "config-before.json"
if record.get("headless_changed"):
if hashlib.sha256(autostart._read(backup)).hexdigest() != record.get("config_before_sha256"):
raise RuntimeError("The profile backup changed outside FrameBridge; nothing was forgotten.")
owned_metadata = [directory / "connection-intent.json", directory / "manual-stop.json", directory / "boot-policy.json"]
for path in owned_metadata:
if path.exists() or path.is_symlink():
autostart._read(path)
autostart.mark_manual_stop(state)
autostart._systemctl("disable", "--now", unit.name)
connection = connection_background.disconnect(state)
if connection.get("ok") is False or connection.get("alive"):
raise RuntimeError("The connection is still stopping; its saved material was retained.")
local = _settled_runtime_stop(state, runtime.stop(state))
if not _runtime_stopped(local):
raise RuntimeError("Local tools are still stopping; the saved connection material was retained.")
blob.unlink()
unit.unlink()
if record.get("headless_changed"):
backup.unlink()
for path in owned_metadata:
path.unlink(missing_ok=True)
(directory / "installation.json").unlink()
autostart._systemctl("daemon-reload")
return {"ok": True, "state": "forgotten", "encrypted_credential_removed": True,
"managed_configuration_removed": True, "profile_preserved": True,
"linger_preserved": True, "boot_enabled": False}
return autostart._configuration_action(state, action)