refactor(gateway): fold pairing dir merge, bool-default table in GatewayConfig.from_dict

This commit is contained in:
Teknium
2026-09-02 19:22:13 -07:00
parent 838220617a
commit cbefa96124
3 changed files with 31 additions and 49 deletions
+7 -12
View File
@@ -506,22 +506,17 @@ def format_directory_for_display(platforms: Optional[Dict[str, Any]] = None) ->
f"{plat_name}:<chat_id>, or bare '{plat_name}' for the home channel)"
)
elif plat_name == "discord":
# Group Discord channels by guild; DMs last.
# Group Discord channels by guild (sorted by name); DMs last, in discovery order.
guilds: Dict[str, List] = {}
dms: List = []
for ch in channels:
guild = ch.get("guild")
(guilds.setdefault(guild, []) if guild else dms).append(ch)
for guild_name, guild_channels in sorted(guilds.items()):
lines.append(f"Discord ({guild_name}):")
lines.extend(
f" discord:{_channel_target_name(plat_name, ch)}"
for ch in sorted(guild_channels, key=lambda c: c["name"])
)
(guilds.setdefault(ch["guild"], []) if ch.get("guild") else dms).append(ch)
groups = [(f"Discord ({g}):", sorted(chs, key=lambda c: c["name"])) for g, chs in sorted(guilds.items())]
if dms:
lines.append("Discord (DMs):")
lines.extend(f" discord:{_channel_target_name(plat_name, ch)}" for ch in dms)
groups.append(("Discord (DMs):", dms))
for header, group in groups:
lines.append(header)
lines.extend(f" discord:{_channel_target_name(plat_name, ch)}" for ch in group)
else:
lines.append(f"{plat_name.title()}:")
lines.extend(f" {plat_name}:{_channel_target_name(plat_name, ch)}" for ch in channels)
+9 -8
View File
@@ -158,9 +158,7 @@ def coerce_systemd_watchdog_seconds(
if parsed is None:
logger.warning("Ignoring invalid %s (expected a positive integer)", key)
return 0
if parsed == 0:
return 0
if not 0 < parsed <= _SYSTEMD_WATCHDOG_MAX_SECONDS:
if parsed and not 0 < parsed <= _SYSTEMD_WATCHDOG_MAX_SECONDS:
logger.warning("Ignoring invalid %s (expected an integer from 1 to %d)", key, _SYSTEMD_WATCHDOG_MAX_SECONDS)
return 0
return parsed
@@ -562,6 +560,13 @@ _PLATFORM_CONNECTED_CHECKERS: dict[Platform, Callable[[PlatformConfig], bool]] =
}
# Top-level bool-ish keys read verbatim (no nested ``gateway.`` fallback) with their defaults.
_TOPLEVEL_BOOL_DEFAULTS = {
"write_sessions_json": True, "always_log_local": True, "filter_silence_narration": True,
"group_sessions_per_user": True, "thread_sessions_per_user": False,
}
@dataclass
class GatewayConfig:
"""Main gateway configuration: platform connections, session policies, delivery settings."""
@@ -772,13 +777,9 @@ class GatewayConfig:
reset_triggers=data.get("reset_triggers", ["/new", "/reset"]),
quick_commands=_coerce_dict(data.get("quick_commands", {})),
sessions_dir=Path(data["sessions_dir"]) if "sessions_dir" in data else get_hermes_home() / "sessions",
write_sessions_json=_coerce_bool(data.get("write_sessions_json"), True),
always_log_local=_coerce_bool(data.get("always_log_local"), True),
filter_silence_narration=_coerce_bool(data.get("filter_silence_narration"), True),
**{name: _coerce_bool(data.get(name), default) for name, default in _TOPLEVEL_BOOL_DEFAULTS.items()},
stt_enabled=_coerce_bool(stt_setting("stt_enabled", "enabled"), True),
stt_echo_transcripts=_coerce_bool(stt_setting("stt_echo_transcripts", "echo_transcripts"), True),
group_sessions_per_user=_coerce_bool(data.get("group_sessions_per_user"), True),
thread_sessions_per_user=_coerce_bool(data.get("thread_sessions_per_user"), False),
multiplex_profiles=_coerce_bool(multiplex_profiles, False),
multiplex_profile_allowlist=pick("multiplex_profile_allowlist"),
room_link_url=room_link_url if isinstance(room_link_url, str) else None,
+15 -29
View File
@@ -297,41 +297,27 @@ def _save_json_file(path: Path, data: dict) -> None:
_secure_write(path, json.dumps(data, indent=2, ensure_ascii=False))
def _merge_pairing_dir(active_dir: Path, alternate_dir: Path) -> None:
def _migrate_split_pairing_dirs(*, home: Optional[Path] = None, active: Optional[Path] = None) -> None:
"""Merge split legacy (``pairing``) / new (``platforms/pairing``) data into the active dir.
If both exist, approved users in the inactive location must not be
silently ignored (they would be asked for a fresh code).
If both exist, approved users in the inactive location must not be silently
ignored (they would be asked for a fresh code). Active data wins on key conflict.
"""
if not alternate_dir.exists() or active_dir.resolve() == alternate_dir.resolve():
return
active_dir.mkdir(parents=True, exist_ok=True)
for src in alternate_dir.glob("*.json"):
if not src.is_file():
continue
dest = active_dir / src.name
merged = _load_json_file(src)
if not merged:
continue
current = _load_json_file(dest)
before = dict(current)
# Active data wins on key conflict; otherwise union the inactive data.
merged.update(current)
if merged != before:
_save_json_file(dest, merged)
def _migrate_split_pairing_dirs(
*,
home: Optional[Path] = None,
active: Optional[Path] = None,
) -> None:
home = home or get_hermes_home()
old_dir = home / "pairing"
new_dir = home / "platforms" / "pairing"
active = active if active is not None else _default_pairing_dir()
alternate = new_dir if active.resolve() == old_dir.resolve() else old_dir
_merge_pairing_dir(active, alternate)
alternate = home / "platforms" / "pairing" if active.resolve() == old_dir.resolve() else old_dir
if not alternate.exists() or active.resolve() == alternate.resolve():
return
active.mkdir(parents=True, exist_ok=True)
for src in alternate.glob("*.json"):
merged = _load_json_file(src) if src.is_file() else {}
if not merged:
continue
current = _load_json_file(active / src.name)
merged.update(current)
if merged != current:
_save_json_file(active / src.name, merged)
def _secure_write(path: Path, data: str) -> None: