This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

tools.hermes-voice: add the Hermes gateway plugin

Hermes registers no agent-callable send_message tool on any platform
(tools/send_message_tool.py: "intentionally NOT registered as an
agent-callable model tool"; toolsets.py: "deliberately no agent-callable
send_message tool"), so there is no config switch, platform toolsets
included, that lets voice sessions on the API server write to Matrix. The
plugin adds matrix_dm_send, limited to the Matrix home room (the owner's
DM) and sent through the gateway's live adapter by Hermes's own send
helper, which also mirrors the message into the DM's session. Reading the
DM needs nothing new: session_search (chat_history_lookup) already reads
the Matrix session's transcript from any session.

/assist approve|deny [code] answers an approval hermes-voice asked in the
DM. A Matrix /approve cannot do it: it only resolves approvals of the
chat's own session, and API runs are keyed by run id. /voice is taken by
Hermes itself.

+106
+100
tools/hermes-voice/hermes-plugin/__init__.py
··· 1 + """hermes-voice's side inside the Hermes gateway (tools/hermes-voice). 2 + 3 + matrix_dm_send lets every session, the voice calls on the API server included, 4 + write to the owner's Matrix DM (the gateway's Matrix home room). Hermes 5 + registers its send_message tool for no session (tools/send_message_tool.py 6 + keeps it out of the model's tools on purpose); this one reaches that single 7 + room and uses the same sender as cron delivery, through the gateway's live 8 + Matrix adapter. Sent messages are mirrored into the DM's session, so the 9 + Matrix side sees what was asked when the user answers there. 10 + 11 + /assist approve|deny [code] answers an approval that hermes-voice asked in the 12 + DM because no call could hear it. The gateway only dispatches commands of 13 + allowed users (MATRIX_ALLOWED_USERS), so the answer is the user's own; the 14 + agent cannot issue it. The code may be left out while exactly one approval 15 + waits. 16 + """ 17 + 18 + import json 19 + import os 20 + import urllib.error 21 + import urllib.request 22 + 23 + SEND_SCHEMA = { 24 + "name": "matrix_dm_send", 25 + "description": ( 26 + "Send a message to the user's Matrix chat with you (their DM with the Hermes bot). Use " 27 + "it when the user asks you to tell or send them something there, or when you need to ask " 28 + "them something while no conversation with them is open: their answer arrives in that " 29 + "Matrix chat. To read that chat, look up the Matrix DM session with session_search " 30 + "(chat_history_lookup) and read its messages." 31 + ), 32 + "parameters": { 33 + "type": "object", 34 + "properties": { 35 + "message": {"type": "string", "description": "The message, Markdown allowed."}, 36 + }, 37 + "required": ["message"], 38 + }, 39 + } 40 + 41 + CHOICES = {"approve": "once", "deny": "deny"} 42 + 43 + 44 + def send(args, **_kw): 45 + from tools.send_message_tool import send_message_tool 46 + 47 + return send_message_tool( 48 + {"action": "send", "target": "matrix", "message": args.get("message", "")} 49 + ) 50 + 51 + 52 + def assist(raw_args): 53 + from agent.secret_scope import get_secret 54 + 55 + words = raw_args.split() 56 + if not words or words[0] not in CHOICES or len(words) > 2: 57 + return "Usage: /assist approve|deny [code]" 58 + answer = {"choice": CHOICES[words[0]]} 59 + if len(words) == 2: 60 + answer["code"] = words[1] 61 + request = urllib.request.Request( 62 + os.environ["HERMES_VOICE_URL"].rstrip("/") + "/approve", 63 + data=json.dumps(answer).encode(), 64 + method="POST", 65 + headers={ 66 + "Authorization": f"Bearer {get_secret('API_SERVER_KEY')}", 67 + "Content-Type": "application/json", 68 + }, 69 + ) 70 + try: 71 + with urllib.request.urlopen(request, timeout=30) as response: 72 + result = json.load(response) 73 + except urllib.error.HTTPError as error: 74 + try: 75 + reason = json.load(error).get("error") 76 + except ValueError: 77 + reason = error.reason 78 + return f"hermes-voice: {reason}" 79 + except (urllib.error.URLError, OSError) as error: 80 + return f"hermes-voice unreachable: {error}" 81 + what = result.get("description") or "the command" 82 + if answer["choice"] == "deny": 83 + return f"Denied: {what}." 84 + return f"Approved: {what}. Hermes continues." 85 + 86 + 87 + def register(ctx): 88 + ctx.register_tool( 89 + name="matrix_dm_send", 90 + toolset="matrix_dm", 91 + schema=SEND_SCHEMA, 92 + handler=send, 93 + emoji="💬", 94 + ) 95 + ctx.register_command( 96 + "assist", 97 + handler=assist, 98 + description="Answer an approval from a voice call (assist.lassul.us)", 99 + args_hint="approve|deny [code]", 100 + )
+6
tools/hermes-voice/hermes-plugin/plugin.yaml
··· 1 + name: hermes-voice 2 + version: 1.0.0 3 + description: "hermes-voice's side in the gateway: sessions may write to the owner's Matrix DM (the Matrix home room), and /assist answers approvals of voice-call runs from Matrix." 4 + author: lassulus 5 + provides_tools: 6 + - matrix_dm_send