fix(cli): give a profile created without a clone a usable model block
Creating a bot from the desktop dialog builds the profile tree but no config.yaml, so the profile resolves no provider and its first turn dies with "No LLM provider configured" — created, but unable to run. Every bot made that way was dead on arrival. Seed the active profile's model block at creation. It is a copy, not a link: profiles stay independent islands and editing either afterwards never touches the other. "Fresh" means fresh skills and SOUL, not unreachable.
This commit is contained in:
@@ -758,6 +758,41 @@ def _read_config_model(profile_dir: Path) -> tuple:
|
||||
return None, None
|
||||
|
||||
|
||||
def _seed_model_config(profile_dir: Path) -> None:
|
||||
"""Give a profile created without a clone source a usable model block.
|
||||
|
||||
Such a profile gets its directory tree but no ``config.yaml`` at all, so it
|
||||
resolves no provider and its first turn dies with "No LLM provider
|
||||
configured" — created, but unable to run. Copy the active profile's
|
||||
``model`` block over at creation time.
|
||||
|
||||
This is a copy, not a link: profiles remain independent islands, and
|
||||
editing either one afterwards never touches the other. "Fresh" means fresh
|
||||
skills and SOUL, not unreachable.
|
||||
"""
|
||||
config_path = profile_dir / "config.yaml"
|
||||
if config_path.exists():
|
||||
return
|
||||
try:
|
||||
import yaml
|
||||
from hermes_constants import get_hermes_home
|
||||
from hermes_cli.config import read_user_config_raw
|
||||
|
||||
source = get_hermes_home() / "config.yaml"
|
||||
if not source.is_file():
|
||||
return
|
||||
model_cfg = read_user_config_raw(source).get("model")
|
||||
if not model_cfg:
|
||||
return
|
||||
config_path.write_text(
|
||||
yaml.safe_dump({"model": model_cfg}, sort_keys=False),
|
||||
encoding="utf-8",
|
||||
)
|
||||
except Exception:
|
||||
# Creation must not fail over this; `hermes model` still sets it later.
|
||||
pass
|
||||
|
||||
|
||||
def _check_gateway_running(profile_dir: Path) -> bool:
|
||||
"""Check if a gateway is running for a given profile directory.
|
||||
|
||||
@@ -1209,6 +1244,9 @@ def create_profile(
|
||||
for subdir in _PROFILE_DIRS:
|
||||
(profile_dir / subdir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if source_dir is None:
|
||||
_seed_model_config(profile_dir)
|
||||
|
||||
# Clone config files from source
|
||||
if source_dir is not None:
|
||||
for filename in _CLONE_CONFIG_FILES:
|
||||
|
||||
@@ -133,6 +133,46 @@ class TestCreateProfile:
|
||||
assert mode == 0o600
|
||||
|
||||
|
||||
def test_fresh_profile_inherits_a_usable_model(self, profile_env):
|
||||
"""A profile created without a clone source still resolves a provider.
|
||||
|
||||
Without this it gets no config.yaml at all, so its very first turn dies
|
||||
with "No LLM provider configured" — created, but unable to run. Fresh
|
||||
means fresh skills and SOUL, not unreachable.
|
||||
"""
|
||||
default_home = profile_env / ".hermes"
|
||||
(default_home / "config.yaml").write_text(
|
||||
"model:\n provider: nous\n default: some/model\n"
|
||||
)
|
||||
|
||||
profile_dir = create_profile("coder", no_alias=True)
|
||||
|
||||
cfg = yaml.safe_load((profile_dir / "config.yaml").read_text())
|
||||
assert cfg["model"]["provider"] == "nous"
|
||||
assert cfg["model"]["default"] == "some/model"
|
||||
|
||||
|
||||
def test_fresh_profile_model_is_copied_not_linked(self, profile_env):
|
||||
"""Profiles stay independent islands.
|
||||
|
||||
The model block is copied at creation, so later edits to the source
|
||||
profile never reach one already created from it.
|
||||
"""
|
||||
default_home = profile_env / ".hermes"
|
||||
(default_home / "config.yaml").write_text(
|
||||
"model:\n provider: nous\n default: some/model\n"
|
||||
)
|
||||
profile_dir = create_profile("coder", no_alias=True)
|
||||
|
||||
(default_home / "config.yaml").write_text(
|
||||
"model:\n provider: other\n default: changed/model\n"
|
||||
)
|
||||
|
||||
cfg = yaml.safe_load((profile_dir / "config.yaml").read_text())
|
||||
assert cfg["model"]["provider"] == "nous"
|
||||
assert cfg["model"]["default"] == "some/model"
|
||||
|
||||
|
||||
|
||||
|
||||
def test_clone_config_copies_files(self, profile_env):
|
||||
|
||||
Reference in New Issue
Block a user