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:
@@ -33,7 +33,6 @@ _AUDIO_EXTS = frozenset({'.ogg', '.opus', '.mp3', '.wav', '.m4a', '.flac'})
|
||||
# delivered as a regular document.
|
||||
_TELEGRAM_AUDIO_ATTACHMENT_EXTS = frozenset({'.mp3', '.m4a'})
|
||||
_TELEGRAM_VOICE_EXTS = frozenset({'.ogg', '.opus'})
|
||||
_POST_DELIVERY_CALLBACK_TIMEOUT_SECONDS = 30.0
|
||||
|
||||
|
||||
def _platform_name(platform) -> str:
|
||||
@@ -1545,13 +1544,6 @@ class SendResult:
|
||||
message_id: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
raw_response: Any = None
|
||||
# Adapter-specific metadata. Cross-layer contracts that affect delivery
|
||||
# semantics must be documented at the producer and consumer sites. Current
|
||||
# known contract: Telegram edit overflow partials set
|
||||
# raw_response["partial_overflow"] with delivered_chunks, total_chunks,
|
||||
# last_message_id, delivered_prefix, and continuation_message_ids so the
|
||||
# stream consumer can send the missing tail instead of marking a clipped
|
||||
# response complete.
|
||||
retryable: bool = False # True for transient connection errors — base will retry automatically
|
||||
# When the adapter had to split an oversized payload across multiple
|
||||
# platform messages (e.g. Telegram edit_message overflow split-and-deliver),
|
||||
@@ -1803,26 +1795,11 @@ class BasePlatformAdapter(ABC):
|
||||
|
||||
# Whether this platform renders triple-backtick fenced code blocks (i.e.
|
||||
# ``format_message`` translates/preserves markdown fences into a real code
|
||||
# block). Capability flag for markdown-aware presentation choices.
|
||||
# block). Drives presentation choices like rendering a ``terminal`` tool
|
||||
# call's command as a ```bash block instead of a flat preview line.
|
||||
# Default False (plain-text platforms); markdown-rendering adapters set True.
|
||||
# Tool-progress uses this to render a terminal command as a bare fenced code
|
||||
# block (no language tag — Slack mrkdwn would print the tag as a literal
|
||||
# first code line). Plain-text platforms fall back to the short truncated
|
||||
# preview (see gateway/run.py progress_callback).
|
||||
supports_code_blocks: bool = False
|
||||
|
||||
# The command prefix users can always TYPE on this platform to reach
|
||||
# Hermes commands. Default "/" (most platforms deliver "/approve" etc.
|
||||
# as plain message text). Platforms where typing a leading "/" is
|
||||
# intercepted or restricted by the client (Slack blocks native slash
|
||||
# commands inside threads; Matrix clients reserve "/" for client-local
|
||||
# commands) ship a "!" alias rewrite in their adapter and set this to
|
||||
# "!" so user-facing instruction text ("Reply `!approve` ...") tells
|
||||
# users the form that actually works everywhere. Capability flag —
|
||||
# shared prompt builders read it via getattr(adapter,
|
||||
# "typed_command_prefix", "/"); no per-platform branching at call sites.
|
||||
typed_command_prefix: str = "/"
|
||||
|
||||
def __init__(self, config: PlatformConfig, platform: Platform):
|
||||
self.config = config
|
||||
self.platform = platform
|
||||
@@ -4482,15 +4459,6 @@ class BasePlatformAdapter(ABC):
|
||||
except Exception:
|
||||
pass # Last resort — don't let error reporting crash the handler
|
||||
finally:
|
||||
# Stop typing before any deferred callback work. Post-delivery
|
||||
# callbacks may perform platform I/O; a stuck callback must not
|
||||
# leave the typing refresh task running indefinitely.
|
||||
await _stop_typing_task()
|
||||
try:
|
||||
if hasattr(self, "stop_typing"):
|
||||
await self.stop_typing(event.source.chat_id)
|
||||
except Exception:
|
||||
pass
|
||||
# Fire any one-shot post-delivery callback registered for this
|
||||
# session (e.g. deferred background-review notifications).
|
||||
#
|
||||
@@ -4518,12 +4486,11 @@ class BasePlatformAdapter(ABC):
|
||||
try:
|
||||
_post_result = _post_cb()
|
||||
if inspect.isawaitable(_post_result):
|
||||
await asyncio.wait_for(
|
||||
_post_result,
|
||||
timeout=_POST_DELIVERY_CALLBACK_TIMEOUT_SECONDS,
|
||||
)
|
||||
except (asyncio.TimeoutError, Exception):
|
||||
await _post_result
|
||||
except Exception:
|
||||
pass
|
||||
# Stop typing indicator
|
||||
await _stop_typing_task()
|
||||
# Also cancel any platform-level persistent typing tasks (e.g. Discord)
|
||||
# that may have been recreated by _keep_typing after the last stop_typing()
|
||||
try:
|
||||
@@ -4681,7 +4648,6 @@ class BasePlatformAdapter(ABC):
|
||||
guild_id: Optional[str] = None,
|
||||
parent_chat_id: Optional[str] = None,
|
||||
message_id: Optional[str] = None,
|
||||
role_authorized: bool = False,
|
||||
) -> SessionSource:
|
||||
"""Helper to build a SessionSource for this platform."""
|
||||
# Normalize empty topic to None
|
||||
@@ -4702,7 +4668,6 @@ class BasePlatformAdapter(ABC):
|
||||
guild_id=str(guild_id) if guild_id else None,
|
||||
parent_chat_id=str(parent_chat_id) if parent_chat_id else None,
|
||||
message_id=str(message_id) if message_id else None,
|
||||
role_authorized=role_authorized,
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
|
||||
Reference in New Issue
Block a user