fix(cli): make gateway persistence explicit
This commit is contained in:
+26
-3
@@ -167,14 +167,31 @@ def create_gateway_app(
|
||||
loaded_config=cfg,
|
||||
)
|
||||
)
|
||||
promoted = False
|
||||
if result.ok or result.message == "gateway_already_running":
|
||||
GatewayClientLease(runtime, kind="gateway-background").mark_persistent()
|
||||
promoted = GatewayClientLease(
|
||||
runtime,
|
||||
kind="gateway-background",
|
||||
).mark_persistent()
|
||||
if result.ok:
|
||||
console.print("[green]Gateway started in the background.[/green]")
|
||||
print_status(result.status)
|
||||
return
|
||||
if result.message == "gateway_already_running":
|
||||
console.print("[yellow]Gateway is already running in the background.[/yellow]")
|
||||
if promoted:
|
||||
console.print(
|
||||
"[green]Existing on-demand gateway promoted to persistent "
|
||||
"background mode.[/green]"
|
||||
)
|
||||
console.print(
|
||||
"[dim]It will keep running after all local clients exit; "
|
||||
"use `nanobot gateway stop` to stop it.[/dim]"
|
||||
)
|
||||
else:
|
||||
console.print(
|
||||
"[yellow]Gateway is already running in persistent "
|
||||
"background mode.[/yellow]"
|
||||
)
|
||||
print_status(result.status)
|
||||
return
|
||||
console.print(f"[yellow]Gateway was not started: {result.message}[/yellow]")
|
||||
@@ -267,10 +284,16 @@ def create_gateway_app(
|
||||
timeout_s=timeout,
|
||||
)
|
||||
if result.ok:
|
||||
GatewayClientLease(runtime, kind="gateway-restart").mark_persistent()
|
||||
console.print("[green]Gateway restarted in the background.[/green]")
|
||||
print_status(result.status)
|
||||
return
|
||||
if result.message == "gateway_not_running":
|
||||
console.print("[yellow]Gateway is not running; there is nothing to restart.[/yellow]")
|
||||
console.print(
|
||||
"[dim]Start a persistent gateway with `nanobot gateway --background`.[/dim]"
|
||||
)
|
||||
print_status(result.status)
|
||||
raise typer.Exit(1)
|
||||
console.print(f"[red]Gateway restart failed: {result.message}[/red]")
|
||||
print_status(result.status)
|
||||
raise typer.Exit(1)
|
||||
|
||||
+18
-18
@@ -82,7 +82,7 @@ def webui(
|
||||
background: bool = typer.Option(
|
||||
False,
|
||||
"--background",
|
||||
help="Keep the gateway running after this command exits",
|
||||
help="Deprecated; use `nanobot gateway --background`",
|
||||
),
|
||||
dev: bool = typer.Option(
|
||||
False,
|
||||
@@ -107,10 +107,24 @@ def webui(
|
||||
)
|
||||
|
||||
cli_terminal._ensure_interactive_tty_mode()
|
||||
if dev and background:
|
||||
console.print("[red]Error: --dev cannot be combined with --background.[/red]")
|
||||
raise typer.Exit(1)
|
||||
config_path = _resolve_webui_config_path(config)
|
||||
if background:
|
||||
import shlex
|
||||
|
||||
command = ["nanobot", "gateway", "--background", "--config", str(config_path)]
|
||||
if workspace:
|
||||
command.extend(
|
||||
["--workspace", str(Path(workspace).expanduser().resolve(strict=False))]
|
||||
)
|
||||
console.print(
|
||||
"[red]`nanobot webui --background` no longer owns gateway lifecycle.[/red]"
|
||||
)
|
||||
console.print("Start the persistent gateway explicitly, then open the WebUI:")
|
||||
console.print(" [cyan]" + " ".join(shlex.quote(part) for part in command) + "[/cyan]")
|
||||
console.print(
|
||||
" [cyan]nanobot webui --config " + shlex.quote(str(config_path)) + "[/cyan]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
created_config = not config_path.exists()
|
||||
if created_config:
|
||||
console.print(f"[yellow]No config found at {config_path}.[/yellow]")
|
||||
@@ -134,12 +148,6 @@ def webui(
|
||||
if settings_setup_error:
|
||||
console.print(f"[yellow]Model setup is incomplete: {provider_error}[/yellow]")
|
||||
console.print("Configure a provider and model in WebUI Settings → Models.")
|
||||
if background:
|
||||
console.print(
|
||||
"[red]First-time WebUI setup must run in the foreground. "
|
||||
"Run `nanobot webui` without --background.[/red]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
elif provider_error:
|
||||
console.print(f"[dim]Provider check: {provider_error}[/dim]")
|
||||
setup_config = _run_quick_start_for_webui(
|
||||
@@ -270,14 +278,6 @@ def webui(
|
||||
f"[cyan]{_gateway_instance_command('stop', config_path=config_path, workspace=workspace)}[/cyan]"
|
||||
)
|
||||
|
||||
if background:
|
||||
ensure_shared_gateway()
|
||||
GatewayClientLease(runtime, kind="webui-background").mark_persistent()
|
||||
print_shared_gateway_controls()
|
||||
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:
|
||||
|
||||
@@ -102,6 +102,17 @@ class GatewayRuntime(ManagedProcessRuntime[ProcessStartOptions]):
|
||||
def _build_child_command(self, options: ProcessStartOptions) -> list[str]:
|
||||
return build_gateway_command(self.python_executable, options)
|
||||
|
||||
def restart(self, options: ProcessStartOptions, *, timeout_s: int = 20) -> ProcessResult:
|
||||
"""Restart an existing gateway without creating a new persistent instance."""
|
||||
with self._lifecycle_lock():
|
||||
status = self.status()
|
||||
if not status.running:
|
||||
return ProcessResult(False, "gateway_not_running", status)
|
||||
stop_result = self._stop(timeout_s=timeout_s)
|
||||
if not stop_result.ok:
|
||||
return stop_result
|
||||
return self._start_background(options)
|
||||
|
||||
|
||||
class GatewayClientLease:
|
||||
"""Reference-count an on-demand gateway shared by local interactive clients."""
|
||||
@@ -144,12 +155,14 @@ class GatewayClientLease:
|
||||
state["auto_stop"] = True
|
||||
self._write_state(state)
|
||||
|
||||
def mark_persistent(self) -> None:
|
||||
"""Keep an explicitly backgrounded gateway alive without client leases."""
|
||||
def mark_persistent(self) -> bool:
|
||||
"""Keep an explicitly backgrounded gateway alive; return whether it was promoted."""
|
||||
with self.lock:
|
||||
state = self._live_state()
|
||||
promoted = bool(state.get("auto_stop"))
|
||||
state["auto_stop"] = False
|
||||
self._write_or_clear(state)
|
||||
return promoted
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Forget leases after an explicit gateway stop."""
|
||||
|
||||
Reference in New Issue
Block a user