feat(webui): add guided setup flows
* 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
This commit is contained in:
+252
-46
@@ -5,6 +5,7 @@ import os
|
||||
import select
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
from collections.abc import Callable, Iterable
|
||||
from contextlib import nullcontext, suppress
|
||||
from pathlib import Path
|
||||
@@ -75,6 +76,7 @@ from nanobot.cli.gateway import create_gateway_app # noqa: E402
|
||||
from nanobot.cli.stream import StreamRenderer, ThinkingSpinner # noqa: E402
|
||||
from nanobot.config.paths import get_workspace_path, is_default_workspace # noqa: E402
|
||||
from nanobot.config.schema import Config # noqa: E402
|
||||
from nanobot.security.network import is_loopback_host # noqa: E402
|
||||
from nanobot.utils.evaluator import evaluate_response # noqa: E402
|
||||
from nanobot.utils.helpers import sync_workspace_templates # noqa: E402
|
||||
from nanobot.utils.restart import ( # noqa: E402
|
||||
@@ -82,6 +84,11 @@ from nanobot.utils.restart import ( # noqa: E402
|
||||
format_restart_completed_message,
|
||||
should_show_cli_restart_notice,
|
||||
)
|
||||
from nanobot.webui.build import ( # noqa: E402
|
||||
BuildMode,
|
||||
WebUIBuildError,
|
||||
ensure_webui_bundle,
|
||||
)
|
||||
from nanobot.webui.sidebar_state import read_webui_sidebar_state # noqa: E402
|
||||
|
||||
|
||||
@@ -724,25 +731,12 @@ def onboard(
|
||||
)
|
||||
|
||||
|
||||
def _merge_missing_defaults(existing: Any, defaults: Any) -> Any:
|
||||
"""Recursively fill in missing values from defaults without overwriting user config."""
|
||||
if not isinstance(existing, dict) or not isinstance(defaults, dict):
|
||||
return existing
|
||||
|
||||
merged = dict(existing)
|
||||
for key, value in defaults.items():
|
||||
if key not in merged:
|
||||
merged[key] = value
|
||||
else:
|
||||
merged[key] = _merge_missing_defaults(merged[key], value)
|
||||
return merged
|
||||
|
||||
|
||||
def _onboard_plugins(config_path: Path) -> None:
|
||||
"""Inject default config for all discovered channels (built-in + plugins)."""
|
||||
import json
|
||||
|
||||
from nanobot.channels.registry import discover_all
|
||||
from nanobot.config.loader import merge_missing_defaults
|
||||
|
||||
all_channels = discover_all()
|
||||
if not all_channels:
|
||||
@@ -756,7 +750,7 @@ def _onboard_plugins(config_path: Path) -> None:
|
||||
if name not in channels:
|
||||
channels[name] = cls.default_config()
|
||||
else:
|
||||
channels[name] = _merge_missing_defaults(channels[name], cls.default_config())
|
||||
channels[name] = merge_missing_defaults(channels[name], cls.default_config())
|
||||
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
@@ -883,11 +877,7 @@ def _confirm_webui_action(message: str, *, yes: bool) -> None:
|
||||
"""Confirm a WebUI first-run mutation or fail clearly in non-interactive shells."""
|
||||
if yes:
|
||||
return
|
||||
try:
|
||||
interactive = sys.stdin.isatty()
|
||||
except Exception:
|
||||
interactive = False
|
||||
if not interactive:
|
||||
if not _cli_can_prompt():
|
||||
console.print(
|
||||
"[red]Error: WebUI setup needs confirmation. Re-run with --yes or use "
|
||||
"`nanobot onboard --wizard`.[/red]"
|
||||
@@ -898,6 +888,19 @@ def _confirm_webui_action(message: str, *, yes: bool) -> None:
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
def _cli_can_prompt() -> bool:
|
||||
try:
|
||||
return sys.stdin.isatty()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _webui_build_mode_for_interactive(*, yes: bool = False) -> BuildMode:
|
||||
if yes:
|
||||
return "auto"
|
||||
return "prompt" if _cli_can_prompt() else "warn"
|
||||
|
||||
|
||||
def _resolve_webui_config_path(config: str | None) -> Path:
|
||||
"""Resolve the config path used by ``nanobot webui`` and bind loader state."""
|
||||
from nanobot.config.loader import get_config_path, set_config_path
|
||||
@@ -942,6 +945,43 @@ def _webui_config_dict(config: Config) -> dict[str, Any]:
|
||||
return model.model_dump(by_alias=True, exclude_none=True)
|
||||
|
||||
|
||||
def _webui_channel_enabled(config: Config) -> bool:
|
||||
from nanobot.channels.websocket import WebSocketConfig
|
||||
|
||||
current = getattr(config.channels, "websocket", None) or {}
|
||||
return bool(WebSocketConfig.model_validate(current).enabled)
|
||||
|
||||
|
||||
def _prepare_webui_bundle_for_gateway(
|
||||
config: Config,
|
||||
*,
|
||||
mode: BuildMode,
|
||||
webui_static_dist: bool = True,
|
||||
) -> None:
|
||||
"""Refresh or warn about stale bundled WebUI assets before gateway startup."""
|
||||
if not webui_static_dist or not _webui_channel_enabled(config):
|
||||
return
|
||||
|
||||
def _print(message: str) -> None:
|
||||
console.print(f"[yellow]{escape(message)}[/yellow]")
|
||||
|
||||
def _confirm(message: str) -> bool:
|
||||
return typer.confirm(message, default=True)
|
||||
|
||||
try:
|
||||
ensure_webui_bundle(
|
||||
mode=mode,
|
||||
confirm=_confirm if mode == "prompt" else None,
|
||||
output=_print,
|
||||
)
|
||||
except WebUIBuildError as exc:
|
||||
if mode == "warn":
|
||||
console.print(f"[yellow]Warning: {escape(str(exc))}[/yellow]")
|
||||
return
|
||||
console.print(f"[red]Error: {escape(str(exc))}[/red]")
|
||||
raise typer.Exit(1) from exc
|
||||
|
||||
|
||||
def _host_for_local_browser(host: str) -> str:
|
||||
"""Map bind hosts to a browser-openable local host."""
|
||||
if host in {"0.0.0.0", ""}:
|
||||
@@ -1041,7 +1081,6 @@ def _warn_webui_bind_scope(config: Config) -> None:
|
||||
|
||||
def _wait_for_webui(url: str, *, timeout_s: float = 5.0) -> None:
|
||||
"""Best-effort wait for the WebUI listener before opening a browser."""
|
||||
import socket
|
||||
import time
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@@ -1050,11 +1089,75 @@ def _wait_for_webui(url: str, *, timeout_s: float = 5.0) -> None:
|
||||
port = parsed.port or (443 if parsed.scheme == "https" else 80)
|
||||
deadline = time.monotonic() + timeout_s
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
with socket.create_connection((host, port), timeout=0.2):
|
||||
return
|
||||
except OSError:
|
||||
time.sleep(0.1)
|
||||
if _tcp_endpoint_reachable(host, port, timeout_s=0.2):
|
||||
return
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
def _tcp_endpoint_reachable(host: str, port: int, *, timeout_s: float = 0.25) -> bool:
|
||||
"""Return whether a local TCP endpoint accepts connections."""
|
||||
import socket
|
||||
|
||||
try:
|
||||
with socket.create_connection((host, port), timeout=timeout_s):
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def _gateway_health_ready(host: str, port: int, *, timeout_s: float = 0.4) -> bool:
|
||||
"""Return whether the nanobot gateway health endpoint responds OK."""
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
browser_host = _host_for_local_browser(host)
|
||||
try:
|
||||
with urllib.request.urlopen(
|
||||
f"http://{browser_host}:{port}/health",
|
||||
timeout=timeout_s,
|
||||
) as response:
|
||||
if response.status != 200:
|
||||
return False
|
||||
body = response.read(1024)
|
||||
except (OSError, urllib.error.URLError, TimeoutError, ValueError):
|
||||
return False
|
||||
|
||||
try:
|
||||
payload = json.loads(body.decode("utf-8"))
|
||||
except (UnicodeDecodeError, json.JSONDecodeError):
|
||||
return False
|
||||
return payload.get("status") == "ok"
|
||||
|
||||
|
||||
def _webui_endpoint_reachable(url: str, *, timeout_s: float = 0.25) -> bool:
|
||||
"""Return whether the WebUI URL's TCP endpoint is already listening."""
|
||||
from urllib.parse import urlparse
|
||||
|
||||
parsed = urlparse(url)
|
||||
host = parsed.hostname or "127.0.0.1"
|
||||
port = parsed.port or (443 if parsed.scheme == "https" else 80)
|
||||
return _tcp_endpoint_reachable(host, port, timeout_s=timeout_s)
|
||||
|
||||
|
||||
def _print_foreground_port_conflict(
|
||||
*,
|
||||
webui_url: str,
|
||||
gateway_host: str,
|
||||
gateway_port: int,
|
||||
) -> None:
|
||||
console.print(
|
||||
"[red]Error: nanobot cannot start because one of its local ports is already in use.[/red]"
|
||||
)
|
||||
console.print(f" WebUI: [cyan]{webui_url}[/cyan]")
|
||||
console.print(
|
||||
f" Gateway health: [cyan]http://{_host_for_local_browser(gateway_host)}:{gateway_port}/health[/cyan]"
|
||||
)
|
||||
console.print()
|
||||
console.print("If this is an existing nanobot instance, use it or stop it first:")
|
||||
console.print(" [cyan]nanobot gateway status[/cyan]")
|
||||
console.print(" [cyan]nanobot gateway stop[/cyan]")
|
||||
console.print("Or choose different ports with [cyan]--port[/cyan] and [cyan]--gateway-port[/cyan].")
|
||||
|
||||
|
||||
def _open_webui_browser(url: str, *, wait: bool = True) -> None:
|
||||
@@ -1063,11 +1166,41 @@ def _open_webui_browser(url: str, *, wait: bool = True) -> None:
|
||||
|
||||
if wait:
|
||||
_wait_for_webui(url)
|
||||
display_url = _webui_display_url(url)
|
||||
try:
|
||||
webbrowser.open(url)
|
||||
console.print(f"[green]✓[/green] Opened WebUI: [cyan]{url}[/cyan]")
|
||||
console.print(f"[green]✓[/green] Opened WebUI: [cyan]{display_url}[/cyan]")
|
||||
except Exception as exc:
|
||||
console.print(f"[yellow]Could not open browser ({exc}); visit {url}[/yellow]")
|
||||
console.print(f"[yellow]Could not open browser ({exc}); visit {display_url}[/yellow]")
|
||||
|
||||
|
||||
def _print_webui_foreground_lifecycle(*, attached: bool) -> None:
|
||||
"""Explain how the browser and gateway lifecycles differ."""
|
||||
console.print()
|
||||
if attached:
|
||||
console.print("[green]nanobot is attached to the existing gateway.[/green]")
|
||||
else:
|
||||
console.print("[green]nanobot is running in this terminal.[/green]")
|
||||
console.print("[dim]Closing the browser does not stop channels or automations.[/dim]")
|
||||
console.print("[dim]Press Ctrl+C here to stop nanobot.[/dim]")
|
||||
|
||||
|
||||
def _attach_to_background_gateway(runtime: Any) -> None:
|
||||
"""Keep a foreground WebUI command attached to a managed gateway."""
|
||||
_print_webui_foreground_lifecycle(attached=True)
|
||||
try:
|
||||
while runtime.status().running:
|
||||
time.sleep(0.5)
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n[yellow]Stopping nanobot...[/yellow]")
|
||||
result = runtime.stop()
|
||||
if result.ok or result.message == "gateway_not_running":
|
||||
console.print("[green]Gateway stopped.[/green]")
|
||||
return
|
||||
console.print(f"[red]Gateway could not be stopped: {result.message}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
console.print("[yellow]Gateway stopped.[/yellow]")
|
||||
|
||||
|
||||
def _gateway_instance_command(
|
||||
@@ -1191,9 +1324,9 @@ def serve(
|
||||
port = port if port is not None else api_cfg.port
|
||||
timeout = timeout if timeout is not None else api_cfg.timeout
|
||||
api_key = api_cfg.api_key.strip() if api_cfg.api_key else ""
|
||||
if host in {"0.0.0.0", "::"} and not api_key:
|
||||
if not is_loopback_host(host) and not api_key:
|
||||
console.print(
|
||||
"[red]Error: host is 0.0.0.0 (all interfaces) but api_key is not set. "
|
||||
f"[red]Error: host {host} is available beyond this device but api_key is not set. "
|
||||
"Set api.api_key in config to prevent unauthenticated access.[/red]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
@@ -1217,9 +1350,9 @@ def serve(
|
||||
console.print(f" [cyan]Model[/cyan] : {model_name}{preset_tag}")
|
||||
console.print(" [cyan]Session[/cyan] : api:default")
|
||||
console.print(f" [cyan]Timeout[/cyan] : {timeout}s")
|
||||
if host in {"0.0.0.0", "::"}:
|
||||
if not is_loopback_host(host):
|
||||
console.print(
|
||||
"[yellow]API is bound to all interfaces "
|
||||
"[yellow]API is available beyond this device "
|
||||
"(authentication required).[/yellow]"
|
||||
)
|
||||
console.print()
|
||||
@@ -1256,7 +1389,11 @@ def webui(
|
||||
),
|
||||
workspace: str | None = typer.Option(None, "--workspace", "-w", help="Workspace directory"),
|
||||
config: str | None = typer.Option(None, "--config", "-c", help="Path to config file"),
|
||||
background: bool = typer.Option(False, "--background", help="Start gateway in the background"),
|
||||
background: bool = typer.Option(
|
||||
False,
|
||||
"--background",
|
||||
help="Keep the gateway running after this command exits",
|
||||
),
|
||||
no_open: bool = typer.Option(False, "--no-open", help="Do not open a browser"),
|
||||
yes: bool = typer.Option(
|
||||
False,
|
||||
@@ -1323,21 +1460,25 @@ def webui(
|
||||
f"{config_path}, or rerun without --no-open to open the authenticated URL.[/dim]"
|
||||
)
|
||||
|
||||
if background:
|
||||
config_arg = str(config_path)
|
||||
workspace_arg = str(Path(workspace).expanduser().resolve(strict=False)) if workspace else None
|
||||
runtime = GatewayRuntime(
|
||||
paths=GatewayRuntimePaths.for_instance(
|
||||
data_dir=config_path.parent,
|
||||
workspace=workspace_arg,
|
||||
config_path=config_arg,
|
||||
)
|
||||
)
|
||||
start_options = GatewayStartOptions(
|
||||
port=effective_gateway_port,
|
||||
webui_bundle_mode = _webui_build_mode_for_interactive(yes=yes)
|
||||
|
||||
config_arg = str(config_path)
|
||||
workspace_arg = str(Path(workspace).expanduser().resolve(strict=False)) if workspace else None
|
||||
runtime = GatewayRuntime(
|
||||
paths=GatewayRuntimePaths.for_instance(
|
||||
data_dir=config_path.parent,
|
||||
workspace=workspace_arg,
|
||||
config_path=config_arg,
|
||||
)
|
||||
)
|
||||
start_options = GatewayStartOptions(
|
||||
port=effective_gateway_port,
|
||||
workspace=workspace_arg,
|
||||
config_path=config_arg,
|
||||
)
|
||||
|
||||
if background:
|
||||
_prepare_webui_bundle_for_gateway(runtime_config, mode=webui_bundle_mode)
|
||||
result = runtime.start_background(start_options)
|
||||
restarted = False
|
||||
restart_attempted = False
|
||||
@@ -1365,14 +1506,53 @@ def webui(
|
||||
"View logs: "
|
||||
f"[cyan]{_gateway_instance_command('logs', config_path=config_path, workspace=workspace)}[/cyan]"
|
||||
)
|
||||
console.print("[dim]Closing the browser does not stop channels or automations.[/dim]")
|
||||
console.print(
|
||||
"Stop nanobot: "
|
||||
f"[cyan]{_gateway_instance_command('stop', config_path=config_path, workspace=workspace)}[/cyan]"
|
||||
)
|
||||
if not no_open:
|
||||
_open_webui_browser(webui_url)
|
||||
return
|
||||
|
||||
gateway_ready = _gateway_health_ready(runtime_config.gateway.host, effective_gateway_port)
|
||||
webui_ready = _webui_endpoint_reachable(webui_url)
|
||||
if gateway_ready and webui_ready:
|
||||
console.print("[yellow]Gateway is already running; attaching to the existing WebUI.[/yellow]")
|
||||
console.print(
|
||||
"Restart the gateway if you need it to pick up local source changes: "
|
||||
f"[cyan]{_gateway_instance_command('restart', config_path=config_path, workspace=workspace)}[/cyan]"
|
||||
)
|
||||
if not no_open:
|
||||
_open_webui_browser(webui_url, wait=False)
|
||||
if runtime.status().running:
|
||||
_attach_to_background_gateway(runtime)
|
||||
else:
|
||||
console.print(
|
||||
"[yellow]This gateway is controlled by another foreground command. "
|
||||
"Stop it from that terminal.[/yellow]"
|
||||
)
|
||||
return
|
||||
|
||||
gateway_port_taken = gateway_ready or _tcp_endpoint_reachable(
|
||||
_host_for_local_browser(runtime_config.gateway.host),
|
||||
effective_gateway_port,
|
||||
)
|
||||
webui_port_taken = webui_ready
|
||||
if gateway_port_taken or webui_port_taken:
|
||||
_print_foreground_port_conflict(
|
||||
webui_url=webui_url,
|
||||
gateway_host=runtime_config.gateway.host,
|
||||
gateway_port=effective_gateway_port,
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
_print_webui_foreground_lifecycle(attached=False)
|
||||
_run_gateway(
|
||||
runtime_config,
|
||||
port=effective_gateway_port,
|
||||
open_browser_url=None if no_open else webui_url,
|
||||
webui_bundle_mode=webui_bundle_mode,
|
||||
)
|
||||
|
||||
|
||||
@@ -1387,6 +1567,7 @@ def _run_gateway(
|
||||
port: int | None = None,
|
||||
open_browser_url: str | None = None,
|
||||
webui_static_dist: bool = True,
|
||||
webui_bundle_mode: BuildMode = "warn",
|
||||
webui_runtime_surface: str = "browser",
|
||||
webui_runtime_capabilities: dict[str, Any] | None = None,
|
||||
health_server_enabled: bool = True,
|
||||
@@ -1409,8 +1590,29 @@ def _run_gateway(
|
||||
from nanobot.webui.token_usage import TokenUsageHook
|
||||
|
||||
port = port if port is not None else config.gateway.port
|
||||
webui_url = _webui_browser_url(config)
|
||||
gateway_host_for_browser = _host_for_local_browser(config.gateway.host)
|
||||
if health_server_enabled and _tcp_endpoint_reachable(gateway_host_for_browser, port):
|
||||
_print_foreground_port_conflict(
|
||||
webui_url=webui_url,
|
||||
gateway_host=config.gateway.host,
|
||||
gateway_port=port,
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
if _webui_channel_enabled(config) and _webui_endpoint_reachable(webui_url):
|
||||
_print_foreground_port_conflict(
|
||||
webui_url=webui_url,
|
||||
gateway_host=config.gateway.host,
|
||||
gateway_port=port,
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
console.print(f"{__logo__} Starting nanobot gateway version {__version__} on port {port}...")
|
||||
_prepare_webui_bundle_for_gateway(
|
||||
config,
|
||||
mode=webui_bundle_mode,
|
||||
webui_static_dist=webui_static_dist,
|
||||
)
|
||||
sync_workspace_templates(config.workspace_path)
|
||||
bus = MessageBus()
|
||||
runtime_events = RuntimeEventBus()
|
||||
@@ -1895,6 +2097,10 @@ app.add_typer(
|
||||
log_handler_id=_log_handler_id,
|
||||
load_runtime_config=_load_runtime_config,
|
||||
run_gateway=_run_gateway,
|
||||
prepare_webui_bundle=lambda config, mode: _prepare_webui_bundle_for_gateway(
|
||||
config,
|
||||
mode=mode,
|
||||
),
|
||||
),
|
||||
name="gateway",
|
||||
)
|
||||
@@ -2274,7 +2480,7 @@ def plugins_list(
|
||||
|
||||
@plugins_app.command("enable")
|
||||
def plugins_enable(
|
||||
name: str = typer.Argument(..., help="Feature name (e.g. weixin, matrix, pdf)"),
|
||||
name: str = typer.Argument(..., help="Feature name (e.g. weixin, matrix, bedrock)"),
|
||||
config_path: str | None = typer.Option(None, "--config", "-c", help="Path to config file"),
|
||||
logs: bool = typer.Option(False, "--logs/--no-logs", help="Show optional package install logs"),
|
||||
):
|
||||
|
||||
+19
-2
@@ -25,11 +25,13 @@ from nanobot.gateway.service import (
|
||||
GatewayServiceResult,
|
||||
ServiceManagerKind,
|
||||
)
|
||||
from nanobot.webui.build import BuildMode
|
||||
|
||||
RuntimeConfigLoader = Callable[[str | None, str | None], Config]
|
||||
GatewayRunner = Callable[..., None]
|
||||
GatewayRuntimeFactory = Callable[..., Any]
|
||||
GatewayServiceFactory = Callable[[], Any]
|
||||
WebUIBundlePreparer = Callable[[Config, BuildMode], None]
|
||||
|
||||
|
||||
def create_gateway_app(
|
||||
@@ -40,6 +42,7 @@ def create_gateway_app(
|
||||
run_gateway: GatewayRunner,
|
||||
runtime_factory: GatewayRuntimeFactory | None = None,
|
||||
service_factory: GatewayServiceFactory | None = None,
|
||||
prepare_webui_bundle: WebUIBundlePreparer | None = None,
|
||||
) -> typer.Typer:
|
||||
gateway_app = typer.Typer(
|
||||
help="Start and manage the nanobot gateway.",
|
||||
@@ -81,14 +84,20 @@ def create_gateway_app(
|
||||
def service_installer():
|
||||
return service_factory() if service_factory is not None else GatewayServiceInstaller()
|
||||
|
||||
def interactive_build_mode() -> BuildMode:
|
||||
# `nanobot gateway` is often launched by tests, supervisors, or service managers.
|
||||
# The higher-level `nanobot webui` command owns interactive first-run guidance.
|
||||
return "warn"
|
||||
|
||||
def start_options(
|
||||
*,
|
||||
port: int | None,
|
||||
verbose: bool,
|
||||
workspace: str | None,
|
||||
config: str | None,
|
||||
loaded_config: Config | None = None,
|
||||
) -> GatewayStartOptions:
|
||||
cfg = load_runtime_config(config, workspace)
|
||||
cfg = loaded_config or load_runtime_config(config, workspace)
|
||||
resolved_config = str(Path(config).expanduser().resolve()) if config else None
|
||||
resolved_workspace = str(Path(workspace).expanduser().resolve(strict=False)) if workspace else None
|
||||
return GatewayStartOptions(
|
||||
@@ -139,6 +148,9 @@ def create_gateway_app(
|
||||
console.print("[red]Error: --foreground and --background cannot be used together.[/red]")
|
||||
raise typer.Exit(1)
|
||||
if background:
|
||||
cfg = load_runtime_config(config, workspace)
|
||||
if prepare_webui_bundle is not None:
|
||||
prepare_webui_bundle(cfg, interactive_build_mode())
|
||||
runtime = runtime_for_instance(workspace=workspace, config=config)
|
||||
result = runtime.start_background(
|
||||
start_options(
|
||||
@@ -146,6 +158,7 @@ def create_gateway_app(
|
||||
verbose=verbose,
|
||||
workspace=workspace,
|
||||
config=config,
|
||||
loaded_config=cfg,
|
||||
)
|
||||
)
|
||||
if result.ok:
|
||||
@@ -158,7 +171,7 @@ def create_gateway_app(
|
||||
|
||||
configure_logging(verbose)
|
||||
cfg = load_runtime_config(config, workspace)
|
||||
run_gateway(cfg, port=port)
|
||||
run_gateway(cfg, port=port, webui_bundle_mode=interactive_build_mode())
|
||||
|
||||
@gateway_app.command("status")
|
||||
def gateway_status(
|
||||
@@ -211,6 +224,9 @@ def create_gateway_app(
|
||||
timeout: int = typer.Option(20, "--timeout", help="Restart timeout in seconds"),
|
||||
) -> None:
|
||||
"""Restart the background gateway."""
|
||||
cfg = load_runtime_config(config, workspace)
|
||||
if prepare_webui_bundle is not None:
|
||||
prepare_webui_bundle(cfg, interactive_build_mode())
|
||||
runtime = runtime_for_instance(workspace=workspace, config=config)
|
||||
result = runtime.restart(
|
||||
start_options(
|
||||
@@ -218,6 +234,7 @@ def create_gateway_app(
|
||||
verbose=verbose,
|
||||
workspace=workspace,
|
||||
config=config,
|
||||
loaded_config=cfg,
|
||||
),
|
||||
timeout_s=timeout,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user