refactor(tools/messaging): split send_message into senders/targets tables; dedupe discord/bot_mode/relay helpers; extract feishu_lark shared plumbing; compact graph client/auth

This commit is contained in:
Teknium
2026-09-02 14:41:58 -07:00
parent 20258aad9e
commit 6723628de9
17 changed files with 1272 additions and 1459 deletions
+17 -51
View File
@@ -1,16 +1,12 @@
#!/usr/bin/env python3
"""Let the agent react to a message with an emoji in the Hermes desktop app.
The conversational counterpart to the user's tapback: the same reaction store,
the same one-per-author semantics, just written with ``author="agent"``.
Lives in the ``desktop_ui`` toolset (like the other GUI affordances) so it costs
nothing on every other surface — the platform adapters already expose reactions
through ``send_message(action="react")``, and this is the desktop's equivalent.
Defaults to the message that triggered this turn (the photon precedent: the
model shouldn't have to thread row ids through tool calls), and emits
``message.reaction`` so the renderer paints it without waiting for a resume.
The conversational counterpart to the user's tapback: same reaction store,
same one-per-author semantics, written with ``author="agent"``. Lives in the
``desktop_ui`` toolset so it costs nothing on other surfaces (platform adapters
expose reactions via ``send_message(action="react")``). Defaults to the message
that triggered this turn so the model needn't thread row ids through tool calls,
and emits ``message.reaction`` so the renderer paints it without a resume.
"""
import json
@@ -18,7 +14,6 @@ import json
from gateway.session_context import get_session_env
from tools import desktop_ui
from tools.registry import registry, tool_error
from utils import env_var_enabled
def _open_session_db():
@@ -31,25 +26,13 @@ def _open_session_db():
return None
def _react_to_message_with_db(
emoji: str,
message_row_id=None,
messages_back=None,
*,
db,
session_key: str,
) -> str:
"""Attach (or with an empty ``emoji`` retract) the agent's reaction."""
if not session_key:
return tool_error("No active session — reactions need a persisted conversation.")
def _react(emoji: str, message_row_id, messages_back, *, db, session_key: str) -> str:
row_id = message_row_id
target_role = "user"
if row_id is None:
# Default target: the latest user message. `messages_back` steps to
# earlier user turns (1 = the one before, etc.) for retroactive
# reactions — quoting text would be ambiguous, ids aren't visible to
# the model, but "two messages ago" is how a person thinks about it.
# Default target: the latest user message. `messages_back` steps to earlier
# user turns (1 = the one before) — ids aren't visible to the model and
# quoting text is ambiguous, but "two messages ago" is how a person thinks.
back = max(0, int(messages_back or 0))
row_id = db.latest_message_row_id(session_key, role="user", offset=back)
if row_id is None:
@@ -57,23 +40,18 @@ def _react_to_message_with_db(
f"No user message found {back} back." if back else "No user message to react to yet."
)
else:
row = db.get_message_role(session_key, int(row_id))
target_role = row or "user"
target_role = db.get_message_role(session_key, int(row_id)) or "user"
try:
reactions = db.set_message_reaction(
session_key, int(row_id), emoji or None, author="agent"
)
reactions = db.set_message_reaction(session_key, int(row_id), emoji or None, author="agent")
except Exception as exc:
return tool_error(f"Failed to set the reaction: {exc}")
if reactions is None:
return tool_error(f"Message {row_id} is not part of this conversation.")
# Paint it live. A missing bridge (non-desktop surface) is not an error —
# the reaction is persisted either way and shows on the next load.
# `role` lets the renderer match a live message that doesn't know its
# durable row id yet (it only learns rowId on resume).
# Paint it live. A missing bridge (non-desktop surface) is not an error — the
# reaction is persisted and shows on next load. `role` lets the renderer match
# a live message that doesn't know its durable row id yet (learned on resume).
try:
desktop_ui.emit(
"message.reaction",
@@ -93,22 +71,14 @@ def react_to_message_tool(emoji: str, message_row_id=None, messages_back=None) -
session_key = get_session_env("HERMES_SESSION_KEY", "") or get_session_env(
"HERMES_SESSION_ID", ""
)
if not session_key:
return tool_error("No active session — reactions need a persisted conversation.")
db = _open_session_db()
if db is None:
return tool_error("Session storage is unavailable.")
try:
return _react_to_message_with_db(
emoji,
message_row_id,
messages_back,
db=db,
session_key=session_key,
)
return _react(emoji, message_row_id, messages_back, db=db, session_key=session_key)
finally:
try:
from hermes_state import release_or_close
@@ -118,11 +88,7 @@ def react_to_message_tool(emoji: str, message_row_id=None, messages_back=None) -
def check_react_requirements() -> bool:
"""Opt-in feature flag — surface eligibility is the toolset's job.
``desktop_ui`` already restricts this to GUI sessions. What's left is the
user's own toggle (Settings → Appearance).
"""
"""Opt-in flag (Settings → Appearance); ``desktop_ui`` already restricts to GUI sessions."""
return desktop_ui.user_enabled("message_reactions", default=False)