diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b13b7835..a134359f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,144 +2,63 @@ Docs: https://docs.molt.bot -## 2026.1.27-beta.1 +## 2026.1.29 Status: beta. +### Highlights +- Rebrand: rename the npm package/CLI to `moltbot`, keep a `moltbot` compatibility shim, move extensions to the `@moltbot/*` scope, and update bot.molt bundle IDs/labels/logging subsystems. Thanks @thewilloftheshadow. +- New channels/plugins: Twitch plugin; Google Chat (beta) with Workspace Add-on events + typing indicator. (#1612, #1635) Thanks @tyler6204, @iHildy. +- Security hardening: gateway auth defaults required, hook token query-param deprecation, Windows ACL audits, mDNS minimal discovery, and SSH target option injection fix. (#4001, #2016, #1957, #1882, #2200) +- WebChat: image paste + image-only sends; keep sub-agent announce replies visible. (#1925, #1977) +- Tooling: per-sender group tool policies + tools.alsoAllow additive allowlist. (#1757, #1762) +- Memory Search: allow extra paths for memory indexing. (#3600) Thanks @kira-ariaki. + ### Changes -- Security: harden SSH tunnel target parsing to prevent option injection/DoS. (#4001) Thanks @YLChen-007. -- Rebrand: rename the npm package/CLI to `moltbot`, add a `moltbot` compatibility shim, and move extensions to the `@moltbot/*` scope. -- Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev. -- macOS: limit project-local `node_modules/.bin` PATH preference to debug builds (reduce PATH hijacking risk). -- macOS: finish Moltbot app rename for macOS sources, bundle identifiers, and shared kit paths. (#2844) Thanks @fal3. -- Branding: update launchd labels, mobile bundle IDs, and logging subsystems to bot.molt (legacy com.clawdbot migrations). Thanks @thewilloftheshadow. -- Tools: add per-sender group tool policies and fix precedence. (#1757) Thanks @adam91holt. -- Agents: summarize dropped messages during compaction safeguard pruning. (#2509) Thanks @jogi47. -- Memory Search: allow extra paths for memory indexing (ignores symlinks). (#3600) Thanks @kira-ariaki. -- Skills: add multi-image input support to Nano Banana Pro skill. (#1958) Thanks @tyler6204. -- Agents: honor tools.exec.safeBins in exec allowlist checks. (#2281) -- Matrix: switch plugin SDK to @vector-im/matrix-bot-sdk. -- Docs: tighten Fly private deployment steps. (#2289) Thanks @dguido. -- Docs: add migration guide for moving to a new machine. (#2381) -- Docs: add Northflank one-click deployment guide. (#2167) Thanks @AdeboyeDN. -- Gateway: warn on hook tokens via query params; document header auth preference. (#2200) Thanks @YuriNachos. -- Gateway: add dangerous Control UI device auth bypass flag + audit warnings. (#2248) +- Providers: add Venice AI integration; update Moonshot Kimi references to kimi-k2.5; update MiniMax API endpoint/format. (#2762, #3064) +- Providers: add Xiaomi MiMo (mimo-v2-flash) support and onboarding flow. (#3454) Thanks @WqyJh. +- Telegram: quote replies, edit-message action, silent sends, sticker support + vision caching, linkPreview toggle, plugin sendPayload support. (#2900, #2394, #2382, #2548, #1700, #1917) +- Discord: configurable privileged gateway intents for presences/members. (#2266) Thanks @kentaro. +- Browser: route browser control via gateway/node; fallback URL matching for relay targets. (#1999) +- macOS: add direct gateway transport; preserve custom SSH usernames for remote control; bump Textual to 0.3.1. (#2033, #2046) +- Routing: add per-account DM session scope + guidance for multi-account setups. (#3095) Thanks @jarvis-sam. +- Hooks: make session-memory message count configurable. (#2681) +- Tools: honor tools.exec.safeBins in exec allowlist checks. (#2281) +- Security: add Control UI device auth bypass flag + audit warnings; warn on hook tokens via query params; add security audit CLI surface. (#2248, #2200) - Doctor: warn on gateway exposure without auth. (#2016) Thanks @Alex-Alaniz. -- Config: auto-migrate legacy state/config paths and keep config resolution consistent across legacy filenames. -- Discord: add configurable privileged gateway intents for presences/members. (#2266) Thanks @kentaro. -- Docs: add Vercel AI Gateway to providers sidebar. (#1901) Thanks @jerilynzheng. -- Agents: expand cron tool description with full schema docs. (#1988) Thanks @tomascupr. -- Skills: add missing dependency metadata for GitHub, Notion, Slack, Discord. (#1995) Thanks @jackheuberger. -- Docs: add Render deployment guide. (#1975) Thanks @anurag. -- Docs: add Claude Max API Proxy guide. (#1875) Thanks @atalovesyou. -- Docs: add DigitalOcean deployment guide. (#1870) Thanks @0xJonHoldsCrypto. -- Docs: add Oracle Cloud (OCI) platform guide + cross-links. (#2333) Thanks @hirefrank. -- Docs: add Raspberry Pi install guide. (#1871) Thanks @0xJonHoldsCrypto. -- Docs: add GCP Compute Engine deployment guide. (#1848) Thanks @hougangdev. -- Docs: add LINE channel guide. Thanks @thewilloftheshadow. -- Docs: credit both contributors for Control UI refresh. (#1852) Thanks @EnzeD. -- Onboarding: add Venice API key to non-interactive flow. (#1893) Thanks @jonisjongithub. -- Onboarding: strengthen security warning copy for beta + access control expectations. -- Tlon: format thread reply IDs as @ud. (#1837) Thanks @wca4a. -- Gateway: prefer newest session metadata when combining stores. (#1823) Thanks @emanuelst. -- Web UI: keep sub-agent announce replies visible in WebChat. (#1977) Thanks @andrescardonas7. -- CI: increase Node heap size for macOS checks. (#1890) Thanks @realZachi. -- macOS: avoid crash when rendering code blocks by bumping Textual to 0.3.1. (#2033) Thanks @garricn. -- Browser: fall back to URL matching for extension relay target resolution. (#1999) Thanks @jonit-dev. -- Browser: route browser control via gateway/node; remove standalone browser control command and control URL config. -- Browser: route `browser.request` via node proxies when available; honor proxy timeouts; derive browser ports from `gateway.port`. -- Update: ignore dist/control-ui for dirty checks and restore after ui builds. (#1976) Thanks @Glucksberg. -- Build: bundle A2UI assets during build and stop tracking generated bundles. (#2455) Thanks @0oAstro. -- Telegram: allow caption param for media sends. (#1888) Thanks @mguellsegarra. -- Telegram: support plugin sendPayload channelData (media/buttons) and validate plugin commands. (#1917) Thanks @JoshuaLelon. -- Telegram: avoid block replies when streaming is disabled. (#1885) Thanks @ivancasco. -- Docs: keep docs header sticky so navbar stays visible while scrolling. (#2445) Thanks @chenyuan99. -- Docs: update exe.dev install instructions. (#https://github.com/moltbot/moltbot/pull/3047) Thanks @zackerthescar. -- Security: use Windows ACLs for permission audits and fixes on Windows. (#1957) -- Auth: show copyable Google auth URL after ASCII prompt. (#1787) Thanks @robbyczgw-cla. -- Routing: precompile session key regexes. (#1697) Thanks @Ray0907. -- TUI: avoid width overflow when rendering selection lists. (#1686) Thanks @mossein. -- Telegram: keep topic IDs in restart sentinel notifications. (#1807) Thanks @hsrvc. -- Telegram: add optional silent send flag (disable notifications). (#2382) Thanks @Suksham-sharma. -- Telegram: support editing sent messages via message(action="edit"). (#2394) Thanks @marcelomar21. -- Telegram: support quote replies for message tool and inbound context. (#2900) Thanks @aduk059. -- Telegram: add sticker receive/send with vision caching. (#2629) Thanks @longjos. -- Telegram: send sticker pixels to vision models. (#2650) -- Config: apply config.env before ${VAR} substitution. (#1813) Thanks @spanishflu-est1918. -- Slack: clear ack reaction after streamed replies. (#2044) Thanks @fancyboi999. -- macOS: keep custom SSH usernames in remote target. (#2046) Thanks @algal. -- CLI: use Node's module compile cache for faster startup. (#2808) Thanks @pi0. -- Routing: add per-account DM session scope and document multi-account isolation. (#3095) Thanks @jarvis-sam. +- Config: apply config.env before ${VAR} substitution. (#1813) +- Web search: add Brave freshness filter parameter. (#1688) Thanks @JonUleis. +- Control UI: improve chat session dropdown refresh, URL confirmation flow, config-save guardrails, and chat composer sizing. (#3682, #3578, #1707, #2950) +- Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev. +- CLI: use Node's compile cache for faster startup; recognize versioned node binaries (e.g., node-22). (#2808, #2490) Thanks @pi0, @David-Marsh-Photo. +- Agents: summarize dropped messages during compaction safeguard pruning. (#2509) Thanks @jogi47. +- Skills: add multi-image input support to Nano Banana Pro skill. (#1958) Thanks @tyler6204. +- Matrix: switch plugin SDK to @vector-im/matrix-bot-sdk. +- Docs: new deployment guides (Northflank, Render, Oracle, Raspberry Pi, GCP, DigitalOcean), Claude Max API Proxy, Vercel AI Gateway, migration guide, formal verification updates, and Fly private hardening. (#2167, #1975, #2333, #1871, #1848, #1870, #1875, #1901, #2381, #2289) +- Onboarding: add Venice API key to non-interactive flow; strengthen security warning copy. ### Breaking - **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed). ### Fixes -- Telegram: avoid silent empty replies by tracking normalization skips before fallback. (#3796) -- Mentions: honor mentionPatterns even when explicit mentions are present. (#3303) Thanks @HirokiKobayashi-R. -- Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald. -- Agents: align MiniMax base URL test expectation with default provider config. (#3131) Thanks @bonald. -- Agents: prevent retries on oversized image errors and surface size limits. (#2871) Thanks @Suksham-sharma. -- Agents: inherit provider baseUrl/api for inline models. (#2740) Thanks @lploc94. -- Memory Search: keep auto provider model defaults and only include remote when configured. (#2576) Thanks @papago2355. -- Telegram: include AccountId in native command context for multi-agent routing. (#2942) Thanks @Chloe-VP. -- Telegram: handle video note attachments in media extraction. (#2905) Thanks @mylukin. -- TTS: read OPENAI_TTS_BASE_URL at runtime instead of module load to honor config.env. (#3341) Thanks @hclsys. -- macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee. -- Web UI: auto-expand the chat compose textarea while typing (with sensible max height). (#2950) Thanks @shivamraut101. -- Gateway: prevent crashes on transient network errors (fetch failures, timeouts, DNS). Added fatal error detection to only exit on truly critical errors. Fixes #2895, #2879, #2873. (#2980) Thanks @elliotsecops. -- Agents: guard channel tool listActions to avoid plugin crashes. (#2859) Thanks @mbelinky. -- Discord: stop resolveDiscordTarget from passing directory params into messaging target parsers. Fixes #3167. Thanks @thewilloftheshadow. -- Discord: avoid resolving bare channel names to user DMs when a username matches. Thanks @thewilloftheshadow. -- Discord: fix directory config type import for target resolution. Thanks @thewilloftheshadow. -- Providers: update MiniMax API endpoint and compatibility mode. (#3064) Thanks @hlbbbbbbb. -- Telegram: treat more network errors as recoverable in polling. (#3013) Thanks @ryancontent. -- Discord: resolve usernames to user IDs for outbound messages. (#2649) Thanks @nonggialiang. -- Providers: update Moonshot Kimi model references to kimi-k2.5. (#2762) Thanks @MarvinCui. -- Gateway: suppress AbortError and transient network errors in unhandled rejections. (#2451) Thanks @Glucksberg. -- TTS: keep /tts status replies on text-only commands and avoid duplicate block-stream audio. (#2451) Thanks @Glucksberg. -- Security: pin npm overrides to keep tar@7.5.4 for install toolchains. -- Security: properly test Windows ACL audit for config includes. (#2403) Thanks @dominicnunez. -- CLI: recognize versioned Node executables when parsing argv. (#2490) Thanks @David-Marsh-Photo. -- CLI: avoid prompting for gateway runtime under the spinner. (#2874) -- BlueBubbles: coalesce inbound URL link preview messages. (#1981) Thanks @tyler6204. -- Cron: allow payloads containing "heartbeat" in event filter. (#2219) Thanks @dwfinkelstein. -- CLI: avoid loading config for global help/version while registering plugin commands. (#2212) Thanks @dial481. -- Agents: include memory.md when bootstrapping memory context. (#2318) Thanks @czekaj. -- Agents: release session locks on process termination and cover more signals. (#2483) Thanks @janeexai. -- Agents: skip cooldowned providers during model failover. (#2143) Thanks @YiWang24. -- Telegram: harden polling + retry behavior for transient network errors and Node 22 transport issues. (#2420) Thanks @techboss. -- Telegram: ignore non-forum group message_thread_id while preserving DM thread sessions. (#2731) Thanks @dylanneve1. -- Telegram: wrap reasoning italics per line to avoid raw underscores. (#2181) Thanks @YuriNachos. -- Telegram: centralize API error logging for delivery and bot calls. (#2492) Thanks @altryne. -- Voice Call: enforce Twilio webhook signature verification for ngrok URLs; disable ngrok free tier bypass by default. -- Security: harden Tailscale Serve auth by validating identity via local tailscaled before trusting headers. -- Media: fix text attachment MIME misclassification with CSV/TSV inference and UTF-16 detection; add XML attribute escaping for file output. (#3628) Thanks @frankekn. -- Build: align memory-core peer dependency with lockfile. -- Security: add mDNS discovery mode with minimal default to reduce information disclosure. (#1882) Thanks @orlyjamie. -- Security: harden URL fetches with DNS pinning to reduce rebinding risk. Thanks Chris Zheng. -- Web UI: improve WebChat image paste previews and allow image-only sends. (#1925) Thanks @smartprogrammer93. -- Security: wrap external hook content by default with a per-hook opt-out. (#1827) Thanks @mertcicekci0. -- Gateway: default auth now fail-closed (token/password required; Tailscale Serve identity remains allowed). -- Gateway: treat loopback + non-local Host connections as remote unless trusted proxy headers are present. -- Onboarding: remove unsupported gateway auth "off" choice from onboarding/configure flows and CLI flags. +- Security: harden SSH tunnel target parsing to prevent option injection/DoS. (#4001) Thanks @YLChen-007. +- Security: prevent PATH injection in exec sandbox; harden file serving; pin DNS in URL fetches; verify Twilio webhooks; fix LINE webhook timing-attack edge case; validate Tailscale Serve identity; flag loopback Control UI with auth disabled as critical. (#1616, #1795) +- Gateway: prevent crashes on transient network errors, suppress AbortError/unhandled rejections, sanitize error responses, clean session locks on exit, and harden reverse proxy handling for unauthenticated proxied connects. (#2980, #2451, #2483, #1795) +- Config: auto-migrate legacy state/config paths; honor state dir overrides. +- Packaging: include missing dist/shared and dist/link-understanding outputs in npm tarball installs. +- Telegram: avoid silent empty replies, improve polling/network recovery, handle video notes, keep DM thread sessions, ignore non-forum message_thread_id, centralize API error logging, include AccountId in native command context. (#3796, #3013, #2905, #2731, #2492, #2942) +- Telegram: preserve reasoning tags inside code blocks. (#3952) Thanks @vinaygit18. +- Discord: restore username resolution, resolve outbound usernames to IDs, honor threadId replies, guard forum thread access. (#3131, #2649) +- BlueBubbles: coalesce URL link previews, improve reaction handling, preserve reply-tag GUIDs. (#1981, #1641) +- Voice Call: prevent TTS overlap, validate env-var config, return TwiML for conversation calls. (#1713, #1634) +- Media: fix text attachment MIME classification + XML escaping on Windows. (#3628, #3750) +- Models: inherit provider baseUrl/api for inline models. (#2740) Thanks @lploc94. +- Web UI: auto-scroll on send; fix textarea sizing; improve chat session refresh. (#2471, #2950, #3682) +- CLI/TUI: resume sessions cleanly; guard width overflow; avoid spinner prompt race. (#1921, #1686, #2874) +- Slack: fix file downloads failing on redirects with missing auth header. (#1936) +- iMessage: normalize messaging targets. (#1708) +- Signal: fix reactions and add configurable startup timeout. (#1651, #1677) +- Matrix: decrypt E2EE media with size guard. (#1744) -## 2026.1.24-3 - -### Fixes -- Slack: fix image downloads failing due to missing Authorization header on cross-origin redirects. (#1936) Thanks @sanderhelgesen. -- Gateway: harden reverse proxy handling for local-client detection and unauthenticated proxied connects. (#1795) Thanks @orlyjamie. -- Security audit: flag loopback Control UI with auth disabled as critical. (#1795) Thanks @orlyjamie. -- CLI: resume claude-cli sessions and stream CLI replies to TUI clients. (#1921) Thanks @rmorse. - -## 2026.1.24-2 - -### Fixes -- Packaging: include dist/link-understanding output in npm tarball (fixes missing apply.js import on install). - -## 2026.1.24-1 - -### Fixes -- Packaging: include dist/shared output in npm tarball (fixes missing reasoning-tags import on install). ## 2026.1.24 diff --git a/README.md b/README.md index 70ca70157..ec970bb5b 100644 --- a/README.md +++ b/README.md @@ -479,38 +479,38 @@ Thanks to all clawtributors:

steipete plum-dawg bohdanpodvirnyi iHildy jaydenfyi joaohlisboa mneves75 MatthieuBizien MaudeBot Glucksberg - rahthakor vrknetha radek-paclt vignesh07 Tobias Bischoff joshp123 czekaj mukhtharcm sebslight maxsumrall - xadenryan rodrigouroz juanpablodlc hsrvc magimetal zerone0x tyler6204 meaningfool patelhiren NicholasSpisak - jonisjongithub abhisekbasu1 jamesgroat claude JustYannicc Mariano Belinky Hyaxia dantelex SocialNerd42069 daveonkels - google-labs-jules[bot] lc0rp mousberg adam91holt hougangdev shakkernerd gumadeiras mteam88 hirefrank joeynyc - orlyjamie dbhurley Eng. Juan Combetto TSavo julianengel bradleypriest benithors rohannagpal timolins f-trycua - benostein elliotsecops nachx639 pvoo sreekaransrinath gupsammy cristip73 stefangalescu nachoiacovino Vasanth Rao Naik Sabavat + rahthakor vrknetha radek-paclt vignesh07 joshp123 Tobias Bischoff czekaj mukhtharcm sebslight maxsumrall + xadenryan rodrigouroz juanpablodlc tyler6204 hsrvc magimetal zerone0x meaningfool patelhiren NicholasSpisak + jonisjongithub abhisekbasu1 jamesgroat claude JustYannicc SocialNerd42069 Mariano Belinky Hyaxia dantelex daveonkels + google-labs-jules[bot] lc0rp adam91holt mousberg hougangdev shakkernerd gumadeiras mteam88 hirefrank joeynyc + orlyjamie Eng. Juan Combetto dbhurley TSavo julianengel bradleypriest benithors rohannagpal elliotsecops timolins + benostein f-trycua nachx639 pvoo sreekaransrinath gupsammy cristip73 stefangalescu nachoiacovino Vasanth Rao Naik Sabavat petter-b thewilloftheshadow cpojer scald andranik-sahakyan davidguttman sleontenko denysvitali sircrumpet peschee - nonggialiang rafaelreis-r dominicnunez lploc94 ratulsarna lutr0 danielz1z AdeboyeDN Alg0rix papago2355 - emanuelst KristijanJovanovski rdev rhuanssauro joshrad-dev kiranjd osolmaz adityashaw2 CashWilliams sheeek - ryancontent artuskg Takhoffman onutc pauloportella neooriginal manuelhettich minghinmatthewlam myfunc travisirby - obviyus buddyh connorshea kyleok mcinteerj dependabot[bot] John-Rood timkrase uos-status gerardward2007 - roshanasingh4 tosh-hamburg azade-c dlauer JonUleis shivamraut101 bjesuiter cheeeee robbyczgw-cla Josh Phillips - YuriNachos pookNast Whoaa512 chriseidhof ngutman ysqander aj47 kennyklee superman32432432 Yurii Chukhlib - grp06 antons austinm911 blacksmith-sh[bot] damoahdominic dan-dr HeimdallStrategy imfing jalehman jarvis-medmatic - kkarimi mahmoudashraf93 pkrmf RandyVentures Ryan Lisse dougvk erikpr1994 fal3 Ghost jonasjancarik - Keith the Silly Goose L36 Server Marc mitschabaude-bot mkbehr neist sibbl chrisrodz Friederike Seiler gabriel-trigo - iamadig Jonathan D. Rhyne (DJ-D) Joshua Mitchell Kit koala73 manmal ogulcancelik pasogott petradonka rubyrunsstuff - siddhantjain suminhthanh svkozak VACInc wes-davis zats 24601 ameno- Chris Taylor dguido - Django Navarro evalexpr henrino3 humanwritten larlyssa Lukavyi odysseus0 oswalpalash pcty-nextgen-service-account pi0 - rmorse Roopak Nijhara Syhids Aaron Konyer aaronveklabs andreabadesso Andrii cash-echo-bot Clawd ClawdFx - EnzeD erik-agens Evizero fcatuhe itsjaydesu ivancasco ivanrvpereira Jarvis jayhickey jeffersonwarrior - jeffersonwarrior jverdi longmaba MarvinCui mickahouan mjrussell odnxe p6l-richard philipp-spiess Pocket Clawd - robaxelsen Sash Catanzarite Suksham-sharma T5-AndyML tewatia travisp VAC william arzt zknicker 0oAstro - abhaymundhara aduk059 alejandro maza Alex-Alaniz alexstyl andrewting19 anpoirier araa47 arthyn Asleep123 - bguidolim bolismauro chenyuan99 Clawdbot Maintainers conhecendoia dasilva333 David-Marsh-Photo Developer Dimitrios Ploutarchos Drake Thomsen - dylanneve1 Felix Krause foeken frankekn ganghyun kim grrowl gtsifrikas HazAT hrdwdmrbl hugobarauna - Jamie Openshaw Jane Jarvis Deploy Jefferson Nunn jogi47 kentaro Kevin Lin kira-ariaki kitze Kiwitwitter - levifig Lloyd longjos loukotal louzhixian martinpucik Matt mini mertcicekci0 Miles mrdbstn - MSch Mustafa Tag Eldeen ndraiman nexty5870 Noctivoro ppamment prathamdby ptn1411 reeltimeapps RLTCmpe - Rolf Fredheim Rony Kelner Samrat Jha senoldogann sergical shiv19 shiyuanhai siraht snopoke techboss - testingabc321 The Admiral thesash Ubuntu voidserf Vultr-Clawd Admin Wimmie wolfred wstock YangHuang2280 - yazinsai YiWang24 ymat19 Zach Knickerbocker zackerthescar 0xJonHoldsCrypto aaronn Alphonse-arianee atalovesyou Azade - carlulsoe ddyo Erik latitudeki5223 Manuel Maly Mourad Boustani odrobnik pcty-nextgen-ios-builder Quentin Randy Torres - rhjoh ronak-guliani William Stock + rafaelreis-r nonggialiang dominicnunez lploc94 ratulsarna lutr0 kiranjd danielz1z AdeboyeDN Alg0rix + papago2355 emanuelst KristijanJovanovski CashWilliams rdev rhuanssauro osolmaz joshrad-dev adityashaw2 sheeek + ryancontent artuskg Takhoffman onutc pauloportella HirokiKobayashi-R neooriginal obviyus manuelhettich minghinmatthewlam + myfunc travisirby buddyh connorshea kyleok mcinteerj dependabot[bot] John-Rood timkrase uos-status + gerardward2007 roshanasingh4 tosh-hamburg azade-c dlauer JonUleis shivamraut101 bjesuiter cheeeee robbyczgw-cla + conroywhitney Josh Phillips YuriNachos pookNast Whoaa512 chriseidhof ngutman ysqander aj47 kennyklee + superman32432432 Yurii Chukhlib grp06 antons austinm911 blacksmith-sh[bot] damoahdominic dan-dr HeimdallStrategy imfing + jalehman jarvis-medmatic kkarimi mahmoudashraf93 pkrmf RandyVentures Ryan Lisse dougvk erikpr1994 fal3 + Ghost jonasjancarik Keith the Silly Goose L36 Server Marc mitschabaude-bot mkbehr neist sibbl chrisrodz + Friederike Seiler gabriel-trigo iamadig Jonathan D. Rhyne (DJ-D) Joshua Mitchell Kit koala73 manmal ogulcancelik pasogott + petradonka rubyrunsstuff siddhantjain suminhthanh svkozak VACInc wes-davis zats 24601 ameno- + Chris Taylor dguido Django Navarro evalexpr henrino3 humanwritten larlyssa Lukavyi odysseus0 oswalpalash + pcty-nextgen-service-account pi0 rmorse Roopak Nijhara Syhids Aaron Konyer aaronveklabs andreabadesso Andrii cash-echo-bot + Clawd ClawdFx EnzeD erik-agens Evizero fcatuhe itsjaydesu ivancasco ivanrvpereira Jarvis + jayhickey jeffersonwarrior jeffersonwarrior jverdi longmaba MarvinCui mickahouan mjrussell odnxe p6l-richard + philipp-spiess Pocket Clawd robaxelsen Sash Catanzarite Suksham-sharma T5-AndyML tewatia travisp VAC william arzt + zknicker 0oAstro abhaymundhara aduk059 alejandro maza Alex-Alaniz alexstyl andrewting19 anpoirier araa47 + arthyn Asleep123 bguidolim bolismauro chenyuan99 Chloe-VP Clawdbot Maintainers conhecendoia dasilva333 David-Marsh-Photo + Developer Dimitrios Ploutarchos Drake Thomsen dylanneve1 Felix Krause foeken frankekn ganghyun kim grrowl gtsifrikas + HazAT hrdwdmrbl hugobarauna Jamie Openshaw Jane Jarvis Deploy Jefferson Nunn jogi47 kentaro Kevin Lin + kira-ariaki kitze Kiwitwitter levifig Lloyd longjos loukotal louzhixian martinpucik Matt mini + mertcicekci0 Miles mrdbstn MSch Mustafa Tag Eldeen mylukin ndraiman nexty5870 Noctivoro ppamment + prathamdby ptn1411 reeltimeapps RLTCmpe Rolf Fredheim Rony Kelner Samrat Jha senoldogann Seredeep sergical + shiv19 shiyuanhai siraht snopoke techboss testingabc321 The Admiral thesash Ubuntu voidserf + Vultr-Clawd Admin Wimmie wolfred wstock YangHuang2280 yazinsai YiWang24 ymat19 Zach Knickerbocker zackerthescar + 0xJonHoldsCrypto aaronn Alphonse-arianee atalovesyou Azade carlulsoe ddyo Erik latitudeki5223 Manuel Maly + Mourad Boustani odrobnik pcty-nextgen-ios-builder Quentin Randy Torres rhjoh ronak-guliani William Stock

diff --git a/apps/android/app/build.gradle.kts b/apps/android/app/build.gradle.kts index 3ddcb3b81..97f9a250e 100644 --- a/apps/android/app/build.gradle.kts +++ b/apps/android/app/build.gradle.kts @@ -21,8 +21,8 @@ android { applicationId = "bot.molt.android" minSdk = 31 targetSdk = 36 - versionCode = 202601260 - versionName = "2026.1.27-beta.1" + versionCode = 202601290 + versionName = "2026.1.29" } buildTypes { diff --git a/apps/ios/Sources/Info.plist b/apps/ios/Sources/Info.plist index d3e398ab4..fe6a6154f 100644 --- a/apps/ios/Sources/Info.plist +++ b/apps/ios/Sources/Info.plist @@ -19,9 +19,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2026.1.27-beta.1 + 2026.1.29 CFBundleVersion - 20260126 + 20260129 NSAppTransportSecurity NSAllowsArbitraryLoadsInWebContent diff --git a/apps/ios/Tests/Info.plist b/apps/ios/Tests/Info.plist index a5336c6ad..847ca3b01 100644 --- a/apps/ios/Tests/Info.plist +++ b/apps/ios/Tests/Info.plist @@ -17,8 +17,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2026.1.27-beta.1 + 2026.1.29 CFBundleVersion - 20260126 + 20260129 diff --git a/apps/ios/project.yml b/apps/ios/project.yml index a6728cd98..1d31171b9 100644 --- a/apps/ios/project.yml +++ b/apps/ios/project.yml @@ -81,8 +81,8 @@ targets: properties: CFBundleDisplayName: Moltbot CFBundleIconName: AppIcon - CFBundleShortVersionString: "2026.1.27-beta.1" - CFBundleVersion: "20260126" + CFBundleShortVersionString: "2026.1.29" + CFBundleVersion: "20260129" UILaunchScreen: {} UIApplicationSceneManifest: UIApplicationSupportsMultipleScenes: false @@ -130,5 +130,5 @@ targets: path: Tests/Info.plist properties: CFBundleDisplayName: MoltbotTests - CFBundleShortVersionString: "2026.1.27-beta.1" - CFBundleVersion: "20260126" + CFBundleShortVersionString: "2026.1.29" + CFBundleVersion: "20260129" diff --git a/apps/macos/Sources/Moltbot/Resources/Info.plist b/apps/macos/Sources/Moltbot/Resources/Info.plist index 0c0de8b9e..623ae02b0 100644 --- a/apps/macos/Sources/Moltbot/Resources/Info.plist +++ b/apps/macos/Sources/Moltbot/Resources/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2026.1.27-beta.1 + 2026.1.29 CFBundleVersion - 202601260 + 202601290 CFBundleIconFile Moltbot CFBundleURLTypes diff --git a/docs/docs.json b/docs/docs.json index a463479aa..389adbe51 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -69,6 +69,14 @@ "source": "/minimax/", "destination": "/providers/minimax" }, + { + "source": "/xiaomi", + "destination": "/providers/xiaomi" + }, + { + "source": "/xiaomi/", + "destination": "/providers/xiaomi" + }, { "source": "/openai", "destination": "/providers/openai" diff --git a/docs/platforms/fly.md b/docs/platforms/fly.md index d8db124ac..02d24e84a 100644 --- a/docs/platforms/fly.md +++ b/docs/platforms/fly.md @@ -185,7 +185,7 @@ cat > /data/moltbot.json << 'EOF' "bind": "auto" }, "meta": { - "lastTouchedVersion": "2026.1.27-beta.1" + "lastTouchedVersion": "2026.1.29" } } EOF diff --git a/docs/platforms/mac/release.md b/docs/platforms/mac/release.md index 237eac616..5be621bba 100644 --- a/docs/platforms/mac/release.md +++ b/docs/platforms/mac/release.md @@ -30,17 +30,17 @@ Notes: # From repo root; set release IDs so Sparkle feed is enabled. # APP_BUILD must be numeric + monotonic for Sparkle compare. BUNDLE_ID=bot.molt.mac \ -APP_VERSION=2026.1.27-beta.1 \ +APP_VERSION=2026.1.29 \ APP_BUILD="$(git rev-list --count HEAD)" \ BUILD_CONFIG=release \ SIGN_IDENTITY="Developer ID Application: ()" \ scripts/package-mac-app.sh # Zip for distribution (includes resource forks for Sparkle delta support) -ditto -c -k --sequesterRsrc --keepParent dist/Moltbot.app dist/Moltbot-2026.1.27-beta.1.zip +ditto -c -k --sequesterRsrc --keepParent dist/Moltbot.app dist/Moltbot-2026.1.29.zip # Optional: also build a styled DMG for humans (drag to /Applications) -scripts/create-dmg.sh dist/Moltbot.app dist/Moltbot-2026.1.27-beta.1.dmg +scripts/create-dmg.sh dist/Moltbot.app dist/Moltbot-2026.1.29.dmg # Recommended: build + notarize/staple zip + DMG # First, create a keychain profile once: @@ -48,26 +48,26 @@ scripts/create-dmg.sh dist/Moltbot.app dist/Moltbot-2026.1.27-beta.1.dmg # --apple-id "" --team-id "" --password "" NOTARIZE=1 NOTARYTOOL_PROFILE=moltbot-notary \ BUNDLE_ID=bot.molt.mac \ -APP_VERSION=2026.1.27-beta.1 \ +APP_VERSION=2026.1.29 \ APP_BUILD="$(git rev-list --count HEAD)" \ BUILD_CONFIG=release \ SIGN_IDENTITY="Developer ID Application: ()" \ scripts/package-mac-dist.sh # Optional: ship dSYM alongside the release -ditto -c -k --keepParent apps/macos/.build/release/Moltbot.app.dSYM dist/Moltbot-2026.1.27-beta.1.dSYM.zip +ditto -c -k --keepParent apps/macos/.build/release/Moltbot.app.dSYM dist/Moltbot-2026.1.29.dSYM.zip ``` ## Appcast entry Use the release note generator so Sparkle renders formatted HTML notes: ```bash -SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/Moltbot-2026.1.27-beta.1.zip https://raw.githubusercontent.com/moltbot/moltbot/main/appcast.xml +SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/Moltbot-2026.1.29.zip https://raw.githubusercontent.com/moltbot/moltbot/main/appcast.xml ``` Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/moltbot/moltbot/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry. Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when publishing. ## Publish & verify -- Upload `Moltbot-2026.1.27-beta.1.zip` (and `Moltbot-2026.1.27-beta.1.dSYM.zip`) to the GitHub release for tag `v2026.1.27-beta.1`. +- Upload `Moltbot-2026.1.29.zip` (and `Moltbot-2026.1.29.dSYM.zip`) to the GitHub release for tag `v2026.1.29`. - Ensure the raw appcast URL matches the baked feed: `https://raw.githubusercontent.com/moltbot/moltbot/main/appcast.xml`. - Sanity checks: - `curl -I https://raw.githubusercontent.com/moltbot/moltbot/main/appcast.xml` returns 200. diff --git a/docs/providers/index.md b/docs/providers/index.md index ce638745d..3cd325c1d 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -43,6 +43,7 @@ See [Venice AI](/providers/venice). - [OpenCode Zen](/providers/opencode) - [Amazon Bedrock](/bedrock) - [Z.AI](/providers/zai) +- [Xiaomi](/providers/xiaomi) - [GLM models](/providers/glm) - [MiniMax](/providers/minimax) - [Venius (Venice AI, privacy-focused)](/providers/venice) diff --git a/docs/providers/xiaomi.md b/docs/providers/xiaomi.md new file mode 100644 index 000000000..008c42105 --- /dev/null +++ b/docs/providers/xiaomi.md @@ -0,0 +1,62 @@ +--- +summary: "Use Xiaomi MiMo (mimo-v2-flash) with Moltbot" +read_when: + - You want Xiaomi MiMo models in Moltbot + - You need XIAOMI_API_KEY setup +--- +# Xiaomi MiMo + +Xiaomi MiMo is the API platform for **MiMo** models. It provides REST APIs compatible with +OpenAI and Anthropic formats and uses API keys for authentication. Create your API key in +the [Xiaomi MiMo console](https://platform.xiaomimimo.com/#/console/api-keys). Moltbot uses +the `xiaomi` provider with a Xiaomi MiMo API key. + +## Model overview + +- **mimo-v2-flash**: 262144-token context window, Anthropic Messages API compatible. +- Base URL: `https://api.xiaomimimo.com/anthropic` +- Authorization: `Bearer $XIAOMI_API_KEY` + +## CLI setup + +```bash +moltbot onboard --auth-choice xiaomi-api-key +# or non-interactive +moltbot onboard --auth-choice xiaomi-api-key --xiaomi-api-key "$XIAOMI_API_KEY" +``` + +## Config snippet + +```json5 +{ + env: { XIAOMI_API_KEY: "your-key" }, + agents: { defaults: { model: { primary: "xiaomi/mimo-v2-flash" } } }, + models: { + mode: "merge", + providers: { + xiaomi: { + baseUrl: "https://api.xiaomimimo.com/anthropic", + api: "anthropic-messages", + apiKey: "XIAOMI_API_KEY", + models: [ + { + id: "mimo-v2-flash", + name: "Xiaomi MiMo V2 Flash", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 8192 + } + ] + } + } + } +} +``` + +## Notes + +- Model ref: `xiaomi/mimo-v2-flash`. +- The provider is injected automatically when `XIAOMI_API_KEY` is set (or an auth profile exists). +- See [/concepts/model-providers](/concepts/model-providers) for provider rules. diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index e648fb33c..77a59df29 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -17,7 +17,7 @@ When the operator says “release”, immediately do this preflight (no extra qu - Use Sparkle keys from `~/Library/CloudStorage/Dropbox/Backup/Sparkle` if needed. 1) **Version & metadata** -- [ ] Bump `package.json` version (e.g., `2026.1.27-beta.1`). +- [ ] Bump `package.json` version (e.g., `2026.1.29`). - [ ] Run `pnpm plugins:sync` to align extension package versions + changelogs. - [ ] Update CLI/version strings: [`src/cli/program.ts`](https://github.com/moltbot/moltbot/blob/main/src/cli/program.ts) and the Baileys user agent in [`src/provider-web.ts`](https://github.com/moltbot/moltbot/blob/main/src/provider-web.ts). - [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`moltbot.mjs`](https://github.com/moltbot/moltbot/blob/main/moltbot.mjs) for `moltbot`. diff --git a/extensions/bluebubbles/package.json b/extensions/bluebubbles/package.json index fc1ac34ae..7ffa39845 100644 --- a/extensions/bluebubbles/package.json +++ b/extensions/bluebubbles/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/bluebubbles", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot BlueBubbles channel plugin", "moltbot": { diff --git a/extensions/copilot-proxy/package.json b/extensions/copilot-proxy/package.json index 2d4753446..fd8cbcbbb 100644 --- a/extensions/copilot-proxy/package.json +++ b/extensions/copilot-proxy/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/copilot-proxy", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Copilot Proxy provider plugin", "moltbot": { diff --git a/extensions/diagnostics-otel/package.json b/extensions/diagnostics-otel/package.json index f6560702b..3c6fc084b 100644 --- a/extensions/diagnostics-otel/package.json +++ b/extensions/diagnostics-otel/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/diagnostics-otel", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot diagnostics OpenTelemetry exporter", "moltbot": { diff --git a/extensions/discord/package.json b/extensions/discord/package.json index 9921468b4..9e068ee36 100644 --- a/extensions/discord/package.json +++ b/extensions/discord/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/discord", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Discord channel plugin", "moltbot": { diff --git a/extensions/google-antigravity-auth/package.json b/extensions/google-antigravity-auth/package.json index 8b13861ec..8c828cab0 100644 --- a/extensions/google-antigravity-auth/package.json +++ b/extensions/google-antigravity-auth/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/google-antigravity-auth", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Google Antigravity OAuth provider plugin", "moltbot": { diff --git a/extensions/google-gemini-cli-auth/package.json b/extensions/google-gemini-cli-auth/package.json index 59cbd52a9..452118707 100644 --- a/extensions/google-gemini-cli-auth/package.json +++ b/extensions/google-gemini-cli-auth/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/google-gemini-cli-auth", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Gemini CLI OAuth provider plugin", "moltbot": { diff --git a/extensions/googlechat/package.json b/extensions/googlechat/package.json index 0a01621e6..3893d452a 100644 --- a/extensions/googlechat/package.json +++ b/extensions/googlechat/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/googlechat", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Google Chat channel plugin", "moltbot": { diff --git a/extensions/imessage/package.json b/extensions/imessage/package.json index 29ceb0631..e103bbcd7 100644 --- a/extensions/imessage/package.json +++ b/extensions/imessage/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/imessage", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot iMessage channel plugin", "moltbot": { diff --git a/extensions/line/package.json b/extensions/line/package.json index bd336b158..a45438252 100644 --- a/extensions/line/package.json +++ b/extensions/line/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/line", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot LINE channel plugin", "moltbot": { diff --git a/extensions/llm-task/package.json b/extensions/llm-task/package.json index 247d126a9..94b6e72b0 100644 --- a/extensions/llm-task/package.json +++ b/extensions/llm-task/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/llm-task", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot JSON-only LLM task plugin", "moltbot": { diff --git a/extensions/lobster/package.json b/extensions/lobster/package.json index c95d7021a..ee9b21ac7 100644 --- a/extensions/lobster/package.json +++ b/extensions/lobster/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/lobster", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)", "moltbot": { diff --git a/extensions/matrix/CHANGELOG.md b/extensions/matrix/CHANGELOG.md index 8b7dcb62c..6972449d9 100644 --- a/extensions/matrix/CHANGELOG.md +++ b/extensions/matrix/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2026.1.27-beta.1 +## 2026.1.29 ### Changes - Version alignment with core Moltbot release numbers. diff --git a/extensions/matrix/package.json b/extensions/matrix/package.json index abc608b5b..49c6d9236 100644 --- a/extensions/matrix/package.json +++ b/extensions/matrix/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/matrix", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Matrix channel plugin", "moltbot": { diff --git a/extensions/mattermost/package.json b/extensions/mattermost/package.json index 6e7d3f1fc..0063726eb 100644 --- a/extensions/mattermost/package.json +++ b/extensions/mattermost/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/mattermost", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Mattermost channel plugin", "moltbot": { diff --git a/extensions/memory-core/package.json b/extensions/memory-core/package.json index e863adbd2..b97fd2c68 100644 --- a/extensions/memory-core/package.json +++ b/extensions/memory-core/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/memory-core", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot core memory search plugin", "moltbot": { diff --git a/extensions/memory-lancedb/package.json b/extensions/memory-lancedb/package.json index 0e79ce83a..e0c16ca44 100644 --- a/extensions/memory-lancedb/package.json +++ b/extensions/memory-lancedb/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/memory-lancedb", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot LanceDB-backed long-term memory plugin with auto-recall/capture", "dependencies": { diff --git a/extensions/msteams/CHANGELOG.md b/extensions/msteams/CHANGELOG.md index 09a9e92bd..3a56e1a1b 100644 --- a/extensions/msteams/CHANGELOG.md +++ b/extensions/msteams/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2026.1.27-beta.1 +## 2026.1.29 ### Changes - Version alignment with core Moltbot release numbers. diff --git a/extensions/msteams/package.json b/extensions/msteams/package.json index 29e615862..4b070d3e2 100644 --- a/extensions/msteams/package.json +++ b/extensions/msteams/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/msteams", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Microsoft Teams channel plugin", "moltbot": { diff --git a/extensions/nextcloud-talk/package.json b/extensions/nextcloud-talk/package.json index 5e98956da..0f28a5279 100644 --- a/extensions/nextcloud-talk/package.json +++ b/extensions/nextcloud-talk/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/nextcloud-talk", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Nextcloud Talk channel plugin", "moltbot": { diff --git a/extensions/nostr/CHANGELOG.md b/extensions/nostr/CHANGELOG.md index 65ac7f56e..a1a3fdf63 100644 --- a/extensions/nostr/CHANGELOG.md +++ b/extensions/nostr/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2026.1.27-beta.1 +## 2026.1.29 ### Changes - Version alignment with core Moltbot release numbers. diff --git a/extensions/nostr/package.json b/extensions/nostr/package.json index 8ba9a48d0..ccc4346cf 100644 --- a/extensions/nostr/package.json +++ b/extensions/nostr/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/nostr", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Nostr channel plugin for NIP-04 encrypted DMs", "moltbot": { diff --git a/extensions/open-prose/package.json b/extensions/open-prose/package.json index 89904fcca..a71bbd0d0 100644 --- a/extensions/open-prose/package.json +++ b/extensions/open-prose/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/open-prose", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "OpenProse VM skill pack plugin (slash command + telemetry).", "moltbot": { diff --git a/extensions/signal/package.json b/extensions/signal/package.json index 105a4fee8..986215e15 100644 --- a/extensions/signal/package.json +++ b/extensions/signal/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/signal", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Signal channel plugin", "moltbot": { diff --git a/extensions/slack/package.json b/extensions/slack/package.json index 8ada7de5f..9dc9c62a8 100644 --- a/extensions/slack/package.json +++ b/extensions/slack/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/slack", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Slack channel plugin", "moltbot": { diff --git a/extensions/telegram/package.json b/extensions/telegram/package.json index 0f485d029..a709e269f 100644 --- a/extensions/telegram/package.json +++ b/extensions/telegram/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/telegram", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Telegram channel plugin", "moltbot": { diff --git a/extensions/tlon/package.json b/extensions/tlon/package.json index 2df375b55..a8af17c9c 100644 --- a/extensions/tlon/package.json +++ b/extensions/tlon/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/tlon", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Tlon/Urbit channel plugin", "moltbot": { diff --git a/extensions/twitch/CHANGELOG.md b/extensions/twitch/CHANGELOG.md index 95b5ff2c7..25ff5fb6d 100644 --- a/extensions/twitch/CHANGELOG.md +++ b/extensions/twitch/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2026.1.27-beta.1 +## 2026.1.29 ### Changes - Version alignment with core Moltbot release numbers. diff --git a/extensions/twitch/package.json b/extensions/twitch/package.json index 6654f9bb7..d6a6b4474 100644 --- a/extensions/twitch/package.json +++ b/extensions/twitch/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/twitch", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "description": "Moltbot Twitch channel plugin", "type": "module", "dependencies": { diff --git a/extensions/voice-call/CHANGELOG.md b/extensions/voice-call/CHANGELOG.md index 312e95917..f3ec738ed 100644 --- a/extensions/voice-call/CHANGELOG.md +++ b/extensions/voice-call/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2026.1.27-beta.1 +## 2026.1.29 ### Changes - Version alignment with core Moltbot release numbers. diff --git a/extensions/voice-call/package.json b/extensions/voice-call/package.json index 72bfba03d..b99074aeb 100644 --- a/extensions/voice-call/package.json +++ b/extensions/voice-call/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/voice-call", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot voice-call plugin", "dependencies": { diff --git a/extensions/whatsapp/package.json b/extensions/whatsapp/package.json index d5139e18f..81bb39b71 100644 --- a/extensions/whatsapp/package.json +++ b/extensions/whatsapp/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/whatsapp", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot WhatsApp channel plugin", "moltbot": { diff --git a/extensions/zalo/CHANGELOG.md b/extensions/zalo/CHANGELOG.md index 55766ea8e..6134b5275 100644 --- a/extensions/zalo/CHANGELOG.md +++ b/extensions/zalo/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2026.1.27-beta.1 +## 2026.1.29 ### Changes - Version alignment with core Moltbot release numbers. diff --git a/extensions/zalo/package.json b/extensions/zalo/package.json index 2a6cf9a5f..61350d86b 100644 --- a/extensions/zalo/package.json +++ b/extensions/zalo/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/zalo", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Zalo channel plugin", "moltbot": { diff --git a/extensions/zalouser/CHANGELOG.md b/extensions/zalouser/CHANGELOG.md index e189e2e45..079990a79 100644 --- a/extensions/zalouser/CHANGELOG.md +++ b/extensions/zalouser/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2026.1.27-beta.1 +## 2026.1.29 ### Changes - Version alignment with core Moltbot release numbers. diff --git a/extensions/zalouser/package.json b/extensions/zalouser/package.json index 6bace36e8..eb5f19d25 100644 --- a/extensions/zalouser/package.json +++ b/extensions/zalouser/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/zalouser", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "type": "module", "description": "Moltbot Zalo Personal Account plugin via zca-cli", "dependencies": { diff --git a/package.json b/package.json index 04322f3af..4d38edf18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moltbot", - "version": "2026.1.27-beta.1", + "version": "2026.1.29", "description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent", "type": "module", "main": "dist/index.js", diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 4f56d95b7..effa509cc 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -281,6 +281,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { moonshot: "MOONSHOT_API_KEY", "kimi-code": "KIMICODE_API_KEY", minimax: "MINIMAX_API_KEY", + xiaomi: "XIAOMI_API_KEY", synthetic: "SYNTHETIC_API_KEY", venice: "VENICE_API_KEY", mistral: "MISTRAL_API_KEY", diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index a5df8e64e..ac0a6a383 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -35,6 +35,17 @@ const MINIMAX_API_COST = { cacheWrite: 10, }; +const XIAOMI_BASE_URL = "https://api.xiaomimimo.com/anthropic"; +export const XIAOMI_DEFAULT_MODEL_ID = "mimo-v2-flash"; +const XIAOMI_DEFAULT_CONTEXT_WINDOW = 262144; +const XIAOMI_DEFAULT_MAX_TOKENS = 8192; +const XIAOMI_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1"; const MOONSHOT_DEFAULT_MODEL_ID = "kimi-k2.5"; const MOONSHOT_DEFAULT_CONTEXT_WINDOW = 256000; @@ -346,6 +357,24 @@ function buildSyntheticProvider(): ProviderConfig { }; } +export function buildXiaomiProvider(): ProviderConfig { + return { + baseUrl: XIAOMI_BASE_URL, + api: "anthropic-messages", + models: [ + { + id: XIAOMI_DEFAULT_MODEL_ID, + name: "Xiaomi MiMo V2 Flash", + reasoning: false, + input: ["text"], + cost: XIAOMI_DEFAULT_COST, + contextWindow: XIAOMI_DEFAULT_CONTEXT_WINDOW, + maxTokens: XIAOMI_DEFAULT_MAX_TOKENS, + }, + ], + }; +} + async function buildVeniceProvider(): Promise { const models = await discoverVeniceModels(); return { @@ -426,6 +455,13 @@ export async function resolveImplicitProviders(params: { }; } + const xiaomiKey = + resolveEnvApiKeyVarName("xiaomi") ?? + resolveApiKeyFromProfiles({ provider: "xiaomi", store: authStore }); + if (xiaomiKey) { + providers.xiaomi = { ...buildXiaomiProvider(), apiKey: xiaomiKey }; + } + // Ollama provider - only add if explicitly configured const ollamaKey = resolveEnvApiKeyVarName("ollama") ?? diff --git a/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts b/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts index fef8fa6a4..08a66469f 100644 --- a/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts +++ b/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts @@ -53,6 +53,7 @@ describe("models-config", () => { const previousMoonshot = process.env.MOONSHOT_API_KEY; const previousSynthetic = process.env.SYNTHETIC_API_KEY; const previousVenice = process.env.VENICE_API_KEY; + const previousXiaomi = process.env.XIAOMI_API_KEY; delete process.env.COPILOT_GITHUB_TOKEN; delete process.env.GH_TOKEN; delete process.env.GITHUB_TOKEN; @@ -61,6 +62,7 @@ describe("models-config", () => { delete process.env.MOONSHOT_API_KEY; delete process.env.SYNTHETIC_API_KEY; delete process.env.VENICE_API_KEY; + delete process.env.XIAOMI_API_KEY; try { vi.resetModules(); @@ -93,6 +95,8 @@ describe("models-config", () => { else process.env.SYNTHETIC_API_KEY = previousSynthetic; if (previousVenice === undefined) delete process.env.VENICE_API_KEY; else process.env.VENICE_API_KEY = previousVenice; + if (previousXiaomi === undefined) delete process.env.XIAOMI_API_KEY; + else process.env.XIAOMI_API_KEY = previousXiaomi; } }); }); diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts index 0d3f956c3..859ed9124 100644 --- a/src/cli/program/register.onboard.ts +++ b/src/cli/program/register.onboard.ts @@ -52,7 +52,7 @@ export function registerOnboardCommand(program: Command) { .option("--mode ", "Wizard mode: local|remote") .option( "--auth-choice ", - "Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|together-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip", + "Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|xiaomi-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip|together-api-key", ) .option( "--token-provider ", @@ -72,6 +72,7 @@ export function registerOnboardCommand(program: Command) { .option("--kimi-code-api-key ", "Kimi Code API key") .option("--gemini-api-key ", "Gemini API key") .option("--zai-api-key ", "Z.AI API key") + .option("--xiaomi-api-key ", "Xiaomi API key") .option("--minimax-api-key ", "MiniMax API key") .option("--synthetic-api-key ", "Synthetic API key") .option("--venice-api-key ", "Venice API key") @@ -123,6 +124,7 @@ export function registerOnboardCommand(program: Command) { kimiCodeApiKey: opts.kimiCodeApiKey as string | undefined, geminiApiKey: opts.geminiApiKey as string | undefined, zaiApiKey: opts.zaiApiKey as string | undefined, + xiaomiApiKey: opts.xiaomiApiKey as string | undefined, minimaxApiKey: opts.minimaxApiKey as string | undefined, syntheticApiKey: opts.syntheticApiKey as string | undefined, veniceApiKey: opts.veniceApiKey as string | undefined, diff --git a/src/commands/auth-choice-options.test.ts b/src/commands/auth-choice-options.test.ts index ab75f09cf..8f61448ce 100644 --- a/src/commands/auth-choice-options.test.ts +++ b/src/commands/auth-choice-options.test.ts @@ -33,6 +33,16 @@ describe("buildAuthChoiceOptions", () => { expect(options.some((opt) => opt.value === "zai-api-key")).toBe(true); }); + it("includes Xiaomi auth choice", () => { + const store: AuthProfileStore = { version: 1, profiles: {} }; + const options = buildAuthChoiceOptions({ + store, + includeSkip: false, + }); + + expect(options.some((opt) => opt.value === "xiaomi-api-key")).toBe(true); + }); + it("includes MiniMax auth choice", () => { const store: AuthProfileStore = { version: 1, profiles: {} }; const options = buildAuthChoiceOptions({ diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index 7e3ab6ac0..f3c4988ff 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -16,6 +16,7 @@ export type AuthChoiceGroupId = | "ai-gateway" | "moonshot" | "zai" + | "xiaomi" | "opencode-zen" | "minimax" | "synthetic" @@ -114,6 +115,12 @@ const AUTH_CHOICE_GROUP_DEFS: { hint: "API key", choices: ["zai-api-key"], }, + { + value: "xiaomi", + label: "Xiaomi", + hint: "API key", + choices: ["xiaomi-api-key"], + }, { value: "opencode-zen", label: "OpenCode Zen", @@ -176,6 +183,10 @@ export function buildAuthChoiceOptions(params: { hint: "Uses the bundled Gemini CLI auth plugin", }); options.push({ value: "zai-api-key", label: "Z.AI (GLM 4.7) API key" }); + options.push({ + value: "xiaomi-api-key", + label: "Xiaomi API key", + }); options.push({ value: "qwen-portal", label: "Qwen OAuth" }); options.push({ value: "copilot-proxy", diff --git a/src/commands/auth-choice.apply.api-providers.ts b/src/commands/auth-choice.apply.api-providers.ts index 1efbfbf74..452b0e57c 100644 --- a/src/commands/auth-choice.apply.api-providers.ts +++ b/src/commands/auth-choice.apply.api-providers.ts @@ -29,6 +29,8 @@ import { applyVeniceProviderConfig, applyVercelAiGatewayConfig, applyVercelAiGatewayProviderConfig, + applyXiaomiConfig, + applyXiaomiProviderConfig, applyZaiConfig, KIMI_CODE_MODEL_REF, MOONSHOT_DEFAULT_MODEL_REF, @@ -37,6 +39,7 @@ import { TOGETHER_DEFAULT_MODEL_REF, VENICE_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, + XIAOMI_DEFAULT_MODEL_REF, setGeminiApiKey, setKimiCodeApiKey, setMoonshotApiKey, @@ -46,6 +49,7 @@ import { setTogetherApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, + setXiaomiApiKey, setZaiApiKey, ZAI_DEFAULT_MODEL_REF, } from "./onboard-auth.js"; @@ -83,6 +87,8 @@ export async function applyAuthChoiceApiProviders( authChoice = "gemini-api-key"; } else if (params.opts.tokenProvider === "zai") { authChoice = "zai-api-key"; + } else if (params.opts.tokenProvider === "xiaomi") { + authChoice = "xiaomi-api-key"; } else if (params.opts.tokenProvider === "synthetic") { authChoice = "synthetic-api-key"; } else if (params.opts.tokenProvider === "venice") { @@ -437,6 +443,54 @@ export async function applyAuthChoiceApiProviders( return { config: nextConfig, agentModelOverride }; } + if (authChoice === "xiaomi-api-key") { + let hasCredential = false; + + if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "xiaomi") { + await setXiaomiApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir); + hasCredential = true; + } + + const envKey = resolveEnvApiKey("xiaomi"); + if (envKey) { + const useExisting = await params.prompter.confirm({ + message: `Use existing XIAOMI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, + initialValue: true, + }); + if (useExisting) { + await setXiaomiApiKey(envKey.apiKey, params.agentDir); + hasCredential = true; + } + } + if (!hasCredential) { + const key = await params.prompter.text({ + message: "Enter Xiaomi API key", + validate: validateApiKeyInput, + }); + await setXiaomiApiKey(normalizeApiKeyInput(String(key)), params.agentDir); + } + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "xiaomi:default", + provider: "xiaomi", + mode: "api_key", + }); + { + const applied = await applyDefaultModelChoice({ + config: nextConfig, + setDefaultModel: params.setDefaultModel, + defaultModel: XIAOMI_DEFAULT_MODEL_REF, + applyDefaultConfig: applyXiaomiConfig, + applyProviderConfig: applyXiaomiProviderConfig, + noteDefault: XIAOMI_DEFAULT_MODEL_REF, + noteAgentModel, + prompter: params.prompter, + }); + nextConfig = applied.config; + agentModelOverride = applied.agentModelOverride ?? agentModelOverride; + } + return { config: nextConfig, agentModelOverride }; + } + if (authChoice === "synthetic-api-key") { if (params.opts?.token && params.opts?.tokenProvider === "synthetic") { await setSyntheticApiKey(String(params.opts.token).trim(), params.agentDir); diff --git a/src/commands/auth-choice.preferred-provider.ts b/src/commands/auth-choice.preferred-provider.ts index 2340002df..851f61a4e 100644 --- a/src/commands/auth-choice.preferred-provider.ts +++ b/src/commands/auth-choice.preferred-provider.ts @@ -18,6 +18,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial> = { "google-antigravity": "google-antigravity", "google-gemini-cli": "google-gemini-cli", "zai-api-key": "zai", + "xiaomi-api-key": "xiaomi", "synthetic-api-key": "synthetic", "venice-api-key": "venice", "together-api-key": "together", diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index da0fc374d..e3e74cfe8 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -1,3 +1,4 @@ +import { buildXiaomiProvider, XIAOMI_DEFAULT_MODEL_ID } from "../agents/models-config.providers.js"; import { buildSyntheticModelDefinition, SYNTHETIC_BASE_URL, @@ -22,6 +23,7 @@ import { OPENROUTER_DEFAULT_MODEL_REF, TOGETHER_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, + XIAOMI_DEFAULT_MODEL_REF, ZAI_DEFAULT_MODEL_REF, } from "./onboard-auth.credentials.js"; import { @@ -344,6 +346,77 @@ export function applySyntheticConfig(cfg: MoltbotConfig): MoltbotConfig { }; } +export function applyXiaomiProviderConfig(cfg: MoltbotConfig): MoltbotConfig { + const models = { ...cfg.agents?.defaults?.models }; + models[XIAOMI_DEFAULT_MODEL_REF] = { + ...models[XIAOMI_DEFAULT_MODEL_REF], + alias: models[XIAOMI_DEFAULT_MODEL_REF]?.alias ?? "Xiaomi", + }; + + const providers = { ...cfg.models?.providers }; + const existingProvider = providers.xiaomi; + const defaultProvider = buildXiaomiProvider(); + const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : []; + const defaultModels = defaultProvider.models ?? []; + const hasDefaultModel = existingModels.some((model) => model.id === XIAOMI_DEFAULT_MODEL_ID); + const mergedModels = + existingModels.length > 0 + ? hasDefaultModel + ? existingModels + : [...existingModels, ...defaultModels] + : defaultModels; + const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< + string, + unknown + > as { apiKey?: string }; + const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined; + const normalizedApiKey = resolvedApiKey?.trim(); + providers.xiaomi = { + ...existingProviderRest, + baseUrl: defaultProvider.baseUrl, + api: defaultProvider.api, + ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), + models: mergedModels.length > 0 ? mergedModels : defaultProvider.models, + }; + + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...cfg.agents?.defaults, + models, + }, + }, + models: { + mode: cfg.models?.mode ?? "merge", + providers, + }, + }; +} + +export function applyXiaomiConfig(cfg: MoltbotConfig): MoltbotConfig { + const next = applyXiaomiProviderConfig(cfg); + const existingModel = next.agents?.defaults?.model; + return { + ...next, + agents: { + ...next.agents, + defaults: { + ...next.agents?.defaults, + model: { + ...(existingModel && "fallbacks" in (existingModel as Record) + ? { + fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks, + } + : undefined), + primary: XIAOMI_DEFAULT_MODEL_REF, + }, + }, + }, + }; +} + /** * Apply Venice provider configuration without changing the default model. * Registers Venice models and sets up the provider, but preserves existing model selection. diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index 2563077b2..010d0b2ae 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -113,6 +113,7 @@ export async function setVeniceApiKey(key: string, agentDir?: string) { } export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7"; +export const XIAOMI_DEFAULT_MODEL_REF = "xiaomi/mimo-v2-flash"; export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto"; export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5"; export const TOGETHER_DEFAULT_MODEL_REF = "together/zai-org/GLM-4.7"; @@ -130,6 +131,18 @@ export async function setZaiApiKey(key: string, agentDir?: string) { }); } +export async function setXiaomiApiKey(key: string, agentDir?: string) { + upsertAuthProfile({ + profileId: "xiaomi:default", + credential: { + type: "api_key", + provider: "xiaomi", + key, + }, + agentDir: resolveAuthAgentDir(agentDir), + }); +} + export async function setOpenrouterApiKey(key: string, agentDir?: string) { upsertAuthProfile({ profileId: "openrouter:default", diff --git a/src/commands/onboard-auth.test.ts b/src/commands/onboard-auth.test.ts index 0a2c67f94..80d71852c 100644 --- a/src/commands/onboard-auth.test.ts +++ b/src/commands/onboard-auth.test.ts @@ -15,6 +15,8 @@ import { applyOpenrouterProviderConfig, applySyntheticConfig, applySyntheticProviderConfig, + applyXiaomiConfig, + applyXiaomiProviderConfig, OPENROUTER_DEFAULT_MODEL_REF, SYNTHETIC_DEFAULT_MODEL_ID, SYNTHETIC_DEFAULT_MODEL_REF, @@ -343,6 +345,50 @@ describe("applySyntheticConfig", () => { }); }); +describe("applyXiaomiConfig", () => { + it("adds Xiaomi provider with correct settings", () => { + const cfg = applyXiaomiConfig({}); + expect(cfg.models?.providers?.xiaomi).toMatchObject({ + baseUrl: "https://api.xiaomimimo.com/anthropic", + api: "anthropic-messages", + }); + expect(cfg.agents?.defaults?.model?.primary).toBe("xiaomi/mimo-v2-flash"); + }); + + it("merges Xiaomi models and keeps existing provider overrides", () => { + const cfg = applyXiaomiProviderConfig({ + models: { + providers: { + xiaomi: { + baseUrl: "https://old.example.com", + apiKey: "old-key", + api: "openai-completions", + models: [ + { + id: "custom-model", + name: "Custom", + reasoning: false, + input: ["text"], + cost: { input: 1, output: 2, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 1000, + maxTokens: 100, + }, + ], + }, + }, + }, + }); + + expect(cfg.models?.providers?.xiaomi?.baseUrl).toBe("https://api.xiaomimimo.com/anthropic"); + expect(cfg.models?.providers?.xiaomi?.api).toBe("anthropic-messages"); + expect(cfg.models?.providers?.xiaomi?.apiKey).toBe("old-key"); + expect(cfg.models?.providers?.xiaomi?.models.map((m) => m.id)).toEqual([ + "custom-model", + "mimo-v2-flash", + ]); + }); +}); + describe("applyOpencodeZenProviderConfig", () => { it("adds allowlist entry for the default model", () => { const cfg = applyOpencodeZenProviderConfig({}); diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index 19827193f..6a31ab65a 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -19,6 +19,8 @@ export { applyVeniceProviderConfig, applyVercelAiGatewayConfig, applyVercelAiGatewayProviderConfig, + applyXiaomiConfig, + applyXiaomiProviderConfig, applyZaiConfig, } from "./onboard-auth.config-core.js"; export { @@ -47,9 +49,11 @@ export { setTogetherApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, + setXiaomiApiKey, setZaiApiKey, writeOAuthCredentials, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, + XIAOMI_DEFAULT_MODEL_REF, ZAI_DEFAULT_MODEL_REF, TOGETHER_DEFAULT_MODEL_REF, } from "./onboard-auth.credentials.js"; diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index c1a82a7ff..e0e904188 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -18,6 +18,7 @@ import { applyVeniceConfig, applyTogetherConfig, applyVercelAiGatewayConfig, + applyXiaomiConfig, applyZaiConfig, setAnthropicApiKey, setGeminiApiKey, @@ -30,6 +31,7 @@ import { setVeniceApiKey, setTogetherApiKey, setVercelAiGatewayApiKey, + setXiaomiApiKey, setZaiApiKey, } from "../../onboard-auth.js"; import type { AuthChoice, OnboardOptions } from "../../onboard-types.js"; @@ -179,6 +181,25 @@ export async function applyNonInteractiveAuthChoice(params: { return applyZaiConfig(nextConfig); } + if (authChoice === "xiaomi-api-key") { + const resolved = await resolveNonInteractiveApiKey({ + provider: "xiaomi", + cfg: baseConfig, + flagValue: opts.xiaomiApiKey, + flagName: "--xiaomi-api-key", + envVar: "XIAOMI_API_KEY", + runtime, + }); + if (!resolved) return null; + if (resolved.source !== "profile") await setXiaomiApiKey(resolved.key); + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "xiaomi:default", + provider: "xiaomi", + mode: "api_key", + }); + return applyXiaomiConfig(nextConfig); + } + if (authChoice === "openai-api-key") { const resolved = await resolveNonInteractiveApiKey({ provider: "openai", diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index 351a9bfb6..dc16e613b 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -24,6 +24,7 @@ export type AuthChoice = | "google-antigravity" | "google-gemini-cli" | "zai-api-key" + | "xiaomi-api-key" | "minimax-cloud" | "minimax" | "minimax-api" @@ -68,6 +69,7 @@ export type OnboardOptions = { kimiCodeApiKey?: string; geminiApiKey?: string; zaiApiKey?: string; + xiaomiApiKey?: string; minimaxApiKey?: string; syntheticApiKey?: string; veniceApiKey?: string; diff --git a/src/infra/provider-usage.auth.ts b/src/infra/provider-usage.auth.ts index 90d73bb59..e0d9a6ef9 100644 --- a/src/infra/provider-usage.auth.ts +++ b/src/infra/provider-usage.auth.ts @@ -96,6 +96,33 @@ function resolveMinimaxApiKey(): string | undefined { return undefined; } +function resolveXiaomiApiKey(): string | undefined { + const envDirect = process.env.XIAOMI_API_KEY?.trim(); + if (envDirect) return envDirect; + + const envResolved = resolveEnvApiKey("xiaomi"); + if (envResolved?.apiKey) return envResolved.apiKey; + + const cfg = loadConfig(); + const key = getCustomProviderApiKey(cfg, "xiaomi"); + if (key) return key; + + const store = ensureAuthProfileStore(); + const apiProfile = listProfilesForProvider(store, "xiaomi").find((id) => { + const cred = store.profiles[id]; + return cred?.type === "api_key" || cred?.type === "token"; + }); + if (!apiProfile) return undefined; + const cred = store.profiles[apiProfile]; + if (cred?.type === "api_key" && cred.key?.trim()) { + return cred.key.trim(); + } + if (cred?.type === "token" && cred.token?.trim()) { + return cred.token.trim(); + } + return undefined; +} + async function resolveOAuthToken(params: { provider: UsageProviderId; agentDir?: string; @@ -199,6 +226,11 @@ export async function resolveProviderAuths(params: { if (apiKey) auths.push({ provider, token: apiKey }); continue; } + if (provider === "xiaomi") { + const apiKey = resolveXiaomiApiKey(); + if (apiKey) auths.push({ provider, token: apiKey }); + continue; + } if (!oauthProviders.includes(provider)) continue; const auth = await resolveOAuthToken({ diff --git a/src/infra/provider-usage.load.ts b/src/infra/provider-usage.load.ts index 39a97a86c..5eb101d85 100644 --- a/src/infra/provider-usage.load.ts +++ b/src/infra/provider-usage.load.ts @@ -66,6 +66,12 @@ export async function loadProviderUsageSummary( return await fetchCodexUsage(auth.token, auth.accountId, timeoutMs, fetchFn); case "minimax": return await fetchMinimaxUsage(auth.token, timeoutMs, fetchFn); + case "xiaomi": + return { + provider: "xiaomi", + displayName: PROVIDER_LABELS.xiaomi, + windows: [], + }; case "zai": return await fetchZaiUsage(auth.token, timeoutMs, fetchFn); default: diff --git a/src/infra/provider-usage.shared.ts b/src/infra/provider-usage.shared.ts index 6c8c1d9bb..55eca4757 100644 --- a/src/infra/provider-usage.shared.ts +++ b/src/infra/provider-usage.shared.ts @@ -10,6 +10,7 @@ export const PROVIDER_LABELS: Record = { "google-antigravity": "Antigravity", minimax: "MiniMax", "openai-codex": "Codex", + xiaomi: "Xiaomi", zai: "z.ai", }; @@ -20,6 +21,7 @@ export const usageProviders: UsageProviderId[] = [ "google-antigravity", "minimax", "openai-codex", + "xiaomi", "zai", ]; diff --git a/src/infra/provider-usage.types.ts b/src/infra/provider-usage.types.ts index cef446ceb..0a4637a7d 100644 --- a/src/infra/provider-usage.types.ts +++ b/src/infra/provider-usage.types.ts @@ -24,4 +24,5 @@ export type UsageProviderId = | "google-antigravity" | "minimax" | "openai-codex" + | "xiaomi" | "zai"; diff --git a/src/shared/text/reasoning-tags.test.ts b/src/shared/text/reasoning-tags.test.ts new file mode 100644 index 000000000..d72d0cde2 --- /dev/null +++ b/src/shared/text/reasoning-tags.test.ts @@ -0,0 +1,218 @@ +import { describe, expect, it } from "vitest"; +import { stripReasoningTagsFromText } from "./reasoning-tags.js"; + +describe("stripReasoningTagsFromText", () => { + describe("basic functionality", () => { + it("returns text unchanged when no reasoning tags present", () => { + const input = "Hello, this is a normal message."; + expect(stripReasoningTagsFromText(input)).toBe(input); + }); + + it("strips proper think tags", () => { + const input = "Hello internal reasoning world!"; + expect(stripReasoningTagsFromText(input)).toBe("Hello world!"); + }); + + it("strips thinking tags", () => { + const input = "Before some thought after"; + expect(stripReasoningTagsFromText(input)).toBe("Before after"); + }); + + it("strips thought tags", () => { + const input = "A hmm B"; + expect(stripReasoningTagsFromText(input)).toBe("A B"); + }); + + it("strips antthinking tags", () => { + const input = "X internal Y"; + expect(stripReasoningTagsFromText(input)).toBe("X Y"); + }); + + it("strips multiple reasoning blocks", () => { + const input = "firstAsecondB"; + expect(stripReasoningTagsFromText(input)).toBe("AB"); + }); + }); + + describe("code block preservation (issue #3952)", () => { + it("preserves think tags inside fenced code blocks", () => { + const input = "Use the tag like this:\n```\nreasoning\n```\nThat's it!"; + expect(stripReasoningTagsFromText(input)).toBe(input); + }); + + it("preserves think tags inside inline code", () => { + const input = + "The `` tag is used for reasoning. Don't forget the closing `` tag."; + expect(stripReasoningTagsFromText(input)).toBe(input); + }); + + it("preserves tags in fenced code blocks with language specifier", () => { + const input = "Example:\n```xml\n\n nested\n\n```\nDone!"; + expect(stripReasoningTagsFromText(input)).toBe(input); + }); + + it("handles mixed real tags and code tags", () => { + const input = "hiddenVisible text with `` example."; + expect(stripReasoningTagsFromText(input)).toBe("Visible text with `` example."); + }); + + it("preserves both opening and closing tags in backticks", () => { + const input = "Use `` to open and `` to close."; + expect(stripReasoningTagsFromText(input)).toBe(input); + }); + + it("preserves think tags in code block at EOF without trailing newline", () => { + const input = "Example:\n```\nreasoning\n```"; + expect(stripReasoningTagsFromText(input)).toBe(input); + }); + + it("preserves final tags inside code blocks", () => { + const input = "Use `` for final answers in code: ```\n42\n```"; + expect(stripReasoningTagsFromText(input)).toBe(input); + }); + + it("handles code block followed by real tags", () => { + const input = "```\ncode\n```\nreal hiddenvisible"; + expect(stripReasoningTagsFromText(input)).toBe("```\ncode\n```\nvisible"); + }); + + it("handles multiple code blocks with tags", () => { + const input = "First `` then ```\nblock\n``` then ``"; + expect(stripReasoningTagsFromText(input)).toBe(input); + }); + }); + + describe("edge cases", () => { + it("preserves unclosed { + const input = "Here is how to use { + const input = "You can start with "; + expect(stripReasoningTagsFromText(input)).toBe( + "You can start with { + const input = "A < think >content< /think > B"; + expect(stripReasoningTagsFromText(input)).toBe("A B"); + }); + + it("handles empty input", () => { + expect(stripReasoningTagsFromText("")).toBe(""); + }); + + it("handles null-ish input", () => { + expect(stripReasoningTagsFromText(null as unknown as string)).toBe(null); + }); + + it("preserves think tags inside tilde fenced code blocks", () => { + const input = "Example:\n~~~\nreasoning\n~~~\nDone!"; + expect(stripReasoningTagsFromText(input)).toBe(input); + }); + + it("preserves tags in tilde block at EOF without trailing newline", () => { + const input = "Example:\n~~~js\ncode\n~~~"; + expect(stripReasoningTagsFromText(input)).toBe(input); + }); + + it("handles nested think patterns (first close ends block)", () => { + const input = "outer inner still outervisible"; + expect(stripReasoningTagsFromText(input)).toBe("still outervisible"); + }); + + it("strips final tag markup but preserves content (by design)", () => { + const input = "A1B2C"; + expect(stripReasoningTagsFromText(input)).toBe("A1B2C"); + }); + + it("preserves final tags in inline code (markup only stripped outside)", () => { + const input = "`` in code, visible outside"; + expect(stripReasoningTagsFromText(input)).toBe("`` in code, visible outside"); + }); + + it("handles double backtick inline code with tags", () => { + const input = "Use ``code`` with hidden text"; + expect(stripReasoningTagsFromText(input)).toBe("Use ``code`` with text"); + }); + + it("handles fenced code blocks with content", () => { + const input = "Before\n```\ncode\n```\nAfter with hidden"; + expect(stripReasoningTagsFromText(input)).toBe("Before\n```\ncode\n```\nAfter with"); + }); + + it("does not match mismatched fence types (``` vs ~~~)", () => { + const input = "```\nnot protected\n~~~\ntext"; + const result = stripReasoningTagsFromText(input); + expect(result).toBe(input); + }); + + it("handles unicode content inside and around tags", () => { + const input = "你好 思考 🤔 世界"; + expect(stripReasoningTagsFromText(input)).toBe("你好 世界"); + }); + + it("handles very long content between tags efficiently", () => { + const longContent = "x".repeat(10000); + const input = `${longContent}visible`; + expect(stripReasoningTagsFromText(input)).toBe("visible"); + }); + + it("handles tags with attributes", () => { + const input = "A hidden B"; + expect(stripReasoningTagsFromText(input)).toBe("A B"); + }); + + it("is case-insensitive for tag names", () => { + const input = "A hidden also hidden B"; + expect(stripReasoningTagsFromText(input)).toBe("A B"); + }); + + it("handles pathological nested backtick patterns without hanging", () => { + const input = "`".repeat(100) + "test" + "`".repeat(100); + const start = Date.now(); + stripReasoningTagsFromText(input); + const elapsed = Date.now() - start; + expect(elapsed).toBeLessThan(1000); + }); + + it("handles unclosed inline code gracefully", () => { + const input = "Start `unclosed hidden end"; + const result = stripReasoningTagsFromText(input); + expect(result).toBe("Start `unclosed end"); + }); + }); + + describe("strict vs preserve mode", () => { + it("strict mode truncates on unclosed tag", () => { + const input = "Before unclosed content after"; + expect(stripReasoningTagsFromText(input, { mode: "strict" })).toBe("Before"); + }); + + it("preserve mode keeps content after unclosed tag", () => { + const input = "Before unclosed content after"; + expect(stripReasoningTagsFromText(input, { mode: "preserve" })).toBe( + "Before unclosed content after", + ); + }); + }); + + describe("trim options", () => { + it("trims both sides by default", () => { + const input = " x result y "; + expect(stripReasoningTagsFromText(input)).toBe("result"); + }); + + it("trim=none preserves whitespace", () => { + const input = " x result "; + expect(stripReasoningTagsFromText(input, { trim: "none" })).toBe(" result "); + }); + + it("trim=start only trims start", () => { + const input = " x result "; + expect(stripReasoningTagsFromText(input, { trim: "start" })).toBe("result "); + }); + }); +}); diff --git a/src/shared/text/reasoning-tags.ts b/src/shared/text/reasoning-tags.ts index 822138e55..afb8f891f 100644 --- a/src/shared/text/reasoning-tags.ts +++ b/src/shared/text/reasoning-tags.ts @@ -2,8 +2,40 @@ export type ReasoningTagMode = "strict" | "preserve"; export type ReasoningTagTrim = "none" | "start" | "both"; const QUICK_TAG_RE = /<\s*\/?\s*(?:think(?:ing)?|thought|antthinking|final)\b/i; -const FINAL_TAG_RE = /<\s*\/?\s*final\b[^>]*>/gi; -const THINKING_TAG_RE = /<\s*(\/?)\s*(?:think(?:ing)?|thought|antthinking)\b[^>]*>/gi; +const FINAL_TAG_RE = /<\s*\/?\s*final\b[^<>]*>/gi; +const THINKING_TAG_RE = /<\s*(\/?)\s*(?:think(?:ing)?|thought|antthinking)\b[^<>]*>/gi; + +interface CodeRegion { + start: number; + end: number; +} + +function findCodeRegions(text: string): CodeRegion[] { + const regions: CodeRegion[] = []; + + const fencedRe = /(^|\n)(```|~~~)[^\n]*\n[\s\S]*?(?:\n\2(?:\n|$)|$)/g; + for (const match of text.matchAll(fencedRe)) { + const start = (match.index ?? 0) + match[1].length; + regions.push({ start, end: start + match[0].length - match[1].length }); + } + + const inlineRe = /`+[^`]+`+/g; + for (const match of text.matchAll(inlineRe)) { + const start = match.index ?? 0; + const end = start + match[0].length; + const insideFenced = regions.some((r) => start >= r.start && end <= r.end); + if (!insideFenced) { + regions.push({ start, end }); + } + } + + regions.sort((a, b) => a.start - b.start); + return regions; +} + +function isInsideCode(pos: number, regions: CodeRegion[]): boolean { + return regions.some((r) => pos >= r.start && pos < r.end); +} function applyTrim(value: string, mode: ReasoningTagTrim): string { if (mode === "none") return value; @@ -27,11 +59,29 @@ export function stripReasoningTagsFromText( let cleaned = text; if (FINAL_TAG_RE.test(cleaned)) { FINAL_TAG_RE.lastIndex = 0; - cleaned = cleaned.replace(FINAL_TAG_RE, ""); + const finalMatches: Array<{ start: number; length: number; inCode: boolean }> = []; + const preCodeRegions = findCodeRegions(cleaned); + for (const match of cleaned.matchAll(FINAL_TAG_RE)) { + const start = match.index ?? 0; + finalMatches.push({ + start, + length: match[0].length, + inCode: isInsideCode(start, preCodeRegions), + }); + } + + for (let i = finalMatches.length - 1; i >= 0; i--) { + const m = finalMatches[i]; + if (!m.inCode) { + cleaned = cleaned.slice(0, m.start) + cleaned.slice(m.start + m.length); + } + } } else { FINAL_TAG_RE.lastIndex = 0; } + const codeRegions = findCodeRegions(cleaned); + THINKING_TAG_RE.lastIndex = 0; let result = ""; let lastIndex = 0; @@ -41,6 +91,10 @@ export function stripReasoningTagsFromText( const idx = match.index ?? 0; const isClose = match[1] === "/"; + if (isInsideCode(idx, codeRegions)) { + continue; + } + if (!inThinking) { result += cleaned.slice(lastIndex, idx); if (!isClose) {