Releases: jstnmthw/hexbot
v0.5.0
A character-driven ai-chat plugin lands alongside botlink v2 handshake hardening and the closeout of the 2026-04-21 stability and 2026-04-24 security audits.
Highlights
ai-chatcharacter engine, ambient participation, and self-hosted Ollama provider — the bot now behaves as a channel regular rather than a help-desk agent. Nine JSON-defined character presets (friendly/sarcastic/chaotic/shitposter/deadpan/minimal/gossip/nightowl/oldhead), per-channel character + language assignment, an internal mood engine, social tracking, and ambient idle/unanswered-question/topic reactions. New Ollama provider adapter is private-by-default and works with any local model via Node 20+ native fetch — no SDK ships in the bundle.- Thread-based engagement + unified reply policy (
plugins/ai-chat/{engagement-tracker,reply-policy}.ts) — the old 60-second engagement timer is replaced by IRC-native floor-holding semantics.decideReply()collapses direct-address / engagement / random-chance into one'address' | 'engaged' | 'rolled' | 'skip'decision. Rolled replies are gated by chattiness × activity × recency and share the existing ambient hourly budget. - On-demand game sessions (
plugins/ai-chat/session-manager.ts) — drop a.txtintoplugins/ai-chat/games/and it becomes playable via!ai play <name>. Ships with20questionsandtrivia. Sessions are bound to the creator's services account to defeat nick-takeover hijacks and use a sliding 40-turn window. - BOTLINK v2 handshake (
src/core/botlink/{auth,protocol}.ts) — closes two CRITICAL findings from the 2026-04-24 audit. HMAC challenge-response replaces the replay-ablescryptHELLO; per-botnetlink_saltrequired; hub-side+mre-check on every BSAY fanout; relay router gated on a live DCC party session;listen.hostdefaults to127.0.0.1; per-frame rate buckets on the hub. - Bot-identify state machine + IDENTIFY-before-JOIN (
src/core/{services,connection-lifecycle}.ts) — four-state identity (unknown/pending/identified/unidentified) with first-classbot:identified/bot:deidentifiedevents. Optional GHOST-and-reclaim recovers from nick collisions automatically. Closes the IDENTIFY/ChanServ probe race on non-SASL networks. - Founder-tier refusal gate for
ai-chat— when the bot's ChanServ tier isfounder, ai-chat refuses by default (security.disable_when_founder: true). Checked at trigger, pipeline start, and before every line — a probe resolving mid-send still blocks. Recommended deployment is the two-bot topology: unprivileged AI bot + chanmod-only founder bot.
Breaking changes
ai-chat !ai <freeform>chat command removed. Talk to the bot by nick instead (<botnick>: hello).!aiis now a subcommand console; bare!aior unknown subcommands print a usage hint pointing at!ai help.ai-chat triggers.engagement_secondsremoved. Replaced byengagement.soft_timeout_minutes(default10) andengagement.hard_ceiling_minutes(default30). The legacy key logs a one-time warning on plugin load and is otherwise ignored.ai-chat triggers.commandconfig field removed. The!aisubcommand console is always enabled. The legacy key logs a one-time warning and is ignored.ai-chatprivate messaging removed. The plugin responds only in channels — PMs were a reconnaissance vector for testing prompt injection without channel visibility.- botlink HELLO v2 — every bot in a botnet must update together. Add
botlink.link_salt(≥ 32 hex chars / 16 bytes — generate withopenssl rand -hex 32) to everybot.jsonin the botnet and use the same value across all members. A mismatched salt producesAUTH_FAILEDon every connection attempt. The pre-v2hashPasswordpath has been deleted — there is no compatibility shim. botlink.leaf_cmd_rate_limitrenamed tobotlink.cmd_inbound_rate(same default 50/s). The previous name is not recognized.ai-chatsystem-prompt schema rewrite.promptfield renamed topersona; the in-prompt dash-bullet rules are now a structuredstyle.notes: string[]array; the{channel_profile}template variable is gone (now injected automatically). All 9 shipped characters migrated; custom characters need to be ported.ollama.allow_private_urlconfig key removed. The SSRF guard was removed — ai-chat is text-only with no user-controlled fetch path. Operators running a local Ollama daemon no longer needallow_private_url: true; the key is silently ignored if present.
Other changes
Added
- Bounded retry for channel join failures (
src/core/channel-presence-checker.ts) — channels that failed JOIN with a permanent-error numeric (+b/+i/+k/+r) are no longer stuck until reconnect. Walks a configurable backoff schedule (default[5min, 15min, 45min]) and recovers from time-limited flood bans without operator intervention. Tunable viachannel_retry_schedule_ms. - Per-user token bucket replaces ai-chat cooldowns (
plugins/ai-chat/rate-limiter.ts) — integer token bucket per user (burst 3, one refilled every 12s), plus global RPM/RPD windows, plus ambient per-channel/global hourly budgets. When global RPM crossesrpm_backpressure_pct(80%), per-user burst halves. - Resilient provider wrapper (
plugins/ai-chat/providers/resilient.ts) — bounded exponential backoff with jitter, half-open circuit breaker (5 failures → 5 min open). Failure-counting uses a blacklist so new transient kinds are counted by default. - Anope
NO_ACCESSdeferred-commit flow (plugins/chanmod/chanserv-notice-anope.ts) — Anope emitsACCESS infoandYou are not on the access listas two notices for the same probe; the driver defers theno-accesscommit until theINFOreply arrives so a stalechanserv_accesschanset can't linger. - DI seams for testability across
ChannelState,Permissions,SlidingWindowCounter,RateLimitTracker,PluginLoader, and several ai-chat modules — constructorinitialStateparameters and an optionaldepsbag let tests seed state and inject mocks without touching production paths.
Changed
ai-chatsystem-prompt assembly restructured into explicit sections (plugins/ai-chat/assistant.ts) —renderSystemPrompt()now emitsYou are <nick>.→## Persona→## Right now→## Rules (these override Persona and Right now). Drops the dual "Rules:" heading collision that was plausibly causing llama3.2:3b to imitate the[nick]transcript format. Seedocs/prompt-stitching-review.mdfor the design rationale.- CI runs
pnpm build:pluginsbefore the test suite (.github/workflows/ci.yml) — thePluginLoaderloads bundled plugins fromplugins/<name>/dist/index.js, so integration tests now see current source instead of stale bundles. triggers.random_chanceis now the primary unprompted-reply path. Default stays0(opt-in). Suggested starting range for operators who want the bot to feel alive:0.02–0.05.
Fixed
- 2026-04-21 stability audit closeout (
src/core/services.ts,src/core/connection-lifecycle.ts,plugins/chanmod/) — closed all 10 findings from the post-incident review of the 2026-04-20 outage where a silent SASL failure left the bot unidentified for 12+ hours. Includes SASL-failure password fallback, ChanServ re-probe on bot-identify, services-unavailable STATUS short-circuit, longer verify timeout in the first 30s after reconnect, andbotIdentified/probe-timeout observability in.status. - Ignored users can no longer trigger per-reply chatter through
!aigarbage — the pub-bind handler now checks the ignore list before dispatching subcommands, closing the gap where an ignored user's!ai hellowould still get a "Unknown subcommand" reply. - ai-chat ambient tick-loop no longer stealth-disables itself on a throw —
tick()is wrapped in try/catch and fire-and-forgetsender()rejections route throughapi.warnso an ambient bug surfaces in the log instead of silently leaving the interval running but producing nothing. - ai-chat engagement map leak,
.ai ignoreunbounded growth, and empty character prompts — eviction TTL is floored and capped at 1000 entries;.ai ignoreenforces a 1000-entry soft cap;validateCharacter()rejects whitespace-only prompts so a broken JSON file can't ship an unanchored system prompt.
Full Changelog: v0.4.1...v0.5.0
v0.4.1
Security patch fixing two CodeQL-flagged vulnerabilities in the build script and RSS plugin.
Fixed
- Shell injection fix in plugin build script — switched from
execSyncwith string interpolation toexecFileSyncwith an argument array, preventing paths with spaces or shell metacharacters from altering the command. - ReDoS fix in RSS HTML tag stripper — replaced the
/<[^>]*>/gregex (O(n^2) on pathological input) with a single-pass O(n) character scanner.
Full Changelog: v0.4.0...v0.4.1
v0.4.0
Plugin build pipeline, Docker fixes, and cleanup of deprecated config.
Highlights
- Plugins bundled via tsup — plugins with a
tsup.config.tsare compiled into self-containeddist/index.jsbundles at build time instead of loaded as raw TypeScript. The RSS plugin bundles its CJS dependencies (rss-parser,xml2js,sax) with acreateRequireshim. - Docker build fixed — host-side
node_modules/anddist/directories no longer leak into the image, and ESLint no longer chokes on plugin build output. dcc.nickserv_verifyremoved — deprecated in v0.3.0, now gone. DCC authentication uses per-user passwords exclusively.
Breaking changes
dcc.nickserv_verifyconfig field removed. The strict schema will reject configs that still contain it. Remove the field from yourconfig/bot.json.- Topic plugin
protect_topicrenamed totopic_lock. Update any.chansetvalues. - Production Docker: remove
./pluginsbind mount. The image now contains pre-built plugin bundles. A host bind mount shadows them with unbundled source, breaking plugin loading.
Other changes
.bindsoutput grouped by plugin with section headers for easier scanning.- DCC CHAT rejection notices collapsed into a generic "request denied" — no longer leaks the specific denial reason to the connecting user.
- Mode-grant commands targeting the bot itself silently ignored — previously caused confusing no-ops or mode bounces.
Full Changelog: v0.3.0...v0.4.0
v0.3.0
A large reliability + observability release. Two new subsystems land (DCC password auth, full audit log), four full-codebase audits get applied (security, stability, memory leak, quality + testability), the IRCv3 surface expands, and the reconnect loop is rewritten end-to-end.
Highlights
- DCC CHAT password authentication — Scrypt-hashed per-user passwords, hostmask-only trust path removed. See migration notes below.
mod_logaudit pipeline — every privileged action (core commands, plugin IRC calls, auth failures, lockouts) lands in a structuredmod_logtable. New.modlogoperator pager and.audit-tailREPL stream with rich filter grammar.- RSS plugin — polls feeds, dedupes via KV, announces to channels, with full SSRF defense (DNS-pinned lookups, IP-range blocklist, byte cap, content-type validation, billion-laughs guard).
- Reconnect loop rewritten — HexBot now owns the loop end-to-end; classifies disconnects into transient/rate-limited/fatal tiers; K-line and DNSBL blocks recover automatically; new
Connection:line in.status. - IRCv3 expansion — ISUPPORT-driven mode/prefix handling,
away-notify,account-tag, account-pattern permissions ($a:account), STS enforcement, configurable command prefix, per-target message queue. - DCC console log sink — DCC sessions are now a filtered live view of the bot's log via per-session flags (
+mojwdefault). - Failed-login banner — DCC and REPL surface auth-fail / auth-lockout events from the previous login window above the session banner.
- Multi-instance friendly —
process.titleset sohtop/psidentify each bot by nick + config path; secrets live inbot.envvia_env-suffix fields.
Breaking changes
- DCC CHAT requires per-user passwords. Existing user records have no
password_hashand will be blocked from DCC until an admin sets one. Run.chpass <handle> <newpass>from the REPL for each admin before they next connect.dcc.nickserv_verifyis now a no-op (deprecation warning at startup, removal in 0.4.0). chanmodchannel_modeslegacy format removed. Values must start with+or-. Update e.g."nt"->"+nt".config/bot.jsonno longer accepts inline secrets.services.password,botlink.password,chanmod.nick_recovery_password,proxy.password, and per-channel+kkeys must use<field>_envreferences — see the multi-instance guide.MessageQueue.enqueue(fn)->enqueue(target, fn). Plugin code usingapi.say/api.notice/api.actionis unaffected.Permissions.findByNickremoved in favor offindByHostmask(host, account?)— closes a nick-spoofing path onnick!*@*records.
Audits applied
- Security — Phase 1 (7 criticals), Phase 2 (25 warnings), Phase 3 (22 info). RSS SSRF,
.bot/.chpasstransport hardening, auto-op IRCv3 fast path, BotLinktimingSafeEqual, frame-type allowlist, mod_log metadata caps, plugin-loader path-traversal guard, last-owner deletion guard, and many more. - Stability — 18 critical / 28 warning / 14 info findings across 10 subsystems. Database error classification (
BUSY/FULL/CORRUPT-> degrade/disable/exit), plugin reload fail-loud, services dedupe + cap, BotLink handshake deadline + jittered reconnect, JOIN permanent-failure tracking, DCC zombie eviction, RSS circuit breaker, stability metrics in.status. - Memory leak — flood plugin migrated to leak-safe
SlidingWindowCounterwith hostmask keying and LRU caps;createPluginApinow returns{ api, dispose }so post-unload closures can't fan out;BotEventBus.trackListener/removeByOwnerplus a sharedcleanupPluginResourceshelper close the load/unload drift gap. - Quality —
src/core/dcc/,src/core/botlink/,plugins/rss/,plugins/flood/all split out of god-files. Bot constructor phased; shared helpers extracted acrossirc-bridge,command-handler,channel-state,permissions,irc-commands. Behaviour-preserving. - Testability —
LoggerLikeinterface eliminates 25 logger casts; narrow role interfaces (BanOperator,BindRegistrar, notice backends); botlink hub sub-objects exposed aspublic readonlyfor test seeding. ~25 of 42 unsafe cast sites removed.
Other notable additions
- Bot-link auth brute-force protection — per-IP failure tracking with escalating bans (5 min -> 24 h cap), CIDR whitelist, configurable handshake timeout.
- ChanServ-assisted join error recovery — banned/invite-only/keyed channels now retry through ChanServ with exponential backoff.
- Per-plugin channel scoping via
channelsarray inplugins.json. d(deop) flag — elective flag suppressing auto-op/halfop on join without revoking privileges.- Auto-discovery of plugins from
plugins/— noplugins.jsonentry required. .uptimecommand with bold-red digit formatting.- Owner password bootstrap —
owner.password_envseeds the owner's DCC password hash on first boot for headless/Docker deployments. - Memo system — MemoServ proxy with no local storage.
- Admin console ban management with shared
BanStoreand botlink IP bans. - Cross-dialect ISUPPORT test matrix — Solanum/Libera, InspIRCd, UnrealIRCd, ngIRCd fixtures.
Full Changelog: v0.2.3...v0.3.0
v0.2.3
Full Changelog: v0.2.2...v0.2.3
v0.2.2
Full Changelog: v0.2.1...v0.2.2
v0.2.1
Full Changelog: v0.2.0...v0.2.1
v0.2.0
Full Changelog: v0.1.0...v0.2.0
v0.1.0
Full Changelog: v0.1.0...v0.1.0