Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
"""
|
|
|
|
|
Slack platform adapter.
|
|
|
|
|
|
|
|
|
|
Uses slack-bolt (Python) with Socket Mode for:
|
|
|
|
|
- Receiving messages from channels and DMs
|
|
|
|
|
- Sending responses back
|
|
|
|
|
- Handling slash commands
|
|
|
|
|
- Thread support
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import asyncio
|
2026-03-30 01:51:48 -07:00
|
|
|
import json
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
import logging
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
import os
|
2026-03-09 13:02:59 -07:00
|
|
|
import re
|
fix: prevent duplicate messages — gateway dedup + partial stream guard (#4878)
* fix(gateway): add message deduplication to Discord and Slack adapters (#4777)
Discord RESUME replays events after reconnects (~7/day observed),
and Slack Socket Mode can redeliver events if the ack was lost.
Neither adapter tracked which messages were already processed,
causing duplicate bot responses.
Add _seen_messages dedup cache (message ID → timestamp) with 5-min
TTL and 2000-entry cap to both adapters, matching the pattern already
used by Mattermost, Matrix, WeCom, Feishu, DingTalk, and Email.
The check goes at the very top of the message handler, before any
other logic, so replayed events are silently dropped.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent duplicate messages on partial stream delivery
When streaming fails after tokens are already delivered to the platform,
_interruptible_streaming_api_call re-raised the error into the outer
retry loop, which would make a new API call — creating a duplicate
message.
Now checks deltas_were_sent before re-raising: if partial content was
already streamed, returns a stub response instead. The outer loop treats
the turn as complete (no retry, no fallback, no duplicate).
Inspired by PR #4871 (@trevorgordon981) which identified the bug.
This implementation avoids monkey-patching exception objects and keeps
the fix within the streaming call boundary.
---------
Co-authored-by: Mibayy <mibayy@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:53:52 -07:00
|
|
|
import time
|
2026-04-09 13:37:15 -07:00
|
|
|
from dataclasses import dataclass, field
|
2026-04-08 14:24:59 -06:00
|
|
|
from typing import Dict, Optional, Any, Tuple
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
from slack_bolt.async_app import AsyncApp
|
|
|
|
|
from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler
|
|
|
|
|
from slack_sdk.web.async_client import AsyncWebClient
|
|
|
|
|
SLACK_AVAILABLE = True
|
|
|
|
|
except ImportError:
|
|
|
|
|
SLACK_AVAILABLE = False
|
|
|
|
|
AsyncApp = Any
|
|
|
|
|
AsyncSocketModeHandler = Any
|
|
|
|
|
AsyncWebClient = Any
|
|
|
|
|
|
|
|
|
|
import sys
|
2026-02-21 04:17:27 -08:00
|
|
|
from pathlib import Path as _Path
|
|
|
|
|
sys.path.insert(0, str(_Path(__file__).resolve().parents[2]))
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
|
|
|
|
from gateway.config import Platform, PlatformConfig
|
refactor: extract shared helpers to deduplicate repeated code patterns (#7917)
* refactor: add shared helper modules for code deduplication
New modules:
- gateway/platforms/helpers.py: MessageDeduplicator, TextBatchAggregator,
strip_markdown, ThreadParticipationTracker, redact_phone
- hermes_cli/cli_output.py: print_info/success/warning/error, prompt helpers
- tools/path_security.py: validate_within_dir, has_traversal_component
- utils.py additions: safe_json_loads, read_json_file, read_jsonl,
append_jsonl, env_str/lower/int/bool helpers
- hermes_constants.py additions: get_config_path, get_skills_dir,
get_logs_dir, get_env_path
* refactor: migrate gateway adapters to shared helpers
- MessageDeduplicator: discord, slack, dingtalk, wecom, weixin, mattermost
- strip_markdown: bluebubbles, feishu, sms
- redact_phone: sms, signal
- ThreadParticipationTracker: discord, matrix
- _acquire/_release_platform_lock: telegram, discord, slack, whatsapp,
signal, weixin
Net -316 lines across 19 files.
* refactor: migrate CLI modules to shared helpers
- tools_config.py: use cli_output print/prompt + curses_radiolist (-117 lines)
- setup.py: use cli_output print helpers + curses_radiolist (-101 lines)
- mcp_config.py: use cli_output prompt (-15 lines)
- memory_setup.py: use curses_radiolist (-86 lines)
Net -263 lines across 5 files.
* refactor: migrate to shared utility helpers
- safe_json_loads: agent/display.py (4 sites)
- get_config_path: skill_utils.py, hermes_logging.py, hermes_time.py
- get_skills_dir: skill_utils.py, prompt_builder.py
- Token estimation dedup: skills_tool.py imports from model_metadata
- Path security: skills_tool, cronjob_tools, skill_manager_tool, credential_files
- Non-atomic YAML writes: doctor.py, config.py now use atomic_yaml_write
- Platform dict: new platforms.py, skills_config + tools_config derive from it
- Anthropic key: new get_anthropic_key() in auth.py, used by doctor/status/config/main
* test: update tests for shared helper migrations
- test_dingtalk: use _dedup.is_duplicate() instead of _is_duplicate()
- test_mattermost: use _dedup instead of _seen_posts/_prune_seen
- test_signal: import redact_phone from helpers instead of signal
- test_discord_connect: _platform_lock_identity instead of _token_lock_identity
- test_telegram_conflict: updated lock error message format
- test_skill_manager_tool: 'escapes' instead of 'boundary' in error msgs
2026-04-11 13:59:52 -07:00
|
|
|
from gateway.platforms.helpers import MessageDeduplicator
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
from gateway.platforms.base import (
|
|
|
|
|
BasePlatformAdapter,
|
|
|
|
|
MessageEvent,
|
|
|
|
|
MessageType,
|
|
|
|
|
SendResult,
|
2026-03-09 13:02:59 -07:00
|
|
|
SUPPORTED_DOCUMENT_TYPES,
|
2026-04-10 05:02:17 -07:00
|
|
|
safe_url_for_log,
|
2026-03-09 13:02:59 -07:00
|
|
|
cache_document_from_bytes,
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
2026-04-09 13:37:15 -07:00
|
|
|
@dataclass
|
|
|
|
|
class _ThreadContextCache:
|
|
|
|
|
"""Cache entry for fetched thread context."""
|
|
|
|
|
content: str
|
|
|
|
|
fetched_at: float = field(default_factory=time.monotonic)
|
|
|
|
|
message_count: int = 0
|
|
|
|
|
|
|
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
def check_slack_requirements() -> bool:
|
|
|
|
|
"""Check if Slack dependencies are available."""
|
|
|
|
|
return SLACK_AVAILABLE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SlackAdapter(BasePlatformAdapter):
|
|
|
|
|
"""
|
|
|
|
|
Slack bot adapter using Socket Mode.
|
|
|
|
|
|
|
|
|
|
Requires two tokens:
|
|
|
|
|
- SLACK_BOT_TOKEN (xoxb-...) for API calls
|
|
|
|
|
- SLACK_APP_TOKEN (xapp-...) for Socket Mode connection
|
|
|
|
|
|
|
|
|
|
Features:
|
|
|
|
|
- DMs and channel messages (mention-gated in channels)
|
|
|
|
|
- Thread support
|
|
|
|
|
- File/image/audio attachments
|
|
|
|
|
- Slash commands (/hermes)
|
|
|
|
|
- Typing indicators (not natively supported by Slack bots)
|
|
|
|
|
"""
|
|
|
|
|
|
2026-03-12 17:32:06 -07:00
|
|
|
MAX_MESSAGE_LENGTH = 39000 # Slack API allows 40,000 chars; leave margin
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
|
|
|
|
def __init__(self, config: PlatformConfig):
|
|
|
|
|
super().__init__(config, Platform.SLACK)
|
|
|
|
|
self._app: Optional[AsyncApp] = None
|
|
|
|
|
self._handler: Optional[AsyncSocketModeHandler] = None
|
|
|
|
|
self._bot_user_id: Optional[str] = None
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
self._user_name_cache: Dict[str, str] = {} # user_id → display name
|
2026-03-26 14:36:24 -07:00
|
|
|
self._socket_mode_task: Optional[asyncio.Task] = None
|
2026-03-30 01:51:48 -07:00
|
|
|
# Multi-workspace support
|
|
|
|
|
self._team_clients: Dict[str, AsyncWebClient] = {} # team_id → WebClient
|
|
|
|
|
self._team_bot_user_ids: Dict[str, str] = {} # team_id → bot_user_id
|
|
|
|
|
self._channel_team: Dict[str, str] = {} # channel_id → team_id
|
refactor: extract shared helpers to deduplicate repeated code patterns (#7917)
* refactor: add shared helper modules for code deduplication
New modules:
- gateway/platforms/helpers.py: MessageDeduplicator, TextBatchAggregator,
strip_markdown, ThreadParticipationTracker, redact_phone
- hermes_cli/cli_output.py: print_info/success/warning/error, prompt helpers
- tools/path_security.py: validate_within_dir, has_traversal_component
- utils.py additions: safe_json_loads, read_json_file, read_jsonl,
append_jsonl, env_str/lower/int/bool helpers
- hermes_constants.py additions: get_config_path, get_skills_dir,
get_logs_dir, get_env_path
* refactor: migrate gateway adapters to shared helpers
- MessageDeduplicator: discord, slack, dingtalk, wecom, weixin, mattermost
- strip_markdown: bluebubbles, feishu, sms
- redact_phone: sms, signal
- ThreadParticipationTracker: discord, matrix
- _acquire/_release_platform_lock: telegram, discord, slack, whatsapp,
signal, weixin
Net -316 lines across 19 files.
* refactor: migrate CLI modules to shared helpers
- tools_config.py: use cli_output print/prompt + curses_radiolist (-117 lines)
- setup.py: use cli_output print helpers + curses_radiolist (-101 lines)
- mcp_config.py: use cli_output prompt (-15 lines)
- memory_setup.py: use curses_radiolist (-86 lines)
Net -263 lines across 5 files.
* refactor: migrate to shared utility helpers
- safe_json_loads: agent/display.py (4 sites)
- get_config_path: skill_utils.py, hermes_logging.py, hermes_time.py
- get_skills_dir: skill_utils.py, prompt_builder.py
- Token estimation dedup: skills_tool.py imports from model_metadata
- Path security: skills_tool, cronjob_tools, skill_manager_tool, credential_files
- Non-atomic YAML writes: doctor.py, config.py now use atomic_yaml_write
- Platform dict: new platforms.py, skills_config + tools_config derive from it
- Anthropic key: new get_anthropic_key() in auth.py, used by doctor/status/config/main
* test: update tests for shared helper migrations
- test_dingtalk: use _dedup.is_duplicate() instead of _is_duplicate()
- test_mattermost: use _dedup instead of _seen_posts/_prune_seen
- test_signal: import redact_phone from helpers instead of signal
- test_discord_connect: _platform_lock_identity instead of _token_lock_identity
- test_telegram_conflict: updated lock error message format
- test_skill_manager_tool: 'escapes' instead of 'boundary' in error msgs
2026-04-11 13:59:52 -07:00
|
|
|
# Dedup cache: prevents duplicate bot responses when Socket Mode
|
|
|
|
|
# reconnects redeliver events.
|
|
|
|
|
self._dedup = MessageDeduplicator()
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
# Track pending approval message_ts → resolved flag to prevent
|
|
|
|
|
# double-clicks on approval buttons.
|
|
|
|
|
self._approval_resolved: Dict[str, bool] = {}
|
2026-04-07 11:12:08 -07:00
|
|
|
# Track timestamps of messages sent by the bot so we can respond
|
|
|
|
|
# to thread replies even without an explicit @mention.
|
|
|
|
|
self._bot_message_ts: set = set()
|
|
|
|
|
self._BOT_TS_MAX = 5000 # cap to avoid unbounded growth
|
|
|
|
|
# Track threads where the bot has been @mentioned — once mentioned,
|
|
|
|
|
# respond to ALL subsequent messages in that thread automatically.
|
|
|
|
|
self._mentioned_threads: set = set()
|
|
|
|
|
self._MENTIONED_THREADS_MAX = 5000
|
2026-04-08 14:24:59 -06:00
|
|
|
# Assistant thread metadata keyed by (channel_id, thread_ts). Slack's
|
|
|
|
|
# AI Assistant lifecycle events can arrive before/alongside message
|
|
|
|
|
# events, and they carry the user/thread identity needed for stable
|
|
|
|
|
# session + memory scoping.
|
|
|
|
|
self._assistant_threads: Dict[Tuple[str, str], Dict[str, str]] = {}
|
2026-04-08 22:44:22 -07:00
|
|
|
self._ASSISTANT_THREADS_MAX = 5000
|
2026-04-09 13:37:15 -07:00
|
|
|
# Cache for _fetch_thread_context results: cache_key → _ThreadContextCache
|
|
|
|
|
self._thread_context_cache: Dict[str, _ThreadContextCache] = {}
|
|
|
|
|
self._THREAD_CACHE_TTL = 60.0
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
|
|
|
|
async def connect(self) -> bool:
|
|
|
|
|
"""Connect to Slack via Socket Mode."""
|
|
|
|
|
if not SLACK_AVAILABLE:
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
logger.error(
|
|
|
|
|
"[Slack] slack-bolt not installed. Run: pip install slack-bolt",
|
|
|
|
|
)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
return False
|
|
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
raw_token = self.config.token
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
app_token = os.getenv("SLACK_APP_TOKEN")
|
|
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
if not raw_token:
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
logger.error("[Slack] SLACK_BOT_TOKEN not set")
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
return False
|
|
|
|
|
if not app_token:
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
logger.error("[Slack] SLACK_APP_TOKEN not set")
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
return False
|
|
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
# Support comma-separated bot tokens for multi-workspace
|
|
|
|
|
bot_tokens = [t.strip() for t in raw_token.split(",") if t.strip()]
|
|
|
|
|
|
|
|
|
|
# Also load tokens from OAuth token file
|
|
|
|
|
from hermes_constants import get_hermes_home
|
|
|
|
|
tokens_file = get_hermes_home() / "slack_tokens.json"
|
|
|
|
|
if tokens_file.exists():
|
|
|
|
|
try:
|
|
|
|
|
saved = json.loads(tokens_file.read_text(encoding="utf-8"))
|
|
|
|
|
for team_id, entry in saved.items():
|
|
|
|
|
tok = entry.get("token", "") if isinstance(entry, dict) else ""
|
|
|
|
|
if tok and tok not in bot_tokens:
|
|
|
|
|
bot_tokens.append(tok)
|
|
|
|
|
team_label = entry.get("team_name", team_id) if isinstance(entry, dict) else team_id
|
|
|
|
|
logger.info("[Slack] Loaded saved token for workspace %s", team_label)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.warning("[Slack] Failed to read %s: %s", tokens_file, e)
|
|
|
|
|
|
2026-04-19 08:51:34 +03:00
|
|
|
lock_acquired = False
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
try:
|
refactor: extract shared helpers to deduplicate repeated code patterns (#7917)
* refactor: add shared helper modules for code deduplication
New modules:
- gateway/platforms/helpers.py: MessageDeduplicator, TextBatchAggregator,
strip_markdown, ThreadParticipationTracker, redact_phone
- hermes_cli/cli_output.py: print_info/success/warning/error, prompt helpers
- tools/path_security.py: validate_within_dir, has_traversal_component
- utils.py additions: safe_json_loads, read_json_file, read_jsonl,
append_jsonl, env_str/lower/int/bool helpers
- hermes_constants.py additions: get_config_path, get_skills_dir,
get_logs_dir, get_env_path
* refactor: migrate gateway adapters to shared helpers
- MessageDeduplicator: discord, slack, dingtalk, wecom, weixin, mattermost
- strip_markdown: bluebubbles, feishu, sms
- redact_phone: sms, signal
- ThreadParticipationTracker: discord, matrix
- _acquire/_release_platform_lock: telegram, discord, slack, whatsapp,
signal, weixin
Net -316 lines across 19 files.
* refactor: migrate CLI modules to shared helpers
- tools_config.py: use cli_output print/prompt + curses_radiolist (-117 lines)
- setup.py: use cli_output print helpers + curses_radiolist (-101 lines)
- mcp_config.py: use cli_output prompt (-15 lines)
- memory_setup.py: use curses_radiolist (-86 lines)
Net -263 lines across 5 files.
* refactor: migrate to shared utility helpers
- safe_json_loads: agent/display.py (4 sites)
- get_config_path: skill_utils.py, hermes_logging.py, hermes_time.py
- get_skills_dir: skill_utils.py, prompt_builder.py
- Token estimation dedup: skills_tool.py imports from model_metadata
- Path security: skills_tool, cronjob_tools, skill_manager_tool, credential_files
- Non-atomic YAML writes: doctor.py, config.py now use atomic_yaml_write
- Platform dict: new platforms.py, skills_config + tools_config derive from it
- Anthropic key: new get_anthropic_key() in auth.py, used by doctor/status/config/main
* test: update tests for shared helper migrations
- test_dingtalk: use _dedup.is_duplicate() instead of _is_duplicate()
- test_mattermost: use _dedup instead of _seen_posts/_prune_seen
- test_signal: import redact_phone from helpers instead of signal
- test_discord_connect: _platform_lock_identity instead of _token_lock_identity
- test_telegram_conflict: updated lock error message format
- test_skill_manager_tool: 'escapes' instead of 'boundary' in error msgs
2026-04-11 13:59:52 -07:00
|
|
|
if not self._acquire_platform_lock('slack-app-token', app_token, 'Slack app token'):
|
feat: add profiles — run multiple isolated Hermes instances (#3681)
Each profile is a fully independent HERMES_HOME with its own config,
API keys, memory, sessions, skills, gateway, cron, and state.db.
Core module: hermes_cli/profiles.py (~900 lines)
- Profile CRUD: create, delete, list, show, rename
- Three clone levels: blank, --clone (config), --clone-all (everything)
- Export/import: tar.gz archive for backup and migration
- Wrapper alias scripts (~/.local/bin/<name>)
- Collision detection for alias names
- Sticky default via ~/.hermes/active_profile
- Skill seeding via subprocess (handles module-level caching)
- Auto-stop gateway on delete with disable-before-stop for services
- Tab completion generation for bash and zsh
CLI integration (hermes_cli/main.py):
- _apply_profile_override(): pre-import -p/--profile flag + sticky default
- Full 'hermes profile' subcommand: list, use, create, delete, show,
alias, rename, export, import
- 'hermes completion bash/zsh' command
- Multi-profile skill sync in hermes update
Display (cli.py, banner.py, gateway/run.py):
- CLI prompt: 'coder ❯' when using a non-default profile
- Banner shows profile name
- Gateway startup log includes profile name
Gateway safety:
- Token locks: Discord, Slack, WhatsApp, Signal (extends Telegram pattern)
- Port conflict detection: API server, webhook adapter
Diagnostics (hermes_cli/doctor.py):
- Profile health section: lists profiles, checks config, .env, aliases
- Orphan alias detection: warns when wrapper points to deleted profile
Tests (tests/hermes_cli/test_profiles.py):
- 71 automated tests covering: validation, CRUD, clone levels, rename,
export/import, active profile, isolation, alias collision, completion
- Full suite: 6760 passed, 0 new failures
Documentation:
- website/docs/user-guide/profiles.md: full user guide (12 sections)
- website/docs/reference/profile-commands.md: command reference (12 commands)
- website/docs/reference/faq.md: 6 profile FAQ entries
- website/sidebars.ts: navigation updated
2026-03-29 10:41:20 -07:00
|
|
|
return False
|
2026-04-19 08:51:34 +03:00
|
|
|
lock_acquired = True
|
feat: add profiles — run multiple isolated Hermes instances (#3681)
Each profile is a fully independent HERMES_HOME with its own config,
API keys, memory, sessions, skills, gateway, cron, and state.db.
Core module: hermes_cli/profiles.py (~900 lines)
- Profile CRUD: create, delete, list, show, rename
- Three clone levels: blank, --clone (config), --clone-all (everything)
- Export/import: tar.gz archive for backup and migration
- Wrapper alias scripts (~/.local/bin/<name>)
- Collision detection for alias names
- Sticky default via ~/.hermes/active_profile
- Skill seeding via subprocess (handles module-level caching)
- Auto-stop gateway on delete with disable-before-stop for services
- Tab completion generation for bash and zsh
CLI integration (hermes_cli/main.py):
- _apply_profile_override(): pre-import -p/--profile flag + sticky default
- Full 'hermes profile' subcommand: list, use, create, delete, show,
alias, rename, export, import
- 'hermes completion bash/zsh' command
- Multi-profile skill sync in hermes update
Display (cli.py, banner.py, gateway/run.py):
- CLI prompt: 'coder ❯' when using a non-default profile
- Banner shows profile name
- Gateway startup log includes profile name
Gateway safety:
- Token locks: Discord, Slack, WhatsApp, Signal (extends Telegram pattern)
- Port conflict detection: API server, webhook adapter
Diagnostics (hermes_cli/doctor.py):
- Profile health section: lists profiles, checks config, .env, aliases
- Orphan alias detection: warns when wrapper points to deleted profile
Tests (tests/hermes_cli/test_profiles.py):
- 71 automated tests covering: validation, CRUD, clone levels, rename,
export/import, active profile, isolation, alias collision, completion
- Full suite: 6760 passed, 0 new failures
Documentation:
- website/docs/user-guide/profiles.md: full user guide (12 sections)
- website/docs/reference/profile-commands.md: command reference (12 commands)
- website/docs/reference/faq.md: 6 profile FAQ entries
- website/sidebars.ts: navigation updated
2026-03-29 10:41:20 -07:00
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
# First token is the primary — used for AsyncApp / Socket Mode
|
|
|
|
|
primary_token = bot_tokens[0]
|
|
|
|
|
self._app = AsyncApp(token=primary_token)
|
|
|
|
|
|
|
|
|
|
# Register each bot token and map team_id → client
|
|
|
|
|
for token in bot_tokens:
|
|
|
|
|
client = AsyncWebClient(token=token)
|
|
|
|
|
auth_response = await client.auth_test()
|
|
|
|
|
team_id = auth_response.get("team_id", "")
|
|
|
|
|
bot_user_id = auth_response.get("user_id", "")
|
|
|
|
|
bot_name = auth_response.get("user", "unknown")
|
|
|
|
|
team_name = auth_response.get("team", "unknown")
|
|
|
|
|
|
|
|
|
|
self._team_clients[team_id] = client
|
|
|
|
|
self._team_bot_user_ids[team_id] = bot_user_id
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
# First token sets the primary bot_user_id (backward compat)
|
|
|
|
|
if self._bot_user_id is None:
|
|
|
|
|
self._bot_user_id = bot_user_id
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
"[Slack] Authenticated as @%s in workspace %s (team: %s)",
|
|
|
|
|
bot_name, team_name, team_id,
|
|
|
|
|
)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
|
|
|
|
# Register message event handler
|
|
|
|
|
@self._app.event("message")
|
|
|
|
|
async def handle_message_event(event, say):
|
|
|
|
|
await self._handle_slack_message(event)
|
|
|
|
|
|
2026-03-09 13:02:59 -07:00
|
|
|
# Acknowledge app_mention events to prevent Bolt 404 errors.
|
|
|
|
|
# The "message" handler above already processes @mentions in
|
|
|
|
|
# channels, so this is intentionally a no-op to avoid duplicates.
|
|
|
|
|
@self._app.event("app_mention")
|
|
|
|
|
async def handle_app_mention(event, say):
|
|
|
|
|
pass
|
|
|
|
|
|
2026-04-08 14:24:59 -06:00
|
|
|
@self._app.event("assistant_thread_started")
|
|
|
|
|
async def handle_assistant_thread_started(event, say):
|
|
|
|
|
await self._handle_assistant_thread_lifecycle_event(event)
|
|
|
|
|
|
|
|
|
|
@self._app.event("assistant_thread_context_changed")
|
|
|
|
|
async def handle_assistant_thread_context_changed(event, say):
|
|
|
|
|
await self._handle_assistant_thread_lifecycle_event(event)
|
|
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
# Register slash command handler
|
|
|
|
|
@self._app.command("/hermes")
|
|
|
|
|
async def handle_hermes_command(ack, command):
|
|
|
|
|
await ack()
|
|
|
|
|
await self._handle_slash_command(command)
|
|
|
|
|
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
# Register Block Kit action handlers for approval buttons
|
|
|
|
|
for _action_id in (
|
|
|
|
|
"hermes_approve_once",
|
|
|
|
|
"hermes_approve_session",
|
|
|
|
|
"hermes_approve_always",
|
|
|
|
|
"hermes_deny",
|
|
|
|
|
):
|
|
|
|
|
self._app.action(_action_id)(self._handle_approval_action)
|
|
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
# Start Socket Mode handler in background
|
|
|
|
|
self._handler = AsyncSocketModeHandler(self._app, app_token)
|
2026-03-26 14:36:24 -07:00
|
|
|
self._socket_mode_task = asyncio.create_task(self._handler.start_async())
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
|
|
|
|
self._running = True
|
2026-03-30 01:51:48 -07:00
|
|
|
logger.info(
|
|
|
|
|
"[Slack] Socket Mode connected (%d workspace(s))",
|
|
|
|
|
len(self._team_clients),
|
|
|
|
|
)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
return True
|
|
|
|
|
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
except Exception as e: # pragma: no cover - defensive logging
|
|
|
|
|
logger.error("[Slack] Connection failed: %s", e, exc_info=True)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
return False
|
2026-04-19 08:51:34 +03:00
|
|
|
finally:
|
|
|
|
|
if lock_acquired and not self._running:
|
|
|
|
|
self._release_platform_lock()
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
|
|
|
|
async def disconnect(self) -> None:
|
|
|
|
|
"""Disconnect from Slack."""
|
|
|
|
|
if self._handler:
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
try:
|
|
|
|
|
await self._handler.close_async()
|
|
|
|
|
except Exception as e: # pragma: no cover - defensive logging
|
|
|
|
|
logger.warning("[Slack] Error while closing Socket Mode handler: %s", e, exc_info=True)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
self._running = False
|
feat: add profiles — run multiple isolated Hermes instances (#3681)
Each profile is a fully independent HERMES_HOME with its own config,
API keys, memory, sessions, skills, gateway, cron, and state.db.
Core module: hermes_cli/profiles.py (~900 lines)
- Profile CRUD: create, delete, list, show, rename
- Three clone levels: blank, --clone (config), --clone-all (everything)
- Export/import: tar.gz archive for backup and migration
- Wrapper alias scripts (~/.local/bin/<name>)
- Collision detection for alias names
- Sticky default via ~/.hermes/active_profile
- Skill seeding via subprocess (handles module-level caching)
- Auto-stop gateway on delete with disable-before-stop for services
- Tab completion generation for bash and zsh
CLI integration (hermes_cli/main.py):
- _apply_profile_override(): pre-import -p/--profile flag + sticky default
- Full 'hermes profile' subcommand: list, use, create, delete, show,
alias, rename, export, import
- 'hermes completion bash/zsh' command
- Multi-profile skill sync in hermes update
Display (cli.py, banner.py, gateway/run.py):
- CLI prompt: 'coder ❯' when using a non-default profile
- Banner shows profile name
- Gateway startup log includes profile name
Gateway safety:
- Token locks: Discord, Slack, WhatsApp, Signal (extends Telegram pattern)
- Port conflict detection: API server, webhook adapter
Diagnostics (hermes_cli/doctor.py):
- Profile health section: lists profiles, checks config, .env, aliases
- Orphan alias detection: warns when wrapper points to deleted profile
Tests (tests/hermes_cli/test_profiles.py):
- 71 automated tests covering: validation, CRUD, clone levels, rename,
export/import, active profile, isolation, alias collision, completion
- Full suite: 6760 passed, 0 new failures
Documentation:
- website/docs/user-guide/profiles.md: full user guide (12 sections)
- website/docs/reference/profile-commands.md: command reference (12 commands)
- website/docs/reference/faq.md: 6 profile FAQ entries
- website/sidebars.ts: navigation updated
2026-03-29 10:41:20 -07:00
|
|
|
|
refactor: extract shared helpers to deduplicate repeated code patterns (#7917)
* refactor: add shared helper modules for code deduplication
New modules:
- gateway/platforms/helpers.py: MessageDeduplicator, TextBatchAggregator,
strip_markdown, ThreadParticipationTracker, redact_phone
- hermes_cli/cli_output.py: print_info/success/warning/error, prompt helpers
- tools/path_security.py: validate_within_dir, has_traversal_component
- utils.py additions: safe_json_loads, read_json_file, read_jsonl,
append_jsonl, env_str/lower/int/bool helpers
- hermes_constants.py additions: get_config_path, get_skills_dir,
get_logs_dir, get_env_path
* refactor: migrate gateway adapters to shared helpers
- MessageDeduplicator: discord, slack, dingtalk, wecom, weixin, mattermost
- strip_markdown: bluebubbles, feishu, sms
- redact_phone: sms, signal
- ThreadParticipationTracker: discord, matrix
- _acquire/_release_platform_lock: telegram, discord, slack, whatsapp,
signal, weixin
Net -316 lines across 19 files.
* refactor: migrate CLI modules to shared helpers
- tools_config.py: use cli_output print/prompt + curses_radiolist (-117 lines)
- setup.py: use cli_output print helpers + curses_radiolist (-101 lines)
- mcp_config.py: use cli_output prompt (-15 lines)
- memory_setup.py: use curses_radiolist (-86 lines)
Net -263 lines across 5 files.
* refactor: migrate to shared utility helpers
- safe_json_loads: agent/display.py (4 sites)
- get_config_path: skill_utils.py, hermes_logging.py, hermes_time.py
- get_skills_dir: skill_utils.py, prompt_builder.py
- Token estimation dedup: skills_tool.py imports from model_metadata
- Path security: skills_tool, cronjob_tools, skill_manager_tool, credential_files
- Non-atomic YAML writes: doctor.py, config.py now use atomic_yaml_write
- Platform dict: new platforms.py, skills_config + tools_config derive from it
- Anthropic key: new get_anthropic_key() in auth.py, used by doctor/status/config/main
* test: update tests for shared helper migrations
- test_dingtalk: use _dedup.is_duplicate() instead of _is_duplicate()
- test_mattermost: use _dedup instead of _seen_posts/_prune_seen
- test_signal: import redact_phone from helpers instead of signal
- test_discord_connect: _platform_lock_identity instead of _token_lock_identity
- test_telegram_conflict: updated lock error message format
- test_skill_manager_tool: 'escapes' instead of 'boundary' in error msgs
2026-04-11 13:59:52 -07:00
|
|
|
self._release_platform_lock()
|
feat: add profiles — run multiple isolated Hermes instances (#3681)
Each profile is a fully independent HERMES_HOME with its own config,
API keys, memory, sessions, skills, gateway, cron, and state.db.
Core module: hermes_cli/profiles.py (~900 lines)
- Profile CRUD: create, delete, list, show, rename
- Three clone levels: blank, --clone (config), --clone-all (everything)
- Export/import: tar.gz archive for backup and migration
- Wrapper alias scripts (~/.local/bin/<name>)
- Collision detection for alias names
- Sticky default via ~/.hermes/active_profile
- Skill seeding via subprocess (handles module-level caching)
- Auto-stop gateway on delete with disable-before-stop for services
- Tab completion generation for bash and zsh
CLI integration (hermes_cli/main.py):
- _apply_profile_override(): pre-import -p/--profile flag + sticky default
- Full 'hermes profile' subcommand: list, use, create, delete, show,
alias, rename, export, import
- 'hermes completion bash/zsh' command
- Multi-profile skill sync in hermes update
Display (cli.py, banner.py, gateway/run.py):
- CLI prompt: 'coder ❯' when using a non-default profile
- Banner shows profile name
- Gateway startup log includes profile name
Gateway safety:
- Token locks: Discord, Slack, WhatsApp, Signal (extends Telegram pattern)
- Port conflict detection: API server, webhook adapter
Diagnostics (hermes_cli/doctor.py):
- Profile health section: lists profiles, checks config, .env, aliases
- Orphan alias detection: warns when wrapper points to deleted profile
Tests (tests/hermes_cli/test_profiles.py):
- 71 automated tests covering: validation, CRUD, clone levels, rename,
export/import, active profile, isolation, alias collision, completion
- Full suite: 6760 passed, 0 new failures
Documentation:
- website/docs/user-guide/profiles.md: full user guide (12 sections)
- website/docs/reference/profile-commands.md: command reference (12 commands)
- website/docs/reference/faq.md: 6 profile FAQ entries
- website/sidebars.ts: navigation updated
2026-03-29 10:41:20 -07:00
|
|
|
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
logger.info("[Slack] Disconnected")
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
def _get_client(self, chat_id: str) -> AsyncWebClient:
|
|
|
|
|
"""Return the workspace-specific WebClient for a channel."""
|
|
|
|
|
team_id = self._channel_team.get(chat_id)
|
|
|
|
|
if team_id and team_id in self._team_clients:
|
|
|
|
|
return self._team_clients[team_id]
|
|
|
|
|
return self._app.client # fallback to primary
|
|
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
async def send(
|
|
|
|
|
self,
|
|
|
|
|
chat_id: str,
|
|
|
|
|
content: str,
|
|
|
|
|
reply_to: Optional[str] = None,
|
|
|
|
|
metadata: Optional[Dict[str, Any]] = None,
|
|
|
|
|
) -> SendResult:
|
|
|
|
|
"""Send a message to a Slack channel or DM."""
|
|
|
|
|
if not self._app:
|
|
|
|
|
return SendResult(success=False, error="Not connected")
|
|
|
|
|
|
|
|
|
|
try:
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
# Convert standard markdown → Slack mrkdwn
|
|
|
|
|
formatted = self.format_message(content)
|
|
|
|
|
|
|
|
|
|
# Split long messages, preserving code block boundaries
|
|
|
|
|
chunks = self.truncate_message(formatted, self.MAX_MESSAGE_LENGTH)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
thread_ts = self._resolve_thread_ts(reply_to, metadata)
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
last_result = None
|
|
|
|
|
|
|
|
|
|
# reply_broadcast: also post thread replies to the main channel.
|
|
|
|
|
# Controlled via platform config: gateway.slack.reply_broadcast
|
|
|
|
|
broadcast = self.config.extra.get("reply_broadcast", False)
|
|
|
|
|
|
|
|
|
|
for i, chunk in enumerate(chunks):
|
|
|
|
|
kwargs = {
|
|
|
|
|
"channel": chat_id,
|
|
|
|
|
"text": chunk,
|
fix(slack): comprehensive mrkdwn formatting — 6 bug fixes + 52 tests
Fixes blockquote > escaping, edit_message raw markdown, ***bold italic***
handling, HTML entity double-escaping (&amp;), Wikipedia URL parens
truncation, and step numbering format. Also adds format_message to the
tool-layer _send_to_platform for consistent formatting across all
delivery paths.
Changes:
- Protect Slack entities (<@user>, <https://...|label>, <!here>) from
escaping passes
- Protect blockquote > markers before HTML entity escaping
- Unescape-before-escape for idempotent HTML entity handling
- ***bold italic*** → *_text_* conversion (before **bold** pass)
- URL regex upgraded to handle balanced parentheses
- mrkdwn:True flag on chat_postMessage payloads
- format_message applied in edit_message and send_message_tool
- 52 new tests (format, edit, streaming, splitting, tool chunking)
- Use reversed(dict) idiom for placeholder restoration
Based on PR #3715 by dashed, cherry-picked onto current main.
2026-04-09 13:33:05 -07:00
|
|
|
"mrkdwn": True,
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
}
|
|
|
|
|
if thread_ts:
|
|
|
|
|
kwargs["thread_ts"] = thread_ts
|
|
|
|
|
# Only broadcast the first chunk of the first reply
|
|
|
|
|
if broadcast and i == 0:
|
|
|
|
|
kwargs["reply_broadcast"] = True
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
last_result = await self._get_client(chat_id).chat_postMessage(**kwargs)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
2026-04-07 11:12:08 -07:00
|
|
|
# Track the sent message ts so we can auto-respond to thread
|
|
|
|
|
# replies without requiring @mention.
|
|
|
|
|
sent_ts = last_result.get("ts") if last_result else None
|
|
|
|
|
if sent_ts:
|
|
|
|
|
self._bot_message_ts.add(sent_ts)
|
|
|
|
|
# Also register the thread root so replies-to-my-replies work
|
|
|
|
|
if thread_ts:
|
|
|
|
|
self._bot_message_ts.add(thread_ts)
|
|
|
|
|
if len(self._bot_message_ts) > self._BOT_TS_MAX:
|
|
|
|
|
excess = len(self._bot_message_ts) - self._BOT_TS_MAX // 2
|
|
|
|
|
for old_ts in list(self._bot_message_ts)[:excess]:
|
|
|
|
|
self._bot_message_ts.discard(old_ts)
|
|
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
return SendResult(
|
|
|
|
|
success=True,
|
2026-04-07 11:12:08 -07:00
|
|
|
message_id=sent_ts,
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
raw_response=last_result,
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
)
|
|
|
|
|
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
except Exception as e: # pragma: no cover - defensive logging
|
|
|
|
|
logger.error("[Slack] Send error: %s", e, exc_info=True)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
return SendResult(success=False, error=str(e))
|
|
|
|
|
|
2026-03-05 03:47:51 -08:00
|
|
|
async def edit_message(
|
|
|
|
|
self,
|
|
|
|
|
chat_id: str,
|
|
|
|
|
message_id: str,
|
|
|
|
|
content: str,
|
2026-04-19 23:33:43 +08:00
|
|
|
*,
|
|
|
|
|
finalize: bool = False,
|
2026-03-05 03:47:51 -08:00
|
|
|
) -> SendResult:
|
|
|
|
|
"""Edit a previously sent Slack message."""
|
|
|
|
|
if not self._app:
|
|
|
|
|
return SendResult(success=False, error="Not connected")
|
|
|
|
|
try:
|
2026-04-06 11:59:54 -04:00
|
|
|
formatted = self.format_message(content)
|
2026-03-30 01:51:48 -07:00
|
|
|
await self._get_client(chat_id).chat_update(
|
2026-03-05 03:47:51 -08:00
|
|
|
channel=chat_id,
|
|
|
|
|
ts=message_id,
|
2026-04-06 11:59:54 -04:00
|
|
|
text=formatted,
|
2026-03-05 03:47:51 -08:00
|
|
|
)
|
|
|
|
|
return SendResult(success=True, message_id=message_id)
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
except Exception as e: # pragma: no cover - defensive logging
|
|
|
|
|
logger.error(
|
|
|
|
|
"[Slack] Failed to edit message %s in channel %s: %s",
|
|
|
|
|
message_id,
|
|
|
|
|
chat_id,
|
|
|
|
|
e,
|
|
|
|
|
exc_info=True,
|
|
|
|
|
)
|
2026-03-05 03:47:51 -08:00
|
|
|
return SendResult(success=False, error=str(e))
|
|
|
|
|
|
2026-03-10 06:26:16 -07:00
|
|
|
async def send_typing(self, chat_id: str, metadata=None) -> None:
|
2026-03-12 17:32:06 -07:00
|
|
|
"""Show a typing/status indicator using assistant.threads.setStatus.
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
|
2026-03-12 17:32:06 -07:00
|
|
|
Displays "is thinking..." next to the bot name in a thread.
|
|
|
|
|
Requires the assistant:write or chat:write scope.
|
|
|
|
|
Auto-clears when the bot sends a reply to the thread.
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
"""
|
2026-03-12 17:32:06 -07:00
|
|
|
if not self._app:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
thread_ts = None
|
|
|
|
|
if metadata:
|
|
|
|
|
thread_ts = metadata.get("thread_id") or metadata.get("thread_ts")
|
|
|
|
|
|
|
|
|
|
if not thread_ts:
|
|
|
|
|
return # Can only set status in a thread context
|
|
|
|
|
|
|
|
|
|
try:
|
2026-03-30 01:51:48 -07:00
|
|
|
await self._get_client(chat_id).assistant_threads_setStatus(
|
2026-03-12 17:32:06 -07:00
|
|
|
channel_id=chat_id,
|
|
|
|
|
thread_ts=thread_ts,
|
|
|
|
|
status="is thinking...",
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
# Silently ignore — may lack assistant:write scope or not be
|
|
|
|
|
# in an assistant-enabled context. Falls back to reactions.
|
|
|
|
|
logger.debug("[Slack] assistant.threads.setStatus failed: %s", e)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
2026-04-16 04:22:07 -07:00
|
|
|
def _dm_top_level_threads_as_sessions(self) -> bool:
|
|
|
|
|
"""Whether top-level Slack DMs get per-message session threads.
|
|
|
|
|
|
|
|
|
|
Defaults to ``True`` so each visible DM reply thread is isolated as its
|
|
|
|
|
own Hermes session — matching the per-thread behavior channels already
|
|
|
|
|
have. Set ``platforms.slack.extra.dm_top_level_threads_as_sessions``
|
|
|
|
|
to ``false`` in config.yaml to revert to the legacy behavior where all
|
|
|
|
|
top-level DMs share one continuous session.
|
|
|
|
|
"""
|
|
|
|
|
raw = self.config.extra.get("dm_top_level_threads_as_sessions")
|
|
|
|
|
if raw is None:
|
|
|
|
|
return True # default: each DM thread is its own session
|
|
|
|
|
return str(raw).strip().lower() in ("1", "true", "yes", "on")
|
|
|
|
|
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
def _resolve_thread_ts(
|
|
|
|
|
self,
|
|
|
|
|
reply_to: Optional[str] = None,
|
|
|
|
|
metadata: Optional[Dict[str, Any]] = None,
|
|
|
|
|
) -> Optional[str]:
|
|
|
|
|
"""Resolve the correct thread_ts for a Slack API call.
|
|
|
|
|
|
|
|
|
|
Prefers metadata thread_id (the thread parent's ts, set by the
|
|
|
|
|
gateway) over reply_to (which may be a child message's ts).
|
2026-03-24 07:31:45 +00:00
|
|
|
|
|
|
|
|
When ``reply_in_thread`` is ``false`` in the platform extra config,
|
|
|
|
|
top-level channel messages receive direct channel replies instead of
|
|
|
|
|
thread replies. Messages that originate inside an existing thread are
|
|
|
|
|
always replied to in-thread to preserve conversation context.
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
"""
|
2026-03-24 07:31:45 +00:00
|
|
|
# When reply_in_thread is disabled (default: True for backward compat),
|
|
|
|
|
# only thread messages that are already part of an existing thread.
|
|
|
|
|
if not self.config.extra.get("reply_in_thread", True):
|
|
|
|
|
existing_thread = (metadata or {}).get("thread_id") or (metadata or {}).get("thread_ts")
|
|
|
|
|
return existing_thread or None
|
|
|
|
|
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
if metadata:
|
|
|
|
|
if metadata.get("thread_id"):
|
|
|
|
|
return metadata["thread_id"]
|
|
|
|
|
if metadata.get("thread_ts"):
|
|
|
|
|
return metadata["thread_ts"]
|
|
|
|
|
return reply_to
|
|
|
|
|
|
2026-03-14 02:56:06 -07:00
|
|
|
async def _upload_file(
|
|
|
|
|
self,
|
|
|
|
|
chat_id: str,
|
|
|
|
|
file_path: str,
|
|
|
|
|
caption: Optional[str] = None,
|
|
|
|
|
reply_to: Optional[str] = None,
|
|
|
|
|
metadata: Optional[Dict[str, Any]] = None,
|
|
|
|
|
) -> SendResult:
|
|
|
|
|
"""Upload a local file to Slack."""
|
|
|
|
|
if not self._app:
|
|
|
|
|
return SendResult(success=False, error="Not connected")
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(file_path):
|
|
|
|
|
raise FileNotFoundError(f"File not found: {file_path}")
|
|
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
result = await self._get_client(chat_id).files_upload_v2(
|
2026-03-14 02:56:06 -07:00
|
|
|
channel=chat_id,
|
|
|
|
|
file=file_path,
|
|
|
|
|
filename=os.path.basename(file_path),
|
|
|
|
|
initial_comment=caption or "",
|
|
|
|
|
thread_ts=self._resolve_thread_ts(reply_to, metadata),
|
|
|
|
|
)
|
|
|
|
|
return SendResult(success=True, raw_response=result)
|
|
|
|
|
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
# ----- Markdown → mrkdwn conversion -----
|
|
|
|
|
|
|
|
|
|
def format_message(self, content: str) -> str:
|
|
|
|
|
"""Convert standard markdown to Slack mrkdwn format.
|
|
|
|
|
|
|
|
|
|
Protected regions (code blocks, inline code) are extracted first so
|
|
|
|
|
their contents are never modified. Standard markdown constructs
|
|
|
|
|
(headers, bold, italic, links) are translated to mrkdwn syntax.
|
|
|
|
|
"""
|
|
|
|
|
if not content:
|
|
|
|
|
return content
|
|
|
|
|
|
|
|
|
|
placeholders: dict = {}
|
|
|
|
|
counter = [0]
|
|
|
|
|
|
|
|
|
|
def _ph(value: str) -> str:
|
|
|
|
|
"""Stash value behind a placeholder that survives later passes."""
|
|
|
|
|
key = f"\x00SL{counter[0]}\x00"
|
|
|
|
|
counter[0] += 1
|
|
|
|
|
placeholders[key] = value
|
|
|
|
|
return key
|
|
|
|
|
|
|
|
|
|
text = content
|
|
|
|
|
|
|
|
|
|
# 1) Protect fenced code blocks (``` ... ```)
|
|
|
|
|
text = re.sub(
|
|
|
|
|
r'(```(?:[^\n]*\n)?[\s\S]*?```)',
|
|
|
|
|
lambda m: _ph(m.group(0)),
|
|
|
|
|
text,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 2) Protect inline code (`...`)
|
|
|
|
|
text = re.sub(r'(`[^`]+`)', lambda m: _ph(m.group(0)), text)
|
|
|
|
|
|
|
|
|
|
# 3) Convert markdown links [text](url) → <url|text>
|
fix(slack): comprehensive mrkdwn formatting — 6 bug fixes + 52 tests
Fixes blockquote > escaping, edit_message raw markdown, ***bold italic***
handling, HTML entity double-escaping (&amp;), Wikipedia URL parens
truncation, and step numbering format. Also adds format_message to the
tool-layer _send_to_platform for consistent formatting across all
delivery paths.
Changes:
- Protect Slack entities (<@user>, <https://...|label>, <!here>) from
escaping passes
- Protect blockquote > markers before HTML entity escaping
- Unescape-before-escape for idempotent HTML entity handling
- ***bold italic*** → *_text_* conversion (before **bold** pass)
- URL regex upgraded to handle balanced parentheses
- mrkdwn:True flag on chat_postMessage payloads
- format_message applied in edit_message and send_message_tool
- 52 new tests (format, edit, streaming, splitting, tool chunking)
- Use reversed(dict) idiom for placeholder restoration
Based on PR #3715 by dashed, cherry-picked onto current main.
2026-04-09 13:33:05 -07:00
|
|
|
def _convert_markdown_link(m):
|
|
|
|
|
label = m.group(1)
|
|
|
|
|
url = m.group(2).strip()
|
|
|
|
|
if url.startswith('<') and url.endswith('>'):
|
|
|
|
|
url = url[1:-1].strip()
|
|
|
|
|
return _ph(f'<{url}|{label}>')
|
|
|
|
|
|
|
|
|
|
text = re.sub(
|
|
|
|
|
r'\[([^\]]+)\]\(([^()]*(?:\([^()]*\)[^()]*)*)\)',
|
|
|
|
|
_convert_markdown_link,
|
|
|
|
|
text,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 4) Protect existing Slack entities/manual links so escaping and later
|
|
|
|
|
# formatting passes don't break them.
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
text = re.sub(
|
fix(slack): comprehensive mrkdwn formatting — 6 bug fixes + 52 tests
Fixes blockquote > escaping, edit_message raw markdown, ***bold italic***
handling, HTML entity double-escaping (&amp;), Wikipedia URL parens
truncation, and step numbering format. Also adds format_message to the
tool-layer _send_to_platform for consistent formatting across all
delivery paths.
Changes:
- Protect Slack entities (<@user>, <https://...|label>, <!here>) from
escaping passes
- Protect blockquote > markers before HTML entity escaping
- Unescape-before-escape for idempotent HTML entity handling
- ***bold italic*** → *_text_* conversion (before **bold** pass)
- URL regex upgraded to handle balanced parentheses
- mrkdwn:True flag on chat_postMessage payloads
- format_message applied in edit_message and send_message_tool
- 52 new tests (format, edit, streaming, splitting, tool chunking)
- Use reversed(dict) idiom for placeholder restoration
Based on PR #3715 by dashed, cherry-picked onto current main.
2026-04-09 13:33:05 -07:00
|
|
|
r'(<(?:[@#!]|(?:https?|mailto|tel):)[^>\n]+>)',
|
|
|
|
|
lambda m: _ph(m.group(1)),
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
text,
|
|
|
|
|
)
|
|
|
|
|
|
fix(slack): comprehensive mrkdwn formatting — 6 bug fixes + 52 tests
Fixes blockquote > escaping, edit_message raw markdown, ***bold italic***
handling, HTML entity double-escaping (&amp;), Wikipedia URL parens
truncation, and step numbering format. Also adds format_message to the
tool-layer _send_to_platform for consistent formatting across all
delivery paths.
Changes:
- Protect Slack entities (<@user>, <https://...|label>, <!here>) from
escaping passes
- Protect blockquote > markers before HTML entity escaping
- Unescape-before-escape for idempotent HTML entity handling
- ***bold italic*** → *_text_* conversion (before **bold** pass)
- URL regex upgraded to handle balanced parentheses
- mrkdwn:True flag on chat_postMessage payloads
- format_message applied in edit_message and send_message_tool
- 52 new tests (format, edit, streaming, splitting, tool chunking)
- Use reversed(dict) idiom for placeholder restoration
Based on PR #3715 by dashed, cherry-picked onto current main.
2026-04-09 13:33:05 -07:00
|
|
|
# 5) Protect blockquote markers before escaping
|
|
|
|
|
text = re.sub(r'^(>+\s)', lambda m: _ph(m.group(0)), text, flags=re.MULTILINE)
|
|
|
|
|
|
|
|
|
|
# 6) Escape Slack control characters in remaining plain text.
|
|
|
|
|
# Unescape first so already-escaped input doesn't get double-escaped.
|
|
|
|
|
text = text.replace('&', '&').replace('<', '<').replace('>', '>')
|
|
|
|
|
text = text.replace('&', '&').replace('<', '<').replace('>', '>')
|
|
|
|
|
|
|
|
|
|
# 7) Convert headers (## Title) → *Title* (bold)
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
def _convert_header(m):
|
|
|
|
|
inner = m.group(1).strip()
|
|
|
|
|
# Strip redundant bold markers inside a header
|
|
|
|
|
inner = re.sub(r'\*\*(.+?)\*\*', r'\1', inner)
|
|
|
|
|
return _ph(f'*{inner}*')
|
|
|
|
|
|
|
|
|
|
text = re.sub(
|
|
|
|
|
r'^#{1,6}\s+(.+)$', _convert_header, text, flags=re.MULTILINE
|
|
|
|
|
)
|
|
|
|
|
|
fix(slack): comprehensive mrkdwn formatting — 6 bug fixes + 52 tests
Fixes blockquote > escaping, edit_message raw markdown, ***bold italic***
handling, HTML entity double-escaping (&amp;), Wikipedia URL parens
truncation, and step numbering format. Also adds format_message to the
tool-layer _send_to_platform for consistent formatting across all
delivery paths.
Changes:
- Protect Slack entities (<@user>, <https://...|label>, <!here>) from
escaping passes
- Protect blockquote > markers before HTML entity escaping
- Unescape-before-escape for idempotent HTML entity handling
- ***bold italic*** → *_text_* conversion (before **bold** pass)
- URL regex upgraded to handle balanced parentheses
- mrkdwn:True flag on chat_postMessage payloads
- format_message applied in edit_message and send_message_tool
- 52 new tests (format, edit, streaming, splitting, tool chunking)
- Use reversed(dict) idiom for placeholder restoration
Based on PR #3715 by dashed, cherry-picked onto current main.
2026-04-09 13:33:05 -07:00
|
|
|
# 8) Convert bold+italic: ***text*** → *_text_* (Slack bold wrapping italic)
|
|
|
|
|
text = re.sub(
|
|
|
|
|
r'\*\*\*(.+?)\*\*\*',
|
|
|
|
|
lambda m: _ph(f'*_{m.group(1)}_*'),
|
|
|
|
|
text,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 9) Convert bold: **text** → *text* (Slack bold)
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
text = re.sub(
|
|
|
|
|
r'\*\*(.+?)\*\*',
|
|
|
|
|
lambda m: _ph(f'*{m.group(1)}*'),
|
|
|
|
|
text,
|
|
|
|
|
)
|
|
|
|
|
|
fix(slack): comprehensive mrkdwn formatting — 6 bug fixes + 52 tests
Fixes blockquote > escaping, edit_message raw markdown, ***bold italic***
handling, HTML entity double-escaping (&amp;), Wikipedia URL parens
truncation, and step numbering format. Also adds format_message to the
tool-layer _send_to_platform for consistent formatting across all
delivery paths.
Changes:
- Protect Slack entities (<@user>, <https://...|label>, <!here>) from
escaping passes
- Protect blockquote > markers before HTML entity escaping
- Unescape-before-escape for idempotent HTML entity handling
- ***bold italic*** → *_text_* conversion (before **bold** pass)
- URL regex upgraded to handle balanced parentheses
- mrkdwn:True flag on chat_postMessage payloads
- format_message applied in edit_message and send_message_tool
- 52 new tests (format, edit, streaming, splitting, tool chunking)
- Use reversed(dict) idiom for placeholder restoration
Based on PR #3715 by dashed, cherry-picked onto current main.
2026-04-09 13:33:05 -07:00
|
|
|
# 10) Convert italic: _text_ stays as _text_ (already Slack italic)
|
|
|
|
|
# Single *text* → _text_ (Slack italic)
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
text = re.sub(
|
|
|
|
|
r'(?<!\*)\*([^*\n]+)\*(?!\*)',
|
|
|
|
|
lambda m: _ph(f'_{m.group(1)}_'),
|
|
|
|
|
text,
|
|
|
|
|
)
|
|
|
|
|
|
fix(slack): comprehensive mrkdwn formatting — 6 bug fixes + 52 tests
Fixes blockquote > escaping, edit_message raw markdown, ***bold italic***
handling, HTML entity double-escaping (&amp;), Wikipedia URL parens
truncation, and step numbering format. Also adds format_message to the
tool-layer _send_to_platform for consistent formatting across all
delivery paths.
Changes:
- Protect Slack entities (<@user>, <https://...|label>, <!here>) from
escaping passes
- Protect blockquote > markers before HTML entity escaping
- Unescape-before-escape for idempotent HTML entity handling
- ***bold italic*** → *_text_* conversion (before **bold** pass)
- URL regex upgraded to handle balanced parentheses
- mrkdwn:True flag on chat_postMessage payloads
- format_message applied in edit_message and send_message_tool
- 52 new tests (format, edit, streaming, splitting, tool chunking)
- Use reversed(dict) idiom for placeholder restoration
Based on PR #3715 by dashed, cherry-picked onto current main.
2026-04-09 13:33:05 -07:00
|
|
|
# 11) Convert strikethrough: ~~text~~ → ~text~
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
text = re.sub(
|
|
|
|
|
r'~~(.+?)~~',
|
|
|
|
|
lambda m: _ph(f'~{m.group(1)}~'),
|
|
|
|
|
text,
|
|
|
|
|
)
|
|
|
|
|
|
fix(slack): comprehensive mrkdwn formatting — 6 bug fixes + 52 tests
Fixes blockquote > escaping, edit_message raw markdown, ***bold italic***
handling, HTML entity double-escaping (&amp;), Wikipedia URL parens
truncation, and step numbering format. Also adds format_message to the
tool-layer _send_to_platform for consistent formatting across all
delivery paths.
Changes:
- Protect Slack entities (<@user>, <https://...|label>, <!here>) from
escaping passes
- Protect blockquote > markers before HTML entity escaping
- Unescape-before-escape for idempotent HTML entity handling
- ***bold italic*** → *_text_* conversion (before **bold** pass)
- URL regex upgraded to handle balanced parentheses
- mrkdwn:True flag on chat_postMessage payloads
- format_message applied in edit_message and send_message_tool
- 52 new tests (format, edit, streaming, splitting, tool chunking)
- Use reversed(dict) idiom for placeholder restoration
Based on PR #3715 by dashed, cherry-picked onto current main.
2026-04-09 13:33:05 -07:00
|
|
|
# 12) Blockquotes: > prefix is already protected by step 5 above.
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
|
fix(slack): comprehensive mrkdwn formatting — 6 bug fixes + 52 tests
Fixes blockquote > escaping, edit_message raw markdown, ***bold italic***
handling, HTML entity double-escaping (&amp;), Wikipedia URL parens
truncation, and step numbering format. Also adds format_message to the
tool-layer _send_to_platform for consistent formatting across all
delivery paths.
Changes:
- Protect Slack entities (<@user>, <https://...|label>, <!here>) from
escaping passes
- Protect blockquote > markers before HTML entity escaping
- Unescape-before-escape for idempotent HTML entity handling
- ***bold italic*** → *_text_* conversion (before **bold** pass)
- URL regex upgraded to handle balanced parentheses
- mrkdwn:True flag on chat_postMessage payloads
- format_message applied in edit_message and send_message_tool
- 52 new tests (format, edit, streaming, splitting, tool chunking)
- Use reversed(dict) idiom for placeholder restoration
Based on PR #3715 by dashed, cherry-picked onto current main.
2026-04-09 13:33:05 -07:00
|
|
|
# 13) Restore placeholders in reverse order
|
|
|
|
|
for key in reversed(placeholders):
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
text = text.replace(key, placeholders[key])
|
|
|
|
|
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
# ----- Reactions -----
|
|
|
|
|
|
|
|
|
|
async def _add_reaction(
|
|
|
|
|
self, channel: str, timestamp: str, emoji: str
|
|
|
|
|
) -> bool:
|
|
|
|
|
"""Add an emoji reaction to a message. Returns True on success."""
|
|
|
|
|
if not self._app:
|
|
|
|
|
return False
|
|
|
|
|
try:
|
2026-03-30 01:51:48 -07:00
|
|
|
await self._get_client(channel).reactions_add(
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
channel=channel, timestamp=timestamp, name=emoji
|
|
|
|
|
)
|
|
|
|
|
return True
|
|
|
|
|
except Exception as e:
|
|
|
|
|
# Don't log as error — may fail if already reacted or missing scope
|
|
|
|
|
logger.debug("[Slack] reactions.add failed (%s): %s", emoji, e)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
async def _remove_reaction(
|
|
|
|
|
self, channel: str, timestamp: str, emoji: str
|
|
|
|
|
) -> bool:
|
|
|
|
|
"""Remove an emoji reaction from a message. Returns True on success."""
|
|
|
|
|
if not self._app:
|
|
|
|
|
return False
|
|
|
|
|
try:
|
2026-03-30 01:51:48 -07:00
|
|
|
await self._get_client(channel).reactions_remove(
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
channel=channel, timestamp=timestamp, name=emoji
|
|
|
|
|
)
|
|
|
|
|
return True
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.debug("[Slack] reactions.remove failed (%s): %s", emoji, e)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# ----- User identity resolution -----
|
|
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
async def _resolve_user_name(self, user_id: str, chat_id: str = "") -> str:
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
"""Resolve a Slack user ID to a display name, with caching."""
|
|
|
|
|
if not user_id:
|
|
|
|
|
return ""
|
|
|
|
|
if user_id in self._user_name_cache:
|
|
|
|
|
return self._user_name_cache[user_id]
|
|
|
|
|
|
|
|
|
|
if not self._app:
|
|
|
|
|
return user_id
|
|
|
|
|
|
|
|
|
|
try:
|
2026-03-30 01:51:48 -07:00
|
|
|
client = self._get_client(chat_id) if chat_id else self._app.client
|
|
|
|
|
result = await client.users_info(user=user_id)
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
user = result.get("user", {})
|
|
|
|
|
# Prefer display_name → real_name → user_id
|
|
|
|
|
profile = user.get("profile", {})
|
|
|
|
|
name = (
|
|
|
|
|
profile.get("display_name")
|
|
|
|
|
or profile.get("real_name")
|
|
|
|
|
or user.get("real_name")
|
|
|
|
|
or user.get("name")
|
|
|
|
|
or user_id
|
|
|
|
|
)
|
|
|
|
|
self._user_name_cache[user_id] = name
|
|
|
|
|
return name
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.debug("[Slack] users.info failed for %s: %s", user_id, e)
|
|
|
|
|
self._user_name_cache[user_id] = user_id
|
|
|
|
|
return user_id
|
|
|
|
|
|
2026-03-07 22:57:05 -08:00
|
|
|
async def send_image_file(
|
|
|
|
|
self,
|
|
|
|
|
chat_id: str,
|
|
|
|
|
image_path: str,
|
|
|
|
|
caption: Optional[str] = None,
|
|
|
|
|
reply_to: Optional[str] = None,
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
metadata: Optional[Dict[str, Any]] = None,
|
2026-03-07 22:57:05 -08:00
|
|
|
) -> SendResult:
|
|
|
|
|
"""Send a local image file to Slack by uploading it."""
|
|
|
|
|
try:
|
2026-03-14 02:56:06 -07:00
|
|
|
return await self._upload_file(chat_id, image_path, caption, reply_to, metadata)
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
return SendResult(success=False, error=f"Image file not found: {image_path}")
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
except Exception as e: # pragma: no cover - defensive logging
|
|
|
|
|
logger.error(
|
|
|
|
|
"[%s] Failed to send local Slack image %s: %s",
|
|
|
|
|
self.name,
|
|
|
|
|
image_path,
|
|
|
|
|
e,
|
|
|
|
|
exc_info=True,
|
|
|
|
|
)
|
2026-03-13 04:26:27 +03:00
|
|
|
text = f"🖼️ Image: {image_path}"
|
|
|
|
|
if caption:
|
|
|
|
|
text = f"{caption}\n{text}"
|
|
|
|
|
return await self.send(chat_id, text, reply_to=reply_to, metadata=metadata)
|
2026-03-07 22:57:05 -08:00
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
async def send_image(
|
|
|
|
|
self,
|
|
|
|
|
chat_id: str,
|
|
|
|
|
image_url: str,
|
|
|
|
|
caption: Optional[str] = None,
|
|
|
|
|
reply_to: Optional[str] = None,
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
metadata: Optional[Dict[str, Any]] = None,
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
) -> SendResult:
|
|
|
|
|
"""Send an image to Slack by uploading the URL as a file."""
|
|
|
|
|
if not self._app:
|
|
|
|
|
return SendResult(success=False, error="Not connected")
|
|
|
|
|
|
fix(security): consolidated security hardening — SSRF, timing attack, tar traversal, credential leakage (#5944)
Salvaged from PRs #5800 (memosr), #5806 (memosr), #5915 (Ruzzgar), #5928 (Awsh1).
Changes:
- Use hmac.compare_digest for API key comparison (timing attack prevention)
- Apply provider env var blocklist to Docker containers (credential leakage)
- Replace tar.extractall() with safe extraction in TerminalBench2 (CVE-2007-4559)
- Add SSRF protection via is_safe_url to ALL platform adapters:
base.py (cache_image_from_url, cache_audio_from_url),
discord, slack, telegram, matrix, mattermost, feishu, wecom
(Signal and WhatsApp protected via base.py helpers)
- Update tests: mock is_safe_url in Mattermost download tests
- Add security tests for tar extraction (traversal, symlinks, safe files)
2026-04-07 17:28:37 -07:00
|
|
|
from tools.url_safety import is_safe_url
|
|
|
|
|
if not is_safe_url(image_url):
|
|
|
|
|
logger.warning("[Slack] Blocked unsafe image URL (SSRF protection)")
|
|
|
|
|
return await super().send_image(chat_id, image_url, caption, reply_to, metadata=metadata)
|
|
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
try:
|
|
|
|
|
import httpx
|
|
|
|
|
|
2026-04-10 13:40:12 +03:00
|
|
|
async def _ssrf_redirect_guard(response):
|
|
|
|
|
"""Re-check redirect targets so public URLs cannot bounce into private IPs."""
|
|
|
|
|
if response.is_redirect and response.next_request:
|
|
|
|
|
redirect_url = str(response.next_request.url)
|
|
|
|
|
if not is_safe_url(redirect_url):
|
|
|
|
|
raise ValueError("Blocked redirect to private/internal address")
|
|
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
# Download the image first
|
2026-04-10 13:40:12 +03:00
|
|
|
async with httpx.AsyncClient(
|
|
|
|
|
timeout=30.0,
|
|
|
|
|
follow_redirects=True,
|
|
|
|
|
event_hooks={"response": [_ssrf_redirect_guard]},
|
|
|
|
|
) as client:
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
response = await client.get(image_url)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
result = await self._get_client(chat_id).files_upload_v2(
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
channel=chat_id,
|
|
|
|
|
content=response.content,
|
|
|
|
|
filename="image.png",
|
|
|
|
|
initial_comment=caption or "",
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
thread_ts=self._resolve_thread_ts(reply_to, metadata),
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return SendResult(success=True, raw_response=result)
|
|
|
|
|
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
except Exception as e: # pragma: no cover - defensive logging
|
|
|
|
|
logger.warning(
|
|
|
|
|
"[Slack] Failed to upload image from URL %s, falling back to text: %s",
|
2026-04-10 05:02:17 -07:00
|
|
|
safe_url_for_log(image_url),
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
e,
|
|
|
|
|
exc_info=True,
|
|
|
|
|
)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
# Fall back to sending the URL as text
|
|
|
|
|
text = f"{caption}\n{image_url}" if caption else image_url
|
|
|
|
|
return await self.send(chat_id=chat_id, content=text, reply_to=reply_to)
|
|
|
|
|
|
|
|
|
|
async def send_voice(
|
|
|
|
|
self,
|
|
|
|
|
chat_id: str,
|
|
|
|
|
audio_path: str,
|
|
|
|
|
caption: Optional[str] = None,
|
|
|
|
|
reply_to: Optional[str] = None,
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
metadata: Optional[Dict[str, Any]] = None,
|
2026-03-11 21:36:54 +03:00
|
|
|
**kwargs,
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
) -> SendResult:
|
|
|
|
|
"""Send an audio file to Slack."""
|
|
|
|
|
try:
|
2026-03-14 02:56:06 -07:00
|
|
|
return await self._upload_file(chat_id, audio_path, caption, reply_to, metadata)
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
return SendResult(success=False, error=f"Audio file not found: {audio_path}")
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
except Exception as e: # pragma: no cover - defensive logging
|
|
|
|
|
logger.error(
|
|
|
|
|
"[Slack] Failed to send audio file %s: %s",
|
|
|
|
|
audio_path,
|
|
|
|
|
e,
|
|
|
|
|
exc_info=True,
|
|
|
|
|
)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
return SendResult(success=False, error=str(e))
|
|
|
|
|
|
2026-03-09 13:02:59 -07:00
|
|
|
async def send_video(
|
|
|
|
|
self,
|
|
|
|
|
chat_id: str,
|
|
|
|
|
video_path: str,
|
|
|
|
|
caption: Optional[str] = None,
|
|
|
|
|
reply_to: Optional[str] = None,
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
metadata: Optional[Dict[str, Any]] = None,
|
2026-03-09 13:02:59 -07:00
|
|
|
) -> SendResult:
|
|
|
|
|
"""Send a video file to Slack."""
|
|
|
|
|
if not self._app:
|
|
|
|
|
return SendResult(success=False, error="Not connected")
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(video_path):
|
|
|
|
|
return SendResult(success=False, error=f"Video file not found: {video_path}")
|
|
|
|
|
|
|
|
|
|
try:
|
2026-03-30 01:51:48 -07:00
|
|
|
result = await self._get_client(chat_id).files_upload_v2(
|
2026-03-09 13:02:59 -07:00
|
|
|
channel=chat_id,
|
|
|
|
|
file=video_path,
|
|
|
|
|
filename=os.path.basename(video_path),
|
|
|
|
|
initial_comment=caption or "",
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
thread_ts=self._resolve_thread_ts(reply_to, metadata),
|
2026-03-09 13:02:59 -07:00
|
|
|
)
|
|
|
|
|
return SendResult(success=True, raw_response=result)
|
|
|
|
|
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
except Exception as e: # pragma: no cover - defensive logging
|
|
|
|
|
logger.error(
|
|
|
|
|
"[%s] Failed to send video %s: %s",
|
|
|
|
|
self.name,
|
|
|
|
|
video_path,
|
|
|
|
|
e,
|
|
|
|
|
exc_info=True,
|
|
|
|
|
)
|
2026-03-13 04:26:27 +03:00
|
|
|
text = f"🎬 Video: {video_path}"
|
|
|
|
|
if caption:
|
|
|
|
|
text = f"{caption}\n{text}"
|
|
|
|
|
return await self.send(chat_id, text, reply_to=reply_to, metadata=metadata)
|
2026-03-09 13:02:59 -07:00
|
|
|
|
|
|
|
|
async def send_document(
|
|
|
|
|
self,
|
|
|
|
|
chat_id: str,
|
|
|
|
|
file_path: str,
|
|
|
|
|
caption: Optional[str] = None,
|
|
|
|
|
file_name: Optional[str] = None,
|
|
|
|
|
reply_to: Optional[str] = None,
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
metadata: Optional[Dict[str, Any]] = None,
|
2026-03-09 13:02:59 -07:00
|
|
|
) -> SendResult:
|
|
|
|
|
"""Send a document/file attachment to Slack."""
|
|
|
|
|
if not self._app:
|
|
|
|
|
return SendResult(success=False, error="Not connected")
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(file_path):
|
|
|
|
|
return SendResult(success=False, error=f"File not found: {file_path}")
|
|
|
|
|
|
|
|
|
|
display_name = file_name or os.path.basename(file_path)
|
|
|
|
|
|
|
|
|
|
try:
|
2026-03-30 01:51:48 -07:00
|
|
|
result = await self._get_client(chat_id).files_upload_v2(
|
2026-03-09 13:02:59 -07:00
|
|
|
channel=chat_id,
|
|
|
|
|
file=file_path,
|
|
|
|
|
filename=display_name,
|
|
|
|
|
initial_comment=caption or "",
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
thread_ts=self._resolve_thread_ts(reply_to, metadata),
|
2026-03-09 13:02:59 -07:00
|
|
|
)
|
|
|
|
|
return SendResult(success=True, raw_response=result)
|
|
|
|
|
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
except Exception as e: # pragma: no cover - defensive logging
|
|
|
|
|
logger.error(
|
|
|
|
|
"[%s] Failed to send document %s: %s",
|
|
|
|
|
self.name,
|
|
|
|
|
file_path,
|
|
|
|
|
e,
|
|
|
|
|
exc_info=True,
|
|
|
|
|
)
|
2026-03-13 04:26:27 +03:00
|
|
|
text = f"📎 File: {file_path}"
|
|
|
|
|
if caption:
|
|
|
|
|
text = f"{caption}\n{text}"
|
|
|
|
|
return await self.send(chat_id, text, reply_to=reply_to, metadata=metadata)
|
2026-03-09 13:02:59 -07:00
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
|
|
|
|
|
"""Get information about a Slack channel."""
|
|
|
|
|
if not self._app:
|
|
|
|
|
return {"name": chat_id, "type": "unknown"}
|
|
|
|
|
|
|
|
|
|
try:
|
2026-03-30 01:51:48 -07:00
|
|
|
result = await self._get_client(chat_id).conversations_info(channel=chat_id)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
channel = result.get("channel", {})
|
|
|
|
|
is_dm = channel.get("is_im", False)
|
|
|
|
|
return {
|
|
|
|
|
"name": channel.get("name", chat_id),
|
|
|
|
|
"type": "dm" if is_dm else "group",
|
|
|
|
|
}
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
except Exception as e: # pragma: no cover - defensive logging
|
|
|
|
|
logger.error(
|
|
|
|
|
"[Slack] Failed to fetch chat info for %s: %s",
|
|
|
|
|
chat_id,
|
|
|
|
|
e,
|
|
|
|
|
exc_info=True,
|
|
|
|
|
)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
return {"name": chat_id, "type": "unknown"}
|
|
|
|
|
|
|
|
|
|
# ----- Internal handlers -----
|
|
|
|
|
|
2026-04-08 14:24:59 -06:00
|
|
|
def _assistant_thread_key(self, channel_id: str, thread_ts: str) -> Optional[Tuple[str, str]]:
|
|
|
|
|
"""Return a stable cache key for Slack assistant thread metadata."""
|
|
|
|
|
if not channel_id or not thread_ts:
|
|
|
|
|
return None
|
|
|
|
|
return (str(channel_id), str(thread_ts))
|
|
|
|
|
|
|
|
|
|
def _extract_assistant_thread_metadata(self, event: dict) -> Dict[str, str]:
|
|
|
|
|
"""Extract Slack Assistant thread identity data from an event payload."""
|
|
|
|
|
assistant_thread = event.get("assistant_thread") or {}
|
|
|
|
|
context = assistant_thread.get("context") or event.get("context") or {}
|
|
|
|
|
|
|
|
|
|
channel_id = (
|
|
|
|
|
assistant_thread.get("channel_id")
|
|
|
|
|
or event.get("channel")
|
|
|
|
|
or context.get("channel_id")
|
|
|
|
|
or ""
|
|
|
|
|
)
|
|
|
|
|
thread_ts = (
|
|
|
|
|
assistant_thread.get("thread_ts")
|
|
|
|
|
or event.get("thread_ts")
|
|
|
|
|
or event.get("message_ts")
|
|
|
|
|
or ""
|
|
|
|
|
)
|
|
|
|
|
user_id = (
|
|
|
|
|
assistant_thread.get("user_id")
|
|
|
|
|
or event.get("user")
|
|
|
|
|
or context.get("user_id")
|
|
|
|
|
or ""
|
|
|
|
|
)
|
|
|
|
|
team_id = (
|
|
|
|
|
event.get("team")
|
|
|
|
|
or event.get("team_id")
|
|
|
|
|
or assistant_thread.get("team_id")
|
|
|
|
|
or ""
|
|
|
|
|
)
|
|
|
|
|
context_channel_id = context.get("channel_id") or ""
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"channel_id": str(channel_id) if channel_id else "",
|
|
|
|
|
"thread_ts": str(thread_ts) if thread_ts else "",
|
|
|
|
|
"user_id": str(user_id) if user_id else "",
|
|
|
|
|
"team_id": str(team_id) if team_id else "",
|
|
|
|
|
"context_channel_id": str(context_channel_id) if context_channel_id else "",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _cache_assistant_thread_metadata(self, metadata: Dict[str, str]) -> None:
|
|
|
|
|
"""Remember assistant thread identity data for later message events."""
|
|
|
|
|
channel_id = metadata.get("channel_id", "")
|
|
|
|
|
thread_ts = metadata.get("thread_ts", "")
|
|
|
|
|
key = self._assistant_thread_key(channel_id, thread_ts)
|
|
|
|
|
if not key:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
existing = self._assistant_threads.get(key, {})
|
|
|
|
|
merged = dict(existing)
|
|
|
|
|
merged.update({k: v for k, v in metadata.items() if v})
|
|
|
|
|
self._assistant_threads[key] = merged
|
|
|
|
|
|
2026-04-08 22:44:22 -07:00
|
|
|
# Evict oldest entries when the cache exceeds the limit
|
|
|
|
|
if len(self._assistant_threads) > self._ASSISTANT_THREADS_MAX:
|
|
|
|
|
excess = len(self._assistant_threads) - self._ASSISTANT_THREADS_MAX // 2
|
|
|
|
|
for old_key in list(self._assistant_threads)[:excess]:
|
|
|
|
|
del self._assistant_threads[old_key]
|
|
|
|
|
|
2026-04-08 14:24:59 -06:00
|
|
|
team_id = merged.get("team_id", "")
|
|
|
|
|
if team_id and channel_id:
|
|
|
|
|
self._channel_team[channel_id] = team_id
|
|
|
|
|
|
|
|
|
|
def _lookup_assistant_thread_metadata(
|
|
|
|
|
self,
|
|
|
|
|
event: dict,
|
|
|
|
|
channel_id: str = "",
|
|
|
|
|
thread_ts: str = "",
|
|
|
|
|
) -> Dict[str, str]:
|
|
|
|
|
"""Load cached assistant-thread metadata that matches the current event."""
|
|
|
|
|
metadata = self._extract_assistant_thread_metadata(event)
|
|
|
|
|
if channel_id and not metadata.get("channel_id"):
|
|
|
|
|
metadata["channel_id"] = channel_id
|
|
|
|
|
if thread_ts and not metadata.get("thread_ts"):
|
|
|
|
|
metadata["thread_ts"] = thread_ts
|
|
|
|
|
|
|
|
|
|
key = self._assistant_thread_key(
|
|
|
|
|
metadata.get("channel_id", ""),
|
|
|
|
|
metadata.get("thread_ts", ""),
|
|
|
|
|
)
|
|
|
|
|
cached = self._assistant_threads.get(key, {}) if key else {}
|
|
|
|
|
if cached:
|
|
|
|
|
merged = dict(cached)
|
|
|
|
|
merged.update({k: v for k, v in metadata.items() if v})
|
|
|
|
|
return merged
|
|
|
|
|
return metadata
|
|
|
|
|
|
|
|
|
|
def _seed_assistant_thread_session(self, metadata: Dict[str, str]) -> None:
|
|
|
|
|
"""Prime the session store so assistant threads get stable user scoping."""
|
|
|
|
|
session_store = getattr(self, "_session_store", None)
|
|
|
|
|
if not session_store:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
channel_id = metadata.get("channel_id", "")
|
|
|
|
|
thread_ts = metadata.get("thread_ts", "")
|
|
|
|
|
user_id = metadata.get("user_id", "")
|
|
|
|
|
if not channel_id or not thread_ts or not user_id:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
source = self.build_source(
|
|
|
|
|
chat_id=channel_id,
|
|
|
|
|
chat_name=channel_id,
|
|
|
|
|
chat_type="dm",
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
thread_id=thread_ts,
|
|
|
|
|
chat_topic=metadata.get("context_channel_id") or None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
session_store.get_or_create_session(source)
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.debug(
|
|
|
|
|
"[Slack] Failed to seed assistant thread session for %s/%s",
|
|
|
|
|
channel_id,
|
|
|
|
|
thread_ts,
|
|
|
|
|
exc_info=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
async def _handle_assistant_thread_lifecycle_event(self, event: dict) -> None:
|
|
|
|
|
"""Handle Slack Assistant lifecycle events that carry user/thread identity."""
|
|
|
|
|
metadata = self._extract_assistant_thread_metadata(event)
|
|
|
|
|
self._cache_assistant_thread_metadata(metadata)
|
|
|
|
|
self._seed_assistant_thread_session(metadata)
|
|
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
async def _handle_slack_message(self, event: dict) -> None:
|
|
|
|
|
"""Handle an incoming Slack message event."""
|
fix: prevent duplicate messages — gateway dedup + partial stream guard (#4878)
* fix(gateway): add message deduplication to Discord and Slack adapters (#4777)
Discord RESUME replays events after reconnects (~7/day observed),
and Slack Socket Mode can redeliver events if the ack was lost.
Neither adapter tracked which messages were already processed,
causing duplicate bot responses.
Add _seen_messages dedup cache (message ID → timestamp) with 5-min
TTL and 2000-entry cap to both adapters, matching the pattern already
used by Mattermost, Matrix, WeCom, Feishu, DingTalk, and Email.
The check goes at the very top of the message handler, before any
other logic, so replayed events are silently dropped.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent duplicate messages on partial stream delivery
When streaming fails after tokens are already delivered to the platform,
_interruptible_streaming_api_call re-raised the error into the outer
retry loop, which would make a new API call — creating a duplicate
message.
Now checks deltas_were_sent before re-raising: if partial content was
already streamed, returns a stub response instead. The outer loop treats
the turn as complete (no retry, no fallback, no duplicate).
Inspired by PR #4871 (@trevorgordon981) which identified the bug.
This implementation avoids monkey-patching exception objects and keeps
the fix within the streaming call boundary.
---------
Co-authored-by: Mibayy <mibayy@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:53:52 -07:00
|
|
|
# Dedup: Slack Socket Mode can redeliver events after reconnects (#4777)
|
|
|
|
|
event_ts = event.get("ts", "")
|
refactor: extract shared helpers to deduplicate repeated code patterns (#7917)
* refactor: add shared helper modules for code deduplication
New modules:
- gateway/platforms/helpers.py: MessageDeduplicator, TextBatchAggregator,
strip_markdown, ThreadParticipationTracker, redact_phone
- hermes_cli/cli_output.py: print_info/success/warning/error, prompt helpers
- tools/path_security.py: validate_within_dir, has_traversal_component
- utils.py additions: safe_json_loads, read_json_file, read_jsonl,
append_jsonl, env_str/lower/int/bool helpers
- hermes_constants.py additions: get_config_path, get_skills_dir,
get_logs_dir, get_env_path
* refactor: migrate gateway adapters to shared helpers
- MessageDeduplicator: discord, slack, dingtalk, wecom, weixin, mattermost
- strip_markdown: bluebubbles, feishu, sms
- redact_phone: sms, signal
- ThreadParticipationTracker: discord, matrix
- _acquire/_release_platform_lock: telegram, discord, slack, whatsapp,
signal, weixin
Net -316 lines across 19 files.
* refactor: migrate CLI modules to shared helpers
- tools_config.py: use cli_output print/prompt + curses_radiolist (-117 lines)
- setup.py: use cli_output print helpers + curses_radiolist (-101 lines)
- mcp_config.py: use cli_output prompt (-15 lines)
- memory_setup.py: use curses_radiolist (-86 lines)
Net -263 lines across 5 files.
* refactor: migrate to shared utility helpers
- safe_json_loads: agent/display.py (4 sites)
- get_config_path: skill_utils.py, hermes_logging.py, hermes_time.py
- get_skills_dir: skill_utils.py, prompt_builder.py
- Token estimation dedup: skills_tool.py imports from model_metadata
- Path security: skills_tool, cronjob_tools, skill_manager_tool, credential_files
- Non-atomic YAML writes: doctor.py, config.py now use atomic_yaml_write
- Platform dict: new platforms.py, skills_config + tools_config derive from it
- Anthropic key: new get_anthropic_key() in auth.py, used by doctor/status/config/main
* test: update tests for shared helper migrations
- test_dingtalk: use _dedup.is_duplicate() instead of _is_duplicate()
- test_mattermost: use _dedup instead of _seen_posts/_prune_seen
- test_signal: import redact_phone from helpers instead of signal
- test_discord_connect: _platform_lock_identity instead of _token_lock_identity
- test_telegram_conflict: updated lock error message format
- test_skill_manager_tool: 'escapes' instead of 'boundary' in error msgs
2026-04-11 13:59:52 -07:00
|
|
|
if event_ts and self._dedup.is_duplicate(event_ts):
|
|
|
|
|
return
|
fix: prevent duplicate messages — gateway dedup + partial stream guard (#4878)
* fix(gateway): add message deduplication to Discord and Slack adapters (#4777)
Discord RESUME replays events after reconnects (~7/day observed),
and Slack Socket Mode can redeliver events if the ack was lost.
Neither adapter tracked which messages were already processed,
causing duplicate bot responses.
Add _seen_messages dedup cache (message ID → timestamp) with 5-min
TTL and 2000-entry cap to both adapters, matching the pattern already
used by Mattermost, Matrix, WeCom, Feishu, DingTalk, and Email.
The check goes at the very top of the message handler, before any
other logic, so replayed events are silently dropped.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent duplicate messages on partial stream delivery
When streaming fails after tokens are already delivered to the platform,
_interruptible_streaming_api_call re-raised the error into the outer
retry loop, which would make a new API call — creating a duplicate
message.
Now checks deltas_were_sent before re-raising: if partial content was
already streamed, returns a stub response instead. The outer loop treats
the turn as complete (no retry, no fallback, no duplicate).
Inspired by PR #4871 (@trevorgordon981) which identified the bug.
This implementation avoids monkey-patching exception objects and keeps
the fix within the streaming call boundary.
---------
Co-authored-by: Mibayy <mibayy@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:53:52 -07:00
|
|
|
|
2026-04-09 13:35:13 -07:00
|
|
|
# Bot message filtering (SLACK_ALLOW_BOTS / config allow_bots):
|
|
|
|
|
# "none" — ignore all bot messages (default, backward-compatible)
|
|
|
|
|
# "mentions" — accept bot messages only when they @mention us
|
|
|
|
|
# "all" — accept all bot messages (except our own)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
if event.get("bot_id") or event.get("subtype") == "bot_message":
|
2026-04-09 13:35:13 -07:00
|
|
|
allow_bots = self.config.extra.get("allow_bots", "")
|
|
|
|
|
if not allow_bots:
|
|
|
|
|
allow_bots = os.getenv("SLACK_ALLOW_BOTS", "none")
|
|
|
|
|
allow_bots = str(allow_bots).lower().strip()
|
|
|
|
|
if allow_bots == "none":
|
|
|
|
|
return
|
|
|
|
|
elif allow_bots == "mentions":
|
|
|
|
|
text_check = event.get("text", "")
|
|
|
|
|
if self._bot_user_id and f"<@{self._bot_user_id}>" not in text_check:
|
|
|
|
|
return
|
|
|
|
|
# "all" falls through to process the message
|
|
|
|
|
# Always ignore our own messages to prevent echo loops
|
|
|
|
|
msg_user = event.get("user", "")
|
|
|
|
|
if msg_user and self._bot_user_id and msg_user == self._bot_user_id:
|
|
|
|
|
return
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
|
|
|
|
# Ignore message edits and deletions
|
|
|
|
|
subtype = event.get("subtype")
|
|
|
|
|
if subtype in ("message_changed", "message_deleted"):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
text = event.get("text", "")
|
|
|
|
|
channel_id = event.get("channel", "")
|
|
|
|
|
ts = event.get("ts", "")
|
2026-04-08 14:24:59 -06:00
|
|
|
assistant_meta = self._lookup_assistant_thread_metadata(
|
|
|
|
|
event,
|
|
|
|
|
channel_id=channel_id,
|
|
|
|
|
thread_ts=event.get("thread_ts", ""),
|
|
|
|
|
)
|
|
|
|
|
user_id = event.get("user") or assistant_meta.get("user_id", "")
|
|
|
|
|
if not channel_id:
|
|
|
|
|
channel_id = assistant_meta.get("channel_id", "")
|
|
|
|
|
team_id = (
|
|
|
|
|
event.get("team")
|
|
|
|
|
or event.get("team_id")
|
|
|
|
|
or assistant_meta.get("team_id", "")
|
|
|
|
|
)
|
2026-03-30 01:51:48 -07:00
|
|
|
|
|
|
|
|
# Track which workspace owns this channel
|
|
|
|
|
if team_id and channel_id:
|
|
|
|
|
self._channel_team[channel_id] = team_id
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
|
|
|
|
# Determine if this is a DM or channel message
|
|
|
|
|
channel_type = event.get("channel_type", "")
|
2026-04-08 14:24:59 -06:00
|
|
|
if not channel_type and channel_id.startswith("D"):
|
|
|
|
|
channel_type = "im"
|
2026-04-09 13:36:04 -07:00
|
|
|
is_dm = channel_type in ("im", "mpim") # Both 1:1 and group DMs
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
# Build thread_ts for session keying.
|
|
|
|
|
# In channels: fall back to ts so each top-level @mention starts a
|
|
|
|
|
# new thread/session (the bot always replies in a thread).
|
2026-04-16 04:22:07 -07:00
|
|
|
# In DMs: fall back to ts so each top-level DM reply thread gets
|
|
|
|
|
# its own session key (matching channel behavior). Set
|
|
|
|
|
# dm_top_level_threads_as_sessions: false in config to revert to
|
|
|
|
|
# legacy single-session-per-DM-channel behavior.
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
if is_dm:
|
2026-04-16 04:22:07 -07:00
|
|
|
thread_ts = event.get("thread_ts") or assistant_meta.get("thread_ts")
|
|
|
|
|
if not thread_ts and self._dm_top_level_threads_as_sessions():
|
|
|
|
|
thread_ts = ts
|
fix: Slack thread handling — progress messages, responses, and session isolation
Three bugs fixed in the Slack adapter:
1. Tool progress messages leaked to main channel instead of thread.
Root cause: metadata key mismatch — gateway uses 'thread_id' but
Slack adapter checked for 'thread_ts'. Added _resolve_thread_ts()
helper that checks both keys with correct precedence.
2. Bot responses could escape threads for replies.
Root cause: reply_to was set to the child message's ts, but Slack
API needs the parent message's ts for thread_ts. Now metadata
thread_id (always the parent ts) takes priority over reply_to.
3. All Slack DMs shared one session key ('agent:main:slack:dm'),
so a long-running task blocked all other DM conversations.
Fix: DMs with thread_id now get per-thread session keys. Top-level
DMs still share one session for conversation continuity.
Additional fix: All Slack media methods (send_image, send_voice,
send_video, send_document, send_image_file) now accept metadata
parameter for thread routing. Previously they only accepted reply_to,
which caused media to silently fail to post in threads.
Session key behavior after this change:
- Slack channel @mention: creates thread, thread = session
- Slack thread reply: stays in thread, same session
- Slack DM (top-level): one continuous session
- Slack DM (threaded): per-thread session
- Other platforms: unchanged
2026-03-12 16:05:45 -07:00
|
|
|
else:
|
|
|
|
|
thread_ts = event.get("thread_ts") or ts # ts fallback for channels
|
|
|
|
|
|
2026-04-07 11:12:08 -07:00
|
|
|
# In channels, respond if:
|
2026-04-09 13:31:36 -07:00
|
|
|
# 0. Channel is in free_response_channels, OR require_mention is
|
|
|
|
|
# disabled — always process regardless of mention.
|
2026-04-07 11:12:08 -07:00
|
|
|
# 1. The bot is @mentioned in this message, OR
|
|
|
|
|
# 2. The message is a reply in a thread the bot started/participated in, OR
|
|
|
|
|
# 3. The message is in a thread where the bot was previously @mentioned, OR
|
|
|
|
|
# 4. There's an existing session for this thread (survives restarts)
|
2026-03-30 01:51:48 -07:00
|
|
|
bot_uid = self._team_bot_user_ids.get(team_id, self._bot_user_id)
|
2026-04-06 11:46:17 -04:00
|
|
|
is_mentioned = bot_uid and f"<@{bot_uid}>" in text
|
2026-04-07 11:12:08 -07:00
|
|
|
event_thread_ts = event.get("thread_ts")
|
|
|
|
|
is_thread_reply = bool(event_thread_ts and event_thread_ts != ts)
|
|
|
|
|
|
2026-04-09 13:31:36 -07:00
|
|
|
if not is_dm and bot_uid:
|
|
|
|
|
if channel_id in self._slack_free_response_channels():
|
|
|
|
|
pass # Free-response channel — always process
|
|
|
|
|
elif not self._slack_require_mention():
|
|
|
|
|
pass # Mention requirement disabled globally for Slack
|
|
|
|
|
elif not is_mentioned:
|
|
|
|
|
reply_to_bot_thread = (
|
|
|
|
|
is_thread_reply and event_thread_ts in self._bot_message_ts
|
2026-04-07 11:12:08 -07:00
|
|
|
)
|
2026-04-09 13:31:36 -07:00
|
|
|
in_mentioned_thread = (
|
|
|
|
|
event_thread_ts is not None
|
|
|
|
|
and event_thread_ts in self._mentioned_threads
|
|
|
|
|
)
|
|
|
|
|
has_session = (
|
|
|
|
|
is_thread_reply
|
|
|
|
|
and self._has_active_session_for_thread(
|
|
|
|
|
channel_id=channel_id,
|
|
|
|
|
thread_ts=event_thread_ts,
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
if not reply_to_bot_thread and not in_mentioned_thread and not has_session:
|
|
|
|
|
return
|
2026-04-07 11:12:08 -07:00
|
|
|
|
2026-04-06 11:46:17 -04:00
|
|
|
if is_mentioned:
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
# Strip the bot mention from the text
|
2026-03-30 01:51:48 -07:00
|
|
|
text = text.replace(f"<@{bot_uid}>", "").strip()
|
2026-04-07 11:12:08 -07:00
|
|
|
# Register this thread so all future messages auto-trigger the bot
|
|
|
|
|
if event_thread_ts:
|
|
|
|
|
self._mentioned_threads.add(event_thread_ts)
|
|
|
|
|
if len(self._mentioned_threads) > self._MENTIONED_THREADS_MAX:
|
|
|
|
|
to_remove = list(self._mentioned_threads)[:self._MENTIONED_THREADS_MAX // 2]
|
|
|
|
|
for t in to_remove:
|
|
|
|
|
self._mentioned_threads.discard(t)
|
|
|
|
|
|
|
|
|
|
# When entering a thread for the first time (no existing session),
|
|
|
|
|
# fetch thread context so the agent understands the conversation.
|
|
|
|
|
if is_thread_reply and not self._has_active_session_for_thread(
|
|
|
|
|
channel_id=channel_id,
|
|
|
|
|
thread_ts=event_thread_ts,
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
):
|
|
|
|
|
thread_context = await self._fetch_thread_context(
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
channel_id=channel_id,
|
|
|
|
|
thread_ts=event_thread_ts,
|
2026-04-07 11:12:08 -07:00
|
|
|
current_ts=ts,
|
|
|
|
|
team_id=team_id,
|
|
|
|
|
)
|
|
|
|
|
if thread_context:
|
|
|
|
|
text = thread_context + text
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
# Determine message type
|
|
|
|
|
msg_type = MessageType.TEXT
|
|
|
|
|
if text.startswith("/"):
|
|
|
|
|
msg_type = MessageType.COMMAND
|
|
|
|
|
|
|
|
|
|
# Handle file attachments
|
|
|
|
|
media_urls = []
|
|
|
|
|
media_types = []
|
|
|
|
|
files = event.get("files", [])
|
|
|
|
|
for f in files:
|
|
|
|
|
mimetype = f.get("mimetype", "unknown")
|
|
|
|
|
url = f.get("url_private_download") or f.get("url_private", "")
|
|
|
|
|
if mimetype.startswith("image/") and url:
|
|
|
|
|
try:
|
|
|
|
|
ext = "." + mimetype.split("/")[-1].split(";")[0]
|
|
|
|
|
if ext not in (".jpg", ".jpeg", ".png", ".gif", ".webp"):
|
|
|
|
|
ext = ".jpg"
|
|
|
|
|
# Slack private URLs require the bot token as auth header
|
2026-03-30 01:51:48 -07:00
|
|
|
cached = await self._download_slack_file(url, ext, team_id=team_id)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
media_urls.append(cached)
|
|
|
|
|
media_types.append(mimetype)
|
|
|
|
|
msg_type = MessageType.PHOTO
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
except Exception as e: # pragma: no cover - defensive logging
|
|
|
|
|
logger.warning("[Slack] Failed to cache image from %s: %s", url, e, exc_info=True)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
elif mimetype.startswith("audio/") and url:
|
|
|
|
|
try:
|
|
|
|
|
ext = "." + mimetype.split("/")[-1].split(";")[0]
|
|
|
|
|
if ext not in (".ogg", ".mp3", ".wav", ".webm", ".m4a"):
|
|
|
|
|
ext = ".ogg"
|
2026-03-30 01:51:48 -07:00
|
|
|
cached = await self._download_slack_file(url, ext, audio=True, team_id=team_id)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
media_urls.append(cached)
|
|
|
|
|
media_types.append(mimetype)
|
|
|
|
|
msg_type = MessageType.VOICE
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
except Exception as e: # pragma: no cover - defensive logging
|
|
|
|
|
logger.warning("[Slack] Failed to cache audio from %s: %s", url, e, exc_info=True)
|
2026-03-09 13:02:59 -07:00
|
|
|
elif url:
|
|
|
|
|
# Try to handle as a document attachment
|
|
|
|
|
try:
|
|
|
|
|
original_filename = f.get("name", "")
|
|
|
|
|
ext = ""
|
|
|
|
|
if original_filename:
|
|
|
|
|
_, ext = os.path.splitext(original_filename)
|
|
|
|
|
ext = ext.lower()
|
|
|
|
|
|
|
|
|
|
# Fallback: reverse-lookup from MIME type
|
|
|
|
|
if not ext and mimetype:
|
|
|
|
|
mime_to_ext = {v: k for k, v in SUPPORTED_DOCUMENT_TYPES.items()}
|
|
|
|
|
ext = mime_to_ext.get(mimetype, "")
|
|
|
|
|
|
|
|
|
|
if ext not in SUPPORTED_DOCUMENT_TYPES:
|
|
|
|
|
continue # Skip unsupported file types silently
|
|
|
|
|
|
|
|
|
|
# Check file size (Slack limit: 20 MB for bots)
|
|
|
|
|
file_size = f.get("size", 0)
|
|
|
|
|
MAX_DOC_BYTES = 20 * 1024 * 1024
|
|
|
|
|
if not file_size or file_size > MAX_DOC_BYTES:
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
logger.warning("[Slack] Document too large or unknown size: %s", file_size)
|
2026-03-09 13:02:59 -07:00
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Download and cache
|
2026-03-30 01:51:48 -07:00
|
|
|
raw_bytes = await self._download_slack_file_bytes(url, team_id=team_id)
|
2026-03-09 13:02:59 -07:00
|
|
|
cached_path = cache_document_from_bytes(
|
|
|
|
|
raw_bytes, original_filename or f"document{ext}"
|
|
|
|
|
)
|
|
|
|
|
doc_mime = SUPPORTED_DOCUMENT_TYPES[ext]
|
|
|
|
|
media_urls.append(cached_path)
|
|
|
|
|
media_types.append(doc_mime)
|
|
|
|
|
msg_type = MessageType.DOCUMENT
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
logger.debug("[Slack] Cached user document: %s", cached_path)
|
2026-03-09 13:02:59 -07:00
|
|
|
|
|
|
|
|
# Inject text content for .txt/.md files (capped at 100 KB)
|
|
|
|
|
MAX_TEXT_INJECT_BYTES = 100 * 1024
|
|
|
|
|
if ext in (".md", ".txt") and len(raw_bytes) <= MAX_TEXT_INJECT_BYTES:
|
|
|
|
|
try:
|
|
|
|
|
text_content = raw_bytes.decode("utf-8")
|
|
|
|
|
display_name = original_filename or f"document{ext}"
|
|
|
|
|
display_name = re.sub(r'[^\w.\- ]', '_', display_name)
|
|
|
|
|
injection = f"[Content of {display_name}]:\n{text_content}"
|
|
|
|
|
if text:
|
|
|
|
|
text = f"{injection}\n\n{text}"
|
|
|
|
|
else:
|
|
|
|
|
text = injection
|
|
|
|
|
except UnicodeDecodeError:
|
|
|
|
|
pass # Binary content, skip injection
|
|
|
|
|
|
refactor(slack): replace print statements with structured logging
Replaces all ad-hoc print() calls in the Slack gateway adapter with
proper logging.getLogger(__name__) calls, matching the pattern already
used by every other platform adapter (telegram, discord, whatsapp,
signal, homeassistant).
Changes:
- Add import logging + module-level logger
- Use logger.error for failures, logger.warning for non-critical
fallbacks, logger.info for status, logger.debug for routine ops
- Add exc_info=True for full stack traces on all error/warning paths
- Use %s format strings (lazy evaluation) instead of f-strings
- Wrap disconnect() in try/except for safety
- Add structured context (file paths, channel IDs, URLs) to log messages
- Convert document handling prints added after the original PR
Cherry-picked from PR #778 by aydnOktay, rebased onto current main
with conflict resolution and extended to cover document/video methods
added since the PR was created.
Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
2026-03-11 05:34:43 -07:00
|
|
|
except Exception as e: # pragma: no cover - defensive logging
|
|
|
|
|
logger.warning("[Slack] Failed to cache document from %s: %s", url, e, exc_info=True)
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
# Resolve user display name (cached after first lookup)
|
2026-03-30 01:51:48 -07:00
|
|
|
user_name = await self._resolve_user_name(user_id, chat_id=channel_id)
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
# Build source
|
|
|
|
|
source = self.build_source(
|
|
|
|
|
chat_id=channel_id,
|
|
|
|
|
chat_name=channel_id, # Will be resolved later if needed
|
|
|
|
|
chat_type="dm" if is_dm else "group",
|
|
|
|
|
user_id=user_id,
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
user_name=user_name,
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
thread_id=thread_ts,
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-15 16:26:26 -07:00
|
|
|
# Per-channel ephemeral prompt
|
|
|
|
|
from gateway.platforms.base import resolve_channel_prompt
|
|
|
|
|
_channel_prompt = resolve_channel_prompt(
|
|
|
|
|
self.config.extra, channel_id, None,
|
|
|
|
|
)
|
|
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
msg_event = MessageEvent(
|
|
|
|
|
text=text,
|
|
|
|
|
message_type=msg_type,
|
|
|
|
|
source=source,
|
|
|
|
|
raw_message=event,
|
|
|
|
|
message_id=ts,
|
|
|
|
|
media_urls=media_urls,
|
|
|
|
|
media_types=media_types,
|
|
|
|
|
reply_to_message_id=thread_ts if thread_ts != ts else None,
|
2026-04-15 16:26:26 -07:00
|
|
|
channel_prompt=_channel_prompt,
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
)
|
|
|
|
|
|
2026-04-09 13:36:04 -07:00
|
|
|
# Only react when bot is directly addressed (DM or @mention).
|
|
|
|
|
# In listen-all channels (require_mention=false), reacting to every
|
|
|
|
|
# casual message would be noisy.
|
|
|
|
|
_should_react = is_dm or is_mentioned
|
|
|
|
|
|
|
|
|
|
if _should_react:
|
|
|
|
|
await self._add_reaction(channel_id, ts, "eyes")
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
await self.handle_message(msg_event)
|
|
|
|
|
|
2026-04-09 13:36:04 -07:00
|
|
|
if _should_react:
|
|
|
|
|
await self._remove_reaction(channel_id, ts, "eyes")
|
|
|
|
|
await self._add_reaction(channel_id, ts, "white_check_mark")
|
feat: Slack adapter improvements — formatting, reactions, user resolution, commands
1. Markdown → mrkdwn conversion (format_message override):
- **bold** → *bold*, *italic* → _italic_
- ## Headers → *Headers* (bold)
- [link](url) → <url|link>
- ~~strike~~ → ~strike~
- Code blocks and inline code preserved unchanged
- Placeholder-based approach (same pattern as Telegram)
2. Message length splitting:
- send() now calls format_message() + truncate_message()
- Long responses split at natural boundaries (newlines, spaces)
- Code blocks properly closed/reopened across chunks
- Chunk indicators (1/N) appended for multi-part messages
3. Reaction-based acknowledgment:
- 👀 (eyes) reaction added on message receipt
- Replaced with ✅ (white_check_mark) when response is complete
- Graceful error handling (missing scopes, already-reacted)
- Serves as visual feedback since Slack has no bot typing API
4. User identity resolution:
- Resolves Slack user IDs to display names via users.info API
- LRU-style in-memory cache (one API call per user)
- Fallback chain: display_name → real_name → user_id
- user_name now included in MessageEvent source
5. Expanded slash commands (/hermes <subcommand>):
- Added: compact, compress, resume, background, usage,
insights, title, reasoning, provider, rollback
- Arguments preserved (e.g. /hermes resume my session)
6. reply_broadcast config option:
- When gateway.slack.reply_broadcast is true, first response
in a thread also appears in the main channel
- Disabled by default — thread = session stays clean
30 new tests covering all features.
2026-03-12 16:22:39 -07:00
|
|
|
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
# ----- Approval button support (Block Kit) -----
|
|
|
|
|
|
|
|
|
|
async def send_exec_approval(
|
|
|
|
|
self, chat_id: str, command: str, session_key: str,
|
|
|
|
|
description: str = "dangerous command",
|
|
|
|
|
metadata: Optional[Dict[str, Any]] = None,
|
|
|
|
|
) -> SendResult:
|
|
|
|
|
"""Send a Block Kit approval prompt with interactive buttons.
|
|
|
|
|
|
|
|
|
|
The buttons call ``resolve_gateway_approval()`` to unblock the waiting
|
|
|
|
|
agent thread — same mechanism as the text ``/approve`` flow.
|
|
|
|
|
"""
|
|
|
|
|
if not self._app:
|
|
|
|
|
return SendResult(success=False, error="Not connected")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
cmd_preview = command[:2900] + "..." if len(command) > 2900 else command
|
|
|
|
|
thread_ts = self._resolve_thread_ts(None, metadata)
|
|
|
|
|
|
|
|
|
|
blocks = [
|
|
|
|
|
{
|
|
|
|
|
"type": "section",
|
|
|
|
|
"text": {
|
|
|
|
|
"type": "mrkdwn",
|
|
|
|
|
"text": (
|
|
|
|
|
f":warning: *Command Approval Required*\n"
|
|
|
|
|
f"```{cmd_preview}```\n"
|
|
|
|
|
f"Reason: {description}"
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"type": "actions",
|
|
|
|
|
"elements": [
|
|
|
|
|
{
|
|
|
|
|
"type": "button",
|
|
|
|
|
"text": {"type": "plain_text", "text": "Allow Once"},
|
|
|
|
|
"style": "primary",
|
|
|
|
|
"action_id": "hermes_approve_once",
|
|
|
|
|
"value": session_key,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"type": "button",
|
|
|
|
|
"text": {"type": "plain_text", "text": "Allow Session"},
|
|
|
|
|
"action_id": "hermes_approve_session",
|
|
|
|
|
"value": session_key,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"type": "button",
|
|
|
|
|
"text": {"type": "plain_text", "text": "Always Allow"},
|
|
|
|
|
"action_id": "hermes_approve_always",
|
|
|
|
|
"value": session_key,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"type": "button",
|
|
|
|
|
"text": {"type": "plain_text", "text": "Deny"},
|
|
|
|
|
"style": "danger",
|
|
|
|
|
"action_id": "hermes_deny",
|
|
|
|
|
"value": session_key,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
kwargs: Dict[str, Any] = {
|
|
|
|
|
"channel": chat_id,
|
|
|
|
|
"text": f"⚠️ Command approval required: {cmd_preview[:100]}",
|
|
|
|
|
"blocks": blocks,
|
|
|
|
|
}
|
|
|
|
|
if thread_ts:
|
|
|
|
|
kwargs["thread_ts"] = thread_ts
|
|
|
|
|
|
|
|
|
|
result = await self._get_client(chat_id).chat_postMessage(**kwargs)
|
|
|
|
|
msg_ts = result.get("ts", "")
|
|
|
|
|
if msg_ts:
|
|
|
|
|
self._approval_resolved[msg_ts] = False
|
|
|
|
|
|
|
|
|
|
return SendResult(success=True, message_id=msg_ts, raw_response=result)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("[Slack] send_exec_approval failed: %s", e, exc_info=True)
|
|
|
|
|
return SendResult(success=False, error=str(e))
|
|
|
|
|
|
|
|
|
|
async def _handle_approval_action(self, ack, body, action) -> None:
|
|
|
|
|
"""Handle an approval button click from Block Kit."""
|
|
|
|
|
await ack()
|
|
|
|
|
|
|
|
|
|
action_id = action.get("action_id", "")
|
|
|
|
|
session_key = action.get("value", "")
|
|
|
|
|
message = body.get("message", {})
|
|
|
|
|
msg_ts = message.get("ts", "")
|
|
|
|
|
channel_id = body.get("channel", {}).get("id", "")
|
|
|
|
|
user_name = body.get("user", {}).get("name", "unknown")
|
2026-04-09 13:26:37 -07:00
|
|
|
user_id = body.get("user", {}).get("id", "")
|
|
|
|
|
|
|
|
|
|
# Only authorized users may click approval buttons. Button clicks
|
|
|
|
|
# bypass the normal message auth flow in gateway/run.py, so we must
|
|
|
|
|
# check here as well.
|
|
|
|
|
allowed_csv = os.getenv("SLACK_ALLOWED_USERS", "").strip()
|
|
|
|
|
if allowed_csv:
|
|
|
|
|
allowed_ids = {uid.strip() for uid in allowed_csv.split(",") if uid.strip()}
|
|
|
|
|
if "*" not in allowed_ids and user_id not in allowed_ids:
|
|
|
|
|
logger.warning(
|
|
|
|
|
"[Slack] Unauthorized approval click by %s (%s) — ignoring",
|
|
|
|
|
user_name, user_id,
|
|
|
|
|
)
|
|
|
|
|
return
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
|
|
|
|
|
# Map action_id to approval choice
|
|
|
|
|
choice_map = {
|
|
|
|
|
"hermes_approve_once": "once",
|
|
|
|
|
"hermes_approve_session": "session",
|
|
|
|
|
"hermes_approve_always": "always",
|
|
|
|
|
"hermes_deny": "deny",
|
|
|
|
|
}
|
|
|
|
|
choice = choice_map.get(action_id, "deny")
|
|
|
|
|
|
2026-04-10 00:20:35 +08:00
|
|
|
# Prevent double-clicks — atomic pop; first caller gets False, others get True (default)
|
|
|
|
|
if self._approval_resolved.pop(msg_ts, True):
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Update the message to show the decision and remove buttons
|
|
|
|
|
label_map = {
|
|
|
|
|
"once": f"✅ Approved once by {user_name}",
|
|
|
|
|
"session": f"✅ Approved for session by {user_name}",
|
|
|
|
|
"always": f"✅ Approved permanently by {user_name}",
|
|
|
|
|
"deny": f"❌ Denied by {user_name}",
|
|
|
|
|
}
|
|
|
|
|
decision_text = label_map.get(choice, f"Resolved by {user_name}")
|
|
|
|
|
|
|
|
|
|
# Get original text from the section block
|
|
|
|
|
original_text = ""
|
|
|
|
|
for block in message.get("blocks", []):
|
|
|
|
|
if block.get("type") == "section":
|
|
|
|
|
original_text = block.get("text", {}).get("text", "")
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
updated_blocks = [
|
|
|
|
|
{
|
|
|
|
|
"type": "section",
|
|
|
|
|
"text": {
|
|
|
|
|
"type": "mrkdwn",
|
|
|
|
|
"text": original_text or "Command approval request",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"type": "context",
|
|
|
|
|
"elements": [
|
|
|
|
|
{"type": "mrkdwn", "text": decision_text},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
await self._get_client(channel_id).chat_update(
|
|
|
|
|
channel=channel_id,
|
|
|
|
|
ts=msg_ts,
|
|
|
|
|
text=decision_text,
|
|
|
|
|
blocks=updated_blocks,
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.warning("[Slack] Failed to update approval message: %s", e)
|
|
|
|
|
|
|
|
|
|
# Resolve the approval — this unblocks the agent thread
|
|
|
|
|
try:
|
|
|
|
|
from tools.approval import resolve_gateway_approval
|
|
|
|
|
count = resolve_gateway_approval(session_key, choice)
|
|
|
|
|
logger.info(
|
|
|
|
|
"Slack button resolved %d approval(s) for session %s (choice=%s, user=%s)",
|
|
|
|
|
count, session_key, choice, user_name,
|
|
|
|
|
)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
logger.error("Failed to resolve gateway approval from Slack button: %s", exc)
|
|
|
|
|
|
2026-04-10 00:20:35 +08:00
|
|
|
# (approval state already consumed by atomic pop above)
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
|
|
|
|
|
# ----- Thread context fetching -----
|
|
|
|
|
|
|
|
|
|
async def _fetch_thread_context(
|
|
|
|
|
self, channel_id: str, thread_ts: str, current_ts: str,
|
|
|
|
|
team_id: str = "", limit: int = 30,
|
|
|
|
|
) -> str:
|
|
|
|
|
"""Fetch recent thread messages to provide context when the bot is
|
|
|
|
|
mentioned mid-thread for the first time.
|
|
|
|
|
|
2026-04-09 13:37:15 -07:00
|
|
|
This method is only called when there is NO active session for the
|
|
|
|
|
thread (guarded at the call site by _has_active_session_for_thread).
|
|
|
|
|
That guard ensures thread messages are prepended only on the very
|
|
|
|
|
first turn — after that the session history already holds them, so
|
|
|
|
|
there is no duplication across subsequent turns.
|
|
|
|
|
|
|
|
|
|
Results are cached for _THREAD_CACHE_TTL seconds per thread to avoid
|
|
|
|
|
hammering conversations.replies (Tier 3, ~50 req/min).
|
|
|
|
|
|
|
|
|
|
Returns a formatted string with prior thread history, or empty string
|
|
|
|
|
on failure or if the thread has no prior messages.
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
"""
|
2026-04-09 13:37:15 -07:00
|
|
|
cache_key = f"{channel_id}:{thread_ts}"
|
|
|
|
|
now = time.monotonic()
|
|
|
|
|
cached = self._thread_context_cache.get(cache_key)
|
|
|
|
|
if cached and (now - cached.fetched_at) < self._THREAD_CACHE_TTL:
|
|
|
|
|
return cached.content
|
|
|
|
|
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
try:
|
|
|
|
|
client = self._get_client(channel_id)
|
2026-04-09 13:37:15 -07:00
|
|
|
|
|
|
|
|
# Retry with exponential backoff for Tier-3 rate limits (429).
|
|
|
|
|
result = None
|
|
|
|
|
for attempt in range(3):
|
|
|
|
|
try:
|
|
|
|
|
result = await client.conversations_replies(
|
|
|
|
|
channel=channel_id,
|
|
|
|
|
ts=thread_ts,
|
|
|
|
|
limit=limit + 1, # +1 because it includes the current message
|
|
|
|
|
inclusive=True,
|
|
|
|
|
)
|
|
|
|
|
break
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
# Check for rate-limit error from slack_sdk
|
|
|
|
|
err_str = str(exc).lower()
|
|
|
|
|
is_rate_limit = (
|
|
|
|
|
"ratelimited" in err_str
|
|
|
|
|
or "429" in err_str
|
|
|
|
|
or "rate_limited" in err_str
|
|
|
|
|
)
|
|
|
|
|
if is_rate_limit and attempt < 2:
|
|
|
|
|
retry_after = 1.0 * (2 ** attempt) # 1s, 2s
|
|
|
|
|
logger.warning(
|
|
|
|
|
"[Slack] conversations.replies rate limited; retrying in %.1fs (attempt %d/3)",
|
|
|
|
|
retry_after, attempt + 1,
|
|
|
|
|
)
|
|
|
|
|
await asyncio.sleep(retry_after)
|
|
|
|
|
continue
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
if result is None:
|
|
|
|
|
return ""
|
|
|
|
|
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
messages = result.get("messages", [])
|
|
|
|
|
if not messages:
|
|
|
|
|
return ""
|
|
|
|
|
|
2026-04-09 13:37:15 -07:00
|
|
|
bot_uid = self._team_bot_user_ids.get(team_id, self._bot_user_id)
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
context_parts = []
|
|
|
|
|
for msg in messages:
|
|
|
|
|
msg_ts = msg.get("ts", "")
|
2026-04-09 13:37:15 -07:00
|
|
|
# Exclude the current triggering message — it will be delivered
|
|
|
|
|
# as the user message itself, so including it here would duplicate it.
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
if msg_ts == current_ts:
|
|
|
|
|
continue
|
2026-04-09 13:37:15 -07:00
|
|
|
# Exclude our own bot messages to avoid circular context.
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
if msg.get("bot_id") or msg.get("subtype") == "bot_message":
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
msg_text = msg.get("text", "").strip()
|
|
|
|
|
if not msg_text:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Strip bot mentions from context messages
|
|
|
|
|
if bot_uid:
|
|
|
|
|
msg_text = msg_text.replace(f"<@{bot_uid}>", "").strip()
|
|
|
|
|
|
2026-04-09 13:37:15 -07:00
|
|
|
msg_user = msg.get("user", "unknown")
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
is_parent = msg_ts == thread_ts
|
|
|
|
|
prefix = "[thread parent] " if is_parent else ""
|
|
|
|
|
name = await self._resolve_user_name(msg_user, chat_id=channel_id)
|
|
|
|
|
context_parts.append(f"{prefix}{name}: {msg_text}")
|
|
|
|
|
|
2026-04-09 13:37:15 -07:00
|
|
|
content = ""
|
|
|
|
|
if context_parts:
|
|
|
|
|
content = (
|
|
|
|
|
"[Thread context — prior messages in this thread (not yet in conversation history):]\n"
|
|
|
|
|
+ "\n".join(context_parts)
|
|
|
|
|
+ "\n[End of thread context]\n\n"
|
|
|
|
|
)
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
|
2026-04-09 13:37:15 -07:00
|
|
|
self._thread_context_cache[cache_key] = _ThreadContextCache(
|
|
|
|
|
content=content,
|
|
|
|
|
fetched_at=now,
|
|
|
|
|
message_count=len(context_parts),
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
)
|
2026-04-09 13:37:15 -07:00
|
|
|
return content
|
|
|
|
|
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
except Exception as e:
|
|
|
|
|
logger.warning("[Slack] Failed to fetch thread context: %s", e)
|
|
|
|
|
return ""
|
|
|
|
|
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
async def _handle_slash_command(self, command: dict) -> None:
|
|
|
|
|
"""Handle /hermes slash command."""
|
|
|
|
|
text = command.get("text", "").strip()
|
|
|
|
|
user_id = command.get("user_id", "")
|
|
|
|
|
channel_id = command.get("channel_id", "")
|
2026-03-30 01:51:48 -07:00
|
|
|
team_id = command.get("team_id", "")
|
|
|
|
|
|
|
|
|
|
# Track which workspace owns this channel
|
|
|
|
|
if team_id and channel_id:
|
|
|
|
|
self._channel_team[channel_id] = team_id
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
2026-03-16 23:21:03 -07:00
|
|
|
# Map subcommands to gateway commands — derived from central registry.
|
|
|
|
|
# Also keep "compact" as a Slack-specific alias for /compress.
|
|
|
|
|
from hermes_cli.commands import slack_subcommand_map
|
|
|
|
|
subcommand_map = slack_subcommand_map()
|
|
|
|
|
subcommand_map["compact"] = "/compress"
|
2026-02-19 14:31:53 -08:00
|
|
|
first_word = text.split()[0] if text else ""
|
|
|
|
|
if first_word in subcommand_map:
|
|
|
|
|
# Preserve arguments after the subcommand
|
|
|
|
|
rest = text[len(first_word):].strip()
|
|
|
|
|
text = f"{subcommand_map[first_word]} {rest}".strip() if rest else subcommand_map[first_word]
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
elif text:
|
|
|
|
|
pass # Treat as a regular question
|
|
|
|
|
else:
|
|
|
|
|
text = "/help"
|
|
|
|
|
|
|
|
|
|
source = self.build_source(
|
|
|
|
|
chat_id=channel_id,
|
|
|
|
|
chat_type="dm", # Slash commands are always in DM-like context
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
event = MessageEvent(
|
|
|
|
|
text=text,
|
|
|
|
|
message_type=MessageType.COMMAND if text.startswith("/") else MessageType.TEXT,
|
|
|
|
|
source=source,
|
|
|
|
|
raw_message=command,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
await self.handle_message(event)
|
|
|
|
|
|
2026-04-06 11:46:17 -04:00
|
|
|
def _has_active_session_for_thread(
|
|
|
|
|
self,
|
|
|
|
|
channel_id: str,
|
|
|
|
|
thread_ts: str,
|
|
|
|
|
user_id: str,
|
|
|
|
|
) -> bool:
|
|
|
|
|
"""Check if there's an active session for a thread.
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
|
2026-04-06 11:46:17 -04:00
|
|
|
Used to determine if thread replies without @mentions should be
|
|
|
|
|
processed (they should if there's an active session).
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
|
|
|
|
|
Uses ``build_session_key()`` as the single source of truth for key
|
|
|
|
|
construction — avoids the bug where manual key building didn't
|
|
|
|
|
respect ``thread_sessions_per_user`` and ``group_sessions_per_user``
|
|
|
|
|
settings correctly.
|
2026-04-06 11:46:17 -04:00
|
|
|
"""
|
|
|
|
|
session_store = getattr(self, "_session_store", None)
|
|
|
|
|
if not session_store:
|
|
|
|
|
return False
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
|
2026-04-06 11:46:17 -04:00
|
|
|
try:
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
from gateway.session import SessionSource, build_session_key
|
refactor: codebase-wide lint cleanup — unused imports, dead code, and inefficient patterns (#5821)
Comprehensive cleanup across 80 files based on automated (ruff, pyflakes, vulture)
and manual analysis of the entire codebase.
Changes by category:
Unused imports removed (~95 across 55 files):
- Removed genuinely unused imports from all major subsystems
- agent/, hermes_cli/, tools/, gateway/, plugins/, cron/
- Includes imports in try/except blocks that were truly unused
(vs availability checks which were left alone)
Unused variables removed (~25):
- Removed dead variables: connected, inner, channels, last_exc,
source, new_server_names, verify, pconfig, default_terminal,
result, pending_handled, temperature, loop
- Dropped unused argparse subparser assignments in hermes_cli/main.py
(12 instances of add_parser() where result was never used)
Dead code removed:
- run_agent.py: Removed dead ternary (None if False else None) and
surrounding unreachable branch in identity fallback
- run_agent.py: Removed write-only attribute _last_reported_tool
- hermes_cli/providers.py: Removed dead @property decorator on
module-level function (decorator has no effect outside a class)
- gateway/run.py: Removed unused MCP config load before reconnect
- gateway/platforms/slack.py: Removed dead SessionSource construction
Undefined name bugs fixed (would cause NameError at runtime):
- batch_runner.py: Added missing logger = logging.getLogger(__name__)
- tools/environments/daytona.py: Added missing Dict and Path imports
Unnecessary global statements removed (14):
- tools/terminal_tool.py: 5 functions declared global for dicts
they only mutated via .pop()/[key]=value (no rebinding)
- tools/browser_tool.py: cleanup thread loop only reads flag
- tools/rl_training_tool.py: 4 functions only do dict mutations
- tools/mcp_oauth.py: only reads the global
- hermes_time.py: only reads cached values
Inefficient patterns fixed:
- startswith/endswith tuple form: 15 instances of
x.startswith('a') or x.startswith('b') consolidated to
x.startswith(('a', 'b'))
- len(x)==0 / len(x)>0: 13 instances replaced with pythonic
truthiness checks (not x / bool(x))
- in dict.keys(): 5 instances simplified to in dict
- Redefined unused name: removed duplicate _strip_mdv2 import in
send_message_tool.py
Other fixes:
- hermes_cli/doctor.py: Replaced undefined logger.debug() with pass
- hermes_cli/config.py: Consolidated chained .endswith() calls
Test results: 3934 passed, 17 failed (all pre-existing on main),
19 skipped. Zero regressions.
2026-04-07 10:25:31 -07:00
|
|
|
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
source = SessionSource(
|
|
|
|
|
platform=Platform.SLACK,
|
|
|
|
|
chat_id=channel_id,
|
|
|
|
|
chat_type="group",
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
thread_id=thread_ts,
|
2026-04-06 11:46:17 -04:00
|
|
|
)
|
feat(gateway): approval buttons for Slack & Telegram + Slack thread context (#5890)
Slack:
- Add Block Kit interactive buttons for command approval (Allow Once,
Allow Session, Always Allow, Deny) via send_exec_approval()
- Register @app.action handlers for each approval button
- Add _fetch_thread_context() — fetches thread history via
conversations.replies when bot is first @mentioned mid-thread
- Fix _has_active_session_for_thread() to use build_session_key()
instead of manual key construction (fixes session key mismatch bug
where thread_sessions_per_user flag was ignored, ref PR #5833)
Telegram:
- Add InlineKeyboard approval buttons via send_exec_approval()
- Add ea:* callback handling in _handle_callback_query()
- Uses monotonic counter + _approval_state dict to map button clicks
back to session keys (avoids 64-byte callback_data limit)
Both platforms now auto-detected by the gateway runner's
_approval_notify_sync() — any adapter with send_exec_approval() on
its class gets button-based approval instead of text fallback.
Inspired by community PRs #3898 (LevSky22), #2953 (ygd58), #5833
(heathley). Implemented fresh on current main.
Tests: 24 new tests covering button rendering, action handling,
thread context fetching, session key fix, double-click prevention.
2026-04-07 11:03:14 -07:00
|
|
|
|
|
|
|
|
# Read session isolation settings from the store's config
|
|
|
|
|
store_cfg = getattr(session_store, "config", None)
|
|
|
|
|
gspu = getattr(store_cfg, "group_sessions_per_user", True) if store_cfg else True
|
|
|
|
|
tspu = getattr(store_cfg, "thread_sessions_per_user", False) if store_cfg else False
|
|
|
|
|
|
|
|
|
|
session_key = build_session_key(
|
|
|
|
|
source,
|
|
|
|
|
group_sessions_per_user=gspu,
|
|
|
|
|
thread_sessions_per_user=tspu,
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-06 11:46:17 -04:00
|
|
|
session_store._ensure_loaded()
|
|
|
|
|
return session_key in session_store._entries
|
|
|
|
|
except Exception:
|
|
|
|
|
return False
|
|
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
async def _download_slack_file(self, url: str, ext: str, audio: bool = False, team_id: str = "") -> str:
|
2026-03-26 19:33:18 -07:00
|
|
|
"""Download a Slack file using the bot token for auth, with retry."""
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
import httpx
|
|
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
bot_token = self._team_clients[team_id].token if team_id and team_id in self._team_clients else self.config.token
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
|
2026-03-26 19:33:18 -07:00
|
|
|
async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
|
|
|
|
|
for attempt in range(3):
|
|
|
|
|
try:
|
|
|
|
|
response = await client.get(
|
|
|
|
|
url,
|
|
|
|
|
headers={"Authorization": f"Bearer {bot_token}"},
|
|
|
|
|
)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
|
2026-04-10 03:52:46 -07:00
|
|
|
# Slack may return an HTML sign-in/redirect page
|
|
|
|
|
# instead of actual media bytes (e.g. expired token,
|
|
|
|
|
# restricted file access). Detect this early so we
|
|
|
|
|
# don't cache bogus data and confuse downstream tools.
|
|
|
|
|
ct = response.headers.get("content-type", "")
|
|
|
|
|
if "text/html" in ct:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Slack returned HTML instead of media "
|
|
|
|
|
f"(content-type: {ct}); "
|
|
|
|
|
"check bot token scopes and file permissions"
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-26 19:33:18 -07:00
|
|
|
if audio:
|
|
|
|
|
from gateway.platforms.base import cache_audio_from_bytes
|
|
|
|
|
return cache_audio_from_bytes(response.content, ext)
|
|
|
|
|
else:
|
|
|
|
|
from gateway.platforms.base import cache_image_from_bytes
|
|
|
|
|
return cache_image_from_bytes(response.content, ext)
|
|
|
|
|
except (httpx.TimeoutException, httpx.HTTPStatusError) as exc:
|
|
|
|
|
if isinstance(exc, httpx.HTTPStatusError) and exc.response.status_code < 429:
|
|
|
|
|
raise
|
|
|
|
|
if attempt < 2:
|
|
|
|
|
logger.debug("Slack file download retry %d/2 for %s: %s",
|
|
|
|
|
attempt + 1, url[:80], exc)
|
|
|
|
|
await asyncio.sleep(1.5 * (attempt + 1))
|
|
|
|
|
continue
|
|
|
|
|
raise
|
2026-03-09 13:02:59 -07:00
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
async def _download_slack_file_bytes(self, url: str, team_id: str = "") -> bytes:
|
2026-03-26 19:33:18 -07:00
|
|
|
"""Download a Slack file and return raw bytes, with retry."""
|
2026-03-09 13:02:59 -07:00
|
|
|
import httpx
|
|
|
|
|
|
2026-03-30 01:51:48 -07:00
|
|
|
bot_token = self._team_clients[team_id].token if team_id and team_id in self._team_clients else self.config.token
|
2026-03-26 19:33:18 -07:00
|
|
|
|
2026-03-09 13:02:59 -07:00
|
|
|
async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
|
2026-03-26 19:33:18 -07:00
|
|
|
for attempt in range(3):
|
|
|
|
|
try:
|
|
|
|
|
response = await client.get(
|
|
|
|
|
url,
|
|
|
|
|
headers={"Authorization": f"Bearer {bot_token}"},
|
|
|
|
|
)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
return response.content
|
|
|
|
|
except (httpx.TimeoutException, httpx.HTTPStatusError) as exc:
|
|
|
|
|
if isinstance(exc, httpx.HTTPStatusError) and exc.response.status_code < 429:
|
|
|
|
|
raise
|
|
|
|
|
if attempt < 2:
|
|
|
|
|
logger.debug("Slack file download retry %d/2 for %s: %s",
|
|
|
|
|
attempt + 1, url[:80], exc)
|
|
|
|
|
await asyncio.sleep(1.5 * (attempt + 1))
|
|
|
|
|
continue
|
|
|
|
|
raise
|
2026-04-09 13:31:36 -07:00
|
|
|
|
|
|
|
|
# ── Channel mention gating ─────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
def _slack_require_mention(self) -> bool:
|
|
|
|
|
"""Return whether channel messages require an explicit bot mention.
|
|
|
|
|
|
|
|
|
|
Uses explicit-false parsing (like Discord/Matrix) rather than
|
|
|
|
|
truthy parsing, since the safe default is True (gating on).
|
|
|
|
|
Unrecognised or empty values keep gating enabled.
|
|
|
|
|
"""
|
|
|
|
|
configured = self.config.extra.get("require_mention")
|
|
|
|
|
if configured is not None:
|
|
|
|
|
if isinstance(configured, str):
|
|
|
|
|
return configured.lower() not in ("false", "0", "no", "off")
|
|
|
|
|
return bool(configured)
|
|
|
|
|
return os.getenv("SLACK_REQUIRE_MENTION", "true").lower() not in ("false", "0", "no", "off")
|
|
|
|
|
|
|
|
|
|
def _slack_free_response_channels(self) -> set:
|
|
|
|
|
"""Return channel IDs where no @mention is required."""
|
|
|
|
|
raw = self.config.extra.get("free_response_channels")
|
|
|
|
|
if raw is None:
|
|
|
|
|
raw = os.getenv("SLACK_FREE_RESPONSE_CHANNELS", "")
|
|
|
|
|
if isinstance(raw, list):
|
|
|
|
|
return {str(part).strip() for part in raw if str(part).strip()}
|
|
|
|
|
if isinstance(raw, str) and raw.strip():
|
|
|
|
|
return {part.strip() for part in raw.split(",") if part.strip()}
|
|
|
|
|
return set()
|