opentui(phase3): launcher integration — HERMES_TUI_ENGINE dual-engine
hermes --tui launches the native OpenTUI engine (Bun) when HERMES_TUI_ENGINE=opentui (env) or display.tui_engine=opentui (config); Ink stays the default and the shipping path is untouched. - _resolve_tui_engine() (env > config > ink); refuses opentui on Windows/Termux (no Bun) -> falls back to ink with a notice. - _make_opentui_argv() -> [bun, src/entry.real.tsx] (no build step). - _bun_bin() with HERMES_BUN override. - Branch at top of _make_tui_argv BEFORE _ensure_tui_node (Bun-only host must not bootstrap Node). - Gate _launch_tui NODE_OPTIONS/--max-old-space-size on engine==ink (Bun is JSC; the V8 flag errors/ignores). Verified end-to-end via tmux: real hermes --tui -> Bun -> OpenTUI -> real Python gateway streamed a real reply. No-flag default still ink.
This commit is contained in:
+7
-83
@@ -56,42 +56,6 @@ def _coerce_int(value: Any, default: int) -> int:
|
||||
return default
|
||||
|
||||
|
||||
def _coerce_optional_positive_int(value: Any, key: str) -> Optional[int]:
|
||||
"""Coerce an optional positive integer config value.
|
||||
|
||||
``None``/0/negative disable the setting. Malformed values are ignored with
|
||||
a warning so a typo never prevents the gateway from starting.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, bool):
|
||||
logger.warning(
|
||||
"Ignoring invalid %s=%r (expected a positive integer; 0/null disables)",
|
||||
key,
|
||||
value,
|
||||
)
|
||||
return None
|
||||
try:
|
||||
if isinstance(value, float):
|
||||
if not value.is_integer():
|
||||
raise ValueError(value)
|
||||
parsed = int(value)
|
||||
elif isinstance(value, str):
|
||||
parsed = int(value.strip(), 10)
|
||||
else:
|
||||
parsed = int(value)
|
||||
except (TypeError, ValueError):
|
||||
logger.warning(
|
||||
"Ignoring invalid %s=%r (expected a positive integer; 0/null disables)",
|
||||
key,
|
||||
value,
|
||||
)
|
||||
return None
|
||||
if parsed <= 0:
|
||||
return None
|
||||
return parsed
|
||||
|
||||
|
||||
def _normalize_unauthorized_dm_behavior(value: Any, default: str = "pair") -> str:
|
||||
"""Normalize unauthorized DM behavior to a supported value."""
|
||||
if isinstance(value, str):
|
||||
@@ -531,7 +495,6 @@ class GatewayConfig:
|
||||
# Session isolation in shared chats
|
||||
group_sessions_per_user: bool = True # Isolate group/channel sessions per participant when user IDs are available
|
||||
thread_sessions_per_user: bool = False # When False (default), threads are shared across all participants
|
||||
max_concurrent_sessions: Optional[int] = None # Positive int caps simultaneous active chat sessions
|
||||
|
||||
# Unauthorized DM policy
|
||||
unauthorized_dm_behavior: str = "pair" # "pair" or "ignore"
|
||||
@@ -637,7 +600,6 @@ class GatewayConfig:
|
||||
"stt_enabled": self.stt_enabled,
|
||||
"group_sessions_per_user": self.group_sessions_per_user,
|
||||
"thread_sessions_per_user": self.thread_sessions_per_user,
|
||||
"max_concurrent_sessions": self.max_concurrent_sessions,
|
||||
"unauthorized_dm_behavior": self.unauthorized_dm_behavior,
|
||||
"streaming": self.streaming.to_dict(),
|
||||
"session_store_max_age_days": self.session_store_max_age_days,
|
||||
@@ -683,17 +645,6 @@ class GatewayConfig:
|
||||
|
||||
group_sessions_per_user = data.get("group_sessions_per_user")
|
||||
thread_sessions_per_user = data.get("thread_sessions_per_user")
|
||||
nested_gateway = data.get("gateway") if isinstance(data.get("gateway"), dict) else {}
|
||||
if "max_concurrent_sessions" in data:
|
||||
max_concurrent_raw = data.get("max_concurrent_sessions")
|
||||
max_concurrent_key = "max_concurrent_sessions"
|
||||
else:
|
||||
max_concurrent_raw = nested_gateway.get("max_concurrent_sessions")
|
||||
max_concurrent_key = "gateway.max_concurrent_sessions"
|
||||
max_concurrent_sessions = _coerce_optional_positive_int(
|
||||
max_concurrent_raw,
|
||||
max_concurrent_key,
|
||||
)
|
||||
unauthorized_dm_behavior = _normalize_unauthorized_dm_behavior(
|
||||
data.get("unauthorized_dm_behavior"),
|
||||
"pair",
|
||||
@@ -720,7 +671,6 @@ class GatewayConfig:
|
||||
stt_enabled=_coerce_bool(stt_enabled, True),
|
||||
group_sessions_per_user=_coerce_bool(group_sessions_per_user, True),
|
||||
thread_sessions_per_user=_coerce_bool(thread_sessions_per_user, False),
|
||||
max_concurrent_sessions=max_concurrent_sessions,
|
||||
unauthorized_dm_behavior=unauthorized_dm_behavior,
|
||||
streaming=StreamingConfig.from_dict(data.get("streaming", {})),
|
||||
session_store_max_age_days=session_store_max_age_days,
|
||||
@@ -811,13 +761,6 @@ def load_gateway_config() -> GatewayConfig:
|
||||
if "thread_sessions_per_user" in yaml_cfg:
|
||||
gw_data["thread_sessions_per_user"] = yaml_cfg["thread_sessions_per_user"]
|
||||
|
||||
gateway_section = yaml_cfg.get("gateway")
|
||||
if isinstance(gateway_section, dict) and "max_concurrent_sessions" in gateway_section:
|
||||
gw_data["max_concurrent_sessions"] = gateway_section["max_concurrent_sessions"]
|
||||
|
||||
if "max_concurrent_sessions" in yaml_cfg:
|
||||
gw_data["max_concurrent_sessions"] = yaml_cfg["max_concurrent_sessions"]
|
||||
|
||||
streaming_cfg = yaml_cfg.get("streaming")
|
||||
if not isinstance(streaming_cfg, dict):
|
||||
# Fall back to nested gateway.streaming written by
|
||||
@@ -1218,30 +1161,17 @@ def load_gateway_config() -> GatewayConfig:
|
||||
if isinstance(matrix_cfg, dict):
|
||||
if "require_mention" in matrix_cfg and not os.getenv("MATRIX_REQUIRE_MENTION"):
|
||||
os.environ["MATRIX_REQUIRE_MENTION"] = str(matrix_cfg["require_mention"]).lower()
|
||||
allowed_users = matrix_cfg.get("allowed_users")
|
||||
if allowed_users is not None and not os.getenv("MATRIX_ALLOWED_USERS"):
|
||||
if isinstance(allowed_users, list):
|
||||
allowed_users = ",".join(str(v) for v in allowed_users)
|
||||
os.environ["MATRIX_ALLOWED_USERS"] = str(allowed_users)
|
||||
allowed_rooms = matrix_cfg.get("allowed_rooms")
|
||||
if allowed_rooms is not None and not os.getenv("MATRIX_ALLOWED_ROOMS"):
|
||||
if isinstance(allowed_rooms, list):
|
||||
allowed_rooms = ",".join(str(v) for v in allowed_rooms)
|
||||
os.environ["MATRIX_ALLOWED_ROOMS"] = str(allowed_rooms)
|
||||
frc = matrix_cfg.get("free_response_rooms")
|
||||
if frc is not None and not os.getenv("MATRIX_FREE_RESPONSE_ROOMS"):
|
||||
if isinstance(frc, list):
|
||||
frc = ",".join(str(v) for v in frc)
|
||||
os.environ["MATRIX_FREE_RESPONSE_ROOMS"] = str(frc)
|
||||
ignore_patterns = matrix_cfg.get("ignore_user_patterns")
|
||||
if ignore_patterns is not None and not os.getenv("MATRIX_IGNORE_USER_PATTERNS"):
|
||||
if isinstance(ignore_patterns, list):
|
||||
ignore_patterns = ",".join(str(v) for v in ignore_patterns)
|
||||
os.environ["MATRIX_IGNORE_USER_PATTERNS"] = str(ignore_patterns)
|
||||
if "process_notices" in matrix_cfg and not os.getenv("MATRIX_PROCESS_NOTICES"):
|
||||
os.environ["MATRIX_PROCESS_NOTICES"] = str(matrix_cfg["process_notices"]).lower()
|
||||
if "session_scope" in matrix_cfg and not os.getenv("MATRIX_SESSION_SCOPE"):
|
||||
os.environ["MATRIX_SESSION_SCOPE"] = str(matrix_cfg["session_scope"]).lower()
|
||||
# allowed_rooms: if set, bot ONLY responds in these rooms (whitelist)
|
||||
ar = matrix_cfg.get("allowed_rooms")
|
||||
if ar is not None and not os.getenv("MATRIX_ALLOWED_ROOMS"):
|
||||
if isinstance(ar, list):
|
||||
ar = ",".join(str(v) for v in ar)
|
||||
os.environ["MATRIX_ALLOWED_ROOMS"] = str(ar)
|
||||
if "auto_thread" in matrix_cfg and not os.getenv("MATRIX_AUTO_THREAD"):
|
||||
os.environ["MATRIX_AUTO_THREAD"] = str(matrix_cfg["auto_thread"]).lower()
|
||||
if "dm_mention_threads" in matrix_cfg and not os.getenv("MATRIX_DM_MENTION_THREADS"):
|
||||
@@ -1510,14 +1440,8 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
||||
matrix_password = os.getenv("MATRIX_PASSWORD", "")
|
||||
if matrix_password:
|
||||
matrix_config.extra["password"] = matrix_password
|
||||
matrix_e2ee_mode = os.getenv("MATRIX_E2EE_MODE", "").strip().lower()
|
||||
matrix_e2ee = (
|
||||
matrix_e2ee_mode in ("required", "require", "optional", "prefer", "preferred")
|
||||
or os.getenv("MATRIX_ENCRYPTION", "").lower() in ("true", "1", "yes")
|
||||
)
|
||||
matrix_e2ee = os.getenv("MATRIX_ENCRYPTION", "").lower() in {"true", "1", "yes"}
|
||||
matrix_config.extra["encryption"] = matrix_e2ee
|
||||
if matrix_e2ee_mode:
|
||||
matrix_config.extra["e2ee_mode"] = matrix_e2ee_mode
|
||||
matrix_device_id = os.getenv("MATRIX_DEVICE_ID", "")
|
||||
if matrix_device_id:
|
||||
matrix_config.extra["device_id"] = matrix_device_id
|
||||
|
||||
Reference in New Issue
Block a user