Repository navigation
Releases: churnast/hermes-telegram-stickers
Release list
Telegram Stickers v1.2.0
Removed
- The
tool_executionmiddleware and thehide_tool_progresssetting, as the Hermes plugin catalog review asked: plugins may extend Hermes only through public surfaces, and the middleware rebuilt Hermes' pre-call step from a private function (hermes_cli.plugins._dispatch_pre_tool_call_hooks), found its caller by walking the call stack and skipped some of Hermes' own steps for the sticker calls. The sticker tools now run like any other tool, so where tool progress is on, Hermes posts its line (such as🐾 telegram_sticker_send...) before a sticker. To hide the lines in Telegram, setdisplay.platforms.telegram.tool_progress: off(see the README, Tool progress lines).provides_middlewareis gone fromplugin.yaml; ahide_tool_progressleft in your config is ignored. The CI check of the middleware (.github/scripts/check_tool_progress.py) and its tests are removed too.
Changed
- At most
max_learned_packslearned packs (new setting, default 100;0means no limit). Once there are that many, a sticker from a new pack adds nothing: it costs no Telegram call, the setup tells the agent the pack was not saved, and/stickersin a direct chat says the limit is reached./stickers forget(or "remove the pack ...") makes room. Packs learned before are never dropped, also when the setting is lower than their number. Every learned pack is used in every chat and a sync asks Telegram for each new one in turn, so on a bot several people can message directly the list no longer grows without end. - During the setup, the background lookup of a pack's title and size starts only for a sender Hermes let in: right away for the person the setup runs for, and on a fresh install, before the setup starts, once Hermes runs a turn for the sender; the setup turn waits up to 2 seconds for it, as before. Stickers from people Hermes does not answer start no lookup. At most 32 lookups are kept in memory or run at once (finished ones are dropped oldest first); past that, the setup names a pack without its title. The forwarded-sticker notes of the setup are kept for an hour and for at most 50 senders.
Added
- Tests: the limit on learned packs (refused packs, a lower limit,
0, odd values, room after forget, the setup's note), no lookup for senders Hermes did not let in, at most 32 lookups, and thatregister()adds no middleware.
Telegram Stickers v1.1.2
Changed
- Faster search: each description is read once per search and only as far as needed. A plain substring test rules out most stickers before their words are split out; a whole word is looked up in the description's set of words instead of a pattern search per word and sticker; the parts of a compound word ('face' in 'facepalm') are looked up as one set against the description's words; and the cue words of all reactions named in a query are found with one pattern. Picks are the same as in 1.1.0 and 1.1.1. On 2,000 stickers on a Mac the slowest input tried takes about 22 ms (1.1.1: about 48 ms), and a 16,000-character query of long words about 12 ms (1.1.1: about 48 ms; on GitHub's Windows runners 103 to 109 ms, over the tests' 100 ms limit).
- At most 6 reactions named in a query count, like its words.
Added
- A timing test with 200 characters of words the descriptions contain.
Full list of changes: CHANGELOG.md
Telegram Stickers v1.1.1
Changed
- The bot token is read through Hermes' secret scope (
agent.secret_scope.get_secret, in Hermes since 0.20.6) instead of the process environment, as the plugin catalog's rule 11 asks: when one gateway serves several profiles, each profile's stickers go out as its own bot. A read Hermes refuses (several profiles and no profile scope on that thread) counts as no token and never falls back to the environment. The setup's background lookup of a pack's title gets its client on the calling thread, where Hermes' profile scope is set. Without Hermes (the offline tests) the environment is read as before. - The agent's text that goes into a search is cut before anything reads it: a query or a sticker named in words to its first 6 words longer than two letters within the first 200 characters, an emoji to 16 characters, a pack name and an id to 200. A search reads every sticker in every pack in use and learned packs have no cap, so in 1.1.0 a 16,000-character query took about 70 seconds on 2,000 stickers; now the slowest input tried takes about 50 ms there on a Mac. The query is read once per search instead of once per sticker. Picks for ordinary queries are the same as in 1.1.0.
- A sticker id or a Telegram id with more digits than any real one is refused like any unknown id instead of failing with a Python error.
- The pack list of
telegram_sticker_settingsandforget_packread the pack catalog once instead of once per pack.
Added
- Tests with 2,000 stickers: long and hostile text through
telegram_sticker_find,telegram_sticker_sendandtelegram_sticker_settings, each under 100 ms; the token read through a stand-in secret scope, a refused read and no Hermes. - The README now also discloses the settings note: when a Telegram turn's user message mentions stickers or packs, the
pre_llm_callhook puts a fixed note of about 310 characters before the hint (save a change withtelegram_sticker_settings, never confirm a change that was not saved). It came with 1.1.0 and was not in the README.
Full list of changes: CHANGELOG.md
Telegram Stickers v1.1.0
Added
- Setup in the owner's direct chat: on a fresh install, the first direct-chat turn starts it. The
pre_llm_callhook adds a setup paragraph to that person's turns: answer their message first (a serious one gets only an answer), then ask one question per message: how often in this chat (rarely, sometimes, often or a number of their messages), how often in groups (rarely, the same, off or a number), and a sticker from a pack they like, then a couple more. A sticker sent then names its pack with title and size (fetched in a background thread), says when the pack was already saved, forwarded or gone from Telegram. Done sums up packs, both paces and how to change them in words; skip keeps the defaults. Three turns with no answer pause it; "set up stickers" or/stickers setupstarts it again. Only the person who started it gets it, and while it runs, stickers from other people add no packs. An update of a plugin already in use (learned packs, chats, mutes or bans instate.json) does not start it.setup: falseturns it off. telegram_sticker_settings: settings in words.pacesets how often in this chat, in all direct chats or in all groups (rarely,sometimes,often, a number,off,on,on_request,same); in a group anyone may only make it rarer,offoron_requestthere.banandunbantake the last or previous sticker sent in this chat ("don't send this one", "not that one, the one before", "bring it back").forget_packremoves a pack by short name, title words orthis(the pack of the last sticker);forget_allasks first;defaultsturns the default packs off or on;listshows packs and paces. Everything exceptpaceandlistis for the owner in a direct chat; in a group,listshows no pack names./stickers setup, and a line with the paces set in words in/stickers.- No tool progress line before a sticker. With tool progress on (the config Hermes' installer writes up to 0.21.5 sets
display.tool_progress: all, which overrides Telegram's defaultoff), Hermes posted a line such as "🐾 telegram_sticker_send..." in the chat before every sticker. The plugin now registers atool_executionmiddleware (declared asprovides_middleware: [tool_execution]inplugin.yaml). When the agent calls one of the plugin's tools in its tool loop on a Telegram turn, the middleware runs the tool itself and does not hand the call on, so Hermes never reaches the step that posts the line. Before the tool runs, it checks the bot token (as the tools'check_fndid) and calls Hermes' ownpre_tool_callstep, so plugin and shell hooks still block, ask for approval or change the arguments, and Hermes' argument coercion, so a"false"from the model is stillfalse; a block returns{"error": ...}as Hermes does, and a failing handler returns the tools' usual{"error": ...}. That covers every toolregister()registers, also when the agent calls them throughtool_call, which Hermes unwraps before the middleware. Every other call goes on to Hermes unchanged: other tools, the sticker tools on other turns and outside the agent's tool loop, and any call where deciding fails or this Hermes lacks those two functions. For the sticker calls it runs, the tool-loop guardrail's check before a call,transform_tool_resulthooks,tool_executionmiddleware of plugins that run after this one and Hermes' tool start step (the progress line, and thetool_calls.logline undertool_progress: log; on Hermes main also its check for compression markers in arguments) do not run;post_tool_callhooks still do. This uses only Hermes' public middleware surface, sohermes plugins validatepasses on Hermes main,no core overrideincluded. Not withplugins.isolation: host, where Hermes hands the middleware its next step as a placeholder it cannot call (the plugin does not register it there), or with the Codex app-server runtime, which posts the line before any middleware. New settinghide_tool_progress(defaulttrue;falseruns the sticker tools through Hermes' usual path, with the line). The README explains the lines, what the middleware skips, the lines it leaves to Hermes (atool_describeortool_searchlookup, and atool_callHermes refuses, which on Hermes 0.21.5 and older includes two sticker tools in one call) and how to hide every tool's lines in Telegram (display.platforms.telegram.tool_progress: "off"). - CI checks on each Hermes version it runs (0.20.6, 0.21.5 and main) that Hermes' loader registers the middleware; that Hermes' own tool loop step then runs the sticker tools on a Telegram turn without the progress event, after Hermes'
pre_tool_callhooks (a probe plugin's block and modify answers included) and argument coercion, while another tool, a sticker tool on a CLI turn andmodel_tools.handle_function_callgo through Hermes; and thatagent/tool_executor.pystill posts the progress event only from the step at the end of the chain (.github/scripts/check_tool_progress.py).
Changed
- Pacing: a pace set in words comes before
min_messages_between, per chat, then for direct chats or groups. "Only on request" turns the sticker hint off in that chat. - Removing your last pack in words keeps the default packs off instead of bringing them back; "bring back the default packs" turns them on.
- The sticker hint gives the whole call,
telegram_sticker_sendwith{"sticker": ...}, and says to pass it totool_calldirectly when the tool is not listed, withouttool_searchortool_describefirst: each of those lookups posts its own progress line in the chat. Thesticker-etiquetteskill says the same. The hint is about 540 characters, was about 450.
Full list of changes: CHANGELOG.md
Telegram Stickers v1.0.0
The first stable release, with every change since 0.1.2.
Highlights
- The agent answers jokes, news and emotions with stickers nobody asked for: on Telegram turns where a sticker is allowed, the plugin adds a short hint (
turn_hint, on by default). Serious questions get words. - Works out of the box with four default packs made by Telegram. Send the bot a sticker you like in a direct chat and its pack joins, with no limit.
- About one sticker per 6 messages in a chat (was 8), counting every message, the bot's own included.
- An emoji no sticker is tagged with finds packs by its name (a cherry emoji finds a pack called Hot Cherry).
- Hermes Agent 0.20.6 and newer. Every
/stickerscommand in Telegram needs Hermes 0.21.5; before that, the owner commands work in the Hermes CLI.
Added
- Learned packs: a sticker the owner sends the bot in a direct chat adds its pack, so "send the bot a few stickers you like" is the whole setup. Static, animated and video stickers count; groups never do, and forwarded stickers do not either, except on Hermes 0.20.6, which has no Telegram handlers for plugins (there, only static stickers count). A pack is kept only once Hermes runs a turn for that sender in their own direct chat. There is no limit on learned packs: each is kept, with its sticker kind and when it was learned and last seen, until
/stickers forgetor until Telegram no longer has it (it is then forgotten on the next sync). Set names match without case, socatsandCatsare one pack.learn_packs: falseturns this off. - Default packs: with no packs of your own, four packs made by Telegram (TheFoods, MelieTheCavy, OfficeTurkey, Animals) work out of the box.
default_packs: falseturns them off. /stickers forget <pack>(owner only: a direct chat, or the Hermes CLI) removes a learned pack.- A sticker hint on Telegram turns: where a sticker is allowed now (stickers on in the chat, pacing passed; in a direct chat, a turn with a sender; in a group none is needed, since Hermes drops the sender where it observes every message), the
pre_llm_callhook returns one fixed paragraph that Hermes appends to that turn's user message. It says a sticker may answer a joke, banter, good or funny news or a strong emotion, how to calltelegram_sticker_send(throughtool_callwhen tool search hides it), when not to, and to still write one short line after it. It never calls the network;turn_hint: falseturns it off. - An emoji no sticker is tagged with, and with no nearest emoji in feeling, is looked up by the words of its Unicode name in pack titles, short names and descriptions: 🍒 (cherries) finds a pack called Hot Cherry, 🍕 (slice of pizza) one called Pizza Party. Words such as "face", "sign" or "slice" do not count, and only whole words match, so ROCKET does not find "rocking".
telegram_sticker_findsays so in its hint, and the send result names the emoji asked for ininstead_of. - CI checks that Hermes' note for a static sticker still names its pack the way the plugin reads it.
- A card for the Hermes plugin catalog,
docs/card.png(1200x600): one message and a kitten sticker as the reply.docs/banner.pngstays the README banner. - A 15-second demo in the README (animated WebP, plus the same clip as
docs/demo.mp4), re-drawn from live tests: three scenes, each in a fresh chat. Happy news gets a sticker nobody asked for, a smug cat gets a smug kitten, and the third message gets words. - Screenshots re-drawn from live test chats in
docs/screenshots(1280x800, with the agent's name and bot username replaced): 01, 02 and 04 in the README, 03 and 05 for the catalog gallery. - CI also validates the plugin against hermes-agent
main, next to the pinned v2026.9.24 (Hermes Agent 0.21.5). A failure onmainshows up in the run but does not fail the build.
Changed
- Pacing default:
min_messages_betweenis 6, was 8. It counts messages in the chat by Telegram message numbers, the bot's own included, so in a direct chat 6 is about every third message of the person. The README,plugin.yamlandconfig.example.yamlnow say what is counted. - With no cap on learned packs, replies that name packs stay short:
/stickerslists 20 packs and counts the rest, as do its list of muted chats and/stickers forget;telegram_sticker_findwith no arguments lists 20 packs withtotal_packs; the error when no pack loads names five. Every/stickersreply fits one Telegram message. - Supported Hermes: 0.20.6 and newer, was 0.21.5 (
requires_hermes: ">=0.20.6").plugin.yamlusesmanifest_version: 1, since the installers of Hermes 0.21.3 and older refuse version 2, and the first line ofconfig.example.yamlno longer names the path of Hermes' config file, which the install scanner of Hermes 0.20.6 blocks. CI also runshermes plugins doctorand the sticker note check on Hermes 0.20.6. On Hermes 0.20.6, which has no Telegram handlers for plugins, only static stickers teach packs. - Before Hermes 0.21.5, Hermes runs plugin slash commands without the chat they came from, so
/stickersno longer takes a missing chat type for a direct chat. In a chat on such a Hermes,/stickersshows the status (learned packs only as a count, no muted chats) and/stickers syncreloads the packs; the commands for the owner (describe,ban,unban,about,forget,offoronwith a chat id) and/stickers offoronfor the current chat answer that this Hermes does not say which chat a command came from and point to the Hermes CLI on the server, where they work, since the person at the console is the owner. Without this, anyone who may run slash commands in a group could have run the owner's commands there. A command with no chat counts as the owner's only when Hermes' own CLI or TUI command dispatcher called it; anything else is treated like a chat on such a gateway. On that gateway, a command also runs in a worker thread, not on the gateway's event loop. Withplugins.isolation: host(Hermes main), where the plugin never gets the chat,/stickersshows the status and reloads the packs, and the other commands are refused, in Telegram and in the Hermes CLI, with a pointer toplugins.isolation: in_process. On Hermes 0.21.5 and newer withplugins.isolation: in_process, nothing changes. /stickers describetakes learned packs first, newest first, then the rest in pack order, so a pack just learned is described on the next run, not after every pack insettings.packs./stickersnames the learned pack it starts with (in a group, only that it starts with learned packs). Vision calls still run only on the owner's command.- Packs in use:
settings.packsfirst, then learned packs (newest first); the default packs only while both are empty./stickersmarks learned and default packs and states this order. In a direct chat it says when learning is off or only static stickers can be learned; in a group it shows learned packs only as a count. - A changed pack list (a pack learned or forgotten,
settings.packsedited) loads only the packs that are new or loaded more than a week ago; the rest come from the cached list./stickers syncstill loads them all. - With no packs at all (
default_packs: false), the error says "No sticker packs yet" and says how to add packs: by name insettings.packs, or, whilelearn_packsis on, by sending the bot a few stickers. plugin.yamldeclares thepre_llm_callhook and thelearn_packs,default_packsandturn_hintsettings.- The README is rewritten around a Quick start (install and restart, send the bot a few stickers or set packs by name,
/stickersto check), keeps the /stickers commands in one table, and explains stickers nobody asked for (the plugin's own hint on Telegram turns, with no channel prompt needed), who can run what, and Known limitations. - The README banner drops its pills and uses larger text so it reads in small link previews.
- The
telegram_sticker_mutedescription, its result and the error in a muted chat no longer say that only the owner can switch stickers back on:/stickers onin that chat does too (before Hermes 0.21.5,/stickers on <chat id>in the Hermes CLI, which they also name). - CI runs
hermes plugins validate . --install-deps, the same check the plugin catalog runs, and clones Hermes with its tags somaincan tell its own version. sticker-etiquetteskill: the agent may send a sticker nobody asked for where it would otherwise answer with a single emoji, ends with one short line after a sticker, and answers in words, not silence, when stickers are paced.- The
plugin.yamldescription no longer says the packs must be the owner's own: any public pack works.0formin_messages_betweenturns off only that limit, not the cooldown. - The code of conduct points questions to Discussions, and conduct reports go through GitHub's Report content.
config.example.yamlshowsplugins.enablednext toentriesand says not to paste a secondplugins:key: pasted below the onehermes plugins install --enablewrites, it dropped the enabled list.
Fixed
- On Windows, saving
state.jsonwhile another process (the CLI next to the gateway) reads or saves it no longer fails with "Access is denied": the read or replace is retried for up to about two seconds. - Words also match a pack's title and short name, plurals included, with less weight than a sticker's own description: "cherry" or "cherries" now finds the stickers of an undescribed pack called Hot Cherry (HotCherry) instead of nothing. A
_by_<bot>ending and words like "pack" or "stickers" do not count.telegram_sticker_findlimited to one pack lists that pack's stickers with a hint when nothing in it fits, and says when no loaded pack has that name. - After a sticker the agent no longer answers with [SILENT]: Hermes 0.21.5 turns that into an error notice in the chat, so the tool result now asks for one short line that carries the conversation on.
- Wh...
v0.1.2: stickers for reactions
Added
- Reactions: a built-in list of about forty common reactions ("facepalm", "eye roll", "thumbs up", "oh no") points each one to the emojis pack authors tag it with and to the words a description of such a sticker tends to use. Asking for a reaction finds a sticker even when no pack has its exact emoji and no description uses that word.
- An emoji that no pack has falls back to the nearest one in feeling (🤦 to 🙄 or 😑). The send result names the emoji that was asked for in
instead_of. /stickers describe again [n]redoes descriptions made with an older prompt or taken from Hermes' cache, and/stickersshows how many are left. Descriptions you wrote with/stickers aboutare never redone.
Changed
- Emojis match without skin tone, gender sign or variation selector: 🤦♀️, 🤦🏽 and 🤦 find the same stickers.
- A word made of a described word and more, such as "facepalm" for a description with "face", counts as a partial match.
- When nothing fits, the error and the
telegram_sticker_findhint list the emojis the packs have, and the tool descriptions tell the agent to send the closest one. - The vision prompt asks what the sticker shows, the feeling and three to six reaction words, and passes the pack's emoji as a hint. The plugin now uses its own prompt rather than Hermes' sticker prompt.
- When the best match was just sent in the chat, the next sticker that fits at least half as well goes out instead of a repeat.
- Questions and ideas go to GitHub Discussions, linked from the issue form and CONTRIBUTING. Tests also run on macOS.
Fixed
- In a live test the agent asked for "facepalm", then 🙃, then 🤦♀️, got nothing back each time and sent no sticker. All three find one in the same packs now.
Full list of changes: CHANGELOG.md
Telegram Stickers v0.1.1
/stickers on <chat id> and /stickers off <chat id> in a direct chat with the bot switch any chat, and /stickers there lists the chats where stickers are off. In groups /stickers@bot on may not reach the plugin, so a chat the agent muted on request could not be switched back on. The note after a sent sticker now asks the agent not to add a "done" line, and the etiquette skill explains calling the tools through tool_call when Hermes hides plugin tools behind tool search.
hermes plugins update telegram-stickersTelegram Stickers v0.1.0
First public release.
Install
hermes plugins install churnast/hermes-telegram-stickers --enableAdded
telegram_sticker_send: sends one sticker from the owner's packs by a few words, an emoji or an exact id, as a reply to the message being answered and in the same forum topic.telegram_sticker_find: lists packs and emojis, finds stickers by words or emoji, with a short description of each.telegram_sticker_mute: lets the agent switch stickers off in a chat when someone asks./stickerscommand: status,sync,describe [n],offandon,banandunban,about.- Picking by meaning: reuses Hermes' own sticker descriptions and describes the rest with the configured vision model on request, animated and video stickers through their still preview.
- Pacing: about one sticker per 8 messages and a 20 second cooldown per chat, kept across restarts; the last five stickers sent in a chat are not repeated.
- Bundled
sticker-etiquetteskill.
Requires Hermes Agent 0.21.5 or newer. Settings, commands and the privacy section are in the README.