mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
feat(relay): rich Slack status-line parity — advertise supports_status_text, carry live per-tool phrase on typing frames (QA-1)
Native Slack shows dynamic assistant-status text ('Finding answers…',
'is running pytest…') because SlackAdapter sets supports_status_text=True
and renders the set_status_text() phrase in send_typing. The relay lane
advertised nothing, so run.py's live-status lane never fed it phrases and
the connector fell back to the static default.
- supports_status_text: descriptor-gated property (Slack only; other
fronted platforms keep textless bubbles)
- send_typing: carry the stashed phrase as the typing op's content; omit
when unset (empty string is Slack's explicit clear, reserved for
stop_typing). Connector already renders content via
assistant.threads.setStatus (#154).
This commit is contained in:
parent
be9de31967
commit
b493bf63c7
2 changed files with 73 additions and 5 deletions
|
|
@ -133,6 +133,23 @@ class RelayAdapter(BasePlatformAdapter):
|
|||
def message_len_fn(self) -> Callable[[str], int]:
|
||||
return _LEN_FNS.get(self.descriptor.len_unit, len)
|
||||
|
||||
@property
|
||||
def supports_status_text(self) -> bool: # type: ignore[override]
|
||||
"""Whether the fronted platform renders a TEXT status line.
|
||||
|
||||
Native parity (QA-1 rich status): Slack's typing surface is the
|
||||
assistant status line ("Finding answers…" next to the bot name), a
|
||||
text-rendering indicator. When the relay fronts Slack, advertise it so
|
||||
run.py's live-status lane feeds per-tool phrases via
|
||||
``set_status_text()`` — exactly the wiring the native SlackAdapter
|
||||
gets (``supports_status_text = True``). Other fronted platforms keep
|
||||
textless typing bubbles and must NOT receive phrase traffic.
|
||||
|
||||
Property (not class attr) because ONE RelayAdapter class fronts many
|
||||
platforms; the answer depends on the handshaked descriptor.
|
||||
"""
|
||||
return self.descriptor.platform == Platform.SLACK.value
|
||||
|
||||
def supports_draft_streaming(
|
||||
self,
|
||||
chat_type: Optional[str] = None,
|
||||
|
|
@ -892,13 +909,26 @@ class RelayAdapter(BasePlatformAdapter):
|
|||
"""
|
||||
if self._transport is None:
|
||||
return
|
||||
# Rich status parity (QA-1): run.py's live-status lane stashes the
|
||||
# current per-tool phrase via set_status_text() (base class store).
|
||||
# Carry it as the typing frame's content so the connector's Slack
|
||||
# sender renders it on assistant.threads.setStatus — the same phrase
|
||||
# the native adapter shows ("is running pytest…", "Finding answers…").
|
||||
# Absent (None/empty) => omit content; the connector falls back to its
|
||||
# default "is typing…" heartbeat, preserving pre-phrase behaviour on
|
||||
# every platform. Never send empty-string content here: on Slack that
|
||||
# is the explicit CLEAR request reserved for stop_typing.
|
||||
frame: Dict[str, Any] = {
|
||||
"op": "typing",
|
||||
"chat_id": chat_id,
|
||||
"metadata": self._with_scope(chat_id, metadata),
|
||||
}
|
||||
phrase = getattr(self, "_status_text", {}).get(str(chat_id))
|
||||
if phrase:
|
||||
frame["content"] = str(phrase)
|
||||
try:
|
||||
await self._transport.send_outbound(
|
||||
{
|
||||
"op": "typing",
|
||||
"chat_id": chat_id,
|
||||
"metadata": self._with_scope(chat_id, metadata),
|
||||
},
|
||||
frame,
|
||||
platform=self._platform_by_chat.get(str(chat_id)),
|
||||
)
|
||||
except Exception: # noqa: BLE001 - typing is cosmetic, never breaks a turn
|
||||
|
|
|
|||
|
|
@ -208,3 +208,41 @@ async def test_non_slack_dm_approval_keeps_thread_id():
|
|||
await adapter.send_exec_approval("dc1", "cmd", "s", metadata=md)
|
||||
frame = _last_prompt(stub)
|
||||
assert frame["metadata"]["thread_id"] == "9000"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# QA-1 rich status: the relay advertises Slack's text status line and carries
|
||||
# the live per-tool phrase on the typing frame (native set_status_text parity).
|
||||
# ---------------------------------------------------------------------------
|
||||
@pytest.mark.asyncio
|
||||
async def test_slack_relay_advertises_status_text():
|
||||
adapter, _stub = _wire("D1", "dm")
|
||||
assert adapter.supports_status_text is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_non_slack_relay_does_not_advertise_status_text():
|
||||
stub = StubConnector(_slack_desc(platform="discord"))
|
||||
adapter = RelayAdapter(
|
||||
PlatformConfig(), _slack_desc(platform="discord"), transport=stub
|
||||
)
|
||||
assert adapter.supports_status_text is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_typing_carries_live_status_phrase():
|
||||
"""set_status_text() -> the next typing frame carries the phrase as
|
||||
content; clearing it (None) reverts to a content-less heartbeat frame
|
||||
(never an empty string, which is Slack's explicit clear)."""
|
||||
adapter, stub = _wire("D1", "dm", scope_id="T1")
|
||||
adapter.set_status_text("D1", "is running pytest…")
|
||||
await adapter.send_typing("D1", metadata={"scope_id": "T1"})
|
||||
typing = [f for f in stub.sent if f["op"] == "typing"]
|
||||
assert typing and typing[-1].get("content") == "is running pytest…"
|
||||
|
||||
adapter.set_status_text("D1", None)
|
||||
await adapter.send_typing("D1", metadata={"scope_id": "T1"})
|
||||
typing = [f for f in stub.sent if f["op"] == "typing"]
|
||||
assert "content" not in typing[-1], (
|
||||
"cleared phrase must omit content (empty string means CLEAR on Slack)"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue