* feat(channels): add guided setup flows * test(channels): preserve setup config values * fix(channels): reflect saved setup state * refactor(channels): simplify setup state metadata * fix(channels): harden setup lifecycle * refactor(channels): centralize setup contracts * fix(channels): route setup actions through webui shim * fix(channels): adapt settings for compact screens * fix(models): preserve default preset display * feat(models): add curated Codex catalog * fix(webui): stop attached gateway on interrupt * fix(webui): simplify apps catalog * docs(webui): clarify apps and runtime features * feat(settings): add guided capability setup * fix(webui): harden setup and managed services * test: keep managed runtime checks portable * test: scope POSIX runtime coverage * fix(webui): simplify file settings * feat(files): bundle document reading * fix(webui): harden setup request boundaries * fix(webui): prevent channel setup status squeeze * fix(settings): group provider compatibility aliases * refactor(settings): remove redundant setup surfaces * fix(webui): harden guided setup lifecycle * fix(webui): preserve channel setup compatibility
105 lines
3.1 KiB
Python
105 lines
3.1 KiB
Python
"""Gateway-specific configuration for the shared background process runtime."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import subprocess
|
|
import time
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from nanobot.config.paths import get_data_dir
|
|
from nanobot.process_runtime import (
|
|
ManagedProcessRuntime,
|
|
ProcessResult,
|
|
ProcessRuntimePaths,
|
|
ProcessStartOptions,
|
|
ProcessStatus,
|
|
)
|
|
|
|
GatewayStartOptions = ProcessStartOptions
|
|
GatewayStatus = ProcessStatus
|
|
RuntimeResult = ProcessResult
|
|
|
|
|
|
def build_gateway_command(python_executable: str, options: GatewayStartOptions) -> list[str]:
|
|
"""Build a foreground gateway command for process supervisors."""
|
|
command = [
|
|
python_executable,
|
|
"-m",
|
|
"nanobot",
|
|
"gateway",
|
|
"--foreground",
|
|
"--port",
|
|
str(options.port),
|
|
]
|
|
if options.verbose:
|
|
command.append("--verbose")
|
|
if options.workspace:
|
|
command.extend(["--workspace", options.workspace])
|
|
if options.config_path:
|
|
command.extend(["--config", options.config_path])
|
|
return command
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class GatewayRuntimePaths(ProcessRuntimePaths):
|
|
"""Filesystem layout for one gateway runtime instance."""
|
|
|
|
@classmethod
|
|
def for_instance(
|
|
cls,
|
|
*,
|
|
data_dir: Path | None = None,
|
|
workspace: str | None = None,
|
|
config_path: str | None = None,
|
|
) -> "GatewayRuntimePaths":
|
|
base = data_dir or get_data_dir()
|
|
suffix = _instance_suffix(workspace=workspace, config_path=config_path)
|
|
run_dir = base / "run"
|
|
logs_dir = base / "logs"
|
|
stem = "gateway" if suffix is None else f"gateway.{suffix}"
|
|
return cls(
|
|
run_dir=run_dir,
|
|
logs_dir=logs_dir,
|
|
state_path=run_dir / f"{stem}.json",
|
|
log_path=logs_dir / f"{stem}.log",
|
|
)
|
|
|
|
|
|
class GatewayRuntime(ManagedProcessRuntime):
|
|
"""Manage a background ``nanobot gateway`` process."""
|
|
|
|
service_name = "gateway"
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
paths: GatewayRuntimePaths | None = None,
|
|
platform_name: str | None = None,
|
|
python_executable: str | None = None,
|
|
popen: Callable[..., Any] = subprocess.Popen,
|
|
subprocess_run: Callable[..., Any] = subprocess.run,
|
|
sleep: Callable[[float], None] = time.sleep,
|
|
) -> None:
|
|
super().__init__(
|
|
paths=paths or GatewayRuntimePaths.for_instance(),
|
|
platform_name=platform_name,
|
|
python_executable=python_executable,
|
|
popen=popen,
|
|
subprocess_run=subprocess_run,
|
|
sleep=sleep,
|
|
)
|
|
|
|
def _build_child_command(self, options: ProcessStartOptions) -> list[str]:
|
|
return build_gateway_command(self.python_executable, options)
|
|
|
|
|
|
def _instance_suffix(*, workspace: str | None, config_path: str | None) -> str | None:
|
|
raw = "|".join(value for value in (workspace, config_path) if value)
|
|
if not raw:
|
|
return None
|
|
return hashlib.sha1(raw.encode("utf-8")).hexdigest()[:16]
|