Merge branch 'main' into pr-1635
This commit is contained in:
13
README.md
13
README.md
@@ -724,7 +724,10 @@ nanobot provider login openai-codex
|
|||||||
nanobot agent -m "Hello!"
|
nanobot agent -m "Hello!"
|
||||||
|
|
||||||
# Target a specific workspace/config locally
|
# Target a specific workspace/config locally
|
||||||
nanobot agent -w ~/.nanobot/botA -c ~/.nanobot/botA/config.json -m "Hello!"
|
nanobot agent -c ~/.nanobot-telegram/config.json -m "Hello!"
|
||||||
|
|
||||||
|
# One-off workspace override on top of that config
|
||||||
|
nanobot agent -c ~/.nanobot-telegram/config.json -w /tmp/nanobot-telegram-test -m "Hello!"
|
||||||
```
|
```
|
||||||
|
|
||||||
> Docker users: use `docker run -it` for interactive OAuth login.
|
> Docker users: use `docker run -it` for interactive OAuth login.
|
||||||
@@ -930,11 +933,15 @@ When using `--config`, nanobot derives its runtime data directory from the confi
|
|||||||
To open a CLI session against one of these instances locally:
|
To open a CLI session against one of these instances locally:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nanobot agent -w ~/.nanobot/botA -m "Hello from botA"
|
nanobot agent -c ~/.nanobot-telegram/config.json -m "Hello from Telegram instance"
|
||||||
nanobot agent -w ~/.nanobot/botC -c ~/.nanobot/botC/config.json
|
nanobot agent -c ~/.nanobot-discord/config.json -m "Hello from Discord instance"
|
||||||
|
|
||||||
|
# Optional one-off workspace override
|
||||||
|
nanobot agent -c ~/.nanobot-telegram/config.json -w /tmp/nanobot-telegram-test
|
||||||
```
|
```
|
||||||
|
|
||||||
> `nanobot agent` starts a local CLI agent using the selected workspace/config. It does not attach to or proxy through an already running `nanobot gateway` process.
|
> `nanobot agent` starts a local CLI agent using the selected workspace/config. It does not attach to or proxy through an already running `nanobot gateway` process.
|
||||||
|
|
||||||
| Component | Resolved From | Example |
|
| Component | Resolved From | Example |
|
||||||
|-----------|---------------|---------|
|
|-----------|---------------|---------|
|
||||||
| **Config** | `--config` path | `~/.nanobot-A/config.json` |
|
| **Config** | `--config` path | `~/.nanobot-A/config.json` |
|
||||||
|
|||||||
@@ -266,9 +266,17 @@ def _make_provider(config: Config):
|
|||||||
|
|
||||||
def _load_runtime_config(config: str | None = None, workspace: str | None = None) -> Config:
|
def _load_runtime_config(config: str | None = None, workspace: str | None = None) -> Config:
|
||||||
"""Load config and optionally override the active workspace."""
|
"""Load config and optionally override the active workspace."""
|
||||||
from nanobot.config.loader import load_config
|
from nanobot.config.loader import load_config, set_config_path
|
||||||
|
|
||||||
|
config_path = None
|
||||||
|
if config:
|
||||||
|
config_path = Path(config).expanduser().resolve()
|
||||||
|
if not config_path.exists():
|
||||||
|
console.print(f"[red]Error: Config file not found: {config_path}[/red]")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
set_config_path(config_path)
|
||||||
|
console.print(f"[dim]Using config: {config_path}[/dim]")
|
||||||
|
|
||||||
config_path = Path(config) if config else None
|
|
||||||
loaded = load_config(config_path)
|
loaded = load_config(config_path)
|
||||||
if workspace:
|
if workspace:
|
||||||
loaded.agents.defaults.workspace = workspace
|
loaded.agents.defaults.workspace = workspace
|
||||||
@@ -288,16 +296,6 @@ def gateway(
|
|||||||
config: str | None = typer.Option(None, "--config", "-c", help="Path to config file"),
|
config: str | None = typer.Option(None, "--config", "-c", help="Path to config file"),
|
||||||
):
|
):
|
||||||
"""Start the nanobot gateway."""
|
"""Start the nanobot gateway."""
|
||||||
# Set config path if provided (must be done before any imports that use get_data_dir)
|
|
||||||
if config:
|
|
||||||
from nanobot.config.loader import set_config_path
|
|
||||||
config_path = Path(config).expanduser().resolve()
|
|
||||||
if not config_path.exists():
|
|
||||||
console.print(f"[red]Error: Config file not found: {config_path}[/red]")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
set_config_path(config_path)
|
|
||||||
console.print(f"[dim]Using config: {config_path}[/dim]")
|
|
||||||
|
|
||||||
from nanobot.agent.loop import AgentLoop
|
from nanobot.agent.loop import AgentLoop
|
||||||
from nanobot.bus.queue import MessageBus
|
from nanobot.bus.queue import MessageBus
|
||||||
from nanobot.channels.manager import ChannelManager
|
from nanobot.channels.manager import ChannelManager
|
||||||
|
|||||||
@@ -191,13 +191,52 @@ def test_agent_uses_default_config_when_no_workspace_or_config_flags(mock_agent_
|
|||||||
mock_agent_runtime["print_response"].assert_called_once_with("mock-response", render_markdown=True)
|
mock_agent_runtime["print_response"].assert_called_once_with("mock-response", render_markdown=True)
|
||||||
|
|
||||||
|
|
||||||
def test_agent_uses_explicit_config_path(mock_agent_runtime):
|
def test_agent_uses_explicit_config_path(mock_agent_runtime, tmp_path: Path):
|
||||||
config_path = Path("/tmp/agent-config.json")
|
config_path = tmp_path / "agent-config.json"
|
||||||
|
config_path.write_text("{}")
|
||||||
|
|
||||||
result = runner.invoke(app, ["agent", "-m", "hello", "-c", str(config_path)])
|
result = runner.invoke(app, ["agent", "-m", "hello", "-c", str(config_path)])
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert mock_agent_runtime["load_config"].call_args.args == (config_path,)
|
assert mock_agent_runtime["load_config"].call_args.args == (config_path.resolve(),)
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_config_sets_active_path(monkeypatch, tmp_path: Path) -> None:
|
||||||
|
config_file = tmp_path / "instance" / "config.json"
|
||||||
|
config_file.parent.mkdir(parents=True)
|
||||||
|
config_file.write_text("{}")
|
||||||
|
|
||||||
|
config = Config()
|
||||||
|
seen: dict[str, Path] = {}
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"nanobot.config.loader.set_config_path",
|
||||||
|
lambda path: seen.__setitem__("config_path", path),
|
||||||
|
)
|
||||||
|
monkeypatch.setattr("nanobot.config.loader.load_config", lambda _path=None: config)
|
||||||
|
monkeypatch.setattr("nanobot.config.paths.get_cron_dir", lambda: config_file.parent / "cron")
|
||||||
|
monkeypatch.setattr("nanobot.cli.commands.sync_workspace_templates", lambda _path: None)
|
||||||
|
monkeypatch.setattr("nanobot.cli.commands._make_provider", lambda _config: object())
|
||||||
|
monkeypatch.setattr("nanobot.bus.queue.MessageBus", lambda: object())
|
||||||
|
monkeypatch.setattr("nanobot.cron.service.CronService", lambda _store: object())
|
||||||
|
|
||||||
|
class _FakeAgentLoop:
|
||||||
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def process_direct(self, *_args, **_kwargs) -> str:
|
||||||
|
return "ok"
|
||||||
|
|
||||||
|
async def close_mcp(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
monkeypatch.setattr("nanobot.agent.loop.AgentLoop", _FakeAgentLoop)
|
||||||
|
monkeypatch.setattr("nanobot.cli.commands._print_agent_response", lambda *_args, **_kwargs: None)
|
||||||
|
|
||||||
|
result = runner.invoke(app, ["agent", "-m", "hello", "-c", str(config_file)])
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert seen["config_path"] == config_file.resolve()
|
||||||
|
|
||||||
|
|
||||||
def test_agent_overrides_workspace_path(mock_agent_runtime):
|
def test_agent_overrides_workspace_path(mock_agent_runtime):
|
||||||
@@ -211,8 +250,9 @@ def test_agent_overrides_workspace_path(mock_agent_runtime):
|
|||||||
assert mock_agent_runtime["agent_loop_cls"].call_args.kwargs["workspace"] == workspace_path
|
assert mock_agent_runtime["agent_loop_cls"].call_args.kwargs["workspace"] == workspace_path
|
||||||
|
|
||||||
|
|
||||||
def test_agent_workspace_override_wins_over_config_workspace(mock_agent_runtime):
|
def test_agent_workspace_override_wins_over_config_workspace(mock_agent_runtime, tmp_path: Path):
|
||||||
config_path = Path("/tmp/agent-config.json")
|
config_path = tmp_path / "agent-config.json"
|
||||||
|
config_path.write_text("{}")
|
||||||
workspace_path = Path("/tmp/agent-workspace")
|
workspace_path = Path("/tmp/agent-workspace")
|
||||||
|
|
||||||
result = runner.invoke(
|
result = runner.invoke(
|
||||||
@@ -221,7 +261,7 @@ def test_agent_workspace_override_wins_over_config_workspace(mock_agent_runtime)
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert mock_agent_runtime["load_config"].call_args.args == (config_path,)
|
assert mock_agent_runtime["load_config"].call_args.args == (config_path.resolve(),)
|
||||||
assert mock_agent_runtime["config"].agents.defaults.workspace == str(workspace_path)
|
assert mock_agent_runtime["config"].agents.defaults.workspace == str(workspace_path)
|
||||||
assert mock_agent_runtime["sync_templates"].call_args.args == (workspace_path,)
|
assert mock_agent_runtime["sync_templates"].call_args.args == (workspace_path,)
|
||||||
assert mock_agent_runtime["agent_loop_cls"].call_args.kwargs["workspace"] == workspace_path
|
assert mock_agent_runtime["agent_loop_cls"].call_args.kwargs["workspace"] == workspace_path
|
||||||
|
|||||||
Reference in New Issue
Block a user