Skip to content

Releases: jstnmthw/hexbot

v0.5.0

25 Apr 11:53

Choose a tag to compare

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-chat character 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 .txt into plugins/ai-chat/games/ and it becomes playable via !ai play <name>. Ships with 20questions and trivia. 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-able scrypt HELLO; per-botnet link_salt required; hub-side +m re-check on every BSAY fanout; relay router gated on a live DCC party session; listen.host defaults to 127.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-class bot:identified / bot:deidentified events. 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 is founder, 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). !ai is now a subcommand console; bare !ai or unknown subcommands print a usage hint pointing at !ai help.
  • ai-chat triggers.engagement_seconds removed. Replaced by engagement.soft_timeout_minutes (default 10) and engagement.hard_ceiling_minutes (default 30). The legacy key logs a one-time warning on plugin load and is otherwise ignored.
  • ai-chat triggers.command config field removed. The !ai subcommand console is always enabled. The legacy key logs a one-time warning and is ignored.
  • ai-chat private 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 with openssl rand -hex 32) to every bot.json in the botnet and use the same value across all members. A mismatched salt produces AUTH_FAILED on every connection attempt. The pre-v2 hashPassword path has been deleted — there is no compatibility shim.
  • botlink.leaf_cmd_rate_limit renamed to botlink.cmd_inbound_rate (same default 50/s). The previous name is not recognized.
  • ai-chat system-prompt schema rewrite. prompt field renamed to persona; the in-prompt dash-bullet rules are now a structured style.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_url config 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 need allow_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 via channel_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 crosses rpm_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_ACCESS deferred-commit flow (plugins/chanmod/chanserv-notice-anope.ts) — Anope emits ACCESS info and You are not on the access list as two notices for the same probe; the driver defers the no-access commit until the INFO reply arrives so a stale chanserv_access chanset can't linger.
  • DI seams for testability across ChannelState, Permissions, SlidingWindowCounter, RateLimitTracker, PluginLoader, and several ai-chat modules — constructor initialState parameters and an optional deps bag let tests seed state and inject mocks without touching production paths.

Changed

  • ai-chat system-prompt assembly restructured into explicit sections (plugins/ai-chat/assistant.ts) — renderSystemPrompt() now emits You 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. See docs/prompt-stitching-review.md for the design rationale.
  • CI runs pnpm build:plugins before the test suite (.github/workflows/ci.yml) — the PluginLoader loads bundled plugins from plugins/<name>/dist/index.js, so integration tests now see current source instead of stale bundles.
  • triggers.random_chance is now the primary unprompted-reply path. Default stays 0 (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, and botIdentified/probe-timeout observability in .status.
  • Ignored users can no longer trigger per-reply chatter through !ai garbage — the pub-bind handler now checks the ignore list before dispatching subcommands, closing the gap where an ignored user's !ai hello would still get a "Unknown subcommand" reply.
  • ai-chat ambient tick-loop no longer stealth-disables itself on a throwtick() is wrapped in try/catch and fire-and-forget sender() rejections route through api.warn so an ambient bug surfaces in the log instead of silently leaving the interval running but producing nothing.
  • ai-chat engagement map leak, .ai ignore unbounded growth, and empty character prompts — eviction TTL is floored and capped at 1000 entries; .ai ignore enforces 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

16 Apr 09:36

Choose a tag to compare

Security patch fixing two CodeQL-flagged vulnerabilities in the build script and RSS plugin.

Fixed

  • Shell injection fix in plugin build script — switched from execSync with string interpolation to execFileSync with an argument array, preventing paths with spaces or shell metacharacters from altering the command.
  • ReDoS fix in RSS HTML tag stripper — replaced the /<[^>]*>/g regex (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

16 Apr 09:10

Choose a tag to compare

Plugin build pipeline, Docker fixes, and cleanup of deprecated config.

Highlights

  • Plugins bundled via tsup — plugins with a tsup.config.ts are compiled into self-contained dist/index.js bundles at build time instead of loaded as raw TypeScript. The RSS plugin bundles its CJS dependencies (rss-parser, xml2js, sax) with a createRequire shim.
  • Docker build fixed — host-side node_modules/ and dist/ directories no longer leak into the image, and ESLint no longer chokes on plugin build output.
  • dcc.nickserv_verify removed — deprecated in v0.3.0, now gone. DCC authentication uses per-user passwords exclusively.

Breaking changes

  • dcc.nickserv_verify config field removed. The strict schema will reject configs that still contain it. Remove the field from your config/bot.json.
  • Topic plugin protect_topic renamed to topic_lock. Update any .chanset values.
  • Production Docker: remove ./plugins bind mount. The image now contains pre-built plugin bundles. A host bind mount shadows them with unbundled source, breaking plugin loading.

Other changes

  • .binds output 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

16 Apr 04:37

Choose a tag to compare

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_log audit pipeline — every privileged action (core commands, plugin IRC calls, auth failures, lockouts) lands in a structured mod_log table. New .modlog operator pager and .audit-tail REPL 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 (+mojw default).
  • Failed-login banner — DCC and REPL surface auth-fail / auth-lockout events from the previous login window above the session banner.
  • Multi-instance friendlyprocess.title set so htop/ps identify each bot by nick + config path; secrets live in bot.env via _env-suffix fields.

Breaking changes

  • DCC CHAT requires per-user passwords. Existing user records have no password_hash and 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_verify is now a no-op (deprecation warning at startup, removal in 0.4.0).
  • chanmod channel_modes legacy format removed. Values must start with + or -. Update e.g. "nt" -> "+nt".
  • config/bot.json no longer accepts inline secrets. services.password, botlink.password, chanmod.nick_recovery_password, proxy.password, and per-channel +k keys must use <field>_env references — see the multi-instance guide.
  • MessageQueue.enqueue(fn) -> enqueue(target, fn). Plugin code using api.say / api.notice / api.action is unaffected.
  • Permissions.findByNick removed in favor of findByHostmask(host, account?) — closes a nick-spoofing path on nick!*@* records.

Audits applied

  • Security — Phase 1 (7 criticals), Phase 2 (25 warnings), Phase 3 (22 info). RSS SSRF, .bot/.chpass transport hardening, auto-op IRCv3 fast path, BotLink timingSafeEqual, 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 SlidingWindowCounter with hostmask keying and LRU caps; createPluginApi now returns { api, dispose } so post-unload closures can't fan out; BotEventBus.trackListener / removeByOwner plus a shared cleanupPluginResources helper close the load/unload drift gap.
  • Qualitysrc/core/dcc/, src/core/botlink/, plugins/rss/, plugins/flood/ all split out of god-files. Bot constructor phased; shared helpers extracted across irc-bridge, command-handler, channel-state, permissions, irc-commands. Behaviour-preserving.
  • TestabilityLoggerLike interface eliminates 25 logger casts; narrow role interfaces (BanOperator, BindRegistrar, notice backends); botlink hub sub-objects exposed as public readonly for 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 channels array in plugins.json.
  • d (deop) flag — elective flag suppressing auto-op/halfop on join without revoking privileges.
  • Auto-discovery of plugins from plugins/ — no plugins.json entry required.
  • .uptime command with bold-red digit formatting.
  • Owner password bootstrapowner.password_env seeds 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 BanStore and 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

04 Apr 08:04

Choose a tag to compare

Full Changelog: v0.2.2...v0.2.3

v0.2.2

03 Apr 14:29

Choose a tag to compare

Full Changelog: v0.2.1...v0.2.2

v0.2.1

03 Apr 13:43

Choose a tag to compare

Full Changelog: v0.2.0...v0.2.1

v0.2.0

02 Apr 16:54

Choose a tag to compare

Full Changelog: v0.1.0...v0.2.0

v0.1.0

29 Mar 14:03

Choose a tag to compare

Full Changelog: v0.1.0...v0.1.0