* feat: setup profile is minted by the backend and found by role, not by name The guided onboarding runs in a profile the desktop used to create itself (profiles.create with a soul, "already exists" treated as success) and recognise by the literal "hermes-setup". The upcoming setup toolset grants catalog installs to that profile, so the marker that grants it must be written only by the backend. - profile.yaml carries `role: setup`; read_profile_meta / write_profile_meta / ProfileInfo know it; profiles.list and GET /api/profiles report it. - hermes_cli/setup_profile.py: ensure (find by role, adopt a pre-role hermes-setup dir, else clone default + soul + role) and reset (soul, memories, skills back to the created state, in place). The soul text moves here from the renderer. - tui_gateway/methods_onboarding.py: onboarding.ensure_setup_profile and onboarding.reset_setup_profile. Neither takes a name; profiles.create and profiles.configure already reject `role` (unknown key, 4000). - Copies never inherit the role: --clone-all, profile import, and a distribution that ships profile.yaml drop it. - setup.status for a named profile reports `ready` once the boot bootstrap settled. Since one host backend serves every profile (#118246) the desktop's setup-profile probe lands on this branch, which never set `ready`, and the kickoff waited forever. - Desktop: SETUP_PROFILE, ensureSetupProfile(profiles.create) and composeSetupSoul are gone. store/setup-profile.ts holds the name the backend returned (or the roster's role row after a relaunch); kickoff, handoff and the build card use it. The dev reset calls the reset RPC. * fix: write the setup soul as bytes so Windows keeps \n line endings * refactor(desktop): drop the renderer's setup-profile store; the backend is the only owner Kickoff reads the name straight from onboarding.ensure_setup_profile and records it on $setupSession, which every later step already carries. The handoff recovery check reads the roster row's role. No renderer module holds a setup-profile name or a fallback lookup.
60 lines
2.4 KiB
Python
60 lines
2.4 KiB
Python
"""Onboarding JSON-RPC handlers: the backend owns the setup profile (``hermes_cli.setup_profile``).
|
|
Bodies are rebound onto server.py's globals (method_ctx.bind_module) and reference them bare.
|
|
"""
|
|
|
|
from .method_ctx import HandlerRegistry, bind_module
|
|
|
|
_registry = HandlerRegistry()
|
|
method = _registry.method
|
|
|
|
|
|
@method("onboarding.ensure_setup_profile")
|
|
def _(rid, params: dict) -> dict:
|
|
"""Create-or-read the setup profile. Takes no name: the backend picks it and finds it by role."""
|
|
from hermes_cli.setup_profile import ensure_setup_profile
|
|
try:
|
|
setup = ensure_setup_profile()
|
|
if setup.created:
|
|
# Same credential mirroring profiles.create gives the desktop's clones; auth.json stays
|
|
# shared with the root so a token refresh never forks.
|
|
_mirror_launch_credentials(setup.path, {"share_auth": True})
|
|
except Exception as e:
|
|
return _err(rid, 5073, str(e))
|
|
return _ok(rid, {"name": setup.name, "path": str(setup.path), "created": setup.created, "role": "setup"})
|
|
|
|
|
|
@method("onboarding.reset_setup_profile")
|
|
def _(rid, params: dict) -> dict:
|
|
"""Restore the setup profile to its created state in place; clears its session history."""
|
|
from hermes_cli.setup_profile import find_setup_profile, reset_setup_profile
|
|
found = find_setup_profile()
|
|
if found is None:
|
|
return _err(rid, 4072, "no setup profile to reset")
|
|
_clear_setup_sessions(found[1])
|
|
try:
|
|
setup = reset_setup_profile()
|
|
except Exception as e:
|
|
return _err(rid, 5074, str(e))
|
|
return _ok(rid, {"name": setup.name, "path": str(setup.path), "reset": True})
|
|
|
|
|
|
def _clear_setup_sessions(profile_dir) -> None:
|
|
"""Close this process's live sessions in the setup profile, then delete its stored sessions."""
|
|
target = Path(profile_dir).resolve()
|
|
with _sessions_lock:
|
|
live = [sid for sid, sess in _sessions.items()
|
|
if Path(sess.get("profile_home") or _hermes_home).resolve() == target]
|
|
for sid in live:
|
|
_close_session_by_id(sid, end_reason="setup_reset")
|
|
from hermes_state_registry import acquire, release_or_close
|
|
db = acquire(target / "state.db")
|
|
try:
|
|
ids = [row[0] for row in db._read_all("SELECT id FROM sessions")]
|
|
db.delete_sessions(ids, sessions_dir=target / "sessions")
|
|
finally:
|
|
release_or_close(db)
|
|
|
|
|
|
def register(server) -> None:
|
|
bind_module(globals(), server, skip=("_",))
|