fix(cli): make gateway persistence explicit

This commit is contained in:
Xubin Ren
2026-08-17 20:56:10 +08:00
parent 74c11e5d28
commit b8333a2d7e
10 changed files with 147 additions and 189 deletions
+26 -3
View File
@@ -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
View File
@@ -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:
+15 -2
View File
@@ -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."""