mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 08:13:11 +03:00
feat(cli): expose nanobot process identities
This commit is contained in:
@@ -6,6 +6,8 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
|
||||||
|
from nanobot.cli.process_identity import set_cli_process_identity
|
||||||
|
|
||||||
|
|
||||||
def _native_tui_candidate(args: list[str]) -> bool:
|
def _native_tui_candidate(args: list[str]) -> bool:
|
||||||
"""Return whether ``agent`` can start without the classic agent stack."""
|
"""Return whether ``agent`` can start without the classic agent stack."""
|
||||||
@@ -34,6 +36,7 @@ def _configure_windows_console() -> None:
|
|||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Dispatch native TUI startup without importing the complete CLI graph."""
|
"""Dispatch native TUI startup without importing the complete CLI graph."""
|
||||||
|
set_cli_process_identity(sys.argv[1:])
|
||||||
_configure_windows_console()
|
_configure_windows_console()
|
||||||
if _native_tui_candidate(sys.argv[1:]):
|
if _native_tui_candidate(sys.argv[1:]):
|
||||||
import typer
|
import typer
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"""Give nanobot processes recognizable operating-system names."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
from setproctitle import setproctitle
|
||||||
|
|
||||||
|
_ROLES: Final = {"agent", "gateway", "webui"}
|
||||||
|
|
||||||
|
|
||||||
|
def set_cli_process_identity(args: list[str]) -> None:
|
||||||
|
"""Name this CLI process after the nanobot role it is running."""
|
||||||
|
if os.name == "nt":
|
||||||
|
# Windows process managers use the console launcher's executable name,
|
||||||
|
# which packaging already generates as ``nanobot.exe``.
|
||||||
|
return
|
||||||
|
role = args[0] if args and args[0] in _ROLES else None
|
||||||
|
setproctitle(f"nanobot-{role}" if role else "nanobot")
|
||||||
|
|
||||||
|
|
||||||
|
def named_executable(executable: str, *, name: str, directory: Path) -> str:
|
||||||
|
"""Return a stable POSIX symlink whose basename identifies a child process."""
|
||||||
|
if os.name == "nt":
|
||||||
|
return executable
|
||||||
|
try:
|
||||||
|
target = Path(executable).resolve(strict=True)
|
||||||
|
digest = hashlib.sha256(os.fsencode(target)).hexdigest()[:12]
|
||||||
|
link_dir = directory / digest
|
||||||
|
link = link_dir / name
|
||||||
|
link_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||||
|
if link.is_symlink() and link.resolve(strict=False) == target:
|
||||||
|
return str(link)
|
||||||
|
if link.exists():
|
||||||
|
return executable
|
||||||
|
pending = link.with_name(f".{name}.{os.getpid()}")
|
||||||
|
pending.unlink(missing_ok=True)
|
||||||
|
pending.symlink_to(target)
|
||||||
|
os.replace(pending, link)
|
||||||
|
except OSError:
|
||||||
|
return executable
|
||||||
|
return str(link)
|
||||||
@@ -17,6 +17,7 @@ from pathlib import Path
|
|||||||
from typing import TYPE_CHECKING, Any, cast
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
from nanobot import __version__
|
from nanobot import __version__
|
||||||
|
from nanobot.cli.process_identity import named_executable
|
||||||
from nanobot.cli.runtime_config import _model_display
|
from nanobot.cli.runtime_config import _model_display
|
||||||
from nanobot.cli.webui_support import (
|
from nanobot.cli.webui_support import (
|
||||||
_gateway_health_ready,
|
_gateway_health_ready,
|
||||||
@@ -229,7 +230,12 @@ def _resolve_source_tui_command(source_dir: Path, bun: str) -> list[str]:
|
|||||||
detail = (install.stderr or install.stdout).strip().splitlines()
|
detail = (install.stderr or install.stdout).strip().splitlines()
|
||||||
suffix = f": {detail[-1]}" if detail else ""
|
suffix = f": {detail[-1]}" if detail else ""
|
||||||
raise TuiUnavailableError(f"could not install TUI dependencies{suffix}")
|
raise TuiUnavailableError(f"could not install TUI dependencies{suffix}")
|
||||||
return [bun, str(source_dir / "src" / "index.ts")]
|
executable = named_executable(
|
||||||
|
bun,
|
||||||
|
name="nanobot-tui",
|
||||||
|
directory=get_data_dir() / "run" / "executables",
|
||||||
|
)
|
||||||
|
return [executable, str(source_dir / "src" / "index.ts")]
|
||||||
|
|
||||||
|
|
||||||
def _download_release_tui(asset: str) -> Path | None:
|
def _download_release_tui(asset: str) -> Path | None:
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ dependencies = [
|
|||||||
"qrcode[pil]>=8.0",
|
"qrcode[pil]>=8.0",
|
||||||
"croniter>=6.0.0,<7.0.0",
|
"croniter>=6.0.0,<7.0.0",
|
||||||
"prompt-toolkit>=3.0.50,<4.0.0",
|
"prompt-toolkit>=3.0.50,<4.0.0",
|
||||||
|
"setproctitle>=1.3.7,<2.0.0",
|
||||||
"questionary>=2.0.0,<3.0.0",
|
"questionary>=2.0.0,<3.0.0",
|
||||||
"mcp>=1.26.0,<2.0.0",
|
"mcp>=1.26.0,<2.0.0",
|
||||||
"json-repair>=0.57.0,<1.0.0",
|
"json-repair>=0.57.0,<1.0.0",
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from nanobot.cli.process_identity import named_executable, set_cli_process_identity
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("args", "expected"),
|
||||||
|
[
|
||||||
|
(["agent"], "nanobot-agent"),
|
||||||
|
(["gateway", "--background"], "nanobot-gateway"),
|
||||||
|
(["webui"], "nanobot-webui"),
|
||||||
|
(["status"], "nanobot"),
|
||||||
|
([], "nanobot"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_cli_process_identity_uses_product_and_role(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
args: list[str],
|
||||||
|
expected: str,
|
||||||
|
) -> None:
|
||||||
|
titles: list[str] = []
|
||||||
|
monkeypatch.setattr("nanobot.cli.process_identity.os.name", "posix")
|
||||||
|
monkeypatch.setattr("nanobot.cli.process_identity.setproctitle", titles.append)
|
||||||
|
|
||||||
|
set_cli_process_identity(args)
|
||||||
|
|
||||||
|
assert titles == [expected]
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_process_identity_keeps_windows_launcher_name(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
titles: list[str] = []
|
||||||
|
monkeypatch.setattr("nanobot.cli.process_identity.os.name", "nt")
|
||||||
|
monkeypatch.setattr("nanobot.cli.process_identity.setproctitle", titles.append)
|
||||||
|
|
||||||
|
set_cli_process_identity(["agent"])
|
||||||
|
|
||||||
|
assert titles == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_named_executable_creates_stable_role_symlink(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
executable = tmp_path / "bun"
|
||||||
|
executable.write_text("runtime", encoding="utf-8")
|
||||||
|
monkeypatch.setattr("nanobot.cli.process_identity.os.name", "posix")
|
||||||
|
|
||||||
|
first = Path(
|
||||||
|
named_executable(executable.as_posix(), name="nanobot-tui", directory=tmp_path / "run")
|
||||||
|
)
|
||||||
|
second = Path(
|
||||||
|
named_executable(executable.as_posix(), name="nanobot-tui", directory=tmp_path / "run")
|
||||||
|
)
|
||||||
|
|
||||||
|
assert first == second
|
||||||
|
assert first.name == "nanobot-tui"
|
||||||
|
assert first.is_symlink()
|
||||||
|
assert first.resolve() == executable
|
||||||
|
|
||||||
|
|
||||||
|
def test_named_executable_uses_original_on_windows(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
monkeypatch.setattr("nanobot.cli.process_identity.os.name", "nt")
|
||||||
|
|
||||||
|
assert (
|
||||||
|
named_executable("bun.exe", name="nanobot-tui", directory=tmp_path / "run")
|
||||||
|
== "bun.exe"
|
||||||
|
)
|
||||||
@@ -467,9 +467,13 @@ def test_source_checkout_refreshes_locked_tui_dependencies(
|
|||||||
return subprocess.CompletedProcess(command, 0, "", "")
|
return subprocess.CompletedProcess(command, 0, "", "")
|
||||||
|
|
||||||
monkeypatch.setattr("nanobot.cli.tui_launcher.subprocess.run", install)
|
monkeypatch.setattr("nanobot.cli.tui_launcher.subprocess.run", install)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"nanobot.cli.tui_launcher.named_executable",
|
||||||
|
lambda executable, **_kwargs: f"{executable}-named",
|
||||||
|
)
|
||||||
|
|
||||||
assert _resolve_source_tui_command(source_dir, bun) == [
|
assert _resolve_source_tui_command(source_dir, bun) == [
|
||||||
bun,
|
f"{bun}-named",
|
||||||
str(source_dir / "src" / "index.ts"),
|
str(source_dir / "src" / "index.ts"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user