diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c0e1d465b..829604b4c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -64,9 +64,9 @@ updates: - patch open-pull-requests-limit: 5 - # Swift Package Manager - shared ClawdbotKit + # Swift Package Manager - shared MoltbotKit - package-ecosystem: swift - directory: /apps/shared/ClawdbotKit + directory: /apps/shared/MoltbotKit schedule: interval: weekly cooldown: diff --git a/.github/workflows/auto-response.yml b/.github/workflows/auto-response.yml index b610e1718..6d9f55903 100644 --- a/.github/workflows/auto-response.yml +++ b/.github/workflows/auto-response.yml @@ -24,13 +24,26 @@ jobs: with: github-token: ${{ steps.app-token.outputs.token }} script: | + // Labels prefixed with "r:" are auto-response triggers. const rules = [ { - label: "skill-clawdhub", + label: "r: skill", close: true, message: "Thanks for the contribution! New skills should be published to Clawdhub for everyone to use. We’re keeping the core lean on skills, so I’m closing this out.", }, + { + label: "r: support", + close: true, + message: + "Please use our support server https://molt.bot/discord and ask in #help or #users-helping-users to resolve this, or follow the stuck FAQ at https://docs.molt.bot/help/faq#im-stuck-whats-the-fastest-way-to-get-unstuck.", + }, + { + label: "r: third-party-extension", + close: true, + message: + "This would be better made as a third-party extension with our SDK that you maintain yourself. Docs: https://docs.molt.bot/plugin.", + }, ]; const labelName = context.payload.label?.name; diff --git a/.gitignore b/.gitignore index d3fdee6b5..9dc547c9c 100644 --- a/.gitignore +++ b/.gitignore @@ -19,14 +19,14 @@ ui/test-results/ # Bun build artifacts *.bun-build apps/macos/.build/ -apps/shared/ClawdbotKit/.build/ +apps/shared/MoltbotKit/.build/ **/ModuleCache/ bin/ bin/clawdbot-mac bin/docs-list apps/macos/.build-local/ apps/macos/.swiftpm/ -apps/shared/ClawdbotKit/.swiftpm/ +apps/shared/MoltbotKit/.swiftpm/ Core/ apps/ios/*.xcodeproj/ apps/ios/*.xcworkspace/ diff --git a/.swiftformat b/.swiftformat index 6622d0b01..fd8c0e631 100644 --- a/.swiftformat +++ b/.swiftformat @@ -48,4 +48,4 @@ --allman false # Exclusions ---exclude .build,.swiftpm,DerivedData,node_modules,dist,coverage,xcuserdata,Peekaboo,Swabble,apps/android,apps/ios,apps/shared,apps/macos/Sources/ClawdisProtocol,apps/macos/Sources/ClawdbotProtocol +--exclude .build,.swiftpm,DerivedData,node_modules,dist,coverage,xcuserdata,Peekaboo,Swabble,apps/android,apps/ios,apps/shared,apps/macos/Sources/MoltbotProtocol diff --git a/.swiftlint.yml b/.swiftlint.yml index 12500f4c7..b56228801 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -18,7 +18,7 @@ excluded: - coverage - "*.playground" # Generated (protocol-gen-swift.ts) - - apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift + - apps/macos/Sources/MoltbotProtocol/GatewayModels.swift analyzer_rules: - unused_declaration diff --git a/CHANGELOG.md b/CHANGELOG.md index 079c32533..5909c9899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,15 @@ Docs: https://docs.molt.bot -## 2026.1.26 -Status: unreleased. +## 2026.1.27-beta.1 +Status: beta. ### Changes - 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. - Skills: add multi-image input support to Nano Banana Pro skill. (#1958) Thanks @tyler6204. @@ -20,6 +22,7 @@ Status: unreleased. - 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) - 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. @@ -48,6 +51,7 @@ Status: unreleased. - 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. @@ -55,23 +59,41 @@ Status: unreleased. - 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. ### Breaking - **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed). ### Fixes +- 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. - 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. @@ -79,6 +101,7 @@ Status: unreleased. - 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. diff --git a/README.md b/README.md index 9f1f93193..7e884be33 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# 🦞 Clawdbot — Personal AI Assistant +# 🦞 Moltbot — Personal AI Assistant

- Clawdbot + Clawdbot

@@ -9,21 +9,21 @@

- CI status - GitHub release - DeepWiki + CI status + GitHub release + DeepWiki Discord MIT License

-**Clawdbot** is a *personal AI assistant* you run on your own devices. +**Moltbot** is a *personal AI assistant* you run on your own devices. It answers you on the channels you already use (WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, Microsoft Teams, WebChat), plus extension channels like BlueBubbles, Matrix, Zalo, and Zalo Personal. It can speak and listen on macOS/iOS/Android, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant. If you want a personal, single-user assistant that feels local, fast, and always-on, this is it. -[Website](https://molt.bot) · [Docs](https://docs.molt.bot) · [Getting Started](https://docs.molt.bot/start/getting-started) · [Updating](https://docs.molt.bot/install/updating) · [Showcase](https://docs.molt.bot/start/showcase) · [FAQ](https://docs.molt.bot/start/faq) · [Wizard](https://docs.molt.bot/start/wizard) · [Nix](https://github.com/clawdbot/nix-clawdbot) · [Docker](https://docs.molt.bot/install/docker) · [Discord](https://discord.gg/clawd) +[Website](https://molt.bot) · [Docs](https://docs.molt.bot) · [Getting Started](https://docs.molt.bot/start/getting-started) · [Updating](https://docs.molt.bot/install/updating) · [Showcase](https://docs.molt.bot/start/showcase) · [FAQ](https://docs.molt.bot/start/faq) · [Wizard](https://docs.molt.bot/start/wizard) · [Nix](https://github.com/moltbot/nix-clawdbot) · [Docker](https://docs.molt.bot/install/docker) · [Discord](https://discord.gg/clawd) -Preferred setup: run the onboarding wizard (`clawdbot onboard`). It walks through gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**. +Preferred setup: run the onboarding wizard (`moltbot onboard`). It walks through gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**. Works with npm, pnpm, or bun. New install? Start here: [Getting started](https://docs.molt.bot/start/getting-started) @@ -78,7 +78,7 @@ Upgrading? [Updating guide](https://docs.molt.bot/install/updating) (and run `mo - **beta**: prerelease tags (`vYYYY.M.D-beta.N`), npm dist-tag `beta` (macOS app may be missing). - **dev**: moving head of `main`, npm dist-tag `dev` (when published). -Switch channels (git + npm): `clawdbot update --channel stable|beta|dev`. +Switch channels (git + npm): `moltbot update --channel stable|beta|dev`. Details: [Development channels](https://docs.molt.bot/install/development-channels). ## From source (development) @@ -86,8 +86,8 @@ Details: [Development channels](https://docs.molt.bot/install/development-channe Prefer `pnpm` for builds from source. Bun is optional for running TypeScript directly. ```bash -git clone https://github.com/clawdbot/clawdbot.git -cd clawdbot +git clone https://github.com/moltbot/moltbot.git +cd moltbot pnpm install pnpm ui:build # auto-installs UI deps on first run @@ -103,16 +103,16 @@ Note: `pnpm moltbot ...` runs TypeScript directly (via `tsx`). `pnpm build` prod ## Security defaults (DM access) -Clawdbot connects to real messaging surfaces. Treat inbound DMs as **untrusted input**. +Moltbot connects to real messaging surfaces. Treat inbound DMs as **untrusted input**. Full security guide: [Security](https://docs.molt.bot/gateway/security) Default behavior on Telegram/WhatsApp/Signal/iMessage/Microsoft Teams/Discord/Google Chat/Slack: - **DM pairing** (`dmPolicy="pairing"` / `channels.discord.dm.policy="pairing"` / `channels.slack.dm.policy="pairing"`): unknown senders receive a short pairing code and the bot does not process their message. -- Approve with: `clawdbot pairing approve ` (then the sender is added to a local allowlist store). +- Approve with: `moltbot pairing approve ` (then the sender is added to a local allowlist store). - Public inbound DMs require an explicit opt-in: set `dmPolicy="open"` and include `"*"` in the channel allowlist (`allowFrom` / `channels.discord.dm.allowFrom` / `channels.slack.dm.allowFrom`). -Run `clawdbot doctor` to surface risky/misconfigured DM policies. +Run `moltbot doctor` to surface risky/misconfigured DM policies. ## Highlights @@ -127,7 +127,7 @@ Run `clawdbot doctor` to surface risky/misconfigured DM policies. ## Star History -[![Star History Chart](https://api.star-history.com/svg?repos=clawdbot/clawdbot&type=date&legend=top-left)](https://www.star-history.com/#clawdbot/clawdbot&type=date&legend=top-left) +[![Star History Chart](https://api.star-history.com/svg?repos=moltbot/moltbot&type=date&legend=top-left)](https://www.star-history.com/#moltbot/moltbot&type=date&legend=top-left) ## Everything we built so far @@ -149,7 +149,7 @@ Run `clawdbot doctor` to surface risky/misconfigured DM policies. - [macOS node mode](https://docs.molt.bot/nodes): system.run/notify + canvas/camera exposure. ### Tools + automation -- [Browser control](https://docs.molt.bot/tools/browser): dedicated clawd Chrome/Chromium, snapshots, actions, uploads, profiles. +- [Browser control](https://docs.molt.bot/tools/browser): dedicated moltbot Chrome/Chromium, snapshots, actions, uploads, profiles. - [Canvas](https://docs.molt.bot/platforms/mac/canvas): [A2UI](https://docs.molt.bot/platforms/mac/canvas#canvas-a2ui) push/reset, eval, snapshot. - [Nodes](https://docs.molt.bot/nodes): camera snap/clip, screen record, [location.get](https://docs.molt.bot/nodes/location-command), notifications. - [Cron + wakeups](https://docs.molt.bot/automation/cron-jobs); [webhooks](https://docs.molt.bot/automation/webhook); [Gmail Pub/Sub](https://docs.molt.bot/automation/gmail-pubsub). @@ -180,7 +180,7 @@ WhatsApp / Telegram / Slack / Discord / Google Chat / Signal / iMessage / BlueBu └──────────────┬────────────────┘ │ ├─ Pi agent (RPC) - ├─ CLI (clawdbot …) + ├─ CLI (moltbot …) ├─ WebChat UI ├─ macOS app └─ iOS / Android nodes @@ -190,21 +190,21 @@ WhatsApp / Telegram / Slack / Discord / Google Chat / Signal / iMessage / BlueBu - **[Gateway WebSocket network](https://docs.molt.bot/concepts/architecture)** — single WS control plane for clients, tools, and events (plus ops: [Gateway runbook](https://docs.molt.bot/gateway)). - **[Tailscale exposure](https://docs.molt.bot/gateway/tailscale)** — Serve/Funnel for the Gateway dashboard + WS (remote access: [Remote](https://docs.molt.bot/gateway/remote)). -- **[Browser control](https://docs.molt.bot/tools/browser)** — clawd‑managed Chrome/Chromium with CDP control. +- **[Browser control](https://docs.molt.bot/tools/browser)** — moltbot‑managed Chrome/Chromium with CDP control. - **[Canvas + A2UI](https://docs.molt.bot/platforms/mac/canvas)** — agent‑driven visual workspace (A2UI host: [Canvas/A2UI](https://docs.molt.bot/platforms/mac/canvas#canvas-a2ui)). - **[Voice Wake](https://docs.molt.bot/nodes/voicewake) + [Talk Mode](https://docs.molt.bot/nodes/talk)** — always‑on speech and continuous conversation. - **[Nodes](https://docs.molt.bot/nodes)** — Canvas, camera snap/clip, screen record, `location.get`, notifications, plus macOS‑only `system.run`/`system.notify`. ## Tailscale access (Gateway dashboard) -Clawdbot can auto-configure Tailscale **Serve** (tailnet-only) or **Funnel** (public) while the Gateway stays bound to loopback. Configure `gateway.tailscale.mode`: +Moltbot can auto-configure Tailscale **Serve** (tailnet-only) or **Funnel** (public) while the Gateway stays bound to loopback. Configure `gateway.tailscale.mode`: - `off`: no Tailscale automation (default). - `serve`: tailnet-only HTTPS via `tailscale serve` (uses Tailscale identity headers by default). - `funnel`: public HTTPS via `tailscale funnel` (requires shared password auth). Notes: -- `gateway.bind` must stay `loopback` when Serve/Funnel is enabled (Clawdbot enforces this). +- `gateway.bind` must stay `loopback` when Serve/Funnel is enabled (Moltbot enforces this). - Serve can be forced to require a password by setting `gateway.auth.mode: "password"` or `gateway.auth.allowTailscale: false`. - Funnel refuses to start unless `gateway.auth.mode: "password"` is set. - Optional: `gateway.tailscale.resetOnExit` to undo Serve/Funnel on shutdown. @@ -270,7 +270,7 @@ The Gateway alone delivers a great experience. All apps are optional and add ext If you plan to build/run companion apps, follow the platform runbooks below. -### macOS (Clawdbot.app) (optional) +### macOS (Moltbot.app) (optional) - Menu bar control for the Gateway and health. - Voice Wake + push-to-talk overlay. @@ -283,7 +283,7 @@ Note: signed builds required for macOS permissions to stick across rebuilds (see - Pairs as a node via the Bridge. - Voice trigger forwarding + Canvas surface. -- Controlled via `clawdbot nodes …`. +- Controlled via `moltbot nodes …`. Runbook: [iOS connect](https://docs.molt.bot/platforms/ios). @@ -301,7 +301,7 @@ Runbook: [iOS connect](https://docs.molt.bot/platforms/ios). ## Configuration -Minimal `~/.clawdbot/clawdbot.json` (model + defaults): +Minimal `~/.clawdbot/moltbot.json` (model + defaults): ```json5 { @@ -323,7 +323,7 @@ Details: [Security guide](https://docs.molt.bot/gateway/security) · [Docker + s ### [WhatsApp](https://docs.molt.bot/channels/whatsapp) -- Link the device: `pnpm clawdbot channels login` (stores creds in `~/.clawdbot/credentials`). +- Link the device: `pnpm moltbot channels login` (stores creds in `~/.clawdbot/credentials`). - Allowlist who can talk to the assistant via `channels.whatsapp.allowFrom`. - If `channels.whatsapp.groups` is set, it becomes a group allowlist; include `"*"` to allow all. @@ -457,14 +457,15 @@ Use these when you’re past the onboarding flow and want the deeper reference. - [docs.molt.bot/gmail-pubsub](https://docs.molt.bot/automation/gmail-pubsub) -## Clawd +## Molty -Clawdbot was built for **Clawd**, a space lobster AI assistant. 🦞 +Moltbot was built for **Molty**, a space lobster AI assistant. 🦞 by Peter Steinberger and the community. - [clawd.me](https://clawd.me) - [soul.md](https://soul.md) - [steipete.me](https://steipete.me) +- [@moltbot](https://x.com/moltbot) ## Community @@ -478,35 +479,36 @@ Thanks to all clawtributors:

steipete plum-dawg bohdanpodvirnyi iHildy jaydenfyi joaohlisboa mneves75 MatthieuBizien MaudeBot Glucksberg - rahthakor vrknetha radek-paclt Tobias Bischoff joshp123 czekaj mukhtharcm sebslight maxsumrall xadenryan - rodrigouroz juanpablodlc hsrvc magimetal zerone0x meaningfool tyler6204 vignesh07 patelhiren NicholasSpisak + rahthakor vrknetha radek-paclt Tobias Bischoff joshp123 vignesh07 czekaj mukhtharcm sebslight maxsumrall + xadenryan rodrigouroz juanpablodlc hsrvc magimetal zerone0x meaningfool tyler6204 patelhiren NicholasSpisak jonisjongithub abhisekbasu1 jamesgroat claude JustYannicc Hyaxia dantelex SocialNerd42069 daveonkels google-labs-jules[bot] - lc0rp mousberg mteam88 hirefrank joeynyc orlyjamie dbhurley Mariano Belinky Eng. Juan Combetto TSavo - julianengel bradleypriest benithors rohannagpal timolins f-trycua benostein nachx639 pvoo sreekaransrinath - gupsammy cristip73 stefangalescu nachoiacovino Vasanth Rao Naik Sabavat petter-b cpojer scald gumadeiras andranik-sahakyan - davidguttman thewilloftheshadow sleontenko denysvitali shakkernerd sircrumpet peschee rafaelreis-r dominicnunez ratulsarna - lutr0 danielz1z AdeboyeDN Alg0rix emanuelst KristijanJovanovski rdev rhuanssauro joshrad-dev kiranjd - osolmaz adityashaw2 CashWilliams sheeek artuskg Takhoffman onutc pauloportella neooriginal manuelhettich - minghinmatthewlam myfunc travisirby buddyh connorshea kyleok mcinteerj dependabot[bot] John-Rood obviyus - timkrase uos-status gerardward2007 roshanasingh4 tosh-hamburg azade-c JonUleis bjesuiter cheeeee robbyczgw-cla - dlauer Josh Phillips YuriNachos pookNast Whoaa512 chriseidhof ngutman ysqander aj47 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 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 adam91holt ameno- Chris Taylor - dguido Django Navarro evalexpr henrino3 humanwritten larlyssa odysseus0 oswalpalash pcty-nextgen-service-account rmorse - Syhids Aaron Konyer aaronveklabs andreabadesso Andrii cash-echo-bot Clawd ClawdFx EnzeD erik-agens - Evizero fcatuhe itsjaydesu ivancasco ivanrvpereira jayhickey jeffersonwarrior jeffersonwarrior jverdi longmaba - mickahouan mjrussell odnxe p6l-richard philipp-spiess Pocket Clawd robaxelsen Sash Catanzarite T5-AndyML travisp - VAC william arzt zknicker abhaymundhara alejandro maza Alex-Alaniz alexstyl andrewting19 anpoirier arthyn - Asleep123 bolismauro chenyuan99 Clawdbot Maintainers conhecendoia dasilva333 David-Marsh-Photo Developer Dimitrios Ploutarchos Drake Thomsen - fal3 Felix Krause foeken ganghyun kim grrowl gtsifrikas HazAT hrdwdmrbl hugobarauna Jamie Openshaw - Jane Jarvis Jefferson Nunn kentaro Kevin Lin kitze Kiwitwitter levifig Lloyd 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 - Seredeep sergical shiv19 shiyuanhai siraht snopoke Suksham-sharma techboss testingabc321 The Admiral - thesash Ubuntu voidserf Vultr-Clawd Admin Wimmie wolfred wstock yazinsai ymat19 Zach Knickerbocker - 0xJonHoldsCrypto aaronn Alphonse-arianee atalovesyou Azade carlulsoe ddyo Erik hougangdev latitudeki5223 - Manuel Maly Mourad Boustani odrobnik pcty-nextgen-ios-builder Quentin Randy Torres rhjoh ronak-guliani William Stock + lc0rp mousberg adam91holt hougangdev gumadeiras mteam88 hirefrank joeynyc orlyjamie dbhurley + Mariano Belinky Eng. Juan Combetto TSavo julianengel bradleypriest benithors rohannagpal timolins f-trycua benostein + nachx639 shakkernerd pvoo sreekaransrinath gupsammy cristip73 stefangalescu nachoiacovino Vasanth Rao Naik Sabavat petter-b + cpojer scald thewilloftheshadow andranik-sahakyan davidguttman sleontenko denysvitali sircrumpet peschee rafaelreis-r + dominicnunez ratulsarna lutr0 danielz1z AdeboyeDN Alg0rix papago2355 emanuelst KristijanJovanovski rdev + rhuanssauro joshrad-dev kiranjd osolmaz adityashaw2 CashWilliams sheeek artuskg Takhoffman onutc + pauloportella neooriginal manuelhettich minghinmatthewlam myfunc travisirby buddyh connorshea kyleok obviyus + mcinteerj dependabot[bot] John-Rood timkrase uos-status gerardward2007 roshanasingh4 tosh-hamburg azade-c dlauer + JonUleis 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 + odysseus0 oswalpalash pcty-nextgen-service-account rmorse Syhids Aaron Konyer aaronveklabs andreabadesso Andrii cash-echo-bot + Clawd ClawdFx EnzeD erik-agens Evizero fcatuhe itsjaydesu ivancasco ivanrvpereira jayhickey + jeffersonwarrior jeffersonwarrior jverdi longmaba mickahouan mjrussell odnxe p6l-richard philipp-spiess Pocket Clawd + robaxelsen Sash Catanzarite T5-AndyML travisp VAC william arzt zknicker 0oAstro abhaymundhara alejandro maza + Alex-Alaniz alexstyl andrewting19 anpoirier arthyn Asleep123 bolismauro chenyuan99 Clawdbot Maintainers conhecendoia + dasilva333 David-Marsh-Photo Developer Dimitrios Ploutarchos Drake Thomsen Felix Krause foeken ganghyun kim grrowl gtsifrikas + HazAT hrdwdmrbl hugobarauna Jamie Openshaw Jane Jarvis Jefferson Nunn jogi47 kentaro Kevin Lin + 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 Seredeep sergical shiv19 shiyuanhai + siraht snopoke Suksham-sharma techboss testingabc321 The Admiral thesash Ubuntu voidserf Vultr-Clawd Admin + Wimmie wolfred wstock yazinsai YiWang24 ymat19 Zach Knickerbocker 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 b9f7d7682..3ddcb3b81 100644 --- a/apps/android/app/build.gradle.kts +++ b/apps/android/app/build.gradle.kts @@ -8,21 +8,21 @@ plugins { } android { - namespace = "com.clawdbot.android" + namespace = "bot.molt.android" compileSdk = 36 sourceSets { getByName("main") { - assets.srcDir(file("../../shared/ClawdbotKit/Sources/ClawdbotKit/Resources")) + assets.srcDir(file("../../shared/MoltbotKit/Sources/MoltbotKit/Resources")) } } defaultConfig { - applicationId = "com.clawdbot.android" + applicationId = "bot.molt.android" minSdk = 31 targetSdk = 36 versionCode = 202601260 - versionName = "2026.1.26" + versionName = "2026.1.27-beta.1" } buildTypes { diff --git a/apps/android/app/src/main/java/com/clawdbot/android/CameraHudState.kt b/apps/android/app/src/main/java/bot/molt/android/CameraHudState.kt similarity index 85% rename from apps/android/app/src/main/java/com/clawdbot/android/CameraHudState.kt rename to apps/android/app/src/main/java/bot/molt/android/CameraHudState.kt index 1c9b3986f..91531bb5e 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/CameraHudState.kt +++ b/apps/android/app/src/main/java/bot/molt/android/CameraHudState.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android enum class CameraHudKind { Photo, diff --git a/apps/android/app/src/main/java/com/clawdbot/android/DeviceNames.kt b/apps/android/app/src/main/java/bot/molt/android/DeviceNames.kt similarity index 95% rename from apps/android/app/src/main/java/com/clawdbot/android/DeviceNames.kt rename to apps/android/app/src/main/java/bot/molt/android/DeviceNames.kt index dfe5c590b..36cc3e8e2 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/DeviceNames.kt +++ b/apps/android/app/src/main/java/bot/molt/android/DeviceNames.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android import android.content.Context import android.os.Build diff --git a/apps/android/app/src/main/java/com/clawdbot/android/LocationMode.kt b/apps/android/app/src/main/java/bot/molt/android/LocationMode.kt similarity index 91% rename from apps/android/app/src/main/java/com/clawdbot/android/LocationMode.kt rename to apps/android/app/src/main/java/bot/molt/android/LocationMode.kt index 4df77f632..40005c324 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/LocationMode.kt +++ b/apps/android/app/src/main/java/bot/molt/android/LocationMode.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android enum class LocationMode(val rawValue: String) { Off("off"), diff --git a/apps/android/app/src/main/java/com/clawdbot/android/MainActivity.kt b/apps/android/app/src/main/java/bot/molt/android/MainActivity.kt similarity index 97% rename from apps/android/app/src/main/java/com/clawdbot/android/MainActivity.kt rename to apps/android/app/src/main/java/bot/molt/android/MainActivity.kt index 92ad6077a..1e8707e72 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/MainActivity.kt +++ b/apps/android/app/src/main/java/bot/molt/android/MainActivity.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android import android.Manifest import android.content.pm.ApplicationInfo @@ -18,8 +18,8 @@ import androidx.core.view.WindowInsetsControllerCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.clawdbot.android.ui.RootScreen -import com.clawdbot.android.ui.MoltbotTheme +import bot.molt.android.ui.RootScreen +import bot.molt.android.ui.MoltbotTheme import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { diff --git a/apps/android/app/src/main/java/com/clawdbot/android/MainViewModel.kt b/apps/android/app/src/main/java/bot/molt/android/MainViewModel.kt similarity index 94% rename from apps/android/app/src/main/java/com/clawdbot/android/MainViewModel.kt rename to apps/android/app/src/main/java/bot/molt/android/MainViewModel.kt index 1329f06d4..a4f5ee23e 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/MainViewModel.kt +++ b/apps/android/app/src/main/java/bot/molt/android/MainViewModel.kt @@ -1,13 +1,13 @@ -package com.clawdbot.android +package bot.molt.android import android.app.Application import androidx.lifecycle.AndroidViewModel -import com.clawdbot.android.gateway.GatewayEndpoint -import com.clawdbot.android.chat.OutgoingAttachment -import com.clawdbot.android.node.CameraCaptureManager -import com.clawdbot.android.node.CanvasController -import com.clawdbot.android.node.ScreenRecordManager -import com.clawdbot.android.node.SmsManager +import bot.molt.android.gateway.GatewayEndpoint +import bot.molt.android.chat.OutgoingAttachment +import bot.molt.android.node.CameraCaptureManager +import bot.molt.android.node.CanvasController +import bot.molt.android.node.ScreenRecordManager +import bot.molt.android.node.SmsManager import kotlinx.coroutines.flow.StateFlow class MainViewModel(app: Application) : AndroidViewModel(app) { diff --git a/apps/android/app/src/main/java/com/clawdbot/android/NodeApp.kt b/apps/android/app/src/main/java/bot/molt/android/NodeApp.kt similarity index 94% rename from apps/android/app/src/main/java/com/clawdbot/android/NodeApp.kt rename to apps/android/app/src/main/java/bot/molt/android/NodeApp.kt index 228794ff3..53b2c58f5 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/NodeApp.kt +++ b/apps/android/app/src/main/java/bot/molt/android/NodeApp.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android import android.app.Application import android.os.StrictMode diff --git a/apps/android/app/src/main/java/com/clawdbot/android/NodeForegroundService.kt b/apps/android/app/src/main/java/bot/molt/android/NodeForegroundService.kt similarity index 98% rename from apps/android/app/src/main/java/com/clawdbot/android/NodeForegroundService.kt rename to apps/android/app/src/main/java/bot/molt/android/NodeForegroundService.kt index a3074f8b1..03cc42f2d 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/NodeForegroundService.kt +++ b/apps/android/app/src/main/java/bot/molt/android/NodeForegroundService.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android import android.app.Notification import android.app.NotificationChannel @@ -163,7 +163,7 @@ class NodeForegroundService : Service() { private const val CHANNEL_ID = "connection" private const val NOTIFICATION_ID = 1 - private const val ACTION_STOP = "com.clawdbot.android.action.STOP" + private const val ACTION_STOP = "bot.molt.android.action.STOP" fun start(context: Context) { val intent = Intent(context, NodeForegroundService::class.java) diff --git a/apps/android/app/src/main/java/com/clawdbot/android/NodeRuntime.kt b/apps/android/app/src/main/java/bot/molt/android/NodeRuntime.kt similarity index 96% rename from apps/android/app/src/main/java/com/clawdbot/android/NodeRuntime.kt rename to apps/android/app/src/main/java/bot/molt/android/NodeRuntime.kt index 46e486100..5fd429e9e 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/NodeRuntime.kt +++ b/apps/android/app/src/main/java/bot/molt/android/NodeRuntime.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android import android.Manifest import android.content.Context @@ -7,35 +7,35 @@ import android.location.LocationManager import android.os.Build import android.os.SystemClock import androidx.core.content.ContextCompat -import com.clawdbot.android.chat.ChatController -import com.clawdbot.android.chat.ChatMessage -import com.clawdbot.android.chat.ChatPendingToolCall -import com.clawdbot.android.chat.ChatSessionEntry -import com.clawdbot.android.chat.OutgoingAttachment -import com.clawdbot.android.gateway.DeviceAuthStore -import com.clawdbot.android.gateway.DeviceIdentityStore -import com.clawdbot.android.gateway.GatewayClientInfo -import com.clawdbot.android.gateway.GatewayConnectOptions -import com.clawdbot.android.gateway.GatewayDiscovery -import com.clawdbot.android.gateway.GatewayEndpoint -import com.clawdbot.android.gateway.GatewaySession -import com.clawdbot.android.gateway.GatewayTlsParams -import com.clawdbot.android.node.CameraCaptureManager -import com.clawdbot.android.node.LocationCaptureManager -import com.clawdbot.android.BuildConfig -import com.clawdbot.android.node.CanvasController -import com.clawdbot.android.node.ScreenRecordManager -import com.clawdbot.android.node.SmsManager -import com.clawdbot.android.protocol.MoltbotCapability -import com.clawdbot.android.protocol.MoltbotCameraCommand -import com.clawdbot.android.protocol.MoltbotCanvasA2UIAction -import com.clawdbot.android.protocol.MoltbotCanvasA2UICommand -import com.clawdbot.android.protocol.MoltbotCanvasCommand -import com.clawdbot.android.protocol.MoltbotScreenCommand -import com.clawdbot.android.protocol.MoltbotLocationCommand -import com.clawdbot.android.protocol.MoltbotSmsCommand -import com.clawdbot.android.voice.TalkModeManager -import com.clawdbot.android.voice.VoiceWakeManager +import bot.molt.android.chat.ChatController +import bot.molt.android.chat.ChatMessage +import bot.molt.android.chat.ChatPendingToolCall +import bot.molt.android.chat.ChatSessionEntry +import bot.molt.android.chat.OutgoingAttachment +import bot.molt.android.gateway.DeviceAuthStore +import bot.molt.android.gateway.DeviceIdentityStore +import bot.molt.android.gateway.GatewayClientInfo +import bot.molt.android.gateway.GatewayConnectOptions +import bot.molt.android.gateway.GatewayDiscovery +import bot.molt.android.gateway.GatewayEndpoint +import bot.molt.android.gateway.GatewaySession +import bot.molt.android.gateway.GatewayTlsParams +import bot.molt.android.node.CameraCaptureManager +import bot.molt.android.node.LocationCaptureManager +import bot.molt.android.BuildConfig +import bot.molt.android.node.CanvasController +import bot.molt.android.node.ScreenRecordManager +import bot.molt.android.node.SmsManager +import bot.molt.android.protocol.MoltbotCapability +import bot.molt.android.protocol.MoltbotCameraCommand +import bot.molt.android.protocol.MoltbotCanvasA2UIAction +import bot.molt.android.protocol.MoltbotCanvasA2UICommand +import bot.molt.android.protocol.MoltbotCanvasCommand +import bot.molt.android.protocol.MoltbotScreenCommand +import bot.molt.android.protocol.MoltbotLocationCommand +import bot.molt.android.protocol.MoltbotSmsCommand +import bot.molt.android.voice.TalkModeManager +import bot.molt.android.voice.VoiceWakeManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job diff --git a/apps/android/app/src/main/java/com/clawdbot/android/PermissionRequester.kt b/apps/android/app/src/main/java/bot/molt/android/PermissionRequester.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/PermissionRequester.kt rename to apps/android/app/src/main/java/bot/molt/android/PermissionRequester.kt index 5e95d7b27..78ae0b62b 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/PermissionRequester.kt +++ b/apps/android/app/src/main/java/bot/molt/android/PermissionRequester.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android import android.content.pm.PackageManager import android.content.Intent diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ScreenCaptureRequester.kt b/apps/android/app/src/main/java/bot/molt/android/ScreenCaptureRequester.kt similarity index 98% rename from apps/android/app/src/main/java/com/clawdbot/android/ScreenCaptureRequester.kt rename to apps/android/app/src/main/java/bot/molt/android/ScreenCaptureRequester.kt index f7cf6708c..29d662044 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ScreenCaptureRequester.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ScreenCaptureRequester.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android import android.app.Activity import android.content.Context diff --git a/apps/android/app/src/main/java/com/clawdbot/android/SecurePrefs.kt b/apps/android/app/src/main/java/bot/molt/android/SecurePrefs.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/SecurePrefs.kt rename to apps/android/app/src/main/java/bot/molt/android/SecurePrefs.kt index 1c464f961..7ee3294dc 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/SecurePrefs.kt +++ b/apps/android/app/src/main/java/bot/molt/android/SecurePrefs.kt @@ -1,6 +1,6 @@ @file:Suppress("DEPRECATION") -package com.clawdbot.android +package bot.molt.android import android.content.Context import androidx.core.content.edit diff --git a/apps/android/app/src/main/java/com/clawdbot/android/SessionKey.kt b/apps/android/app/src/main/java/bot/molt/android/SessionKey.kt similarity index 92% rename from apps/android/app/src/main/java/com/clawdbot/android/SessionKey.kt rename to apps/android/app/src/main/java/bot/molt/android/SessionKey.kt index e1aae9ec0..e64051649 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/SessionKey.kt +++ b/apps/android/app/src/main/java/bot/molt/android/SessionKey.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android internal fun normalizeMainKey(raw: String?): String { val trimmed = raw?.trim() diff --git a/apps/android/app/src/main/java/com/clawdbot/android/VoiceWakeMode.kt b/apps/android/app/src/main/java/bot/molt/android/VoiceWakeMode.kt similarity index 90% rename from apps/android/app/src/main/java/com/clawdbot/android/VoiceWakeMode.kt rename to apps/android/app/src/main/java/bot/molt/android/VoiceWakeMode.kt index 6c3e2c201..e0862cc25 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/VoiceWakeMode.kt +++ b/apps/android/app/src/main/java/bot/molt/android/VoiceWakeMode.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android enum class VoiceWakeMode(val rawValue: String) { Off("off"), diff --git a/apps/android/app/src/main/java/com/clawdbot/android/WakeWords.kt b/apps/android/app/src/main/java/bot/molt/android/WakeWords.kt similarity index 95% rename from apps/android/app/src/main/java/com/clawdbot/android/WakeWords.kt rename to apps/android/app/src/main/java/bot/molt/android/WakeWords.kt index d54ed1e08..56b85a5df 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/WakeWords.kt +++ b/apps/android/app/src/main/java/bot/molt/android/WakeWords.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android object WakeWords { const val maxWords: Int = 32 diff --git a/apps/android/app/src/main/java/com/clawdbot/android/chat/ChatController.kt b/apps/android/app/src/main/java/bot/molt/android/chat/ChatController.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/chat/ChatController.kt rename to apps/android/app/src/main/java/bot/molt/android/chat/ChatController.kt index a8e64048c..eef66fece 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/chat/ChatController.kt +++ b/apps/android/app/src/main/java/bot/molt/android/chat/ChatController.kt @@ -1,6 +1,6 @@ -package com.clawdbot.android.chat +package bot.molt.android.chat -import com.clawdbot.android.gateway.GatewaySession +import bot.molt.android.gateway.GatewaySession import java.util.UUID import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.CoroutineScope diff --git a/apps/android/app/src/main/java/com/clawdbot/android/chat/ChatModels.kt b/apps/android/app/src/main/java/bot/molt/android/chat/ChatModels.kt similarity index 96% rename from apps/android/app/src/main/java/com/clawdbot/android/chat/ChatModels.kt rename to apps/android/app/src/main/java/bot/molt/android/chat/ChatModels.kt index ad84e8c69..340624452 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/chat/ChatModels.kt +++ b/apps/android/app/src/main/java/bot/molt/android/chat/ChatModels.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.chat +package bot.molt.android.chat data class ChatMessage( val id: String, diff --git a/apps/android/app/src/main/java/com/clawdbot/android/gateway/BonjourEscapes.kt b/apps/android/app/src/main/java/bot/molt/android/gateway/BonjourEscapes.kt similarity index 96% rename from apps/android/app/src/main/java/com/clawdbot/android/gateway/BonjourEscapes.kt rename to apps/android/app/src/main/java/bot/molt/android/gateway/BonjourEscapes.kt index c05d41b4b..2c0c34d68 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/gateway/BonjourEscapes.kt +++ b/apps/android/app/src/main/java/bot/molt/android/gateway/BonjourEscapes.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.gateway +package bot.molt.android.gateway object BonjourEscapes { fun decode(input: String): String { diff --git a/apps/android/app/src/main/java/com/clawdbot/android/gateway/DeviceAuthStore.kt b/apps/android/app/src/main/java/bot/molt/android/gateway/DeviceAuthStore.kt similarity index 90% rename from apps/android/app/src/main/java/com/clawdbot/android/gateway/DeviceAuthStore.kt rename to apps/android/app/src/main/java/bot/molt/android/gateway/DeviceAuthStore.kt index 88643d8d7..6b90b4672 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/gateway/DeviceAuthStore.kt +++ b/apps/android/app/src/main/java/bot/molt/android/gateway/DeviceAuthStore.kt @@ -1,6 +1,6 @@ -package com.clawdbot.android.gateway +package bot.molt.android.gateway -import com.clawdbot.android.SecurePrefs +import bot.molt.android.SecurePrefs class DeviceAuthStore(private val prefs: SecurePrefs) { fun loadToken(deviceId: String, role: String): String? { diff --git a/apps/android/app/src/main/java/com/clawdbot/android/gateway/DeviceIdentityStore.kt b/apps/android/app/src/main/java/bot/molt/android/gateway/DeviceIdentityStore.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/gateway/DeviceIdentityStore.kt rename to apps/android/app/src/main/java/bot/molt/android/gateway/DeviceIdentityStore.kt index 4499e0ce7..58a0aceff 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/gateway/DeviceIdentityStore.kt +++ b/apps/android/app/src/main/java/bot/molt/android/gateway/DeviceIdentityStore.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.gateway +package bot.molt.android.gateway import android.content.Context import android.util.Base64 diff --git a/apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewayDiscovery.kt b/apps/android/app/src/main/java/bot/molt/android/gateway/GatewayDiscovery.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewayDiscovery.kt rename to apps/android/app/src/main/java/bot/molt/android/gateway/GatewayDiscovery.kt index b1d50e28b..53bdb5588 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewayDiscovery.kt +++ b/apps/android/app/src/main/java/bot/molt/android/gateway/GatewayDiscovery.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.gateway +package bot.molt.android.gateway import android.content.Context import android.net.ConnectivityManager diff --git a/apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewayEndpoint.kt b/apps/android/app/src/main/java/bot/molt/android/gateway/GatewayEndpoint.kt similarity index 94% rename from apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewayEndpoint.kt rename to apps/android/app/src/main/java/bot/molt/android/gateway/GatewayEndpoint.kt index ab8aeacc9..2c524cc67 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewayEndpoint.kt +++ b/apps/android/app/src/main/java/bot/molt/android/gateway/GatewayEndpoint.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.gateway +package bot.molt.android.gateway data class GatewayEndpoint( val stableId: String, diff --git a/apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewayProtocol.kt b/apps/android/app/src/main/java/bot/molt/android/gateway/GatewayProtocol.kt similarity index 51% rename from apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewayProtocol.kt rename to apps/android/app/src/main/java/bot/molt/android/gateway/GatewayProtocol.kt index 4873de122..6836331be 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewayProtocol.kt +++ b/apps/android/app/src/main/java/bot/molt/android/gateway/GatewayProtocol.kt @@ -1,3 +1,3 @@ -package com.clawdbot.android.gateway +package bot.molt.android.gateway const val GATEWAY_PROTOCOL_VERSION = 3 diff --git a/apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewaySession.kt b/apps/android/app/src/main/java/bot/molt/android/gateway/GatewaySession.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewaySession.kt rename to apps/android/app/src/main/java/bot/molt/android/gateway/GatewaySession.kt index a54460e0a..13074b918 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewaySession.kt +++ b/apps/android/app/src/main/java/bot/molt/android/gateway/GatewaySession.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.gateway +package bot.molt.android.gateway import android.util.Log import java.util.Locale diff --git a/apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewayTls.kt b/apps/android/app/src/main/java/bot/molt/android/gateway/GatewayTls.kt similarity index 98% rename from apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewayTls.kt rename to apps/android/app/src/main/java/bot/molt/android/gateway/GatewayTls.kt index bcca51583..673d60c8f 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/gateway/GatewayTls.kt +++ b/apps/android/app/src/main/java/bot/molt/android/gateway/GatewayTls.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.gateway +package bot.molt.android.gateway import android.annotation.SuppressLint import java.security.MessageDigest diff --git a/apps/android/app/src/main/java/com/clawdbot/android/node/CameraCaptureManager.kt b/apps/android/app/src/main/java/bot/molt/android/node/CameraCaptureManager.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/node/CameraCaptureManager.kt rename to apps/android/app/src/main/java/bot/molt/android/node/CameraCaptureManager.kt index f4c4d5794..cb15a3915 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/node/CameraCaptureManager.kt +++ b/apps/android/app/src/main/java/bot/molt/android/node/CameraCaptureManager.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.node +package bot.molt.android.node import android.Manifest import android.content.Context @@ -22,7 +22,7 @@ import androidx.camera.video.VideoRecordEvent import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.checkSelfPermission import androidx.core.graphics.scale -import com.clawdbot.android.PermissionRequester +import bot.molt.android.PermissionRequester import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout diff --git a/apps/android/app/src/main/java/com/clawdbot/android/node/CanvasController.kt b/apps/android/app/src/main/java/bot/molt/android/node/CanvasController.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/node/CanvasController.kt rename to apps/android/app/src/main/java/bot/molt/android/node/CanvasController.kt index 4c955f7ea..4d33ed0a6 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/node/CanvasController.kt +++ b/apps/android/app/src/main/java/bot/molt/android/node/CanvasController.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.node +package bot.molt.android.node import android.graphics.Bitmap import android.graphics.Canvas @@ -17,7 +17,7 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive -import com.clawdbot.android.BuildConfig +import bot.molt.android.BuildConfig import kotlin.coroutines.resume class CanvasController { diff --git a/apps/android/app/src/main/java/com/clawdbot/android/node/JpegSizeLimiter.kt b/apps/android/app/src/main/java/bot/molt/android/node/JpegSizeLimiter.kt similarity index 98% rename from apps/android/app/src/main/java/com/clawdbot/android/node/JpegSizeLimiter.kt rename to apps/android/app/src/main/java/bot/molt/android/node/JpegSizeLimiter.kt index ec71e9a4b..8fb6c35d4 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/node/JpegSizeLimiter.kt +++ b/apps/android/app/src/main/java/bot/molt/android/node/JpegSizeLimiter.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.node +package bot.molt.android.node import kotlin.math.max import kotlin.math.min diff --git a/apps/android/app/src/main/java/com/clawdbot/android/node/LocationCaptureManager.kt b/apps/android/app/src/main/java/bot/molt/android/node/LocationCaptureManager.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/node/LocationCaptureManager.kt rename to apps/android/app/src/main/java/bot/molt/android/node/LocationCaptureManager.kt index c701be70d..c56eee03a 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/node/LocationCaptureManager.kt +++ b/apps/android/app/src/main/java/bot/molt/android/node/LocationCaptureManager.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.node +package bot.molt.android.node import android.Manifest import android.content.Context diff --git a/apps/android/app/src/main/java/com/clawdbot/android/node/ScreenRecordManager.kt b/apps/android/app/src/main/java/bot/molt/android/node/ScreenRecordManager.kt similarity index 96% rename from apps/android/app/src/main/java/com/clawdbot/android/node/ScreenRecordManager.kt rename to apps/android/app/src/main/java/bot/molt/android/node/ScreenRecordManager.kt index 4486fc5f0..0e785c245 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/node/ScreenRecordManager.kt +++ b/apps/android/app/src/main/java/bot/molt/android/node/ScreenRecordManager.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.node +package bot.molt.android.node import android.content.Context import android.hardware.display.DisplayManager @@ -6,7 +6,7 @@ import android.media.MediaRecorder import android.media.projection.MediaProjectionManager import android.os.Build import android.util.Base64 -import com.clawdbot.android.ScreenCaptureRequester +import bot.molt.android.ScreenCaptureRequester import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext @@ -17,13 +17,13 @@ class ScreenRecordManager(private val context: Context) { data class Payload(val payloadJson: String) @Volatile private var screenCaptureRequester: ScreenCaptureRequester? = null - @Volatile private var permissionRequester: com.clawdbot.android.PermissionRequester? = null + @Volatile private var permissionRequester: bot.molt.android.PermissionRequester? = null fun attachScreenCaptureRequester(requester: ScreenCaptureRequester) { screenCaptureRequester = requester } - fun attachPermissionRequester(requester: com.clawdbot.android.PermissionRequester) { + fun attachPermissionRequester(requester: bot.molt.android.PermissionRequester) { permissionRequester = requester } diff --git a/apps/android/app/src/main/java/com/clawdbot/android/node/SmsManager.kt b/apps/android/app/src/main/java/bot/molt/android/node/SmsManager.kt similarity index 98% rename from apps/android/app/src/main/java/com/clawdbot/android/node/SmsManager.kt rename to apps/android/app/src/main/java/bot/molt/android/node/SmsManager.kt index 3e12a56df..0314ee1a7 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/node/SmsManager.kt +++ b/apps/android/app/src/main/java/bot/molt/android/node/SmsManager.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.node +package bot.molt.android.node import android.Manifest import android.content.Context @@ -11,7 +11,7 @@ import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.jsonObject import kotlinx.serialization.encodeToString -import com.clawdbot.android.PermissionRequester +import bot.molt.android.PermissionRequester /** * Sends SMS messages via the Android SMS API. diff --git a/apps/android/app/src/main/java/com/clawdbot/android/protocol/ClawdbotCanvasA2UIAction.kt b/apps/android/app/src/main/java/bot/molt/android/protocol/ClawdbotCanvasA2UIAction.kt similarity index 98% rename from apps/android/app/src/main/java/com/clawdbot/android/protocol/ClawdbotCanvasA2UIAction.kt rename to apps/android/app/src/main/java/bot/molt/android/protocol/ClawdbotCanvasA2UIAction.kt index 4ff1a7421..f73879bb2 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/protocol/ClawdbotCanvasA2UIAction.kt +++ b/apps/android/app/src/main/java/bot/molt/android/protocol/ClawdbotCanvasA2UIAction.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.protocol +package bot.molt.android.protocol import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive diff --git a/apps/android/app/src/main/java/com/clawdbot/android/protocol/ClawdbotProtocolConstants.kt b/apps/android/app/src/main/java/bot/molt/android/protocol/ClawdbotProtocolConstants.kt similarity index 97% rename from apps/android/app/src/main/java/com/clawdbot/android/protocol/ClawdbotProtocolConstants.kt rename to apps/android/app/src/main/java/bot/molt/android/protocol/ClawdbotProtocolConstants.kt index 09a8bb49d..27d46c3f1 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/protocol/ClawdbotProtocolConstants.kt +++ b/apps/android/app/src/main/java/bot/molt/android/protocol/ClawdbotProtocolConstants.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.protocol +package bot.molt.android.protocol enum class MoltbotCapability(val rawValue: String) { Canvas("canvas"), diff --git a/apps/android/app/src/main/java/com/clawdbot/android/tools/ToolDisplay.kt b/apps/android/app/src/main/java/bot/molt/android/tools/ToolDisplay.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/tools/ToolDisplay.kt rename to apps/android/app/src/main/java/bot/molt/android/tools/ToolDisplay.kt index aed5d0b4b..6f4862887 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/tools/ToolDisplay.kt +++ b/apps/android/app/src/main/java/bot/molt/android/tools/ToolDisplay.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.tools +package bot.molt.android.tools import android.content.Context import kotlinx.serialization.Serializable diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/CameraHudOverlay.kt b/apps/android/app/src/main/java/bot/molt/android/ui/CameraHudOverlay.kt similarity index 97% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/CameraHudOverlay.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/CameraHudOverlay.kt index 2143ba7f8..7b45efae9 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/CameraHudOverlay.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/CameraHudOverlay.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.ui +package bot.molt.android.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/ChatSheet.kt b/apps/android/app/src/main/java/bot/molt/android/ui/ChatSheet.kt similarity index 52% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/ChatSheet.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/ChatSheet.kt index 6f15e5922..21af1a4c6 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/ChatSheet.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/ChatSheet.kt @@ -1,8 +1,8 @@ -package com.clawdbot.android.ui +package bot.molt.android.ui import androidx.compose.runtime.Composable -import com.clawdbot.android.MainViewModel -import com.clawdbot.android.ui.chat.ChatSheetContent +import bot.molt.android.MainViewModel +import bot.molt.android.ui.chat.ChatSheetContent @Composable fun ChatSheet(viewModel: MainViewModel) { diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/ClawdbotTheme.kt b/apps/android/app/src/main/java/bot/molt/android/ui/ClawdbotTheme.kt similarity index 97% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/ClawdbotTheme.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/ClawdbotTheme.kt index 01d5a6796..c292aa25d 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/ClawdbotTheme.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/ClawdbotTheme.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.ui +package bot.molt.android.ui import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/RootScreen.kt b/apps/android/app/src/main/java/bot/molt/android/ui/RootScreen.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/RootScreen.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/RootScreen.kt index 763052559..67d76b82f 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/RootScreen.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/RootScreen.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.ui +package bot.molt.android.ui import android.annotation.SuppressLint import android.Manifest @@ -65,8 +65,8 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupProperties import androidx.core.content.ContextCompat -import com.clawdbot.android.CameraHudKind -import com.clawdbot.android.MainViewModel +import bot.molt.android.CameraHudKind +import bot.molt.android.MainViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/SettingsSheet.kt b/apps/android/app/src/main/java/bot/molt/android/ui/SettingsSheet.kt similarity index 98% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/SettingsSheet.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/SettingsSheet.kt index 6b3564e14..f96731acf 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/SettingsSheet.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/SettingsSheet.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.ui +package bot.molt.android.ui import android.Manifest import android.content.Context @@ -58,12 +58,12 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat -import com.clawdbot.android.BuildConfig -import com.clawdbot.android.LocationMode -import com.clawdbot.android.MainViewModel -import com.clawdbot.android.NodeForegroundService -import com.clawdbot.android.VoiceWakeMode -import com.clawdbot.android.WakeWords +import bot.molt.android.BuildConfig +import bot.molt.android.LocationMode +import bot.molt.android.MainViewModel +import bot.molt.android.NodeForegroundService +import bot.molt.android.VoiceWakeMode +import bot.molt.android.WakeWords @Composable fun SettingsSheet(viewModel: MainViewModel) { diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/StatusPill.kt b/apps/android/app/src/main/java/bot/molt/android/ui/StatusPill.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/StatusPill.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/StatusPill.kt index 564d96b52..199bcbf82 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/StatusPill.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/StatusPill.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.ui +package bot.molt.android.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/TalkOrbOverlay.kt b/apps/android/app/src/main/java/bot/molt/android/ui/TalkOrbOverlay.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/TalkOrbOverlay.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/TalkOrbOverlay.kt index 32225b486..9098c06ff 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/TalkOrbOverlay.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/TalkOrbOverlay.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.ui +package bot.molt.android.ui import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatComposer.kt b/apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatComposer.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatComposer.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatComposer.kt index 1f30938e0..bc0d9917f 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatComposer.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatComposer.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.ui.chat +package bot.molt.android.ui.chat import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -38,7 +38,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import com.clawdbot.android.chat.ChatSessionEntry +import bot.molt.android.chat.ChatSessionEntry @Composable fun ChatComposer( diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatMarkdown.kt b/apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatMarkdown.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatMarkdown.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatMarkdown.kt index f15673fb3..10cf25b81 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatMarkdown.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatMarkdown.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.ui.chat +package bot.molt.android.ui.chat import android.graphics.BitmapFactory import android.util.Base64 diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatMessageListCard.kt b/apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatMessageListCard.kt similarity index 96% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatMessageListCard.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatMessageListCard.kt index a3229d4a2..1091de6c8 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatMessageListCard.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatMessageListCard.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.ui.chat +package bot.molt.android.ui.chat import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -20,8 +20,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.unit.dp -import com.clawdbot.android.chat.ChatMessage -import com.clawdbot.android.chat.ChatPendingToolCall +import bot.molt.android.chat.ChatMessage +import bot.molt.android.chat.ChatPendingToolCall @Composable fun ChatMessageListCard( diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatMessageViews.kt b/apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatMessageViews.kt similarity index 97% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatMessageViews.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatMessageViews.kt index 59479744e..59445be37 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatMessageViews.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatMessageViews.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.ui.chat +package bot.molt.android.ui.chat import android.graphics.BitmapFactory import android.util.Base64 @@ -31,10 +31,10 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.foundation.Image -import com.clawdbot.android.chat.ChatMessage -import com.clawdbot.android.chat.ChatMessageContent -import com.clawdbot.android.chat.ChatPendingToolCall -import com.clawdbot.android.tools.ToolDisplayRegistry +import bot.molt.android.chat.ChatMessage +import bot.molt.android.chat.ChatMessageContent +import bot.molt.android.chat.ChatPendingToolCall +import bot.molt.android.tools.ToolDisplayRegistry import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import androidx.compose.ui.platform.LocalContext diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatSessionsDialog.kt b/apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatSessionsDialog.kt similarity index 97% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatSessionsDialog.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatSessionsDialog.kt index 9474b2362..377a13daa 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatSessionsDialog.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatSessionsDialog.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.ui.chat +package bot.molt.android.ui.chat import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -20,7 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.clawdbot.android.chat.ChatSessionEntry +import bot.molt.android.chat.ChatSessionEntry @Composable fun ChatSessionsDialog( diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatSheetContent.kt b/apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatSheetContent.kt similarity index 97% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatSheetContent.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatSheetContent.kt index 2b58c626b..5632be70f 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/ChatSheetContent.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/chat/ChatSheetContent.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.ui.chat +package bot.molt.android.ui.chat import android.content.ContentResolver import android.net.Uri @@ -19,8 +19,8 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import com.clawdbot.android.MainViewModel -import com.clawdbot.android.chat.OutgoingAttachment +import bot.molt.android.MainViewModel +import bot.molt.android.chat.OutgoingAttachment import java.io.ByteArrayOutputStream import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/SessionFilters.kt b/apps/android/app/src/main/java/bot/molt/android/ui/chat/SessionFilters.kt similarity index 94% rename from apps/android/app/src/main/java/com/clawdbot/android/ui/chat/SessionFilters.kt rename to apps/android/app/src/main/java/bot/molt/android/ui/chat/SessionFilters.kt index da08dbd1e..227fb0a02 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/ui/chat/SessionFilters.kt +++ b/apps/android/app/src/main/java/bot/molt/android/ui/chat/SessionFilters.kt @@ -1,6 +1,6 @@ -package com.clawdbot.android.ui.chat +package bot.molt.android.ui.chat -import com.clawdbot.android.chat.ChatSessionEntry +import bot.molt.android.chat.ChatSessionEntry private const val RECENT_WINDOW_MS = 24 * 60 * 60 * 1000L diff --git a/apps/android/app/src/main/java/com/clawdbot/android/voice/StreamingMediaDataSource.kt b/apps/android/app/src/main/java/bot/molt/android/voice/StreamingMediaDataSource.kt similarity index 98% rename from apps/android/app/src/main/java/com/clawdbot/android/voice/StreamingMediaDataSource.kt rename to apps/android/app/src/main/java/bot/molt/android/voice/StreamingMediaDataSource.kt index 6b1536ad5..7a7f61165 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/voice/StreamingMediaDataSource.kt +++ b/apps/android/app/src/main/java/bot/molt/android/voice/StreamingMediaDataSource.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.voice +package bot.molt.android.voice import android.media.MediaDataSource import kotlin.math.min diff --git a/apps/android/app/src/main/java/com/clawdbot/android/voice/TalkDirectiveParser.kt b/apps/android/app/src/main/java/bot/molt/android/voice/TalkDirectiveParser.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/voice/TalkDirectiveParser.kt rename to apps/android/app/src/main/java/bot/molt/android/voice/TalkDirectiveParser.kt index 02d2c3967..0d969e4d1 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/voice/TalkDirectiveParser.kt +++ b/apps/android/app/src/main/java/bot/molt/android/voice/TalkDirectiveParser.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.voice +package bot.molt.android.voice import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement diff --git a/apps/android/app/src/main/java/com/clawdbot/android/voice/TalkModeManager.kt b/apps/android/app/src/main/java/bot/molt/android/voice/TalkModeManager.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/voice/TalkModeManager.kt rename to apps/android/app/src/main/java/bot/molt/android/voice/TalkModeManager.kt index 41f98140d..f050f8bd2 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/voice/TalkModeManager.kt +++ b/apps/android/app/src/main/java/bot/molt/android/voice/TalkModeManager.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.voice +package bot.molt.android.voice import android.Manifest import android.content.Context @@ -20,9 +20,9 @@ import android.speech.tts.TextToSpeech import android.speech.tts.UtteranceProgressListener import android.util.Log import androidx.core.content.ContextCompat -import com.clawdbot.android.gateway.GatewaySession -import com.clawdbot.android.isCanonicalMainSessionKey -import com.clawdbot.android.normalizeMainKey +import bot.molt.android.gateway.GatewaySession +import bot.molt.android.isCanonicalMainSessionKey +import bot.molt.android.normalizeMainKey import java.net.HttpURLConnection import java.net.URL import java.util.UUID diff --git a/apps/android/app/src/main/java/com/clawdbot/android/voice/VoiceWakeCommandExtractor.kt b/apps/android/app/src/main/java/bot/molt/android/voice/VoiceWakeCommandExtractor.kt similarity index 97% rename from apps/android/app/src/main/java/com/clawdbot/android/voice/VoiceWakeCommandExtractor.kt rename to apps/android/app/src/main/java/bot/molt/android/voice/VoiceWakeCommandExtractor.kt index 1f527b8c8..8da4e3289 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/voice/VoiceWakeCommandExtractor.kt +++ b/apps/android/app/src/main/java/bot/molt/android/voice/VoiceWakeCommandExtractor.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.voice +package bot.molt.android.voice object VoiceWakeCommandExtractor { fun extractCommand(text: String, triggerWords: List): String? { diff --git a/apps/android/app/src/main/java/com/clawdbot/android/voice/VoiceWakeManager.kt b/apps/android/app/src/main/java/bot/molt/android/voice/VoiceWakeManager.kt similarity index 99% rename from apps/android/app/src/main/java/com/clawdbot/android/voice/VoiceWakeManager.kt rename to apps/android/app/src/main/java/bot/molt/android/voice/VoiceWakeManager.kt index 69863b4cc..b27d0e3c7 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/voice/VoiceWakeManager.kt +++ b/apps/android/app/src/main/java/bot/molt/android/voice/VoiceWakeManager.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.voice +package bot.molt.android.voice import android.content.Context import android.content.Intent diff --git a/apps/android/app/src/test/java/com/clawdbot/android/NodeForegroundServiceTest.kt b/apps/android/app/src/test/java/bot/molt/android/NodeForegroundServiceTest.kt similarity index 97% rename from apps/android/app/src/test/java/com/clawdbot/android/NodeForegroundServiceTest.kt rename to apps/android/app/src/test/java/bot/molt/android/NodeForegroundServiceTest.kt index cb1c8b898..77ab3ae36 100644 --- a/apps/android/app/src/test/java/com/clawdbot/android/NodeForegroundServiceTest.kt +++ b/apps/android/app/src/test/java/bot/molt/android/NodeForegroundServiceTest.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android import android.app.Notification import android.content.Intent diff --git a/apps/android/app/src/test/java/com/clawdbot/android/WakeWordsTest.kt b/apps/android/app/src/test/java/bot/molt/android/WakeWordsTest.kt similarity index 98% rename from apps/android/app/src/test/java/com/clawdbot/android/WakeWordsTest.kt rename to apps/android/app/src/test/java/bot/molt/android/WakeWordsTest.kt index 9363e810c..f18ba187e 100644 --- a/apps/android/app/src/test/java/com/clawdbot/android/WakeWordsTest.kt +++ b/apps/android/app/src/test/java/bot/molt/android/WakeWordsTest.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android +package bot.molt.android import org.junit.Assert.assertEquals import org.junit.Assert.assertNull diff --git a/apps/android/app/src/test/java/com/clawdbot/android/gateway/BonjourEscapesTest.kt b/apps/android/app/src/test/java/bot/molt/android/gateway/BonjourEscapesTest.kt similarity index 93% rename from apps/android/app/src/test/java/com/clawdbot/android/gateway/BonjourEscapesTest.kt rename to apps/android/app/src/test/java/bot/molt/android/gateway/BonjourEscapesTest.kt index e6acf833e..026d35a8a 100644 --- a/apps/android/app/src/test/java/com/clawdbot/android/gateway/BonjourEscapesTest.kt +++ b/apps/android/app/src/test/java/bot/molt/android/gateway/BonjourEscapesTest.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.gateway +package bot.molt.android.gateway import org.junit.Assert.assertEquals import org.junit.Test diff --git a/apps/android/app/src/test/java/com/clawdbot/android/node/CanvasControllerSnapshotParamsTest.kt b/apps/android/app/src/test/java/bot/molt/android/node/CanvasControllerSnapshotParamsTest.kt similarity index 97% rename from apps/android/app/src/test/java/com/clawdbot/android/node/CanvasControllerSnapshotParamsTest.kt rename to apps/android/app/src/test/java/bot/molt/android/node/CanvasControllerSnapshotParamsTest.kt index 0b0a42ed5..c9803c56f 100644 --- a/apps/android/app/src/test/java/com/clawdbot/android/node/CanvasControllerSnapshotParamsTest.kt +++ b/apps/android/app/src/test/java/bot/molt/android/node/CanvasControllerSnapshotParamsTest.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.node +package bot.molt.android.node import org.junit.Assert.assertEquals import org.junit.Assert.assertNull diff --git a/apps/android/app/src/test/java/com/clawdbot/android/node/JpegSizeLimiterTest.kt b/apps/android/app/src/test/java/bot/molt/android/node/JpegSizeLimiterTest.kt similarity index 97% rename from apps/android/app/src/test/java/com/clawdbot/android/node/JpegSizeLimiterTest.kt rename to apps/android/app/src/test/java/bot/molt/android/node/JpegSizeLimiterTest.kt index 2c22c2d6a..8f114b3ec 100644 --- a/apps/android/app/src/test/java/com/clawdbot/android/node/JpegSizeLimiterTest.kt +++ b/apps/android/app/src/test/java/bot/molt/android/node/JpegSizeLimiterTest.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.node +package bot.molt.android.node import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue diff --git a/apps/android/app/src/test/java/com/clawdbot/android/node/SmsManagerTest.kt b/apps/android/app/src/test/java/bot/molt/android/node/SmsManagerTest.kt similarity index 98% rename from apps/android/app/src/test/java/com/clawdbot/android/node/SmsManagerTest.kt rename to apps/android/app/src/test/java/bot/molt/android/node/SmsManagerTest.kt index 4748a5683..d09bbc6bb 100644 --- a/apps/android/app/src/test/java/com/clawdbot/android/node/SmsManagerTest.kt +++ b/apps/android/app/src/test/java/bot/molt/android/node/SmsManagerTest.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.node +package bot.molt.android.node import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive diff --git a/apps/android/app/src/test/java/com/clawdbot/android/protocol/ClawdbotCanvasA2UIActionTest.kt b/apps/android/app/src/test/java/bot/molt/android/protocol/ClawdbotCanvasA2UIActionTest.kt similarity index 97% rename from apps/android/app/src/test/java/com/clawdbot/android/protocol/ClawdbotCanvasA2UIActionTest.kt rename to apps/android/app/src/test/java/bot/molt/android/protocol/ClawdbotCanvasA2UIActionTest.kt index adb522767..5ed5e505f 100644 --- a/apps/android/app/src/test/java/com/clawdbot/android/protocol/ClawdbotCanvasA2UIActionTest.kt +++ b/apps/android/app/src/test/java/bot/molt/android/protocol/ClawdbotCanvasA2UIActionTest.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.protocol +package bot.molt.android.protocol import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonObject diff --git a/apps/android/app/src/test/java/com/clawdbot/android/protocol/ClawdbotProtocolConstantsTest.kt b/apps/android/app/src/test/java/bot/molt/android/protocol/ClawdbotProtocolConstantsTest.kt similarity index 97% rename from apps/android/app/src/test/java/com/clawdbot/android/protocol/ClawdbotProtocolConstantsTest.kt rename to apps/android/app/src/test/java/bot/molt/android/protocol/ClawdbotProtocolConstantsTest.kt index 1b96ee9a9..998f6600c 100644 --- a/apps/android/app/src/test/java/com/clawdbot/android/protocol/ClawdbotProtocolConstantsTest.kt +++ b/apps/android/app/src/test/java/bot/molt/android/protocol/ClawdbotProtocolConstantsTest.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.protocol +package bot.molt.android.protocol import org.junit.Assert.assertEquals import org.junit.Test diff --git a/apps/android/app/src/test/java/com/clawdbot/android/ui/chat/SessionFiltersTest.kt b/apps/android/app/src/test/java/bot/molt/android/ui/chat/SessionFiltersTest.kt similarity index 93% rename from apps/android/app/src/test/java/com/clawdbot/android/ui/chat/SessionFiltersTest.kt rename to apps/android/app/src/test/java/bot/molt/android/ui/chat/SessionFiltersTest.kt index b945ad66f..e410cc365 100644 --- a/apps/android/app/src/test/java/com/clawdbot/android/ui/chat/SessionFiltersTest.kt +++ b/apps/android/app/src/test/java/bot/molt/android/ui/chat/SessionFiltersTest.kt @@ -1,6 +1,6 @@ -package com.clawdbot.android.ui.chat +package bot.molt.android.ui.chat -import com.clawdbot.android.chat.ChatSessionEntry +import bot.molt.android.chat.ChatSessionEntry import org.junit.Assert.assertEquals import org.junit.Test diff --git a/apps/android/app/src/test/java/com/clawdbot/android/voice/TalkDirectiveParserTest.kt b/apps/android/app/src/test/java/bot/molt/android/voice/TalkDirectiveParserTest.kt similarity index 97% rename from apps/android/app/src/test/java/com/clawdbot/android/voice/TalkDirectiveParserTest.kt rename to apps/android/app/src/test/java/bot/molt/android/voice/TalkDirectiveParserTest.kt index a42b88e3a..8f57a9aca 100644 --- a/apps/android/app/src/test/java/com/clawdbot/android/voice/TalkDirectiveParserTest.kt +++ b/apps/android/app/src/test/java/bot/molt/android/voice/TalkDirectiveParserTest.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.voice +package bot.molt.android.voice import org.junit.Assert.assertEquals import org.junit.Assert.assertNull diff --git a/apps/android/app/src/test/java/com/clawdbot/android/voice/VoiceWakeCommandExtractorTest.kt b/apps/android/app/src/test/java/bot/molt/android/voice/VoiceWakeCommandExtractorTest.kt similarity index 95% rename from apps/android/app/src/test/java/com/clawdbot/android/voice/VoiceWakeCommandExtractorTest.kt rename to apps/android/app/src/test/java/bot/molt/android/voice/VoiceWakeCommandExtractorTest.kt index f6e512fa3..3460ba7a8 100644 --- a/apps/android/app/src/test/java/com/clawdbot/android/voice/VoiceWakeCommandExtractorTest.kt +++ b/apps/android/app/src/test/java/bot/molt/android/voice/VoiceWakeCommandExtractorTest.kt @@ -1,4 +1,4 @@ -package com.clawdbot.android.voice +package bot.molt.android.voice import org.junit.Assert.assertEquals import org.junit.Assert.assertNull diff --git a/apps/ios/README.md b/apps/ios/README.md index 72eb5f7e2..58aceff8b 100644 --- a/apps/ios/README.md +++ b/apps/ios/README.md @@ -15,7 +15,7 @@ open Clawdbot.xcodeproj ``` ## Shared packages -- `../shared/ClawdbotKit` — shared types/constants used by iOS (and later macOS bridge + gateway routing). +- `../shared/MoltbotKit` — shared types/constants used by iOS (and later macOS bridge + gateway routing). ## fastlane ```bash diff --git a/apps/ios/Sources/Gateway/GatewayDiscoveryModel.swift b/apps/ios/Sources/Gateway/GatewayDiscoveryModel.swift index aaba5a863..19be913f4 100644 --- a/apps/ios/Sources/Gateway/GatewayDiscoveryModel.swift +++ b/apps/ios/Sources/Gateway/GatewayDiscoveryModel.swift @@ -104,7 +104,7 @@ final class GatewayDiscoveryModel { } self.browsers[domain] = browser - browser.start(queue: DispatchQueue(label: "com.clawdbot.ios.gateway-discovery.\(domain)")) + browser.start(queue: DispatchQueue(label: "bot.molt.ios.gateway-discovery.\(domain)")) } } diff --git a/apps/ios/Sources/Gateway/GatewaySettingsStore.swift b/apps/ios/Sources/Gateway/GatewaySettingsStore.swift index 52ada8d80..1c78b7869 100644 --- a/apps/ios/Sources/Gateway/GatewaySettingsStore.swift +++ b/apps/ios/Sources/Gateway/GatewaySettingsStore.swift @@ -1,9 +1,11 @@ import Foundation enum GatewaySettingsStore { - private static let gatewayService = "com.clawdbot.gateway" + private static let gatewayService = "bot.molt.gateway" + private static let legacyGatewayService = "com.clawdbot.gateway" private static let legacyBridgeService = "com.clawdbot.bridge" - private static let nodeService = "com.clawdbot.node" + private static let nodeService = "bot.molt.node" + private static let legacyNodeService = "com.clawdbot.node" private static let instanceIdDefaultsKey = "node.instanceId" private static let preferredGatewayStableIDDefaultsKey = "gateway.preferredStableID" @@ -33,8 +35,22 @@ enum GatewaySettingsStore { } static func loadStableInstanceID() -> String? { - KeychainStore.loadString(service: self.nodeService, account: self.instanceIdAccount)? - .trimmingCharacters(in: .whitespacesAndNewlines) + if let value = KeychainStore.loadString(service: self.nodeService, account: self.instanceIdAccount)? + .trimmingCharacters(in: .whitespacesAndNewlines), + !value.isEmpty + { + return value + } + + if let legacy = KeychainStore.loadString(service: self.legacyNodeService, account: self.instanceIdAccount)? + .trimmingCharacters(in: .whitespacesAndNewlines), + !legacy.isEmpty + { + _ = KeychainStore.saveString(legacy, service: self.nodeService, account: self.instanceIdAccount) + return legacy + } + + return nil } static func saveStableInstanceID(_ instanceId: String) { @@ -42,8 +58,29 @@ enum GatewaySettingsStore { } static func loadPreferredGatewayStableID() -> String? { - KeychainStore.loadString(service: self.gatewayService, account: self.preferredGatewayStableIDAccount)? - .trimmingCharacters(in: .whitespacesAndNewlines) + if let value = KeychainStore.loadString( + service: self.gatewayService, + account: self.preferredGatewayStableIDAccount + )?.trimmingCharacters(in: .whitespacesAndNewlines), + !value.isEmpty + { + return value + } + + if let legacy = KeychainStore.loadString( + service: self.legacyGatewayService, + account: self.preferredGatewayStableIDAccount + )?.trimmingCharacters(in: .whitespacesAndNewlines), + !legacy.isEmpty + { + _ = KeychainStore.saveString( + legacy, + service: self.gatewayService, + account: self.preferredGatewayStableIDAccount) + return legacy + } + + return nil } static func savePreferredGatewayStableID(_ stableID: String) { @@ -54,8 +91,29 @@ enum GatewaySettingsStore { } static func loadLastDiscoveredGatewayStableID() -> String? { - KeychainStore.loadString(service: self.gatewayService, account: self.lastDiscoveredGatewayStableIDAccount)? - .trimmingCharacters(in: .whitespacesAndNewlines) + if let value = KeychainStore.loadString( + service: self.gatewayService, + account: self.lastDiscoveredGatewayStableIDAccount + )?.trimmingCharacters(in: .whitespacesAndNewlines), + !value.isEmpty + { + return value + } + + if let legacy = KeychainStore.loadString( + service: self.legacyGatewayService, + account: self.lastDiscoveredGatewayStableIDAccount + )?.trimmingCharacters(in: .whitespacesAndNewlines), + !legacy.isEmpty + { + _ = KeychainStore.saveString( + legacy, + service: self.gatewayService, + account: self.lastDiscoveredGatewayStableIDAccount) + return legacy + } + + return nil } static func saveLastDiscoveredGatewayStableID(_ stableID: String) { diff --git a/apps/ios/Sources/Info.plist b/apps/ios/Sources/Info.plist index 37e0bad49..d3e398ab4 100644 --- a/apps/ios/Sources/Info.plist +++ b/apps/ios/Sources/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2026.1.26 + 2026.1.27-beta.1 CFBundleVersion 20260126 NSAppTransportSecurity diff --git a/apps/ios/Sources/Screen/ScreenRecordService.swift b/apps/ios/Sources/Screen/ScreenRecordService.swift index 9011487ba..849add38d 100644 --- a/apps/ios/Sources/Screen/ScreenRecordService.swift +++ b/apps/ios/Sources/Screen/ScreenRecordService.swift @@ -55,7 +55,7 @@ final class ScreenRecordService: @unchecked Sendable { outPath: outPath) let state = CaptureState() - let recordQueue = DispatchQueue(label: "com.clawdbot.screenrecord") + let recordQueue = DispatchQueue(label: "bot.molt.screenrecord") try await self.startCapture(state: state, config: config, recordQueue: recordQueue) try await Task.sleep(nanoseconds: UInt64(config.durationMs) * 1_000_000) diff --git a/apps/ios/Sources/Voice/TalkModeManager.swift b/apps/ios/Sources/Voice/TalkModeManager.swift index 0a3872424..c0ae8b454 100644 --- a/apps/ios/Sources/Voice/TalkModeManager.swift +++ b/apps/ios/Sources/Voice/TalkModeManager.swift @@ -48,7 +48,7 @@ final class TalkModeManager: NSObject { private var chatSubscribedSessionKeys = Set() - private let logger = Logger(subsystem: "com.clawdbot", category: "TalkMode") + private let logger = Logger(subsystem: "bot.molt", category: "TalkMode") func attachGateway(_ gateway: GatewayNodeSession) { self.gateway = gateway diff --git a/apps/ios/SwiftSources.input.xcfilelist b/apps/ios/SwiftSources.input.xcfilelist index 70d0f39d6..c9d7ff46c 100644 --- a/apps/ios/SwiftSources.input.xcfilelist +++ b/apps/ios/SwiftSources.input.xcfilelist @@ -24,37 +24,37 @@ Sources/Status/VoiceWakeToast.swift Sources/Voice/VoiceTab.swift Sources/Voice/VoiceWakeManager.swift Sources/Voice/VoiceWakePreferences.swift -../shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatComposer.swift -../shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatMarkdownRenderer.swift -../shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatMarkdownPreprocessor.swift -../shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatMessageViews.swift -../shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatModels.swift -../shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatPayloadDecoding.swift -../shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatSessions.swift -../shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatSheets.swift -../shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatTheme.swift -../shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatTransport.swift -../shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatView.swift -../shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatViewModel.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/AnyCodable.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/BonjourEscapes.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/BonjourTypes.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/BridgeFrames.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/CameraCommands.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/CanvasA2UIAction.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/CanvasA2UICommands.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/CanvasA2UIJSONL.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/CanvasCommandParams.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/CanvasCommands.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/Capabilities.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/ClawdbotKitResources.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/DeepLinks.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/JPEGTranscoder.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/NodeError.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/ScreenCommands.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/StoragePaths.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/SystemCommands.swift -../shared/ClawdbotKit/Sources/ClawdbotKit/TalkDirective.swift +../shared/MoltbotKit/Sources/MoltbotChatUI/ChatComposer.swift +../shared/MoltbotKit/Sources/MoltbotChatUI/ChatMarkdownRenderer.swift +../shared/MoltbotKit/Sources/MoltbotChatUI/ChatMarkdownPreprocessor.swift +../shared/MoltbotKit/Sources/MoltbotChatUI/ChatMessageViews.swift +../shared/MoltbotKit/Sources/MoltbotChatUI/ChatModels.swift +../shared/MoltbotKit/Sources/MoltbotChatUI/ChatPayloadDecoding.swift +../shared/MoltbotKit/Sources/MoltbotChatUI/ChatSessions.swift +../shared/MoltbotKit/Sources/MoltbotChatUI/ChatSheets.swift +../shared/MoltbotKit/Sources/MoltbotChatUI/ChatTheme.swift +../shared/MoltbotKit/Sources/MoltbotChatUI/ChatTransport.swift +../shared/MoltbotKit/Sources/MoltbotChatUI/ChatView.swift +../shared/MoltbotKit/Sources/MoltbotChatUI/ChatViewModel.swift +../shared/MoltbotKit/Sources/MoltbotKit/AnyCodable.swift +../shared/MoltbotKit/Sources/MoltbotKit/BonjourEscapes.swift +../shared/MoltbotKit/Sources/MoltbotKit/BonjourTypes.swift +../shared/MoltbotKit/Sources/MoltbotKit/BridgeFrames.swift +../shared/MoltbotKit/Sources/MoltbotKit/CameraCommands.swift +../shared/MoltbotKit/Sources/MoltbotKit/CanvasA2UIAction.swift +../shared/MoltbotKit/Sources/MoltbotKit/CanvasA2UICommands.swift +../shared/MoltbotKit/Sources/MoltbotKit/CanvasA2UIJSONL.swift +../shared/MoltbotKit/Sources/MoltbotKit/CanvasCommandParams.swift +../shared/MoltbotKit/Sources/MoltbotKit/CanvasCommands.swift +../shared/MoltbotKit/Sources/MoltbotKit/Capabilities.swift +../shared/MoltbotKit/Sources/MoltbotKit/ClawdbotKitResources.swift +../shared/MoltbotKit/Sources/MoltbotKit/DeepLinks.swift +../shared/MoltbotKit/Sources/MoltbotKit/JPEGTranscoder.swift +../shared/MoltbotKit/Sources/MoltbotKit/NodeError.swift +../shared/MoltbotKit/Sources/MoltbotKit/ScreenCommands.swift +../shared/MoltbotKit/Sources/MoltbotKit/StoragePaths.swift +../shared/MoltbotKit/Sources/MoltbotKit/SystemCommands.swift +../shared/MoltbotKit/Sources/MoltbotKit/TalkDirective.swift ../../Swabble/Sources/SwabbleKit/WakeWordGate.swift Sources/Voice/TalkModeManager.swift Sources/Voice/TalkOrbOverlay.swift diff --git a/apps/ios/Tests/GatewaySettingsStoreTests.swift b/apps/ios/Tests/GatewaySettingsStoreTests.swift index 2f4df7964..746bf8fdf 100644 --- a/apps/ios/Tests/GatewaySettingsStoreTests.swift +++ b/apps/ios/Tests/GatewaySettingsStoreTests.swift @@ -7,8 +7,8 @@ private struct KeychainEntry: Hashable { let account: String } -private let gatewayService = "com.clawdbot.gateway" -private let nodeService = "com.clawdbot.node" +private let gatewayService = "bot.molt.gateway" +private let nodeService = "bot.molt.node" private let instanceIdEntry = KeychainEntry(service: nodeService, account: "instanceId") private let preferredGatewayEntry = KeychainEntry(service: gatewayService, account: "preferredStableID") private let lastGatewayEntry = KeychainEntry(service: gatewayService, account: "lastDiscoveredStableID") diff --git a/apps/ios/Tests/Info.plist b/apps/ios/Tests/Info.plist index f3eb12b09..a5336c6ad 100644 --- a/apps/ios/Tests/Info.plist +++ b/apps/ios/Tests/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2026.1.26 + 2026.1.27-beta.1 CFBundleVersion 20260126 diff --git a/apps/ios/Tests/KeychainStoreTests.swift b/apps/ios/Tests/KeychainStoreTests.swift index 798137b7e..8aa5ae071 100644 --- a/apps/ios/Tests/KeychainStoreTests.swift +++ b/apps/ios/Tests/KeychainStoreTests.swift @@ -4,7 +4,7 @@ import Testing @Suite struct KeychainStoreTests { @Test func saveLoadUpdateDeleteRoundTrip() { - let service = "com.clawdbot.tests.\(UUID().uuidString)" + let service = "bot.molt.tests.\(UUID().uuidString)" let account = "value" #expect(KeychainStore.delete(service: service, account: account)) diff --git a/apps/ios/fastlane/Appfile b/apps/ios/fastlane/Appfile index 7942da625..adaa3fc29 100644 --- a/apps/ios/fastlane/Appfile +++ b/apps/ios/fastlane/Appfile @@ -1,4 +1,4 @@ -app_identifier("com.clawdbot.ios") +app_identifier("bot.molt.ios") # Auth is expected via App Store Connect API key. # Provide either: diff --git a/apps/ios/project.yml b/apps/ios/project.yml index 2f6b0ec47..a6728cd98 100644 --- a/apps/ios/project.yml +++ b/apps/ios/project.yml @@ -1,6 +1,6 @@ name: Moltbot options: - bundleIdPrefix: com.clawdbot + bundleIdPrefix: bot.molt deploymentTarget: iOS: "18.0" xcodeVersion: "16.0" @@ -11,7 +11,7 @@ settings: packages: MoltbotKit: - path: ../shared/ClawdbotKit + path: ../shared/MoltbotKit Swabble: path: ../../Swabble @@ -71,8 +71,8 @@ targets: CODE_SIGN_IDENTITY: "Apple Development" CODE_SIGN_STYLE: Manual DEVELOPMENT_TEAM: Y5PE65HELJ - PRODUCT_BUNDLE_IDENTIFIER: com.clawdbot.ios - PROVISIONING_PROFILE_SPECIFIER: "com.clawdbot.ios Development" + PRODUCT_BUNDLE_IDENTIFIER: bot.molt.ios + PROVISIONING_PROFILE_SPECIFIER: "bot.molt.ios Development" SWIFT_VERSION: "6.0" SWIFT_STRICT_CONCURRENCY: complete ENABLE_APPINTENTS_METADATA: NO @@ -81,7 +81,7 @@ targets: properties: CFBundleDisplayName: Moltbot CFBundleIconName: AppIcon - CFBundleShortVersionString: "2026.1.26" + CFBundleShortVersionString: "2026.1.27-beta.1" CFBundleVersion: "20260126" UILaunchScreen: {} UIApplicationSceneManifest: @@ -121,7 +121,7 @@ targets: - sdk: AppIntents.framework settings: base: - PRODUCT_BUNDLE_IDENTIFIER: com.clawdbot.ios.tests + PRODUCT_BUNDLE_IDENTIFIER: bot.molt.ios.tests SWIFT_VERSION: "6.0" SWIFT_STRICT_CONCURRENCY: complete TEST_HOST: "$(BUILT_PRODUCTS_DIR)/Moltbot.app/Moltbot" @@ -130,5 +130,5 @@ targets: path: Tests/Info.plist properties: CFBundleDisplayName: MoltbotTests - CFBundleShortVersionString: "2026.1.26" + CFBundleShortVersionString: "2026.1.27-beta.1" CFBundleVersion: "20260126" diff --git a/apps/macos/Package.resolved b/apps/macos/Package.resolved index ef9609649..302a4c78a 100644 --- a/apps/macos/Package.resolved +++ b/apps/macos/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "f847d54db16b371dbb1a79271d50436cdec572179b0f0cf14cfe1b75df8dfbc2", + "originHash" : "c86f22da7772193c6f161fc9db81747cc00c8b8c96b45f9479de1e65c2c4b17e", "pins" : [ { "identity" : "axorcist", @@ -24,7 +24,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/steipete/ElevenLabsKit", "state" : { - "revision" : "c8679fbd37416a8780fe43be88a497ff16209e2d", + "revision" : "7e3c948d8340abe3977014f3de020edf221e9269", "version" : "0.1.0" } }, @@ -78,8 +78,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "bc386b95f2a16ccd0150a8235e7c69eab2b866ca", - "version" : "1.8.0" + "revision" : "2778fd4e5a12a8aaa30a3ee8285f4ce54c5f3181", + "version" : "1.9.1" } }, { @@ -96,8 +96,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swiftlang/swift-subprocess.git", "state" : { - "revision" : "44922dfe46380cd354ca4b0208e717a3e92b13dd", - "version" : "0.2.1" + "revision" : "ba5888ad7758cbcbe7abebac37860b1652af2d9c", + "version" : "0.3.0" } }, { @@ -105,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-system", "state" : { - "revision" : "395a77f0aa927f0ff73941d7ac35f2b46d47c9db", - "version" : "1.6.3" + "revision" : "7c6ad0fc39d0763e0b699210e4124afd5041c5df", + "version" : "1.6.4" } }, { diff --git a/apps/macos/Package.swift b/apps/macos/Package.swift index ac6691493..b3cae1184 100644 --- a/apps/macos/Package.swift +++ b/apps/macos/Package.swift @@ -20,7 +20,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-log.git", from: "1.8.0"), .package(url: "https://github.com/sparkle-project/Sparkle", from: "2.8.1"), .package(url: "https://github.com/steipete/Peekaboo.git", branch: "main"), - .package(path: "../shared/ClawdbotKit"), + .package(path: "../shared/MoltbotKit"), .package(path: "../../Swabble"), ], targets: [ diff --git a/apps/macos/README.md b/apps/macos/README.md index ae35b772e..4a460d275 100644 --- a/apps/macos/README.md +++ b/apps/macos/README.md @@ -1,4 +1,4 @@ -# Clawdbot macOS app (dev + signing) +# Moltbot macOS app (dev + signing) ## Quick dev run @@ -20,7 +20,7 @@ scripts/restart-mac.sh --sign # force code signing (requires cert) scripts/package-mac-app.sh ``` -Creates `dist/Clawdbot.app` and signs it via `scripts/codesign-mac-app.sh`. +Creates `dist/Moltbot.app` and signs it via `scripts/codesign-mac-app.sh`. ## Signing behavior diff --git a/apps/macos/Sources/Clawdbot/AboutSettings.swift b/apps/macos/Sources/Moltbot/AboutSettings.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/AboutSettings.swift rename to apps/macos/Sources/Moltbot/AboutSettings.swift diff --git a/apps/macos/Sources/Clawdbot/AgeFormatting.swift b/apps/macos/Sources/Moltbot/AgeFormatting.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/AgeFormatting.swift rename to apps/macos/Sources/Moltbot/AgeFormatting.swift diff --git a/apps/macos/Sources/Clawdbot/AgentEventStore.swift b/apps/macos/Sources/Moltbot/AgentEventStore.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/AgentEventStore.swift rename to apps/macos/Sources/Moltbot/AgentEventStore.swift diff --git a/apps/macos/Sources/Clawdbot/AgentEventsWindow.swift b/apps/macos/Sources/Moltbot/AgentEventsWindow.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/AgentEventsWindow.swift rename to apps/macos/Sources/Moltbot/AgentEventsWindow.swift diff --git a/apps/macos/Sources/Clawdbot/AgentWorkspace.swift b/apps/macos/Sources/Moltbot/AgentWorkspace.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/AgentWorkspace.swift rename to apps/macos/Sources/Moltbot/AgentWorkspace.swift index bad27d3b7..02e725a83 100644 --- a/apps/macos/Sources/Clawdbot/AgentWorkspace.swift +++ b/apps/macos/Sources/Moltbot/AgentWorkspace.swift @@ -2,7 +2,7 @@ import Foundation import OSLog enum AgentWorkspace { - private static let logger = Logger(subsystem: "com.clawdbot", category: "workspace") + private static let logger = Logger(subsystem: "bot.molt", category: "workspace") static let agentsFilename = "AGENTS.md" static let soulFilename = "SOUL.md" static let identityFilename = "IDENTITY.md" diff --git a/apps/macos/Sources/Clawdbot/AnthropicAuthControls.swift b/apps/macos/Sources/Moltbot/AnthropicAuthControls.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/AnthropicAuthControls.swift rename to apps/macos/Sources/Moltbot/AnthropicAuthControls.swift diff --git a/apps/macos/Sources/Clawdbot/AnthropicOAuth.swift b/apps/macos/Sources/Moltbot/AnthropicOAuth.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/AnthropicOAuth.swift rename to apps/macos/Sources/Moltbot/AnthropicOAuth.swift index 1a29644b2..a13275d7a 100644 --- a/apps/macos/Sources/Clawdbot/AnthropicOAuth.swift +++ b/apps/macos/Sources/Moltbot/AnthropicOAuth.swift @@ -58,7 +58,7 @@ enum AnthropicAuthResolver { } enum AnthropicOAuth { - private static let logger = Logger(subsystem: "com.clawdbot", category: "anthropic-oauth") + private static let logger = Logger(subsystem: "bot.molt", category: "anthropic-oauth") private static let clientId = "9d1c250a-e61b-44d9-88ed-5944d1962f5e" private static let authorizeURL = URL(string: "https://claude.ai/oauth/authorize")! @@ -226,7 +226,7 @@ enum MoltbotOAuthStore { } static func oauthDir() -> URL { - if let override = ProcessInfo.processInfo.environment[self.clawdbotOAuthDirEnv]? + if let override = ProcessInfo.processInfo.environment[self.moltbotOAuthDirEnv]? .trimmingCharacters(in: .whitespacesAndNewlines), !override.isEmpty { diff --git a/apps/macos/Sources/Clawdbot/AnthropicOAuthCodeState.swift b/apps/macos/Sources/Moltbot/AnthropicOAuthCodeState.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/AnthropicOAuthCodeState.swift rename to apps/macos/Sources/Moltbot/AnthropicOAuthCodeState.swift diff --git a/apps/macos/Sources/Clawdbot/AnyCodable+Helpers.swift b/apps/macos/Sources/Moltbot/AnyCodable+Helpers.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/AnyCodable+Helpers.swift rename to apps/macos/Sources/Moltbot/AnyCodable+Helpers.swift diff --git a/apps/macos/Sources/Clawdbot/AppState.swift b/apps/macos/Sources/Moltbot/AppState.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/AppState.swift rename to apps/macos/Sources/Moltbot/AppState.swift diff --git a/apps/macos/Sources/Clawdbot/AudioInputDeviceObserver.swift b/apps/macos/Sources/Moltbot/AudioInputDeviceObserver.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/AudioInputDeviceObserver.swift rename to apps/macos/Sources/Moltbot/AudioInputDeviceObserver.swift index bc296972c..4411016f5 100644 --- a/apps/macos/Sources/Clawdbot/AudioInputDeviceObserver.swift +++ b/apps/macos/Sources/Moltbot/AudioInputDeviceObserver.swift @@ -3,7 +3,7 @@ import Foundation import OSLog final class AudioInputDeviceObserver { - private let logger = Logger(subsystem: "com.clawdbot", category: "audio.devices") + private let logger = Logger(subsystem: "bot.molt", category: "audio.devices") private var isActive = false private var devicesListener: AudioObjectPropertyListenerBlock? private var defaultInputListener: AudioObjectPropertyListenerBlock? diff --git a/apps/macos/Sources/Clawdbot/CLIInstallPrompter.swift b/apps/macos/Sources/Moltbot/CLIInstallPrompter.swift similarity index 93% rename from apps/macos/Sources/Clawdbot/CLIInstallPrompter.swift rename to apps/macos/Sources/Moltbot/CLIInstallPrompter.swift index 75c0b04d4..80cd695fd 100644 --- a/apps/macos/Sources/Clawdbot/CLIInstallPrompter.swift +++ b/apps/macos/Sources/Moltbot/CLIInstallPrompter.swift @@ -5,7 +5,7 @@ import OSLog @MainActor final class CLIInstallPrompter { static let shared = CLIInstallPrompter() - private let logger = Logger(subsystem: "com.clawdbot", category: "cli.prompt") + private let logger = Logger(subsystem: "bot.molt", category: "cli.prompt") private var isPrompting = false func checkAndPromptIfNeeded(reason: String) { @@ -62,7 +62,7 @@ final class CLIInstallPrompter { SettingsTabRouter.request(tab) SettingsWindowOpener.shared.open() DispatchQueue.main.async { - NotificationCenter.default.post(name: .clawdbotSelectSettingsTab, object: tab) + NotificationCenter.default.post(name: .moltbotSelectSettingsTab, object: tab) } } diff --git a/apps/macos/Sources/Clawdbot/CLIInstaller.swift b/apps/macos/Sources/Moltbot/CLIInstaller.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CLIInstaller.swift rename to apps/macos/Sources/Moltbot/CLIInstaller.swift diff --git a/apps/macos/Sources/Clawdbot/CameraCaptureService.swift b/apps/macos/Sources/Moltbot/CameraCaptureService.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/CameraCaptureService.swift rename to apps/macos/Sources/Moltbot/CameraCaptureService.swift index 49a15262e..ee70a3006 100644 --- a/apps/macos/Sources/Clawdbot/CameraCaptureService.swift +++ b/apps/macos/Sources/Moltbot/CameraCaptureService.swift @@ -36,7 +36,7 @@ actor CameraCaptureService { } } - private let logger = Logger(subsystem: "com.clawdbot", category: "camera") + private let logger = Logger(subsystem: "bot.molt", category: "camera") func listDevices() -> [CameraDeviceInfo] { Self.availableCameras().map { device in diff --git a/apps/macos/Sources/Clawdbot/CanvasA2UIActionMessageHandler.swift b/apps/macos/Sources/Moltbot/CanvasA2UIActionMessageHandler.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CanvasA2UIActionMessageHandler.swift rename to apps/macos/Sources/Moltbot/CanvasA2UIActionMessageHandler.swift diff --git a/apps/macos/Sources/Clawdbot/CanvasChromeContainerView.swift b/apps/macos/Sources/Moltbot/CanvasChromeContainerView.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CanvasChromeContainerView.swift rename to apps/macos/Sources/Moltbot/CanvasChromeContainerView.swift diff --git a/apps/macos/Sources/Clawdbot/CanvasFileWatcher.swift b/apps/macos/Sources/Moltbot/CanvasFileWatcher.swift similarity index 97% rename from apps/macos/Sources/Clawdbot/CanvasFileWatcher.swift rename to apps/macos/Sources/Moltbot/CanvasFileWatcher.swift index 131e68748..bef341fdc 100644 --- a/apps/macos/Sources/Clawdbot/CanvasFileWatcher.swift +++ b/apps/macos/Sources/Moltbot/CanvasFileWatcher.swift @@ -10,7 +10,7 @@ final class CanvasFileWatcher: @unchecked Sendable { init(url: URL, onChange: @escaping () -> Void) { self.url = url - self.queue = DispatchQueue(label: "com.clawdbot.canvaswatcher") + self.queue = DispatchQueue(label: "bot.molt.canvaswatcher") self.onChange = onChange } diff --git a/apps/macos/Sources/Clawdbot/CanvasManager.swift b/apps/macos/Sources/Moltbot/CanvasManager.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/CanvasManager.swift rename to apps/macos/Sources/Moltbot/CanvasManager.swift index 9a0f32d61..8100934ab 100644 --- a/apps/macos/Sources/Clawdbot/CanvasManager.swift +++ b/apps/macos/Sources/Moltbot/CanvasManager.swift @@ -8,7 +8,7 @@ import OSLog final class CanvasManager { static let shared = CanvasManager() - private static let logger = Logger(subsystem: "com.clawdbot", category: "CanvasManager") + private static let logger = Logger(subsystem: "bot.molt", category: "CanvasManager") private var panelController: CanvasWindowController? private var panelSessionKey: String? diff --git a/apps/macos/Sources/Clawdbot/CanvasScheme.swift b/apps/macos/Sources/Moltbot/CanvasScheme.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CanvasScheme.swift rename to apps/macos/Sources/Moltbot/CanvasScheme.swift diff --git a/apps/macos/Sources/Clawdbot/CanvasSchemeHandler.swift b/apps/macos/Sources/Moltbot/CanvasSchemeHandler.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/CanvasSchemeHandler.swift rename to apps/macos/Sources/Moltbot/CanvasSchemeHandler.swift index 92bc8e71b..3e47026a2 100644 --- a/apps/macos/Sources/Clawdbot/CanvasSchemeHandler.swift +++ b/apps/macos/Sources/Moltbot/CanvasSchemeHandler.swift @@ -3,7 +3,7 @@ import Foundation import OSLog import WebKit -private let canvasLogger = Logger(subsystem: "com.clawdbot", category: "Canvas") +private let canvasLogger = Logger(subsystem: "bot.molt", category: "Canvas") final class CanvasSchemeHandler: NSObject, WKURLSchemeHandler { private let root: URL diff --git a/apps/macos/Sources/Clawdbot/CanvasWindow.swift b/apps/macos/Sources/Moltbot/CanvasWindow.swift similarity index 88% rename from apps/macos/Sources/Clawdbot/CanvasWindow.swift rename to apps/macos/Sources/Moltbot/CanvasWindow.swift index 47e0a4128..27306f88a 100644 --- a/apps/macos/Sources/Clawdbot/CanvasWindow.swift +++ b/apps/macos/Sources/Moltbot/CanvasWindow.swift @@ -1,6 +1,6 @@ import AppKit -let canvasWindowLogger = Logger(subsystem: "com.clawdbot", category: "Canvas") +let canvasWindowLogger = Logger(subsystem: "bot.molt", category: "Canvas") enum CanvasLayout { static let panelSize = NSSize(width: 520, height: 680) diff --git a/apps/macos/Sources/Clawdbot/CanvasWindowController+Helpers.swift b/apps/macos/Sources/Moltbot/CanvasWindowController+Helpers.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CanvasWindowController+Helpers.swift rename to apps/macos/Sources/Moltbot/CanvasWindowController+Helpers.swift diff --git a/apps/macos/Sources/Clawdbot/CanvasWindowController+Navigation.swift b/apps/macos/Sources/Moltbot/CanvasWindowController+Navigation.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CanvasWindowController+Navigation.swift rename to apps/macos/Sources/Moltbot/CanvasWindowController+Navigation.swift diff --git a/apps/macos/Sources/Clawdbot/CanvasWindowController+Testing.swift b/apps/macos/Sources/Moltbot/CanvasWindowController+Testing.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CanvasWindowController+Testing.swift rename to apps/macos/Sources/Moltbot/CanvasWindowController+Testing.swift diff --git a/apps/macos/Sources/Clawdbot/CanvasWindowController+Window.swift b/apps/macos/Sources/Moltbot/CanvasWindowController+Window.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CanvasWindowController+Window.swift rename to apps/macos/Sources/Moltbot/CanvasWindowController+Window.swift diff --git a/apps/macos/Sources/Clawdbot/CanvasWindowController.swift b/apps/macos/Sources/Moltbot/CanvasWindowController.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CanvasWindowController.swift rename to apps/macos/Sources/Moltbot/CanvasWindowController.swift diff --git a/apps/macos/Sources/Clawdbot/ChannelConfigForm.swift b/apps/macos/Sources/Moltbot/ChannelConfigForm.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ChannelConfigForm.swift rename to apps/macos/Sources/Moltbot/ChannelConfigForm.swift diff --git a/apps/macos/Sources/Clawdbot/ChannelsSettings+ChannelSections.swift b/apps/macos/Sources/Moltbot/ChannelsSettings+ChannelSections.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ChannelsSettings+ChannelSections.swift rename to apps/macos/Sources/Moltbot/ChannelsSettings+ChannelSections.swift diff --git a/apps/macos/Sources/Clawdbot/ChannelsSettings+ChannelState.swift b/apps/macos/Sources/Moltbot/ChannelsSettings+ChannelState.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ChannelsSettings+ChannelState.swift rename to apps/macos/Sources/Moltbot/ChannelsSettings+ChannelState.swift diff --git a/apps/macos/Sources/Clawdbot/ChannelsSettings+Helpers.swift b/apps/macos/Sources/Moltbot/ChannelsSettings+Helpers.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ChannelsSettings+Helpers.swift rename to apps/macos/Sources/Moltbot/ChannelsSettings+Helpers.swift diff --git a/apps/macos/Sources/Clawdbot/ChannelsSettings+View.swift b/apps/macos/Sources/Moltbot/ChannelsSettings+View.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ChannelsSettings+View.swift rename to apps/macos/Sources/Moltbot/ChannelsSettings+View.swift diff --git a/apps/macos/Sources/Clawdbot/ChannelsSettings.swift b/apps/macos/Sources/Moltbot/ChannelsSettings.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ChannelsSettings.swift rename to apps/macos/Sources/Moltbot/ChannelsSettings.swift diff --git a/apps/macos/Sources/Clawdbot/ChannelsStore+Config.swift b/apps/macos/Sources/Moltbot/ChannelsStore+Config.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ChannelsStore+Config.swift rename to apps/macos/Sources/Moltbot/ChannelsStore+Config.swift diff --git a/apps/macos/Sources/Clawdbot/ChannelsStore+Lifecycle.swift b/apps/macos/Sources/Moltbot/ChannelsStore+Lifecycle.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ChannelsStore+Lifecycle.swift rename to apps/macos/Sources/Moltbot/ChannelsStore+Lifecycle.swift diff --git a/apps/macos/Sources/Clawdbot/ChannelsStore.swift b/apps/macos/Sources/Moltbot/ChannelsStore.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ChannelsStore.swift rename to apps/macos/Sources/Moltbot/ChannelsStore.swift diff --git a/apps/macos/Sources/Clawdbot/ClawdbotConfigFile.swift b/apps/macos/Sources/Moltbot/ClawdbotConfigFile.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/ClawdbotConfigFile.swift rename to apps/macos/Sources/Moltbot/ClawdbotConfigFile.swift index 0ca77af30..2c796d4ea 100644 --- a/apps/macos/Sources/Clawdbot/ClawdbotConfigFile.swift +++ b/apps/macos/Sources/Moltbot/ClawdbotConfigFile.swift @@ -2,7 +2,7 @@ import MoltbotProtocol import Foundation enum MoltbotConfigFile { - private static let logger = Logger(subsystem: "com.clawdbot", category: "config") + private static let logger = Logger(subsystem: "bot.molt", category: "config") static func url() -> URL { MoltbotPaths.configURL diff --git a/apps/macos/Sources/Clawdbot/ClawdbotPaths.swift b/apps/macos/Sources/Moltbot/ClawdbotPaths.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ClawdbotPaths.swift rename to apps/macos/Sources/Moltbot/ClawdbotPaths.swift diff --git a/apps/macos/Sources/Clawdbot/CommandResolver.swift b/apps/macos/Sources/Moltbot/CommandResolver.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/CommandResolver.swift rename to apps/macos/Sources/Moltbot/CommandResolver.swift index 99a738541..427accbbf 100644 --- a/apps/macos/Sources/Clawdbot/CommandResolver.swift +++ b/apps/macos/Sources/Moltbot/CommandResolver.swift @@ -87,7 +87,7 @@ enum CommandResolver { // Dev-only convenience. Avoid project-local PATH hijacking in release builds. extras.insert(projectRoot.appendingPathComponent("node_modules/.bin").path, at: 0) #endif - let moltbotPaths = self.clawdbotManagedPaths(home: home) + let moltbotPaths = self.moltbotManagedPaths(home: home) if !moltbotPaths.isEmpty { extras.insert(contentsOf: moltbotPaths, at: 1) } @@ -207,7 +207,7 @@ enum CommandResolver { } static func hasAnyMoltbotInvoker(searchPaths: [String]? = nil) -> Bool { - if self.clawdbotExecutable(searchPaths: searchPaths) != nil { return true } + if self.moltbotExecutable(searchPaths: searchPaths) != nil { return true } if self.findExecutable(named: "pnpm", searchPaths: searchPaths) != nil { return true } if self.findExecutable(named: "node", searchPaths: searchPaths) != nil, self.nodeCliPath() != nil @@ -253,7 +253,7 @@ enum CommandResolver { // Use --silent to avoid pnpm lifecycle banners that would corrupt JSON outputs. return [pnpm, "--silent", "moltbot", subcommand] + extraArgs } - if let moltbotPath = self.clawdbotExecutable(searchPaths: searchPaths) { + if let moltbotPath = self.moltbotExecutable(searchPaths: searchPaths) { return [moltbotPath, subcommand] + extraArgs } @@ -275,7 +275,7 @@ enum CommandResolver { configRoot: [String: Any]? = nil, searchPaths: [String]? = nil) -> [String] { - self.clawdbotNodeCommand( + self.moltbotNodeCommand( subcommand: subcommand, extraArgs: extraArgs, defaults: defaults, diff --git a/apps/macos/Sources/Clawdbot/ConfigFileWatcher.swift b/apps/macos/Sources/Moltbot/ConfigFileWatcher.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/ConfigFileWatcher.swift rename to apps/macos/Sources/Moltbot/ConfigFileWatcher.swift index c21b002a7..b7904f73f 100644 --- a/apps/macos/Sources/Clawdbot/ConfigFileWatcher.swift +++ b/apps/macos/Sources/Moltbot/ConfigFileWatcher.swift @@ -13,7 +13,7 @@ final class ConfigFileWatcher: @unchecked Sendable { init(url: URL, onChange: @escaping () -> Void) { self.url = url - self.queue = DispatchQueue(label: "com.clawdbot.configwatcher") + self.queue = DispatchQueue(label: "bot.molt.configwatcher") self.onChange = onChange self.watchedDir = url.deletingLastPathComponent() self.targetPath = url.path diff --git a/apps/macos/Sources/Clawdbot/ConfigSchemaSupport.swift b/apps/macos/Sources/Moltbot/ConfigSchemaSupport.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ConfigSchemaSupport.swift rename to apps/macos/Sources/Moltbot/ConfigSchemaSupport.swift diff --git a/apps/macos/Sources/Clawdbot/ConfigSettings.swift b/apps/macos/Sources/Moltbot/ConfigSettings.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ConfigSettings.swift rename to apps/macos/Sources/Moltbot/ConfigSettings.swift diff --git a/apps/macos/Sources/Clawdbot/ConfigStore.swift b/apps/macos/Sources/Moltbot/ConfigStore.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ConfigStore.swift rename to apps/macos/Sources/Moltbot/ConfigStore.swift diff --git a/apps/macos/Sources/Clawdbot/ConnectionModeCoordinator.swift b/apps/macos/Sources/Moltbot/ConnectionModeCoordinator.swift similarity index 97% rename from apps/macos/Sources/Clawdbot/ConnectionModeCoordinator.swift rename to apps/macos/Sources/Moltbot/ConnectionModeCoordinator.swift index 00f93bd85..28bb5795b 100644 --- a/apps/macos/Sources/Clawdbot/ConnectionModeCoordinator.swift +++ b/apps/macos/Sources/Moltbot/ConnectionModeCoordinator.swift @@ -5,7 +5,7 @@ import OSLog final class ConnectionModeCoordinator { static let shared = ConnectionModeCoordinator() - private let logger = Logger(subsystem: "com.clawdbot", category: "connection") + private let logger = Logger(subsystem: "bot.molt", category: "connection") private var lastMode: AppState.ConnectionMode? /// Apply the requested connection mode by starting/stopping local gateway, diff --git a/apps/macos/Sources/Clawdbot/ConnectionModeResolver.swift b/apps/macos/Sources/Moltbot/ConnectionModeResolver.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ConnectionModeResolver.swift rename to apps/macos/Sources/Moltbot/ConnectionModeResolver.swift diff --git a/apps/macos/Sources/Clawdbot/Constants.swift b/apps/macos/Sources/Moltbot/Constants.swift similarity index 96% rename from apps/macos/Sources/Clawdbot/Constants.swift rename to apps/macos/Sources/Moltbot/Constants.swift index dcb36d4a9..5905d3f1b 100644 --- a/apps/macos/Sources/Clawdbot/Constants.swift +++ b/apps/macos/Sources/Moltbot/Constants.swift @@ -1,7 +1,7 @@ import Foundation -let launchdLabel = "com.clawdbot.mac" -let gatewayLaunchdLabel = "com.clawdbot.gateway" +let launchdLabel = "bot.molt.mac" +let gatewayLaunchdLabel = "bot.molt.gateway" let onboardingVersionKey = "moltbot.onboardingVersion" let currentOnboardingVersion = 7 let pauseDefaultsKey = "moltbot.pauseEnabled" diff --git a/apps/macos/Sources/Clawdbot/ContextMenuCardView.swift b/apps/macos/Sources/Moltbot/ContextMenuCardView.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ContextMenuCardView.swift rename to apps/macos/Sources/Moltbot/ContextMenuCardView.swift diff --git a/apps/macos/Sources/Clawdbot/ContextUsageBar.swift b/apps/macos/Sources/Moltbot/ContextUsageBar.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ContextUsageBar.swift rename to apps/macos/Sources/Moltbot/ContextUsageBar.swift diff --git a/apps/macos/Sources/Clawdbot/ControlChannel.swift b/apps/macos/Sources/Moltbot/ControlChannel.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/ControlChannel.swift rename to apps/macos/Sources/Moltbot/ControlChannel.swift index 02f7e7686..2af7c721d 100644 --- a/apps/macos/Sources/Clawdbot/ControlChannel.swift +++ b/apps/macos/Sources/Moltbot/ControlChannel.swift @@ -76,7 +76,7 @@ final class ControlChannel { private(set) var lastPingMs: Double? private(set) var authSourceLabel: String? - private let logger = Logger(subsystem: "com.clawdbot", category: "control") + private let logger = Logger(subsystem: "bot.molt", category: "control") private var eventTask: Task? private var recoveryTask: Task? diff --git a/apps/macos/Sources/Clawdbot/CostUsageMenuView.swift b/apps/macos/Sources/Moltbot/CostUsageMenuView.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CostUsageMenuView.swift rename to apps/macos/Sources/Moltbot/CostUsageMenuView.swift diff --git a/apps/macos/Sources/Clawdbot/CritterIconRenderer.swift b/apps/macos/Sources/Moltbot/CritterIconRenderer.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CritterIconRenderer.swift rename to apps/macos/Sources/Moltbot/CritterIconRenderer.swift diff --git a/apps/macos/Sources/Clawdbot/CritterStatusLabel+Behavior.swift b/apps/macos/Sources/Moltbot/CritterStatusLabel+Behavior.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CritterStatusLabel+Behavior.swift rename to apps/macos/Sources/Moltbot/CritterStatusLabel+Behavior.swift diff --git a/apps/macos/Sources/Clawdbot/CritterStatusLabel.swift b/apps/macos/Sources/Moltbot/CritterStatusLabel.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CritterStatusLabel.swift rename to apps/macos/Sources/Moltbot/CritterStatusLabel.swift diff --git a/apps/macos/Sources/Clawdbot/CronJobEditor+Helpers.swift b/apps/macos/Sources/Moltbot/CronJobEditor+Helpers.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CronJobEditor+Helpers.swift rename to apps/macos/Sources/Moltbot/CronJobEditor+Helpers.swift diff --git a/apps/macos/Sources/Clawdbot/CronJobEditor+Testing.swift b/apps/macos/Sources/Moltbot/CronJobEditor+Testing.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CronJobEditor+Testing.swift rename to apps/macos/Sources/Moltbot/CronJobEditor+Testing.swift diff --git a/apps/macos/Sources/Clawdbot/CronJobEditor.swift b/apps/macos/Sources/Moltbot/CronJobEditor.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CronJobEditor.swift rename to apps/macos/Sources/Moltbot/CronJobEditor.swift diff --git a/apps/macos/Sources/Clawdbot/CronJobsStore.swift b/apps/macos/Sources/Moltbot/CronJobsStore.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/CronJobsStore.swift rename to apps/macos/Sources/Moltbot/CronJobsStore.swift index 36a8b95a3..81503921b 100644 --- a/apps/macos/Sources/Clawdbot/CronJobsStore.swift +++ b/apps/macos/Sources/Moltbot/CronJobsStore.swift @@ -22,7 +22,7 @@ final class CronJobsStore { var lastError: String? var statusMessage: String? - private let logger = Logger(subsystem: "com.clawdbot", category: "cron.ui") + private let logger = Logger(subsystem: "bot.molt", category: "cron.ui") private var refreshTask: Task? private var runsTask: Task? private var eventTask: Task? diff --git a/apps/macos/Sources/Clawdbot/CronModels.swift b/apps/macos/Sources/Moltbot/CronModels.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CronModels.swift rename to apps/macos/Sources/Moltbot/CronModels.swift diff --git a/apps/macos/Sources/Clawdbot/CronSettings+Actions.swift b/apps/macos/Sources/Moltbot/CronSettings+Actions.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CronSettings+Actions.swift rename to apps/macos/Sources/Moltbot/CronSettings+Actions.swift diff --git a/apps/macos/Sources/Clawdbot/CronSettings+Helpers.swift b/apps/macos/Sources/Moltbot/CronSettings+Helpers.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CronSettings+Helpers.swift rename to apps/macos/Sources/Moltbot/CronSettings+Helpers.swift diff --git a/apps/macos/Sources/Clawdbot/CronSettings+Layout.swift b/apps/macos/Sources/Moltbot/CronSettings+Layout.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CronSettings+Layout.swift rename to apps/macos/Sources/Moltbot/CronSettings+Layout.swift diff --git a/apps/macos/Sources/Clawdbot/CronSettings+Rows.swift b/apps/macos/Sources/Moltbot/CronSettings+Rows.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CronSettings+Rows.swift rename to apps/macos/Sources/Moltbot/CronSettings+Rows.swift diff --git a/apps/macos/Sources/Clawdbot/CronSettings+Testing.swift b/apps/macos/Sources/Moltbot/CronSettings+Testing.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CronSettings+Testing.swift rename to apps/macos/Sources/Moltbot/CronSettings+Testing.swift diff --git a/apps/macos/Sources/Clawdbot/CronSettings.swift b/apps/macos/Sources/Moltbot/CronSettings.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/CronSettings.swift rename to apps/macos/Sources/Moltbot/CronSettings.swift diff --git a/apps/macos/Sources/Clawdbot/DebugActions.swift b/apps/macos/Sources/Moltbot/DebugActions.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/DebugActions.swift rename to apps/macos/Sources/Moltbot/DebugActions.swift diff --git a/apps/macos/Sources/Clawdbot/DebugSettings.swift b/apps/macos/Sources/Moltbot/DebugSettings.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/DebugSettings.swift rename to apps/macos/Sources/Moltbot/DebugSettings.swift diff --git a/apps/macos/Sources/Clawdbot/DeepLinks.swift b/apps/macos/Sources/Moltbot/DeepLinks.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/DeepLinks.swift rename to apps/macos/Sources/Moltbot/DeepLinks.swift index 4308cf47f..1d8b42d96 100644 --- a/apps/macos/Sources/Clawdbot/DeepLinks.swift +++ b/apps/macos/Sources/Moltbot/DeepLinks.swift @@ -4,7 +4,7 @@ import Foundation import OSLog import Security -private let deepLinkLogger = Logger(subsystem: "com.clawdbot", category: "DeepLink") +private let deepLinkLogger = Logger(subsystem: "bot.molt", category: "DeepLink") @MainActor final class DeepLinkHandler { diff --git a/apps/macos/Sources/Clawdbot/DeviceModelCatalog.swift b/apps/macos/Sources/Moltbot/DeviceModelCatalog.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/DeviceModelCatalog.swift rename to apps/macos/Sources/Moltbot/DeviceModelCatalog.swift diff --git a/apps/macos/Sources/Clawdbot/DevicePairingApprovalPrompter.swift b/apps/macos/Sources/Moltbot/DevicePairingApprovalPrompter.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/DevicePairingApprovalPrompter.swift rename to apps/macos/Sources/Moltbot/DevicePairingApprovalPrompter.swift index b282a394b..39ec6d8ac 100644 --- a/apps/macos/Sources/Clawdbot/DevicePairingApprovalPrompter.swift +++ b/apps/macos/Sources/Moltbot/DevicePairingApprovalPrompter.swift @@ -10,7 +10,7 @@ import OSLog final class DevicePairingApprovalPrompter { static let shared = DevicePairingApprovalPrompter() - private let logger = Logger(subsystem: "com.clawdbot", category: "device-pairing") + private let logger = Logger(subsystem: "bot.molt", category: "device-pairing") private var task: Task? private var isStopping = false private var isPresenting = false diff --git a/apps/macos/Sources/Clawdbot/DiagnosticsFileLog.swift b/apps/macos/Sources/Moltbot/DiagnosticsFileLog.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/DiagnosticsFileLog.swift rename to apps/macos/Sources/Moltbot/DiagnosticsFileLog.swift diff --git a/apps/macos/Sources/Clawdbot/DockIconManager.swift b/apps/macos/Sources/Moltbot/DockIconManager.swift similarity index 97% rename from apps/macos/Sources/Clawdbot/DockIconManager.swift rename to apps/macos/Sources/Moltbot/DockIconManager.swift index 59eacee29..b00cfe953 100644 --- a/apps/macos/Sources/Clawdbot/DockIconManager.swift +++ b/apps/macos/Sources/Moltbot/DockIconManager.swift @@ -6,7 +6,7 @@ final class DockIconManager: NSObject, @unchecked Sendable { static let shared = DockIconManager() private var windowsObservation: NSKeyValueObservation? - private let logger = Logger(subsystem: "com.clawdbot", category: "DockIconManager") + private let logger = Logger(subsystem: "bot.molt", category: "DockIconManager") override private init() { super.init() diff --git a/apps/macos/Sources/Clawdbot/ExecApprovals.swift b/apps/macos/Sources/Moltbot/ExecApprovals.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/ExecApprovals.swift rename to apps/macos/Sources/Moltbot/ExecApprovals.swift index c79c96e84..6fe92626c 100644 --- a/apps/macos/Sources/Clawdbot/ExecApprovals.swift +++ b/apps/macos/Sources/Moltbot/ExecApprovals.swift @@ -189,7 +189,7 @@ struct ExecApprovalsResolvedDefaults { } enum ExecApprovalsStore { - private static let logger = Logger(subsystem: "com.clawdbot", category: "exec-approvals") + private static let logger = Logger(subsystem: "bot.molt", category: "exec-approvals") private static let defaultAgentId = "main" private static let defaultSecurity: ExecSecurity = .deny private static let defaultAsk: ExecAsk = .onMiss diff --git a/apps/macos/Sources/Clawdbot/ExecApprovalsGatewayPrompter.swift b/apps/macos/Sources/Moltbot/ExecApprovalsGatewayPrompter.swift similarity index 97% rename from apps/macos/Sources/Clawdbot/ExecApprovalsGatewayPrompter.swift rename to apps/macos/Sources/Moltbot/ExecApprovalsGatewayPrompter.swift index 29d1be50b..02b344b58 100644 --- a/apps/macos/Sources/Clawdbot/ExecApprovalsGatewayPrompter.swift +++ b/apps/macos/Sources/Moltbot/ExecApprovalsGatewayPrompter.swift @@ -8,7 +8,7 @@ import OSLog final class ExecApprovalsGatewayPrompter { static let shared = ExecApprovalsGatewayPrompter() - private let logger = Logger(subsystem: "com.clawdbot", category: "exec-approvals.gateway") + private let logger = Logger(subsystem: "bot.molt", category: "exec-approvals.gateway") private var task: Task? struct GatewayApprovalRequest: Codable, Sendable { diff --git a/apps/macos/Sources/Clawdbot/ExecApprovalsSocket.swift b/apps/macos/Sources/Moltbot/ExecApprovalsSocket.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/ExecApprovalsSocket.swift rename to apps/macos/Sources/Moltbot/ExecApprovalsSocket.swift index b5591dbd6..dea2bd5df 100644 --- a/apps/macos/Sources/Clawdbot/ExecApprovalsSocket.swift +++ b/apps/macos/Sources/Moltbot/ExecApprovalsSocket.swift @@ -589,7 +589,7 @@ private enum ExecHostExecutor { } private final class ExecApprovalsSocketServer: @unchecked Sendable { - private let logger = Logger(subsystem: "com.clawdbot", category: "exec-approvals.socket") + private let logger = Logger(subsystem: "bot.molt", category: "exec-approvals.socket") private let socketPath: String private let token: String private let onPrompt: @Sendable (ExecApprovalPromptRequest) async -> ExecApprovalDecision diff --git a/apps/macos/Sources/Clawdbot/FileHandle+SafeRead.swift b/apps/macos/Sources/Moltbot/FileHandle+SafeRead.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/FileHandle+SafeRead.swift rename to apps/macos/Sources/Moltbot/FileHandle+SafeRead.swift diff --git a/apps/macos/Sources/Clawdbot/GatewayAutostartPolicy.swift b/apps/macos/Sources/Moltbot/GatewayAutostartPolicy.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/GatewayAutostartPolicy.swift rename to apps/macos/Sources/Moltbot/GatewayAutostartPolicy.swift diff --git a/apps/macos/Sources/Clawdbot/GatewayConnection.swift b/apps/macos/Sources/Moltbot/GatewayConnection.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/GatewayConnection.swift rename to apps/macos/Sources/Moltbot/GatewayConnection.swift index 5b655d3ac..d733c9c86 100644 --- a/apps/macos/Sources/Clawdbot/GatewayConnection.swift +++ b/apps/macos/Sources/Moltbot/GatewayConnection.swift @@ -4,7 +4,7 @@ import MoltbotProtocol import Foundation import OSLog -private let gatewayConnectionLogger = Logger(subsystem: "com.clawdbot", category: "gateway.connection") +private let gatewayConnectionLogger = Logger(subsystem: "bot.molt", category: "gateway.connection") enum GatewayAgentChannel: String, Codable, CaseIterable, Sendable { case last diff --git a/apps/macos/Sources/Clawdbot/GatewayConnectivityCoordinator.swift b/apps/macos/Sources/Moltbot/GatewayConnectivityCoordinator.swift similarity index 95% rename from apps/macos/Sources/Clawdbot/GatewayConnectivityCoordinator.swift rename to apps/macos/Sources/Moltbot/GatewayConnectivityCoordinator.swift index ac65ec0ac..8a5f15aa0 100644 --- a/apps/macos/Sources/Clawdbot/GatewayConnectivityCoordinator.swift +++ b/apps/macos/Sources/Moltbot/GatewayConnectivityCoordinator.swift @@ -7,7 +7,7 @@ import OSLog final class GatewayConnectivityCoordinator { static let shared = GatewayConnectivityCoordinator() - private let logger = Logger(subsystem: "com.clawdbot", category: "gateway.connectivity") + private let logger = Logger(subsystem: "bot.molt", category: "gateway.connectivity") private var endpointTask: Task? private var lastResolvedURL: URL? diff --git a/apps/macos/Sources/Clawdbot/GatewayDiscoveryHelpers.swift b/apps/macos/Sources/Moltbot/GatewayDiscoveryHelpers.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/GatewayDiscoveryHelpers.swift rename to apps/macos/Sources/Moltbot/GatewayDiscoveryHelpers.swift diff --git a/apps/macos/Sources/Clawdbot/GatewayDiscoveryMenu.swift b/apps/macos/Sources/Moltbot/GatewayDiscoveryMenu.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/GatewayDiscoveryMenu.swift rename to apps/macos/Sources/Moltbot/GatewayDiscoveryMenu.swift diff --git a/apps/macos/Sources/Clawdbot/GatewayDiscoveryPreferences.swift b/apps/macos/Sources/Moltbot/GatewayDiscoveryPreferences.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/GatewayDiscoveryPreferences.swift rename to apps/macos/Sources/Moltbot/GatewayDiscoveryPreferences.swift diff --git a/apps/macos/Sources/Clawdbot/GatewayEndpointStore.swift b/apps/macos/Sources/Moltbot/GatewayEndpointStore.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/GatewayEndpointStore.swift rename to apps/macos/Sources/Moltbot/GatewayEndpointStore.swift index a5c3a756e..08c4249b0 100644 --- a/apps/macos/Sources/Clawdbot/GatewayEndpointStore.swift +++ b/apps/macos/Sources/Moltbot/GatewayEndpointStore.swift @@ -23,7 +23,7 @@ actor GatewayEndpointStore { "custom", ] private static let remoteConnectingDetail = "Connecting to remote gateway…" - private static let staticLogger = Logger(subsystem: "com.clawdbot", category: "gateway-endpoint") + private static let staticLogger = Logger(subsystem: "bot.molt", category: "gateway-endpoint") private enum EnvOverrideWarningKind: Sendable { case token case password @@ -230,7 +230,7 @@ actor GatewayEndpointStore { } private let deps: Deps - private let logger = Logger(subsystem: "com.clawdbot", category: "gateway-endpoint") + private let logger = Logger(subsystem: "bot.molt", category: "gateway-endpoint") private var state: GatewayEndpointState private var subscribers: [UUID: AsyncStream.Continuation] = [:] diff --git a/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift b/apps/macos/Sources/Moltbot/GatewayEnvironment.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/GatewayEnvironment.swift rename to apps/macos/Sources/Moltbot/GatewayEnvironment.swift index ff92f308c..0dbcf9780 100644 --- a/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift +++ b/apps/macos/Sources/Moltbot/GatewayEnvironment.swift @@ -68,7 +68,7 @@ struct GatewayCommandResolution { } enum GatewayEnvironment { - private static let logger = Logger(subsystem: "com.clawdbot", category: "gateway.env") + private static let logger = Logger(subsystem: "bot.molt", category: "gateway.env") private static let supportedBindModes: Set = ["loopback", "tailnet", "lan", "auto"] static func gatewayPort() -> Int { @@ -123,7 +123,7 @@ enum GatewayEnvironment { requiredGateway: expectedString, message: RuntimeLocator.describeFailure(err)) case let .success(runtime): - let gatewayBin = CommandResolver.clawdbotExecutable() + let gatewayBin = CommandResolver.moltbotExecutable() if gatewayBin == nil, projectEntrypoint == nil { return GatewayEnvironmentStatus( @@ -181,7 +181,7 @@ enum GatewayEnvironment { let projectRoot = CommandResolver.projectRoot() let projectEntrypoint = CommandResolver.gatewayEntrypoint(in: projectRoot) let status = self.check() - let gatewayBin = CommandResolver.clawdbotExecutable() + let gatewayBin = CommandResolver.moltbotExecutable() let runtime = RuntimeLocator.resolve(searchPaths: CommandResolver.preferredPaths()) guard case .ok = status.kind else { diff --git a/apps/macos/Sources/Clawdbot/GatewayLaunchAgentManager.swift b/apps/macos/Sources/Moltbot/GatewayLaunchAgentManager.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/GatewayLaunchAgentManager.swift rename to apps/macos/Sources/Moltbot/GatewayLaunchAgentManager.swift index f0896e691..cc78b7e10 100644 --- a/apps/macos/Sources/Clawdbot/GatewayLaunchAgentManager.swift +++ b/apps/macos/Sources/Moltbot/GatewayLaunchAgentManager.swift @@ -1,7 +1,7 @@ import Foundation enum GatewayLaunchAgentManager { - private static let logger = Logger(subsystem: "com.clawdbot", category: "gateway.launchd") + private static let logger = Logger(subsystem: "bot.molt", category: "gateway.launchd") private static let disableLaunchAgentMarker = ".clawdbot/disable-launchagent" private static var disableLaunchAgentMarkerURL: URL { @@ -143,7 +143,7 @@ extension GatewayLaunchAgentManager { timeout: Double, quiet: Bool) async -> CommandResult { - let command = CommandResolver.clawdbotCommand( + let command = CommandResolver.moltbotCommand( subcommand: "gateway", extraArgs: self.withJsonFlag(args), // Launchd management must always run locally, even if remote mode is configured. diff --git a/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift b/apps/macos/Sources/Moltbot/GatewayProcessManager.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/GatewayProcessManager.swift rename to apps/macos/Sources/Moltbot/GatewayProcessManager.swift index 60964fa39..86dfc851f 100644 --- a/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift +++ b/apps/macos/Sources/Moltbot/GatewayProcessManager.swift @@ -45,7 +45,7 @@ final class GatewayProcessManager { #if DEBUG private var testingConnection: GatewayConnection? #endif - private let logger = Logger(subsystem: "com.clawdbot", category: "gateway.process") + private let logger = Logger(subsystem: "bot.molt", category: "gateway.process") private let logLimit = 20000 // characters to keep in-memory private let environmentRefreshMinInterval: TimeInterval = 30 diff --git a/apps/macos/Sources/Clawdbot/GatewayRemoteConfig.swift b/apps/macos/Sources/Moltbot/GatewayRemoteConfig.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/GatewayRemoteConfig.swift rename to apps/macos/Sources/Moltbot/GatewayRemoteConfig.swift diff --git a/apps/macos/Sources/Clawdbot/GeneralSettings.swift b/apps/macos/Sources/Moltbot/GeneralSettings.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/GeneralSettings.swift rename to apps/macos/Sources/Moltbot/GeneralSettings.swift diff --git a/apps/macos/Sources/Clawdbot/HealthStore.swift b/apps/macos/Sources/Moltbot/HealthStore.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/HealthStore.swift rename to apps/macos/Sources/Moltbot/HealthStore.swift index 0410dcb4c..6e4c2437b 100644 --- a/apps/macos/Sources/Clawdbot/HealthStore.swift +++ b/apps/macos/Sources/Moltbot/HealthStore.swift @@ -72,7 +72,7 @@ enum HealthState: Equatable { final class HealthStore { static let shared = HealthStore() - private static let logger = Logger(subsystem: "com.clawdbot", category: "health") + private static let logger = Logger(subsystem: "bot.molt", category: "health") private(set) var snapshot: HealthSnapshot? private(set) var lastSuccess: Date? diff --git a/apps/macos/Sources/Clawdbot/HeartbeatStore.swift b/apps/macos/Sources/Moltbot/HeartbeatStore.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/HeartbeatStore.swift rename to apps/macos/Sources/Moltbot/HeartbeatStore.swift diff --git a/apps/macos/Sources/Clawdbot/HoverHUD.swift b/apps/macos/Sources/Moltbot/HoverHUD.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/HoverHUD.swift rename to apps/macos/Sources/Moltbot/HoverHUD.swift diff --git a/apps/macos/Sources/Clawdbot/IconState.swift b/apps/macos/Sources/Moltbot/IconState.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/IconState.swift rename to apps/macos/Sources/Moltbot/IconState.swift diff --git a/apps/macos/Sources/Clawdbot/InstancesSettings.swift b/apps/macos/Sources/Moltbot/InstancesSettings.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/InstancesSettings.swift rename to apps/macos/Sources/Moltbot/InstancesSettings.swift diff --git a/apps/macos/Sources/Clawdbot/InstancesStore.swift b/apps/macos/Sources/Moltbot/InstancesStore.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/InstancesStore.swift rename to apps/macos/Sources/Moltbot/InstancesStore.swift index 41685b463..65b20df29 100644 --- a/apps/macos/Sources/Clawdbot/InstancesStore.swift +++ b/apps/macos/Sources/Moltbot/InstancesStore.swift @@ -41,7 +41,7 @@ final class InstancesStore { var statusMessage: String? var isLoading = false - private let logger = Logger(subsystem: "com.clawdbot", category: "instances") + private let logger = Logger(subsystem: "bot.molt", category: "instances") private var task: Task? private let interval: TimeInterval = 30 private var eventTask: Task? diff --git a/apps/macos/Sources/Clawdbot/LaunchAgentManager.swift b/apps/macos/Sources/Moltbot/LaunchAgentManager.swift similarity index 79% rename from apps/macos/Sources/Clawdbot/LaunchAgentManager.swift rename to apps/macos/Sources/Moltbot/LaunchAgentManager.swift index 6b0225a65..fdc1785ba 100644 --- a/apps/macos/Sources/Clawdbot/LaunchAgentManager.swift +++ b/apps/macos/Sources/Moltbot/LaunchAgentManager.swift @@ -1,15 +1,20 @@ import Foundation enum LaunchAgentManager { - private static let legacyLaunchdLabel = "com.steipete.clawdbot" + private static let legacyLaunchdLabels = [ + "com.steipete.clawdbot", + "com.clawdbot.mac", + ] private static var plistURL: URL { FileManager().homeDirectoryForCurrentUser - .appendingPathComponent("Library/LaunchAgents/com.clawdbot.mac.plist") + .appendingPathComponent("Library/LaunchAgents/bot.molt.mac.plist") } - private static var legacyPlistURL: URL { - FileManager().homeDirectoryForCurrentUser - .appendingPathComponent("Library/LaunchAgents/\(legacyLaunchdLabel).plist") + private static var legacyPlistURLs: [URL] { + self.legacyLaunchdLabels.map { label in + FileManager().homeDirectoryForCurrentUser + .appendingPathComponent("Library/LaunchAgents/\(label).plist") + } } static func status() async -> Bool { @@ -20,8 +25,12 @@ enum LaunchAgentManager { static func set(enabled: Bool, bundlePath: String) async { if enabled { - _ = await self.runLaunchctl(["bootout", "gui/\(getuid())/\(self.legacyLaunchdLabel)"]) - try? FileManager().removeItem(at: self.legacyPlistURL) + for legacyLabel in self.legacyLaunchdLabels { + _ = await self.runLaunchctl(["bootout", "gui/\(getuid())/\(legacyLabel)"]) + } + for legacyURL in self.legacyPlistURLs { + try? FileManager().removeItem(at: legacyURL) + } self.writePlist(bundlePath: bundlePath) _ = await self.runLaunchctl(["bootout", "gui/\(getuid())/\(launchdLabel)"]) _ = await self.runLaunchctl(["bootstrap", "gui/\(getuid())", self.plistURL.path]) @@ -40,7 +49,7 @@ enum LaunchAgentManager { Label - com.clawdbot.mac + bot.molt.mac ProgramArguments \(bundlePath)/Contents/MacOS/Moltbot diff --git a/apps/macos/Sources/Clawdbot/Launchctl.swift b/apps/macos/Sources/Moltbot/Launchctl.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/Launchctl.swift rename to apps/macos/Sources/Moltbot/Launchctl.swift diff --git a/apps/macos/Sources/Clawdbot/LaunchdManager.swift b/apps/macos/Sources/Moltbot/LaunchdManager.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/LaunchdManager.swift rename to apps/macos/Sources/Moltbot/LaunchdManager.swift diff --git a/apps/macos/Sources/Clawdbot/LogLocator.swift b/apps/macos/Sources/Moltbot/LogLocator.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/LogLocator.swift rename to apps/macos/Sources/Moltbot/LogLocator.swift diff --git a/apps/macos/Sources/Clawdbot/Logging/ClawdbotLogging.swift b/apps/macos/Sources/Moltbot/Logging/ClawdbotLogging.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/Logging/ClawdbotLogging.swift rename to apps/macos/Sources/Moltbot/Logging/ClawdbotLogging.swift index c966aaa05..2ac8e8003 100644 --- a/apps/macos/Sources/Clawdbot/Logging/ClawdbotLogging.swift +++ b/apps/macos/Sources/Moltbot/Logging/ClawdbotLogging.swift @@ -74,7 +74,7 @@ enum MoltbotLogging { static func parseLabel(_ label: String) -> (String, String) { guard let range = label.range(of: labelSeparator) else { - return ("com.clawdbot", label) + return ("bot.molt", label) } let subsystem = String(label[.. Void)? private var running = false diff --git a/apps/macos/Sources/Clawdbot/ModelCatalogLoader.swift b/apps/macos/Sources/Moltbot/ModelCatalogLoader.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/ModelCatalogLoader.swift rename to apps/macos/Sources/Moltbot/ModelCatalogLoader.swift index 4fc652b11..1ef60104e 100644 --- a/apps/macos/Sources/Clawdbot/ModelCatalogLoader.swift +++ b/apps/macos/Sources/Moltbot/ModelCatalogLoader.swift @@ -3,7 +3,7 @@ import JavaScriptCore enum ModelCatalogLoader { static var defaultPath: String { self.resolveDefaultPath() } - private static let logger = Logger(subsystem: "com.clawdbot", category: "models") + private static let logger = Logger(subsystem: "bot.molt", category: "models") private nonisolated static let appSupportDir: URL = { let base = FileManager().urls(for: .applicationSupportDirectory, in: .userDomainMask).first! return base.appendingPathComponent("Moltbot", isDirectory: true) diff --git a/apps/macos/Sources/Clawdbot/NSAttributedString+VoiceWake.swift b/apps/macos/Sources/Moltbot/NSAttributedString+VoiceWake.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/NSAttributedString+VoiceWake.swift rename to apps/macos/Sources/Moltbot/NSAttributedString+VoiceWake.swift diff --git a/apps/macos/Sources/Clawdbot/NodeMode/MacNodeLocationService.swift b/apps/macos/Sources/Moltbot/NodeMode/MacNodeLocationService.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/NodeMode/MacNodeLocationService.swift rename to apps/macos/Sources/Moltbot/NodeMode/MacNodeLocationService.swift diff --git a/apps/macos/Sources/Clawdbot/NodeMode/MacNodeModeCoordinator.swift b/apps/macos/Sources/Moltbot/NodeMode/MacNodeModeCoordinator.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/NodeMode/MacNodeModeCoordinator.swift rename to apps/macos/Sources/Moltbot/NodeMode/MacNodeModeCoordinator.swift index 818a329ad..3d619f53b 100644 --- a/apps/macos/Sources/Clawdbot/NodeMode/MacNodeModeCoordinator.swift +++ b/apps/macos/Sources/Moltbot/NodeMode/MacNodeModeCoordinator.swift @@ -6,7 +6,7 @@ import OSLog final class MacNodeModeCoordinator { static let shared = MacNodeModeCoordinator() - private let logger = Logger(subsystem: "com.clawdbot", category: "mac-node") + private let logger = Logger(subsystem: "bot.molt", category: "mac-node") private var task: Task? private let runtime = MacNodeRuntime() private let session = GatewayNodeSession() diff --git a/apps/macos/Sources/Clawdbot/NodeMode/MacNodeRuntime.swift b/apps/macos/Sources/Moltbot/NodeMode/MacNodeRuntime.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/NodeMode/MacNodeRuntime.swift rename to apps/macos/Sources/Moltbot/NodeMode/MacNodeRuntime.swift diff --git a/apps/macos/Sources/Clawdbot/NodeMode/MacNodeRuntimeMainActorServices.swift b/apps/macos/Sources/Moltbot/NodeMode/MacNodeRuntimeMainActorServices.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/NodeMode/MacNodeRuntimeMainActorServices.swift rename to apps/macos/Sources/Moltbot/NodeMode/MacNodeRuntimeMainActorServices.swift diff --git a/apps/macos/Sources/Clawdbot/NodeMode/MacNodeScreenCommands.swift b/apps/macos/Sources/Moltbot/NodeMode/MacNodeScreenCommands.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/NodeMode/MacNodeScreenCommands.swift rename to apps/macos/Sources/Moltbot/NodeMode/MacNodeScreenCommands.swift diff --git a/apps/macos/Sources/Clawdbot/NodePairingApprovalPrompter.swift b/apps/macos/Sources/Moltbot/NodePairingApprovalPrompter.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/NodePairingApprovalPrompter.swift rename to apps/macos/Sources/Moltbot/NodePairingApprovalPrompter.swift index ef0735ca2..3f2aff19d 100644 --- a/apps/macos/Sources/Clawdbot/NodePairingApprovalPrompter.swift +++ b/apps/macos/Sources/Moltbot/NodePairingApprovalPrompter.swift @@ -22,7 +22,7 @@ enum NodePairingReconcilePolicy { final class NodePairingApprovalPrompter { static let shared = NodePairingApprovalPrompter() - private let logger = Logger(subsystem: "com.clawdbot", category: "node-pairing") + private let logger = Logger(subsystem: "bot.molt", category: "node-pairing") private var task: Task? private var reconcileTask: Task? private var reconcileOnceTask: Task? diff --git a/apps/macos/Sources/Clawdbot/NodeServiceManager.swift b/apps/macos/Sources/Moltbot/NodeServiceManager.swift similarity index 97% rename from apps/macos/Sources/Clawdbot/NodeServiceManager.swift rename to apps/macos/Sources/Moltbot/NodeServiceManager.swift index 2dd62d1e6..bcf17d972 100644 --- a/apps/macos/Sources/Clawdbot/NodeServiceManager.swift +++ b/apps/macos/Sources/Moltbot/NodeServiceManager.swift @@ -2,7 +2,7 @@ import Foundation import OSLog enum NodeServiceManager { - private static let logger = Logger(subsystem: "com.clawdbot", category: "node.service") + private static let logger = Logger(subsystem: "bot.molt", category: "node.service") static func start() async -> String? { let result = await self.runServiceCommandResult( @@ -52,7 +52,7 @@ extension NodeServiceManager { timeout: Double, quiet: Bool) async -> CommandResult { - let command = CommandResolver.clawdbotCommand( + let command = CommandResolver.moltbotCommand( subcommand: "service", extraArgs: self.withJsonFlag(args), // Service management must always run locally, even if remote mode is configured. diff --git a/apps/macos/Sources/Clawdbot/NodesMenu.swift b/apps/macos/Sources/Moltbot/NodesMenu.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/NodesMenu.swift rename to apps/macos/Sources/Moltbot/NodesMenu.swift diff --git a/apps/macos/Sources/Clawdbot/NodesStore.swift b/apps/macos/Sources/Moltbot/NodesStore.swift similarity index 97% rename from apps/macos/Sources/Clawdbot/NodesStore.swift rename to apps/macos/Sources/Moltbot/NodesStore.swift index 51d43336d..ae21a902c 100644 --- a/apps/macos/Sources/Clawdbot/NodesStore.swift +++ b/apps/macos/Sources/Moltbot/NodesStore.swift @@ -38,7 +38,7 @@ final class NodesStore { var statusMessage: String? var isLoading = false - private let logger = Logger(subsystem: "com.clawdbot", category: "nodes") + private let logger = Logger(subsystem: "bot.molt", category: "nodes") private var task: Task? private let interval: TimeInterval = 30 private var startCount = 0 diff --git a/apps/macos/Sources/Clawdbot/NotificationManager.swift b/apps/macos/Sources/Moltbot/NotificationManager.swift similarity index 96% rename from apps/macos/Sources/Clawdbot/NotificationManager.swift rename to apps/macos/Sources/Moltbot/NotificationManager.swift index 20d7a35b3..53659e15d 100644 --- a/apps/macos/Sources/Clawdbot/NotificationManager.swift +++ b/apps/macos/Sources/Moltbot/NotificationManager.swift @@ -5,7 +5,7 @@ import UserNotifications @MainActor struct NotificationManager { - private let logger = Logger(subsystem: "com.clawdbot", category: "notifications") + private let logger = Logger(subsystem: "bot.molt", category: "notifications") private static let hasTimeSensitiveEntitlement: Bool = { guard let task = SecTaskCreateFromSelf(nil) else { return false } diff --git a/apps/macos/Sources/Clawdbot/NotifyOverlay.swift b/apps/macos/Sources/Moltbot/NotifyOverlay.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/NotifyOverlay.swift rename to apps/macos/Sources/Moltbot/NotifyOverlay.swift diff --git a/apps/macos/Sources/Clawdbot/Onboarding.swift b/apps/macos/Sources/Moltbot/Onboarding.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/Onboarding.swift rename to apps/macos/Sources/Moltbot/Onboarding.swift diff --git a/apps/macos/Sources/Clawdbot/OnboardingView+Actions.swift b/apps/macos/Sources/Moltbot/OnboardingView+Actions.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/OnboardingView+Actions.swift rename to apps/macos/Sources/Moltbot/OnboardingView+Actions.swift index 80dadcf94..79e7d4d48 100644 --- a/apps/macos/Sources/Clawdbot/OnboardingView+Actions.swift +++ b/apps/macos/Sources/Moltbot/OnboardingView+Actions.swift @@ -47,7 +47,7 @@ extension OnboardingView { SettingsTabRouter.request(tab) self.openSettings() DispatchQueue.main.async { - NotificationCenter.default.post(name: .clawdbotSelectSettingsTab, object: tab) + NotificationCenter.default.post(name: .moltbotSelectSettingsTab, object: tab) } } diff --git a/apps/macos/Sources/Clawdbot/OnboardingView+Chat.swift b/apps/macos/Sources/Moltbot/OnboardingView+Chat.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/OnboardingView+Chat.swift rename to apps/macos/Sources/Moltbot/OnboardingView+Chat.swift diff --git a/apps/macos/Sources/Clawdbot/OnboardingView+Layout.swift b/apps/macos/Sources/Moltbot/OnboardingView+Layout.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/OnboardingView+Layout.swift rename to apps/macos/Sources/Moltbot/OnboardingView+Layout.swift diff --git a/apps/macos/Sources/Clawdbot/OnboardingView+Monitoring.swift b/apps/macos/Sources/Moltbot/OnboardingView+Monitoring.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/OnboardingView+Monitoring.swift rename to apps/macos/Sources/Moltbot/OnboardingView+Monitoring.swift diff --git a/apps/macos/Sources/Clawdbot/OnboardingView+Pages.swift b/apps/macos/Sources/Moltbot/OnboardingView+Pages.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/OnboardingView+Pages.swift rename to apps/macos/Sources/Moltbot/OnboardingView+Pages.swift diff --git a/apps/macos/Sources/Clawdbot/OnboardingView+Testing.swift b/apps/macos/Sources/Moltbot/OnboardingView+Testing.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/OnboardingView+Testing.swift rename to apps/macos/Sources/Moltbot/OnboardingView+Testing.swift diff --git a/apps/macos/Sources/Clawdbot/OnboardingView+Wizard.swift b/apps/macos/Sources/Moltbot/OnboardingView+Wizard.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/OnboardingView+Wizard.swift rename to apps/macos/Sources/Moltbot/OnboardingView+Wizard.swift diff --git a/apps/macos/Sources/Clawdbot/OnboardingView+Workspace.swift b/apps/macos/Sources/Moltbot/OnboardingView+Workspace.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/OnboardingView+Workspace.swift rename to apps/macos/Sources/Moltbot/OnboardingView+Workspace.swift diff --git a/apps/macos/Sources/Clawdbot/OnboardingWidgets.swift b/apps/macos/Sources/Moltbot/OnboardingWidgets.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/OnboardingWidgets.swift rename to apps/macos/Sources/Moltbot/OnboardingWidgets.swift diff --git a/apps/macos/Sources/Clawdbot/OnboardingWizard.swift b/apps/macos/Sources/Moltbot/OnboardingWizard.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/OnboardingWizard.swift rename to apps/macos/Sources/Moltbot/OnboardingWizard.swift index 4c0ce8de4..f06636071 100644 --- a/apps/macos/Sources/Clawdbot/OnboardingWizard.swift +++ b/apps/macos/Sources/Moltbot/OnboardingWizard.swift @@ -5,7 +5,7 @@ import Observation import OSLog import SwiftUI -private let onboardingWizardLogger = Logger(subsystem: "com.clawdbot", category: "onboarding.wizard") +private let onboardingWizardLogger = Logger(subsystem: "bot.molt", category: "onboarding.wizard") // MARK: - Swift 6 AnyCodable Bridging Helpers diff --git a/apps/macos/Sources/Clawdbot/PeekabooBridgeHostCoordinator.swift b/apps/macos/Sources/Moltbot/PeekabooBridgeHostCoordinator.swift similarity index 96% rename from apps/macos/Sources/Clawdbot/PeekabooBridgeHostCoordinator.swift rename to apps/macos/Sources/Moltbot/PeekabooBridgeHostCoordinator.swift index 76777b57f..16f5f554e 100644 --- a/apps/macos/Sources/Clawdbot/PeekabooBridgeHostCoordinator.swift +++ b/apps/macos/Sources/Moltbot/PeekabooBridgeHostCoordinator.swift @@ -9,7 +9,7 @@ import Security final class PeekabooBridgeHostCoordinator { static let shared = PeekabooBridgeHostCoordinator() - private let logger = Logger(subsystem: "com.clawdbot", category: "PeekabooBridge") + private let logger = Logger(subsystem: "bot.molt", category: "PeekabooBridge") private var host: PeekabooBridgeHost? private var services: MoltbotPeekabooBridgeServices? @@ -102,7 +102,7 @@ private final class MoltbotPeekabooBridgeServices: PeekabooBridgeServiceProvidin let snapshots: any SnapshotManagerProtocol init() { - let logging = LoggingService(subsystem: "com.clawdbot.peekaboo") + let logging = LoggingService(subsystem: "bot.molt.peekaboo") let feedbackClient: any AutomationFeedbackClient = NoopAutomationFeedbackClient() let snapshots = InMemorySnapshotManager(options: .init( diff --git a/apps/macos/Sources/Clawdbot/PermissionManager.swift b/apps/macos/Sources/Moltbot/PermissionManager.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/PermissionManager.swift rename to apps/macos/Sources/Moltbot/PermissionManager.swift index e0d7b2404..f001827a0 100644 --- a/apps/macos/Sources/Clawdbot/PermissionManager.swift +++ b/apps/macos/Sources/Moltbot/PermissionManager.swift @@ -373,7 +373,7 @@ final class LocationPermissionRequester: NSObject, CLLocationManagerDelegate { } enum AppleScriptPermission { - private static let logger = Logger(subsystem: "com.clawdbot", category: "AppleScriptPermission") + private static let logger = Logger(subsystem: "bot.molt", category: "AppleScriptPermission") /// Sends a benign AppleScript to Terminal to verify Automation permission. @MainActor diff --git a/apps/macos/Sources/Clawdbot/PermissionsSettings.swift b/apps/macos/Sources/Moltbot/PermissionsSettings.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/PermissionsSettings.swift rename to apps/macos/Sources/Moltbot/PermissionsSettings.swift diff --git a/apps/macos/Sources/Clawdbot/PointingHandCursor.swift b/apps/macos/Sources/Moltbot/PointingHandCursor.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/PointingHandCursor.swift rename to apps/macos/Sources/Moltbot/PointingHandCursor.swift diff --git a/apps/macos/Sources/Clawdbot/PortGuardian.swift b/apps/macos/Sources/Moltbot/PortGuardian.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/PortGuardian.swift rename to apps/macos/Sources/Moltbot/PortGuardian.swift index c28e3eda0..c96b66802 100644 --- a/apps/macos/Sources/Clawdbot/PortGuardian.swift +++ b/apps/macos/Sources/Moltbot/PortGuardian.swift @@ -22,7 +22,7 @@ actor PortGuardian { } private var records: [Record] = [] - private let logger = Logger(subsystem: "com.clawdbot", category: "portguard") + private let logger = Logger(subsystem: "bot.molt", category: "portguard") private nonisolated static let appSupportDir: URL = { let base = FileManager().urls(for: .applicationSupportDirectory, in: .userDomainMask).first! return base.appendingPathComponent("Moltbot", isDirectory: true) diff --git a/apps/macos/Sources/Clawdbot/PresenceReporter.swift b/apps/macos/Sources/Moltbot/PresenceReporter.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/PresenceReporter.swift rename to apps/macos/Sources/Moltbot/PresenceReporter.swift index 8bffaefa0..369e277d6 100644 --- a/apps/macos/Sources/Clawdbot/PresenceReporter.swift +++ b/apps/macos/Sources/Moltbot/PresenceReporter.swift @@ -7,7 +7,7 @@ import OSLog final class PresenceReporter { static let shared = PresenceReporter() - private let logger = Logger(subsystem: "com.clawdbot", category: "presence") + private let logger = Logger(subsystem: "bot.molt", category: "presence") private var task: Task? private let interval: TimeInterval = 180 // a few minutes private let instanceId: String = InstanceIdentity.instanceId diff --git a/apps/macos/Sources/Clawdbot/Process+PipeRead.swift b/apps/macos/Sources/Moltbot/Process+PipeRead.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/Process+PipeRead.swift rename to apps/macos/Sources/Moltbot/Process+PipeRead.swift diff --git a/apps/macos/Sources/Clawdbot/ProcessInfo+Clawdbot.swift b/apps/macos/Sources/Moltbot/ProcessInfo+Clawdbot.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ProcessInfo+Clawdbot.swift rename to apps/macos/Sources/Moltbot/ProcessInfo+Clawdbot.swift diff --git a/apps/macos/Sources/Clawdbot/RemotePortTunnel.swift b/apps/macos/Sources/Moltbot/RemotePortTunnel.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/RemotePortTunnel.swift rename to apps/macos/Sources/Moltbot/RemotePortTunnel.swift index e95f3f50d..8c6db89a3 100644 --- a/apps/macos/Sources/Clawdbot/RemotePortTunnel.swift +++ b/apps/macos/Sources/Moltbot/RemotePortTunnel.swift @@ -9,7 +9,7 @@ import Darwin /// /// Uses `ssh -N -L` to forward the remote gateway ports to localhost. final class RemotePortTunnel { - private static let logger = Logger(subsystem: "com.clawdbot", category: "remote.tunnel") + private static let logger = Logger(subsystem: "bot.molt", category: "remote.tunnel") let process: Process let localPort: UInt16? @@ -186,7 +186,7 @@ final class RemotePortTunnel { } return try await withCheckedThrowingContinuation { cont in - let queue = DispatchQueue(label: "com.clawdbot.remote.tunnel.port", qos: .utility) + let queue = DispatchQueue(label: "bot.molt.remote.tunnel.port", qos: .utility) do { let listener = try NWListener(using: .tcp, on: .any) listener.newConnectionHandler = { connection in connection.cancel() } diff --git a/apps/macos/Sources/Clawdbot/RemoteTunnelManager.swift b/apps/macos/Sources/Moltbot/RemoteTunnelManager.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/RemoteTunnelManager.swift rename to apps/macos/Sources/Moltbot/RemoteTunnelManager.swift index 78a5154a9..f199ff9fe 100644 --- a/apps/macos/Sources/Clawdbot/RemoteTunnelManager.swift +++ b/apps/macos/Sources/Moltbot/RemoteTunnelManager.swift @@ -5,7 +5,7 @@ import OSLog actor RemoteTunnelManager { static let shared = RemoteTunnelManager() - private let logger = Logger(subsystem: "com.clawdbot", category: "remote-tunnel") + private let logger = Logger(subsystem: "bot.molt", category: "remote-tunnel") private var controlTunnel: RemotePortTunnel? private var restartInFlight = false private var lastRestartAt: Date? diff --git a/apps/macos/Sources/Clawdbot/Resources/DeviceModels/LICENSE.apple-device-identifiers.txt b/apps/macos/Sources/Moltbot/Resources/DeviceModels/LICENSE.apple-device-identifiers.txt similarity index 100% rename from apps/macos/Sources/Clawdbot/Resources/DeviceModels/LICENSE.apple-device-identifiers.txt rename to apps/macos/Sources/Moltbot/Resources/DeviceModels/LICENSE.apple-device-identifiers.txt diff --git a/apps/macos/Sources/Clawdbot/Resources/DeviceModels/NOTICE.md b/apps/macos/Sources/Moltbot/Resources/DeviceModels/NOTICE.md similarity index 100% rename from apps/macos/Sources/Clawdbot/Resources/DeviceModels/NOTICE.md rename to apps/macos/Sources/Moltbot/Resources/DeviceModels/NOTICE.md diff --git a/apps/macos/Sources/Clawdbot/Resources/DeviceModels/ios-device-identifiers.json b/apps/macos/Sources/Moltbot/Resources/DeviceModels/ios-device-identifiers.json similarity index 100% rename from apps/macos/Sources/Clawdbot/Resources/DeviceModels/ios-device-identifiers.json rename to apps/macos/Sources/Moltbot/Resources/DeviceModels/ios-device-identifiers.json diff --git a/apps/macos/Sources/Clawdbot/Resources/DeviceModels/mac-device-identifiers.json b/apps/macos/Sources/Moltbot/Resources/DeviceModels/mac-device-identifiers.json similarity index 100% rename from apps/macos/Sources/Clawdbot/Resources/DeviceModels/mac-device-identifiers.json rename to apps/macos/Sources/Moltbot/Resources/DeviceModels/mac-device-identifiers.json diff --git a/apps/macos/Sources/Clawdbot/Resources/Info.plist b/apps/macos/Sources/Moltbot/Resources/Info.plist similarity index 95% rename from apps/macos/Sources/Clawdbot/Resources/Info.plist rename to apps/macos/Sources/Moltbot/Resources/Info.plist index 83a81468b..0c0de8b9e 100644 --- a/apps/macos/Sources/Clawdbot/Resources/Info.plist +++ b/apps/macos/Sources/Moltbot/Resources/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable Moltbot CFBundleIdentifier - com.clawdbot.mac + bot.molt.mac CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2026.1.26 + 2026.1.27-beta.1 CFBundleVersion 202601260 CFBundleIconFile @@ -24,7 +24,7 @@ CFBundleURLName - com.clawdbot.mac.deeplink + bot.molt.mac.deeplink CFBundleURLSchemes moltbot diff --git a/apps/macos/Sources/Clawdbot/Resources/Clawdbot.icns b/apps/macos/Sources/Moltbot/Resources/Moltbot.icns similarity index 100% rename from apps/macos/Sources/Clawdbot/Resources/Clawdbot.icns rename to apps/macos/Sources/Moltbot/Resources/Moltbot.icns diff --git a/apps/macos/Sources/Clawdbot/RuntimeLocator.swift b/apps/macos/Sources/Moltbot/RuntimeLocator.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/RuntimeLocator.swift rename to apps/macos/Sources/Moltbot/RuntimeLocator.swift index 775613457..270e209d3 100644 --- a/apps/macos/Sources/Clawdbot/RuntimeLocator.swift +++ b/apps/macos/Sources/Moltbot/RuntimeLocator.swift @@ -51,7 +51,7 @@ enum RuntimeResolutionError: Error { } enum RuntimeLocator { - private static let logger = Logger(subsystem: "com.clawdbot", category: "runtime") + private static let logger = Logger(subsystem: "bot.molt", category: "runtime") private static let minNode = RuntimeVersion(major: 22, minor: 0, patch: 0) static func resolve( diff --git a/apps/macos/Sources/Clawdbot/ScreenRecordService.swift b/apps/macos/Sources/Moltbot/ScreenRecordService.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/ScreenRecordService.swift rename to apps/macos/Sources/Moltbot/ScreenRecordService.swift index ecbe99692..a46f00780 100644 --- a/apps/macos/Sources/Clawdbot/ScreenRecordService.swift +++ b/apps/macos/Sources/Moltbot/ScreenRecordService.swift @@ -25,7 +25,7 @@ final class ScreenRecordService { } } - private let logger = Logger(subsystem: "com.clawdbot", category: "screenRecord") + private let logger = Logger(subsystem: "bot.molt", category: "screenRecord") func record( screenIndex: Int?, @@ -110,7 +110,7 @@ final class ScreenRecordService { } private final class StreamRecorder: NSObject, SCStreamOutput, SCStreamDelegate, @unchecked Sendable { - let queue = DispatchQueue(label: "com.clawdbot.screenRecord.writer") + let queue = DispatchQueue(label: "bot.molt.screenRecord.writer") private let logger: Logger private let writer: AVAssetWriter diff --git a/apps/macos/Sources/Clawdbot/ScreenshotSize.swift b/apps/macos/Sources/Moltbot/ScreenshotSize.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ScreenshotSize.swift rename to apps/macos/Sources/Moltbot/ScreenshotSize.swift diff --git a/apps/macos/Sources/Clawdbot/SessionActions.swift b/apps/macos/Sources/Moltbot/SessionActions.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/SessionActions.swift rename to apps/macos/Sources/Moltbot/SessionActions.swift diff --git a/apps/macos/Sources/Clawdbot/SessionData.swift b/apps/macos/Sources/Moltbot/SessionData.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/SessionData.swift rename to apps/macos/Sources/Moltbot/SessionData.swift diff --git a/apps/macos/Sources/Clawdbot/SessionMenuLabelView.swift b/apps/macos/Sources/Moltbot/SessionMenuLabelView.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/SessionMenuLabelView.swift rename to apps/macos/Sources/Moltbot/SessionMenuLabelView.swift diff --git a/apps/macos/Sources/Clawdbot/SessionMenuPreviewView.swift b/apps/macos/Sources/Moltbot/SessionMenuPreviewView.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/SessionMenuPreviewView.swift rename to apps/macos/Sources/Moltbot/SessionMenuPreviewView.swift index dd8222a48..a60a9616c 100644 --- a/apps/macos/Sources/Clawdbot/SessionMenuPreviewView.swift +++ b/apps/macos/Sources/Moltbot/SessionMenuPreviewView.swift @@ -221,7 +221,7 @@ struct SessionMenuPreviewView: View { } enum SessionMenuPreviewLoader { - private static let logger = Logger(subsystem: "com.clawdbot", category: "SessionPreview") + private static let logger = Logger(subsystem: "bot.molt", category: "SessionPreview") private static let previewTimeoutSeconds: Double = 4 private static let cacheMaxAgeSeconds: TimeInterval = 30 private static let previewMaxChars = 240 diff --git a/apps/macos/Sources/Clawdbot/SessionsSettings.swift b/apps/macos/Sources/Moltbot/SessionsSettings.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/SessionsSettings.swift rename to apps/macos/Sources/Moltbot/SessionsSettings.swift diff --git a/apps/macos/Sources/Clawdbot/SettingsComponents.swift b/apps/macos/Sources/Moltbot/SettingsComponents.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/SettingsComponents.swift rename to apps/macos/Sources/Moltbot/SettingsComponents.swift diff --git a/apps/macos/Sources/Clawdbot/SettingsRootView.swift b/apps/macos/Sources/Moltbot/SettingsRootView.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/SettingsRootView.swift rename to apps/macos/Sources/Moltbot/SettingsRootView.swift index 004f15827..97520a31b 100644 --- a/apps/macos/Sources/Clawdbot/SettingsRootView.swift +++ b/apps/macos/Sources/Moltbot/SettingsRootView.swift @@ -77,7 +77,7 @@ struct SettingsRootView: View { .padding(.vertical, 22) .frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight, alignment: .topLeading) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - .onReceive(NotificationCenter.default.publisher(for: .clawdbotSelectSettingsTab)) { note in + .onReceive(NotificationCenter.default.publisher(for: .moltbotSelectSettingsTab)) { note in if let tab = note.object as? SettingsTab { withAnimation(.spring(response: 0.32, dampingFraction: 0.85)) { self.selectedTab = tab diff --git a/apps/macos/Sources/Clawdbot/SettingsWindowOpener.swift b/apps/macos/Sources/Moltbot/SettingsWindowOpener.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/SettingsWindowOpener.swift rename to apps/macos/Sources/Moltbot/SettingsWindowOpener.swift diff --git a/apps/macos/Sources/Clawdbot/ShellExecutor.swift b/apps/macos/Sources/Moltbot/ShellExecutor.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ShellExecutor.swift rename to apps/macos/Sources/Moltbot/ShellExecutor.swift diff --git a/apps/macos/Sources/Clawdbot/SkillsModels.swift b/apps/macos/Sources/Moltbot/SkillsModels.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/SkillsModels.swift rename to apps/macos/Sources/Moltbot/SkillsModels.swift diff --git a/apps/macos/Sources/Clawdbot/SkillsSettings.swift b/apps/macos/Sources/Moltbot/SkillsSettings.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/SkillsSettings.swift rename to apps/macos/Sources/Moltbot/SkillsSettings.swift diff --git a/apps/macos/Sources/Clawdbot/SoundEffects.swift b/apps/macos/Sources/Moltbot/SoundEffects.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/SoundEffects.swift rename to apps/macos/Sources/Moltbot/SoundEffects.swift diff --git a/apps/macos/Sources/Clawdbot/StatusPill.swift b/apps/macos/Sources/Moltbot/StatusPill.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/StatusPill.swift rename to apps/macos/Sources/Moltbot/StatusPill.swift diff --git a/apps/macos/Sources/Clawdbot/String+NonEmpty.swift b/apps/macos/Sources/Moltbot/String+NonEmpty.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/String+NonEmpty.swift rename to apps/macos/Sources/Moltbot/String+NonEmpty.swift diff --git a/apps/macos/Sources/Clawdbot/SystemRunSettingsView.swift b/apps/macos/Sources/Moltbot/SystemRunSettingsView.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/SystemRunSettingsView.swift rename to apps/macos/Sources/Moltbot/SystemRunSettingsView.swift diff --git a/apps/macos/Sources/Clawdbot/TailscaleIntegrationSection.swift b/apps/macos/Sources/Moltbot/TailscaleIntegrationSection.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/TailscaleIntegrationSection.swift rename to apps/macos/Sources/Moltbot/TailscaleIntegrationSection.swift diff --git a/apps/macos/Sources/Clawdbot/TailscaleService.swift b/apps/macos/Sources/Moltbot/TailscaleService.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/TailscaleService.swift rename to apps/macos/Sources/Moltbot/TailscaleService.swift index 413e8d0c8..299045e5a 100644 --- a/apps/macos/Sources/Clawdbot/TailscaleService.swift +++ b/apps/macos/Sources/Moltbot/TailscaleService.swift @@ -18,7 +18,7 @@ final class TailscaleService { /// API request timeout in seconds. private static let apiTimeoutInterval: TimeInterval = 5.0 - private let logger = Logger(subsystem: "com.clawdbot", category: "tailscale") + private let logger = Logger(subsystem: "bot.molt", category: "tailscale") /// Indicates if the Tailscale app is installed on the system. private(set) var isInstalled = false diff --git a/apps/macos/Sources/Clawdbot/TalkAudioPlayer.swift b/apps/macos/Sources/Moltbot/TalkAudioPlayer.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/TalkAudioPlayer.swift rename to apps/macos/Sources/Moltbot/TalkAudioPlayer.swift index af5fdeffb..b137994a3 100644 --- a/apps/macos/Sources/Clawdbot/TalkAudioPlayer.swift +++ b/apps/macos/Sources/Moltbot/TalkAudioPlayer.swift @@ -6,7 +6,7 @@ import OSLog final class TalkAudioPlayer: NSObject, @preconcurrency AVAudioPlayerDelegate { static let shared = TalkAudioPlayer() - private let logger = Logger(subsystem: "com.clawdbot", category: "talk.tts") + private let logger = Logger(subsystem: "bot.molt", category: "talk.tts") private var player: AVAudioPlayer? private var playback: Playback? diff --git a/apps/macos/Sources/Clawdbot/TalkModeController.swift b/apps/macos/Sources/Moltbot/TalkModeController.swift similarity index 95% rename from apps/macos/Sources/Clawdbot/TalkModeController.swift rename to apps/macos/Sources/Moltbot/TalkModeController.swift index a92c0fda0..89eac593b 100644 --- a/apps/macos/Sources/Clawdbot/TalkModeController.swift +++ b/apps/macos/Sources/Moltbot/TalkModeController.swift @@ -5,7 +5,7 @@ import Observation final class TalkModeController { static let shared = TalkModeController() - private let logger = Logger(subsystem: "com.clawdbot", category: "talk.controller") + private let logger = Logger(subsystem: "bot.molt", category: "talk.controller") private(set) var phase: TalkModePhase = .idle private(set) var isPaused: Bool = false diff --git a/apps/macos/Sources/Clawdbot/TalkModeRuntime.swift b/apps/macos/Sources/Moltbot/TalkModeRuntime.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/TalkModeRuntime.swift rename to apps/macos/Sources/Moltbot/TalkModeRuntime.swift index a25a8d7ed..5c33cdb34 100644 --- a/apps/macos/Sources/Clawdbot/TalkModeRuntime.swift +++ b/apps/macos/Sources/Moltbot/TalkModeRuntime.swift @@ -8,8 +8,8 @@ import Speech actor TalkModeRuntime { static let shared = TalkModeRuntime() - private let logger = Logger(subsystem: "com.clawdbot", category: "talk.runtime") - private let ttsLogger = Logger(subsystem: "com.clawdbot", category: "talk.tts") + private let logger = Logger(subsystem: "bot.molt", category: "talk.runtime") + private let ttsLogger = Logger(subsystem: "bot.molt", category: "talk.tts") private static let defaultModelIdFallback = "eleven_v3" private final class RMSMeter: @unchecked Sendable { diff --git a/apps/macos/Sources/Clawdbot/TalkModeTypes.swift b/apps/macos/Sources/Moltbot/TalkModeTypes.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/TalkModeTypes.swift rename to apps/macos/Sources/Moltbot/TalkModeTypes.swift diff --git a/apps/macos/Sources/Clawdbot/TalkOverlay.swift b/apps/macos/Sources/Moltbot/TalkOverlay.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/TalkOverlay.swift rename to apps/macos/Sources/Moltbot/TalkOverlay.swift index 387b6db76..b9d2f6a24 100644 --- a/apps/macos/Sources/Clawdbot/TalkOverlay.swift +++ b/apps/macos/Sources/Moltbot/TalkOverlay.swift @@ -12,7 +12,7 @@ final class TalkOverlayController { static let orbPadding: CGFloat = 12 static let orbHitSlop: CGFloat = 10 - private let logger = Logger(subsystem: "com.clawdbot", category: "talk.overlay") + private let logger = Logger(subsystem: "bot.molt", category: "talk.overlay") struct Model { var isVisible: Bool = false diff --git a/apps/macos/Sources/Clawdbot/TalkOverlayView.swift b/apps/macos/Sources/Moltbot/TalkOverlayView.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/TalkOverlayView.swift rename to apps/macos/Sources/Moltbot/TalkOverlayView.swift diff --git a/apps/macos/Sources/Clawdbot/TerminationSignalWatcher.swift b/apps/macos/Sources/Moltbot/TerminationSignalWatcher.swift similarity index 95% rename from apps/macos/Sources/Clawdbot/TerminationSignalWatcher.swift rename to apps/macos/Sources/Moltbot/TerminationSignalWatcher.swift index 7994016ef..dca6916ac 100644 --- a/apps/macos/Sources/Clawdbot/TerminationSignalWatcher.swift +++ b/apps/macos/Sources/Moltbot/TerminationSignalWatcher.swift @@ -6,7 +6,7 @@ import OSLog final class TerminationSignalWatcher { static let shared = TerminationSignalWatcher() - private let logger = Logger(subsystem: "com.clawdbot", category: "lifecycle") + private let logger = Logger(subsystem: "bot.molt", category: "lifecycle") private var sources: [DispatchSourceSignal] = [] private var terminationRequested = false diff --git a/apps/macos/Sources/Clawdbot/UsageCostData.swift b/apps/macos/Sources/Moltbot/UsageCostData.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/UsageCostData.swift rename to apps/macos/Sources/Moltbot/UsageCostData.swift diff --git a/apps/macos/Sources/Clawdbot/UsageData.swift b/apps/macos/Sources/Moltbot/UsageData.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/UsageData.swift rename to apps/macos/Sources/Moltbot/UsageData.swift diff --git a/apps/macos/Sources/Clawdbot/UsageMenuLabelView.swift b/apps/macos/Sources/Moltbot/UsageMenuLabelView.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/UsageMenuLabelView.swift rename to apps/macos/Sources/Moltbot/UsageMenuLabelView.swift diff --git a/apps/macos/Sources/Clawdbot/ViewMetrics.swift b/apps/macos/Sources/Moltbot/ViewMetrics.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/ViewMetrics.swift rename to apps/macos/Sources/Moltbot/ViewMetrics.swift diff --git a/apps/macos/Sources/Clawdbot/VisualEffectView.swift b/apps/macos/Sources/Moltbot/VisualEffectView.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/VisualEffectView.swift rename to apps/macos/Sources/Moltbot/VisualEffectView.swift diff --git a/apps/macos/Sources/Clawdbot/VoicePushToTalk.swift b/apps/macos/Sources/Moltbot/VoicePushToTalk.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/VoicePushToTalk.swift rename to apps/macos/Sources/Moltbot/VoicePushToTalk.swift index 2bb1ec1f5..fb454a5fe 100644 --- a/apps/macos/Sources/Clawdbot/VoicePushToTalk.swift +++ b/apps/macos/Sources/Moltbot/VoicePushToTalk.swift @@ -89,14 +89,14 @@ final class VoicePushToTalkHotkey: @unchecked Sendable { if chordActive, !self.active { self.active = true Task { - Logger(subsystem: "com.clawdbot", category: "voicewake.ptt") + Logger(subsystem: "bot.molt", category: "voicewake.ptt") .info("ptt hotkey down") await self.beginAction() } } else if !chordActive, self.active { self.active = false Task { - Logger(subsystem: "com.clawdbot", category: "voicewake.ptt") + Logger(subsystem: "bot.molt", category: "voicewake.ptt") .info("ptt hotkey up") await self.endAction() } @@ -112,7 +112,7 @@ final class VoicePushToTalkHotkey: @unchecked Sendable { actor VoicePushToTalk { static let shared = VoicePushToTalk() - private let logger = Logger(subsystem: "com.clawdbot", category: "voicewake.ptt") + private let logger = Logger(subsystem: "bot.molt", category: "voicewake.ptt") private var recognizer: SFSpeechRecognizer? // Lazily created on begin() to avoid creating an AVAudioEngine at app launch, which can switch Bluetooth diff --git a/apps/macos/Sources/Clawdbot/VoiceSessionCoordinator.swift b/apps/macos/Sources/Moltbot/VoiceSessionCoordinator.swift similarity index 98% rename from apps/macos/Sources/Clawdbot/VoiceSessionCoordinator.swift rename to apps/macos/Sources/Moltbot/VoiceSessionCoordinator.swift index d7ee38a53..244d1da28 100644 --- a/apps/macos/Sources/Clawdbot/VoiceSessionCoordinator.swift +++ b/apps/macos/Sources/Moltbot/VoiceSessionCoordinator.swift @@ -19,7 +19,7 @@ final class VoiceSessionCoordinator { var autoSendDelay: TimeInterval? } - private let logger = Logger(subsystem: "com.clawdbot", category: "voicewake.coordinator") + private let logger = Logger(subsystem: "bot.molt", category: "voicewake.coordinator") private var session: Session? // MARK: - API diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeChime.swift b/apps/macos/Sources/Moltbot/VoiceWakeChime.swift similarity index 95% rename from apps/macos/Sources/Clawdbot/VoiceWakeChime.swift rename to apps/macos/Sources/Moltbot/VoiceWakeChime.swift index 8d0cc8b28..ca74d22dd 100644 --- a/apps/macos/Sources/Clawdbot/VoiceWakeChime.swift +++ b/apps/macos/Sources/Moltbot/VoiceWakeChime.swift @@ -41,7 +41,7 @@ enum VoiceWakeChimeCatalog { @MainActor enum VoiceWakeChimePlayer { - private static let logger = Logger(subsystem: "com.clawdbot", category: "voicewake.chime") + private static let logger = Logger(subsystem: "bot.molt", category: "voicewake.chime") private static var lastSound: NSSound? static func play(_ chime: VoiceWakeChime, reason: String? = nil) { diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeForwarder.swift b/apps/macos/Sources/Moltbot/VoiceWakeForwarder.swift similarity index 96% rename from apps/macos/Sources/Clawdbot/VoiceWakeForwarder.swift rename to apps/macos/Sources/Moltbot/VoiceWakeForwarder.swift index 3fd9f827b..7192f2bf4 100644 --- a/apps/macos/Sources/Clawdbot/VoiceWakeForwarder.swift +++ b/apps/macos/Sources/Moltbot/VoiceWakeForwarder.swift @@ -2,7 +2,7 @@ import Foundation import OSLog enum VoiceWakeForwarder { - private static let logger = Logger(subsystem: "com.clawdbot", category: "voicewake.forward") + private static let logger = Logger(subsystem: "bot.molt", category: "voicewake.forward") static func prefixedTranscript(_ transcript: String, machineName: String? = nil) -> String { let resolvedMachine = machineName diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeGlobalSettingsSync.swift b/apps/macos/Sources/Moltbot/VoiceWakeGlobalSettingsSync.swift similarity index 96% rename from apps/macos/Sources/Clawdbot/VoiceWakeGlobalSettingsSync.swift rename to apps/macos/Sources/Moltbot/VoiceWakeGlobalSettingsSync.swift index d08b79d84..b60d07597 100644 --- a/apps/macos/Sources/Clawdbot/VoiceWakeGlobalSettingsSync.swift +++ b/apps/macos/Sources/Moltbot/VoiceWakeGlobalSettingsSync.swift @@ -6,7 +6,7 @@ import OSLog final class VoiceWakeGlobalSettingsSync { static let shared = VoiceWakeGlobalSettingsSync() - private let logger = Logger(subsystem: "com.clawdbot", category: "voicewake.sync") + private let logger = Logger(subsystem: "bot.molt", category: "voicewake.sync") private var task: Task? private struct VoiceWakePayload: Codable, Equatable { diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeHelpers.swift b/apps/macos/Sources/Moltbot/VoiceWakeHelpers.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/VoiceWakeHelpers.swift rename to apps/macos/Sources/Moltbot/VoiceWakeHelpers.swift diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeOverlay.swift b/apps/macos/Sources/Moltbot/VoiceWakeOverlay.swift similarity index 95% rename from apps/macos/Sources/Clawdbot/VoiceWakeOverlay.swift rename to apps/macos/Sources/Moltbot/VoiceWakeOverlay.swift index 278ca1389..dcbd25621 100644 --- a/apps/macos/Sources/Clawdbot/VoiceWakeOverlay.swift +++ b/apps/macos/Sources/Moltbot/VoiceWakeOverlay.swift @@ -8,7 +8,7 @@ import SwiftUI final class VoiceWakeOverlayController { static let shared = VoiceWakeOverlayController() - let logger = Logger(subsystem: "com.clawdbot", category: "voicewake.overlay") + let logger = Logger(subsystem: "bot.molt", category: "voicewake.overlay") let enableUI: Bool /// Keep the voice wake overlay above any other Moltbot windows, but below the system’s pop-up menus. diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeOverlayController+Session.swift b/apps/macos/Sources/Moltbot/VoiceWakeOverlayController+Session.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/VoiceWakeOverlayController+Session.swift rename to apps/macos/Sources/Moltbot/VoiceWakeOverlayController+Session.swift diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeOverlayController+Testing.swift b/apps/macos/Sources/Moltbot/VoiceWakeOverlayController+Testing.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/VoiceWakeOverlayController+Testing.swift rename to apps/macos/Sources/Moltbot/VoiceWakeOverlayController+Testing.swift diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeOverlayController+Window.swift b/apps/macos/Sources/Moltbot/VoiceWakeOverlayController+Window.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/VoiceWakeOverlayController+Window.swift rename to apps/macos/Sources/Moltbot/VoiceWakeOverlayController+Window.swift diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeOverlayTextViews.swift b/apps/macos/Sources/Moltbot/VoiceWakeOverlayTextViews.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/VoiceWakeOverlayTextViews.swift rename to apps/macos/Sources/Moltbot/VoiceWakeOverlayTextViews.swift diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeOverlayView.swift b/apps/macos/Sources/Moltbot/VoiceWakeOverlayView.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/VoiceWakeOverlayView.swift rename to apps/macos/Sources/Moltbot/VoiceWakeOverlayView.swift diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeRuntime.swift b/apps/macos/Sources/Moltbot/VoiceWakeRuntime.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/VoiceWakeRuntime.swift rename to apps/macos/Sources/Moltbot/VoiceWakeRuntime.swift index 06ebfb7ae..805211122 100644 --- a/apps/macos/Sources/Clawdbot/VoiceWakeRuntime.swift +++ b/apps/macos/Sources/Moltbot/VoiceWakeRuntime.swift @@ -13,7 +13,7 @@ actor VoiceWakeRuntime { enum ListeningState { case idle, voiceWake, pushToTalk } - private let logger = Logger(subsystem: "com.clawdbot", category: "voicewake.runtime") + private let logger = Logger(subsystem: "bot.molt", category: "voicewake.runtime") private var recognizer: SFSpeechRecognizer? // Lazily created on start to avoid creating an AVAudioEngine at app launch, which can switch Bluetooth diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeSettings.swift b/apps/macos/Sources/Moltbot/VoiceWakeSettings.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/VoiceWakeSettings.swift rename to apps/macos/Sources/Moltbot/VoiceWakeSettings.swift diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeTestCard.swift b/apps/macos/Sources/Moltbot/VoiceWakeTestCard.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/VoiceWakeTestCard.swift rename to apps/macos/Sources/Moltbot/VoiceWakeTestCard.swift diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeTester.swift b/apps/macos/Sources/Moltbot/VoiceWakeTester.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/VoiceWakeTester.swift rename to apps/macos/Sources/Moltbot/VoiceWakeTester.swift index bf6a883ab..05c8148b6 100644 --- a/apps/macos/Sources/Clawdbot/VoiceWakeTester.swift +++ b/apps/macos/Sources/Moltbot/VoiceWakeTester.swift @@ -30,7 +30,7 @@ final class VoiceWakeTester { private var currentTriggers: [String] = [] private var holdingAfterDetect = false private var detectedText: String? - private let logger = Logger(subsystem: "com.clawdbot", category: "voicewake") + private let logger = Logger(subsystem: "bot.molt", category: "voicewake") private let silenceWindow: TimeInterval = 1.0 init(locale: Locale = .current) { diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeTextUtils.swift b/apps/macos/Sources/Moltbot/VoiceWakeTextUtils.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/VoiceWakeTextUtils.swift rename to apps/macos/Sources/Moltbot/VoiceWakeTextUtils.swift diff --git a/apps/macos/Sources/Clawdbot/WebChatManager.swift b/apps/macos/Sources/Moltbot/WebChatManager.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/WebChatManager.swift rename to apps/macos/Sources/Moltbot/WebChatManager.swift diff --git a/apps/macos/Sources/Clawdbot/WebChatSwiftUI.swift b/apps/macos/Sources/Moltbot/WebChatSwiftUI.swift similarity index 99% rename from apps/macos/Sources/Clawdbot/WebChatSwiftUI.swift rename to apps/macos/Sources/Moltbot/WebChatSwiftUI.swift index 18d3c46c8..c457ceb2a 100644 --- a/apps/macos/Sources/Clawdbot/WebChatSwiftUI.swift +++ b/apps/macos/Sources/Moltbot/WebChatSwiftUI.swift @@ -7,7 +7,7 @@ import OSLog import QuartzCore import SwiftUI -private let webChatSwiftLogger = Logger(subsystem: "com.clawdbot", category: "WebChatSwiftUI") +private let webChatSwiftLogger = Logger(subsystem: "bot.molt", category: "WebChatSwiftUI") private enum WebChatSwiftUILayout { static let windowSize = NSSize(width: 500, height: 840) diff --git a/apps/macos/Sources/Clawdbot/WindowPlacement.swift b/apps/macos/Sources/Moltbot/WindowPlacement.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/WindowPlacement.swift rename to apps/macos/Sources/Moltbot/WindowPlacement.swift diff --git a/apps/macos/Sources/Clawdbot/WorkActivityStore.swift b/apps/macos/Sources/Moltbot/WorkActivityStore.swift similarity index 100% rename from apps/macos/Sources/Clawdbot/WorkActivityStore.swift rename to apps/macos/Sources/Moltbot/WorkActivityStore.swift diff --git a/apps/macos/Sources/ClawdbotDiscovery/GatewayDiscoveryModel.swift b/apps/macos/Sources/MoltbotDiscovery/GatewayDiscoveryModel.swift similarity index 99% rename from apps/macos/Sources/ClawdbotDiscovery/GatewayDiscoveryModel.swift rename to apps/macos/Sources/MoltbotDiscovery/GatewayDiscoveryModel.swift index 6ce854c74..69d8978ec 100644 --- a/apps/macos/Sources/ClawdbotDiscovery/GatewayDiscoveryModel.swift +++ b/apps/macos/Sources/MoltbotDiscovery/GatewayDiscoveryModel.swift @@ -66,7 +66,7 @@ public final class GatewayDiscoveryModel { private var pendingTXTResolvers: [String: GatewayTXTResolver] = [:] private var wideAreaFallbackTask: Task? private var wideAreaFallbackGateways: [DiscoveredGateway] = [] - private let logger = Logger(subsystem: "com.clawdbot", category: "gateway-discovery") + private let logger = Logger(subsystem: "bot.molt", category: "gateway-discovery") public init( localDisplayName: String? = nil, @@ -106,7 +106,7 @@ public final class GatewayDiscoveryModel { } self.browsers[domain] = browser - browser.start(queue: DispatchQueue(label: "com.clawdbot.macos.gateway-discovery.\(domain)")) + browser.start(queue: DispatchQueue(label: "bot.molt.macos.gateway-discovery.\(domain)")) } self.scheduleWideAreaFallback() diff --git a/apps/macos/Sources/ClawdbotDiscovery/WideAreaGatewayDiscovery.swift b/apps/macos/Sources/MoltbotDiscovery/WideAreaGatewayDiscovery.swift similarity index 100% rename from apps/macos/Sources/ClawdbotDiscovery/WideAreaGatewayDiscovery.swift rename to apps/macos/Sources/MoltbotDiscovery/WideAreaGatewayDiscovery.swift diff --git a/apps/macos/Sources/ClawdbotIPC/IPC.swift b/apps/macos/Sources/MoltbotIPC/IPC.swift similarity index 100% rename from apps/macos/Sources/ClawdbotIPC/IPC.swift rename to apps/macos/Sources/MoltbotIPC/IPC.swift diff --git a/apps/macos/Sources/ClawdbotMacCLI/ConnectCommand.swift b/apps/macos/Sources/MoltbotMacCLI/ConnectCommand.swift similarity index 100% rename from apps/macos/Sources/ClawdbotMacCLI/ConnectCommand.swift rename to apps/macos/Sources/MoltbotMacCLI/ConnectCommand.swift diff --git a/apps/macos/Sources/ClawdbotMacCLI/DiscoverCommand.swift b/apps/macos/Sources/MoltbotMacCLI/DiscoverCommand.swift similarity index 100% rename from apps/macos/Sources/ClawdbotMacCLI/DiscoverCommand.swift rename to apps/macos/Sources/MoltbotMacCLI/DiscoverCommand.swift diff --git a/apps/macos/Sources/ClawdbotMacCLI/EntryPoint.swift b/apps/macos/Sources/MoltbotMacCLI/EntryPoint.swift similarity index 100% rename from apps/macos/Sources/ClawdbotMacCLI/EntryPoint.swift rename to apps/macos/Sources/MoltbotMacCLI/EntryPoint.swift diff --git a/apps/macos/Sources/ClawdbotMacCLI/GatewayConfig.swift b/apps/macos/Sources/MoltbotMacCLI/GatewayConfig.swift similarity index 100% rename from apps/macos/Sources/ClawdbotMacCLI/GatewayConfig.swift rename to apps/macos/Sources/MoltbotMacCLI/GatewayConfig.swift diff --git a/apps/macos/Sources/ClawdbotMacCLI/TypeAliases.swift b/apps/macos/Sources/MoltbotMacCLI/TypeAliases.swift similarity index 100% rename from apps/macos/Sources/ClawdbotMacCLI/TypeAliases.swift rename to apps/macos/Sources/MoltbotMacCLI/TypeAliases.swift diff --git a/apps/macos/Sources/ClawdbotMacCLI/WizardCommand.swift b/apps/macos/Sources/MoltbotMacCLI/WizardCommand.swift similarity index 100% rename from apps/macos/Sources/ClawdbotMacCLI/WizardCommand.swift rename to apps/macos/Sources/MoltbotMacCLI/WizardCommand.swift diff --git a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift b/apps/macos/Sources/MoltbotProtocol/GatewayModels.swift similarity index 100% rename from apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift rename to apps/macos/Sources/MoltbotProtocol/GatewayModels.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/AgentEventStoreTests.swift b/apps/macos/Tests/MoltbotIPCTests/AgentEventStoreTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/AgentEventStoreTests.swift rename to apps/macos/Tests/MoltbotIPCTests/AgentEventStoreTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/AgentWorkspaceTests.swift b/apps/macos/Tests/MoltbotIPCTests/AgentWorkspaceTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/AgentWorkspaceTests.swift rename to apps/macos/Tests/MoltbotIPCTests/AgentWorkspaceTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/AnthropicAuthControlsSmokeTests.swift b/apps/macos/Tests/MoltbotIPCTests/AnthropicAuthControlsSmokeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/AnthropicAuthControlsSmokeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/AnthropicAuthControlsSmokeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/AnthropicAuthResolverTests.swift b/apps/macos/Tests/MoltbotIPCTests/AnthropicAuthResolverTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/AnthropicAuthResolverTests.swift rename to apps/macos/Tests/MoltbotIPCTests/AnthropicAuthResolverTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/AnthropicOAuthCodeStateTests.swift b/apps/macos/Tests/MoltbotIPCTests/AnthropicOAuthCodeStateTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/AnthropicOAuthCodeStateTests.swift rename to apps/macos/Tests/MoltbotIPCTests/AnthropicOAuthCodeStateTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/AnyCodableEncodingTests.swift b/apps/macos/Tests/MoltbotIPCTests/AnyCodableEncodingTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/AnyCodableEncodingTests.swift rename to apps/macos/Tests/MoltbotIPCTests/AnyCodableEncodingTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/CLIInstallerTests.swift b/apps/macos/Tests/MoltbotIPCTests/CLIInstallerTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/CLIInstallerTests.swift rename to apps/macos/Tests/MoltbotIPCTests/CLIInstallerTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/CameraCaptureServiceTests.swift b/apps/macos/Tests/MoltbotIPCTests/CameraCaptureServiceTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/CameraCaptureServiceTests.swift rename to apps/macos/Tests/MoltbotIPCTests/CameraCaptureServiceTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/CameraIPCTests.swift b/apps/macos/Tests/MoltbotIPCTests/CameraIPCTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/CameraIPCTests.swift rename to apps/macos/Tests/MoltbotIPCTests/CameraIPCTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/CanvasFileWatcherTests.swift b/apps/macos/Tests/MoltbotIPCTests/CanvasFileWatcherTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/CanvasFileWatcherTests.swift rename to apps/macos/Tests/MoltbotIPCTests/CanvasFileWatcherTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/CanvasIPCTests.swift b/apps/macos/Tests/MoltbotIPCTests/CanvasIPCTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/CanvasIPCTests.swift rename to apps/macos/Tests/MoltbotIPCTests/CanvasIPCTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/CanvasWindowSmokeTests.swift b/apps/macos/Tests/MoltbotIPCTests/CanvasWindowSmokeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/CanvasWindowSmokeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/CanvasWindowSmokeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/ChannelsSettingsSmokeTests.swift b/apps/macos/Tests/MoltbotIPCTests/ChannelsSettingsSmokeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/ChannelsSettingsSmokeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/ChannelsSettingsSmokeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/ClawdbotConfigFileTests.swift b/apps/macos/Tests/MoltbotIPCTests/ClawdbotConfigFileTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/ClawdbotConfigFileTests.swift rename to apps/macos/Tests/MoltbotIPCTests/ClawdbotConfigFileTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/ClawdbotOAuthStoreTests.swift b/apps/macos/Tests/MoltbotIPCTests/ClawdbotOAuthStoreTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/ClawdbotOAuthStoreTests.swift rename to apps/macos/Tests/MoltbotIPCTests/ClawdbotOAuthStoreTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/CommandResolverTests.swift b/apps/macos/Tests/MoltbotIPCTests/CommandResolverTests.swift similarity index 93% rename from apps/macos/Tests/ClawdbotIPCTests/CommandResolverTests.swift rename to apps/macos/Tests/MoltbotIPCTests/CommandResolverTests.swift index 8bc84e51f..d6abe45af 100644 --- a/apps/macos/Tests/ClawdbotIPCTests/CommandResolverTests.swift +++ b/apps/macos/Tests/MoltbotIPCTests/CommandResolverTests.swift @@ -34,7 +34,7 @@ import Testing let moltbotPath = tmp.appendingPathComponent("node_modules/.bin/moltbot") try self.makeExec(at: moltbotPath) - let cmd = CommandResolver.clawdbotCommand(subcommand: "gateway", defaults: defaults, configRoot: [:]) + let cmd = CommandResolver.moltbotCommand(subcommand: "gateway", defaults: defaults, configRoot: [:]) #expect(cmd.prefix(2).elementsEqual([moltbotPath.path, "gateway"])) } @@ -52,7 +52,7 @@ import Testing try FileManager().setAttributes([.posixPermissions: 0o755], ofItemAtPath: nodePath.path) try self.makeExec(at: scriptPath) - let cmd = CommandResolver.clawdbotCommand( + let cmd = CommandResolver.moltbotCommand( subcommand: "rpc", defaults: defaults, configRoot: [:], @@ -76,7 +76,7 @@ import Testing let pnpmPath = tmp.appendingPathComponent("node_modules/.bin/pnpm") try self.makeExec(at: pnpmPath) - let cmd = CommandResolver.clawdbotCommand(subcommand: "rpc", defaults: defaults, configRoot: [:]) + let cmd = CommandResolver.moltbotCommand(subcommand: "rpc", defaults: defaults, configRoot: [:]) #expect(cmd.prefix(4).elementsEqual([pnpmPath.path, "--silent", "moltbot", "rpc"])) } @@ -91,7 +91,7 @@ import Testing let pnpmPath = tmp.appendingPathComponent("node_modules/.bin/pnpm") try self.makeExec(at: pnpmPath) - let cmd = CommandResolver.clawdbotCommand( + let cmd = CommandResolver.moltbotCommand( subcommand: "health", extraArgs: ["--json", "--timeout", "5"], defaults: defaults, @@ -116,7 +116,7 @@ import Testing defaults.set("/tmp/id_ed25519", forKey: remoteIdentityKey) defaults.set("/srv/moltbot", forKey: remoteProjectRootKey) - let cmd = CommandResolver.clawdbotCommand( + let cmd = CommandResolver.moltbotCommand( subcommand: "status", extraArgs: ["--json"], defaults: defaults, @@ -157,7 +157,7 @@ import Testing let moltbotPath = tmp.appendingPathComponent("node_modules/.bin/moltbot") try self.makeExec(at: moltbotPath) - let cmd = CommandResolver.clawdbotCommand( + let cmd = CommandResolver.moltbotCommand( subcommand: "daemon", defaults: defaults, configRoot: ["gateway": ["mode": "local"]]) diff --git a/apps/macos/Tests/ClawdbotIPCTests/ConfigStoreTests.swift b/apps/macos/Tests/MoltbotIPCTests/ConfigStoreTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/ConfigStoreTests.swift rename to apps/macos/Tests/MoltbotIPCTests/ConfigStoreTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/CoverageDumpTests.swift b/apps/macos/Tests/MoltbotIPCTests/CoverageDumpTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/CoverageDumpTests.swift rename to apps/macos/Tests/MoltbotIPCTests/CoverageDumpTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/CritterIconRendererTests.swift b/apps/macos/Tests/MoltbotIPCTests/CritterIconRendererTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/CritterIconRendererTests.swift rename to apps/macos/Tests/MoltbotIPCTests/CritterIconRendererTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/CronJobEditorSmokeTests.swift b/apps/macos/Tests/MoltbotIPCTests/CronJobEditorSmokeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/CronJobEditorSmokeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/CronJobEditorSmokeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/CronModelsTests.swift b/apps/macos/Tests/MoltbotIPCTests/CronModelsTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/CronModelsTests.swift rename to apps/macos/Tests/MoltbotIPCTests/CronModelsTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/DeviceModelCatalogTests.swift b/apps/macos/Tests/MoltbotIPCTests/DeviceModelCatalogTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/DeviceModelCatalogTests.swift rename to apps/macos/Tests/MoltbotIPCTests/DeviceModelCatalogTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/ExecAllowlistTests.swift b/apps/macos/Tests/MoltbotIPCTests/ExecAllowlistTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/ExecAllowlistTests.swift rename to apps/macos/Tests/MoltbotIPCTests/ExecAllowlistTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/ExecApprovalHelpersTests.swift b/apps/macos/Tests/MoltbotIPCTests/ExecApprovalHelpersTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/ExecApprovalHelpersTests.swift rename to apps/macos/Tests/MoltbotIPCTests/ExecApprovalHelpersTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/ExecApprovalsGatewayPrompterTests.swift b/apps/macos/Tests/MoltbotIPCTests/ExecApprovalsGatewayPrompterTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/ExecApprovalsGatewayPrompterTests.swift rename to apps/macos/Tests/MoltbotIPCTests/ExecApprovalsGatewayPrompterTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/FileHandleLegacyAPIGuardTests.swift b/apps/macos/Tests/MoltbotIPCTests/FileHandleLegacyAPIGuardTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/FileHandleLegacyAPIGuardTests.swift rename to apps/macos/Tests/MoltbotIPCTests/FileHandleLegacyAPIGuardTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/FileHandleSafeReadTests.swift b/apps/macos/Tests/MoltbotIPCTests/FileHandleSafeReadTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/FileHandleSafeReadTests.swift rename to apps/macos/Tests/MoltbotIPCTests/FileHandleSafeReadTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayAgentChannelTests.swift b/apps/macos/Tests/MoltbotIPCTests/GatewayAgentChannelTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/GatewayAgentChannelTests.swift rename to apps/macos/Tests/MoltbotIPCTests/GatewayAgentChannelTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayAutostartPolicyTests.swift b/apps/macos/Tests/MoltbotIPCTests/GatewayAutostartPolicyTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/GatewayAutostartPolicyTests.swift rename to apps/macos/Tests/MoltbotIPCTests/GatewayAutostartPolicyTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayChannelConfigureTests.swift b/apps/macos/Tests/MoltbotIPCTests/GatewayChannelConfigureTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/GatewayChannelConfigureTests.swift rename to apps/macos/Tests/MoltbotIPCTests/GatewayChannelConfigureTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayChannelConnectTests.swift b/apps/macos/Tests/MoltbotIPCTests/GatewayChannelConnectTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/GatewayChannelConnectTests.swift rename to apps/macos/Tests/MoltbotIPCTests/GatewayChannelConnectTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayChannelRequestTests.swift b/apps/macos/Tests/MoltbotIPCTests/GatewayChannelRequestTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/GatewayChannelRequestTests.swift rename to apps/macos/Tests/MoltbotIPCTests/GatewayChannelRequestTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayChannelShutdownTests.swift b/apps/macos/Tests/MoltbotIPCTests/GatewayChannelShutdownTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/GatewayChannelShutdownTests.swift rename to apps/macos/Tests/MoltbotIPCTests/GatewayChannelShutdownTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayConnectionControlTests.swift b/apps/macos/Tests/MoltbotIPCTests/GatewayConnectionControlTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/GatewayConnectionControlTests.swift rename to apps/macos/Tests/MoltbotIPCTests/GatewayConnectionControlTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayDiscoveryModelTests.swift b/apps/macos/Tests/MoltbotIPCTests/GatewayDiscoveryModelTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/GatewayDiscoveryModelTests.swift rename to apps/macos/Tests/MoltbotIPCTests/GatewayDiscoveryModelTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayEndpointStoreTests.swift b/apps/macos/Tests/MoltbotIPCTests/GatewayEndpointStoreTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/GatewayEndpointStoreTests.swift rename to apps/macos/Tests/MoltbotIPCTests/GatewayEndpointStoreTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayEnvironmentTests.swift b/apps/macos/Tests/MoltbotIPCTests/GatewayEnvironmentTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/GatewayEnvironmentTests.swift rename to apps/macos/Tests/MoltbotIPCTests/GatewayEnvironmentTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayFrameDecodeTests.swift b/apps/macos/Tests/MoltbotIPCTests/GatewayFrameDecodeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/GatewayFrameDecodeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/GatewayFrameDecodeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayLaunchAgentManagerTests.swift b/apps/macos/Tests/MoltbotIPCTests/GatewayLaunchAgentManagerTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/GatewayLaunchAgentManagerTests.swift rename to apps/macos/Tests/MoltbotIPCTests/GatewayLaunchAgentManagerTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayProcessManagerTests.swift b/apps/macos/Tests/MoltbotIPCTests/GatewayProcessManagerTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/GatewayProcessManagerTests.swift rename to apps/macos/Tests/MoltbotIPCTests/GatewayProcessManagerTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/HealthDecodeTests.swift b/apps/macos/Tests/MoltbotIPCTests/HealthDecodeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/HealthDecodeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/HealthDecodeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/HealthStoreStateTests.swift b/apps/macos/Tests/MoltbotIPCTests/HealthStoreStateTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/HealthStoreStateTests.swift rename to apps/macos/Tests/MoltbotIPCTests/HealthStoreStateTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/HoverHUDControllerTests.swift b/apps/macos/Tests/MoltbotIPCTests/HoverHUDControllerTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/HoverHUDControllerTests.swift rename to apps/macos/Tests/MoltbotIPCTests/HoverHUDControllerTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/InstancesSettingsSmokeTests.swift b/apps/macos/Tests/MoltbotIPCTests/InstancesSettingsSmokeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/InstancesSettingsSmokeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/InstancesSettingsSmokeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/InstancesStoreTests.swift b/apps/macos/Tests/MoltbotIPCTests/InstancesStoreTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/InstancesStoreTests.swift rename to apps/macos/Tests/MoltbotIPCTests/InstancesStoreTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/LogLocatorTests.swift b/apps/macos/Tests/MoltbotIPCTests/LogLocatorTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/LogLocatorTests.swift rename to apps/macos/Tests/MoltbotIPCTests/LogLocatorTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/LowCoverageHelperTests.swift b/apps/macos/Tests/MoltbotIPCTests/LowCoverageHelperTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/LowCoverageHelperTests.swift rename to apps/macos/Tests/MoltbotIPCTests/LowCoverageHelperTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/LowCoverageViewSmokeTests.swift b/apps/macos/Tests/MoltbotIPCTests/LowCoverageViewSmokeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/LowCoverageViewSmokeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/LowCoverageViewSmokeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/MacGatewayChatTransportMappingTests.swift b/apps/macos/Tests/MoltbotIPCTests/MacGatewayChatTransportMappingTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/MacGatewayChatTransportMappingTests.swift rename to apps/macos/Tests/MoltbotIPCTests/MacGatewayChatTransportMappingTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/MacNodeRuntimeTests.swift b/apps/macos/Tests/MoltbotIPCTests/MacNodeRuntimeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/MacNodeRuntimeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/MacNodeRuntimeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/MasterDiscoveryMenuSmokeTests.swift b/apps/macos/Tests/MoltbotIPCTests/MasterDiscoveryMenuSmokeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/MasterDiscoveryMenuSmokeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/MasterDiscoveryMenuSmokeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/MenuContentSmokeTests.swift b/apps/macos/Tests/MoltbotIPCTests/MenuContentSmokeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/MenuContentSmokeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/MenuContentSmokeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/MenuSessionsInjectorTests.swift b/apps/macos/Tests/MoltbotIPCTests/MenuSessionsInjectorTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/MenuSessionsInjectorTests.swift rename to apps/macos/Tests/MoltbotIPCTests/MenuSessionsInjectorTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/ModelCatalogLoaderTests.swift b/apps/macos/Tests/MoltbotIPCTests/ModelCatalogLoaderTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/ModelCatalogLoaderTests.swift rename to apps/macos/Tests/MoltbotIPCTests/ModelCatalogLoaderTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/NodeManagerPathsTests.swift b/apps/macos/Tests/MoltbotIPCTests/NodeManagerPathsTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/NodeManagerPathsTests.swift rename to apps/macos/Tests/MoltbotIPCTests/NodeManagerPathsTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/NodePairingApprovalPrompterTests.swift b/apps/macos/Tests/MoltbotIPCTests/NodePairingApprovalPrompterTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/NodePairingApprovalPrompterTests.swift rename to apps/macos/Tests/MoltbotIPCTests/NodePairingApprovalPrompterTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/NodePairingReconcilePolicyTests.swift b/apps/macos/Tests/MoltbotIPCTests/NodePairingReconcilePolicyTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/NodePairingReconcilePolicyTests.swift rename to apps/macos/Tests/MoltbotIPCTests/NodePairingReconcilePolicyTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/OnboardingCoverageTests.swift b/apps/macos/Tests/MoltbotIPCTests/OnboardingCoverageTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/OnboardingCoverageTests.swift rename to apps/macos/Tests/MoltbotIPCTests/OnboardingCoverageTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/OnboardingViewSmokeTests.swift b/apps/macos/Tests/MoltbotIPCTests/OnboardingViewSmokeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/OnboardingViewSmokeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/OnboardingViewSmokeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/OnboardingWizardStepViewTests.swift b/apps/macos/Tests/MoltbotIPCTests/OnboardingWizardStepViewTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/OnboardingWizardStepViewTests.swift rename to apps/macos/Tests/MoltbotIPCTests/OnboardingWizardStepViewTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/PermissionManagerLocationTests.swift b/apps/macos/Tests/MoltbotIPCTests/PermissionManagerLocationTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/PermissionManagerLocationTests.swift rename to apps/macos/Tests/MoltbotIPCTests/PermissionManagerLocationTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/PermissionManagerTests.swift b/apps/macos/Tests/MoltbotIPCTests/PermissionManagerTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/PermissionManagerTests.swift rename to apps/macos/Tests/MoltbotIPCTests/PermissionManagerTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/Placeholder.swift b/apps/macos/Tests/MoltbotIPCTests/Placeholder.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/Placeholder.swift rename to apps/macos/Tests/MoltbotIPCTests/Placeholder.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/RemotePortTunnelTests.swift b/apps/macos/Tests/MoltbotIPCTests/RemotePortTunnelTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/RemotePortTunnelTests.swift rename to apps/macos/Tests/MoltbotIPCTests/RemotePortTunnelTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/RuntimeLocatorTests.swift b/apps/macos/Tests/MoltbotIPCTests/RuntimeLocatorTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/RuntimeLocatorTests.swift rename to apps/macos/Tests/MoltbotIPCTests/RuntimeLocatorTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/ScreenshotSizeTests.swift b/apps/macos/Tests/MoltbotIPCTests/ScreenshotSizeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/ScreenshotSizeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/ScreenshotSizeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/SemverTests.swift b/apps/macos/Tests/MoltbotIPCTests/SemverTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/SemverTests.swift rename to apps/macos/Tests/MoltbotIPCTests/SemverTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/SessionDataTests.swift b/apps/macos/Tests/MoltbotIPCTests/SessionDataTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/SessionDataTests.swift rename to apps/macos/Tests/MoltbotIPCTests/SessionDataTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/SessionMenuPreviewTests.swift b/apps/macos/Tests/MoltbotIPCTests/SessionMenuPreviewTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/SessionMenuPreviewTests.swift rename to apps/macos/Tests/MoltbotIPCTests/SessionMenuPreviewTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/SettingsViewSmokeTests.swift b/apps/macos/Tests/MoltbotIPCTests/SettingsViewSmokeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/SettingsViewSmokeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/SettingsViewSmokeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/SkillsSettingsSmokeTests.swift b/apps/macos/Tests/MoltbotIPCTests/SkillsSettingsSmokeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/SkillsSettingsSmokeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/SkillsSettingsSmokeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/TailscaleIntegrationSectionTests.swift b/apps/macos/Tests/MoltbotIPCTests/TailscaleIntegrationSectionTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/TailscaleIntegrationSectionTests.swift rename to apps/macos/Tests/MoltbotIPCTests/TailscaleIntegrationSectionTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/TalkAudioPlayerTests.swift b/apps/macos/Tests/MoltbotIPCTests/TalkAudioPlayerTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/TalkAudioPlayerTests.swift rename to apps/macos/Tests/MoltbotIPCTests/TalkAudioPlayerTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/TestIsolation.swift b/apps/macos/Tests/MoltbotIPCTests/TestIsolation.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/TestIsolation.swift rename to apps/macos/Tests/MoltbotIPCTests/TestIsolation.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/UtilitiesTests.swift b/apps/macos/Tests/MoltbotIPCTests/UtilitiesTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/UtilitiesTests.swift rename to apps/macos/Tests/MoltbotIPCTests/UtilitiesTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/VoicePushToTalkHotkeyTests.swift b/apps/macos/Tests/MoltbotIPCTests/VoicePushToTalkHotkeyTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/VoicePushToTalkHotkeyTests.swift rename to apps/macos/Tests/MoltbotIPCTests/VoicePushToTalkHotkeyTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/VoicePushToTalkTests.swift b/apps/macos/Tests/MoltbotIPCTests/VoicePushToTalkTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/VoicePushToTalkTests.swift rename to apps/macos/Tests/MoltbotIPCTests/VoicePushToTalkTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/VoiceWakeForwarderTests.swift b/apps/macos/Tests/MoltbotIPCTests/VoiceWakeForwarderTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/VoiceWakeForwarderTests.swift rename to apps/macos/Tests/MoltbotIPCTests/VoiceWakeForwarderTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/VoiceWakeGlobalSettingsSyncTests.swift b/apps/macos/Tests/MoltbotIPCTests/VoiceWakeGlobalSettingsSyncTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/VoiceWakeGlobalSettingsSyncTests.swift rename to apps/macos/Tests/MoltbotIPCTests/VoiceWakeGlobalSettingsSyncTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/VoiceWakeHelpersTests.swift b/apps/macos/Tests/MoltbotIPCTests/VoiceWakeHelpersTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/VoiceWakeHelpersTests.swift rename to apps/macos/Tests/MoltbotIPCTests/VoiceWakeHelpersTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/VoiceWakeOverlayControllerTests.swift b/apps/macos/Tests/MoltbotIPCTests/VoiceWakeOverlayControllerTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/VoiceWakeOverlayControllerTests.swift rename to apps/macos/Tests/MoltbotIPCTests/VoiceWakeOverlayControllerTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/VoiceWakeOverlayTests.swift b/apps/macos/Tests/MoltbotIPCTests/VoiceWakeOverlayTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/VoiceWakeOverlayTests.swift rename to apps/macos/Tests/MoltbotIPCTests/VoiceWakeOverlayTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/VoiceWakeOverlayViewSmokeTests.swift b/apps/macos/Tests/MoltbotIPCTests/VoiceWakeOverlayViewSmokeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/VoiceWakeOverlayViewSmokeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/VoiceWakeOverlayViewSmokeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/VoiceWakeRuntimeTests.swift b/apps/macos/Tests/MoltbotIPCTests/VoiceWakeRuntimeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/VoiceWakeRuntimeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/VoiceWakeRuntimeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/VoiceWakeTesterTests.swift b/apps/macos/Tests/MoltbotIPCTests/VoiceWakeTesterTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/VoiceWakeTesterTests.swift rename to apps/macos/Tests/MoltbotIPCTests/VoiceWakeTesterTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/WebChatMainSessionKeyTests.swift b/apps/macos/Tests/MoltbotIPCTests/WebChatMainSessionKeyTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/WebChatMainSessionKeyTests.swift rename to apps/macos/Tests/MoltbotIPCTests/WebChatMainSessionKeyTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/WebChatManagerTests.swift b/apps/macos/Tests/MoltbotIPCTests/WebChatManagerTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/WebChatManagerTests.swift rename to apps/macos/Tests/MoltbotIPCTests/WebChatManagerTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/WebChatSwiftUISmokeTests.swift b/apps/macos/Tests/MoltbotIPCTests/WebChatSwiftUISmokeTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/WebChatSwiftUISmokeTests.swift rename to apps/macos/Tests/MoltbotIPCTests/WebChatSwiftUISmokeTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/WideAreaGatewayDiscoveryTests.swift b/apps/macos/Tests/MoltbotIPCTests/WideAreaGatewayDiscoveryTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/WideAreaGatewayDiscoveryTests.swift rename to apps/macos/Tests/MoltbotIPCTests/WideAreaGatewayDiscoveryTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/WindowPlacementTests.swift b/apps/macos/Tests/MoltbotIPCTests/WindowPlacementTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/WindowPlacementTests.swift rename to apps/macos/Tests/MoltbotIPCTests/WindowPlacementTests.swift diff --git a/apps/macos/Tests/ClawdbotIPCTests/WorkActivityStoreTests.swift b/apps/macos/Tests/MoltbotIPCTests/WorkActivityStoreTests.swift similarity index 100% rename from apps/macos/Tests/ClawdbotIPCTests/WorkActivityStoreTests.swift rename to apps/macos/Tests/MoltbotIPCTests/WorkActivityStoreTests.swift diff --git a/apps/shared/ClawdbotKit/Package.swift b/apps/shared/MoltbotKit/Package.swift similarity index 91% rename from apps/shared/ClawdbotKit/Package.swift rename to apps/shared/MoltbotKit/Package.swift index f86582a98..78ced7f0b 100644 --- a/apps/shared/ClawdbotKit/Package.swift +++ b/apps/shared/MoltbotKit/Package.swift @@ -20,17 +20,17 @@ let package = Package( targets: [ .target( name: "MoltbotProtocol", - path: "Sources/ClawdbotProtocol", + path: "Sources/MoltbotProtocol", swiftSettings: [ .enableUpcomingFeature("StrictConcurrency"), ]), .target( name: "MoltbotKit", - path: "Sources/ClawdbotKit", dependencies: [ "MoltbotProtocol", .product(name: "ElevenLabsKit", package: "ElevenLabsKit"), ], + path: "Sources/MoltbotKit", resources: [ .process("Resources"), ], @@ -39,7 +39,6 @@ let package = Package( ]), .target( name: "MoltbotChatUI", - path: "Sources/ClawdbotChatUI", dependencies: [ "MoltbotKit", .product( @@ -47,13 +46,14 @@ let package = Package( package: "textual", condition: .when(platforms: [.macOS, .iOS])), ], + path: "Sources/MoltbotChatUI", swiftSettings: [ .enableUpcomingFeature("StrictConcurrency"), ]), .testTarget( name: "MoltbotKitTests", dependencies: ["MoltbotKit", "MoltbotChatUI"], - path: "Tests/ClawdbotKitTests", + path: "Tests/MoltbotKitTests", swiftSettings: [ .enableUpcomingFeature("StrictConcurrency"), .enableExperimentalFeature("SwiftTesting"), diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/AssistantTextParser.swift b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/AssistantTextParser.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/AssistantTextParser.swift rename to apps/shared/MoltbotKit/Sources/MoltbotChatUI/AssistantTextParser.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatComposer.swift b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatComposer.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatComposer.swift rename to apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatComposer.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatMarkdownPreprocessor.swift b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatMarkdownPreprocessor.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatMarkdownPreprocessor.swift rename to apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatMarkdownPreprocessor.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatMarkdownRenderer.swift b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatMarkdownRenderer.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatMarkdownRenderer.swift rename to apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatMarkdownRenderer.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatMessageViews.swift b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatMessageViews.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatMessageViews.swift rename to apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatMessageViews.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatModels.swift b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatModels.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatModels.swift rename to apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatModels.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatPayloadDecoding.swift b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatPayloadDecoding.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatPayloadDecoding.swift rename to apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatPayloadDecoding.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatSessions.swift b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatSessions.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatSessions.swift rename to apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatSessions.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatSheets.swift b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatSheets.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatSheets.swift rename to apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatSheets.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatTheme.swift b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatTheme.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatTheme.swift rename to apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatTheme.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatTransport.swift b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatTransport.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatTransport.swift rename to apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatTransport.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatView.swift b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatView.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatView.swift rename to apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatView.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatViewModel.swift b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatViewModel.swift similarity index 99% rename from apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatViewModel.swift rename to apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatViewModel.swift index a3eff72f5..6ef6c71b6 100644 --- a/apps/shared/ClawdbotKit/Sources/ClawdbotChatUI/ChatViewModel.swift +++ b/apps/shared/MoltbotKit/Sources/MoltbotChatUI/ChatViewModel.swift @@ -10,7 +10,7 @@ import AppKit import UIKit #endif -private let chatUILogger = Logger(subsystem: "com.clawdbot", category: "MoltbotChatUI") +private let chatUILogger = Logger(subsystem: "bot.molt", category: "MoltbotChatUI") @MainActor @Observable diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/AnyCodable.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/AnyCodable.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/AnyCodable.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/AnyCodable.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/AsyncTimeout.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/AsyncTimeout.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/AsyncTimeout.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/AsyncTimeout.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/AudioStreamingProtocols.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/AudioStreamingProtocols.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/AudioStreamingProtocols.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/AudioStreamingProtocols.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/BonjourEscapes.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/BonjourEscapes.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/BonjourEscapes.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/BonjourEscapes.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/BonjourTypes.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/BonjourTypes.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/BonjourTypes.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/BonjourTypes.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/BridgeFrames.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/BridgeFrames.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/BridgeFrames.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/BridgeFrames.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/CameraCommands.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/CameraCommands.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/CameraCommands.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/CameraCommands.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/CanvasA2UIAction.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/CanvasA2UIAction.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/CanvasA2UIAction.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/CanvasA2UIAction.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/CanvasA2UICommands.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/CanvasA2UICommands.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/CanvasA2UICommands.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/CanvasA2UICommands.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/CanvasA2UIJSONL.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/CanvasA2UIJSONL.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/CanvasA2UIJSONL.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/CanvasA2UIJSONL.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/CanvasCommandParams.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/CanvasCommandParams.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/CanvasCommandParams.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/CanvasCommandParams.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/CanvasCommands.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/CanvasCommands.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/CanvasCommands.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/CanvasCommands.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/Capabilities.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/Capabilities.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/Capabilities.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/Capabilities.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/ClawdbotKitResources.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/ClawdbotKitResources.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/ClawdbotKitResources.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/ClawdbotKitResources.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/DeepLinks.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/DeepLinks.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/DeepLinks.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/DeepLinks.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/DeviceAuthStore.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/DeviceAuthStore.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/DeviceAuthStore.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/DeviceAuthStore.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/DeviceIdentity.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/DeviceIdentity.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/DeviceIdentity.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/DeviceIdentity.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/ElevenLabsKitShim.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/ElevenLabsKitShim.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/ElevenLabsKitShim.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/ElevenLabsKitShim.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayChannel.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayChannel.swift similarity index 99% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayChannel.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayChannel.swift index c1562b2d9..0ead3021c 100644 --- a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayChannel.swift +++ b/apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayChannel.swift @@ -109,7 +109,7 @@ private enum ConnectChallengeError: Error { } public actor GatewayChannelActor { - private let logger = Logger(subsystem: "com.clawdbot", category: "gateway") + private let logger = Logger(subsystem: "bot.molt", category: "gateway") private var task: WebSocketTaskBox? private var pending: [String: CheckedContinuation] = [:] private var connected = false diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayEndpointID.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayEndpointID.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayEndpointID.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayEndpointID.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayErrors.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayErrors.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayErrors.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayErrors.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayNodeSession.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayNodeSession.swift similarity index 99% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayNodeSession.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayNodeSession.swift index daf4397d1..570342ce4 100644 --- a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayNodeSession.swift +++ b/apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayNodeSession.swift @@ -12,7 +12,7 @@ private struct NodeInvokeRequestPayload: Codable, Sendable { } public actor GatewayNodeSession { - private let logger = Logger(subsystem: "com.clawdbot", category: "node.gateway") + private let logger = Logger(subsystem: "bot.molt", category: "node.gateway") private let decoder = JSONDecoder() private let encoder = JSONEncoder() private var channel: GatewayChannelActor? diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayPayloadDecoding.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayPayloadDecoding.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayPayloadDecoding.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayPayloadDecoding.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayPush.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayPush.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayPush.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayPush.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayTLSPinning.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayTLSPinning.swift similarity index 88% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayTLSPinning.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayTLSPinning.swift index f22505eff..4ce98603f 100644 --- a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/GatewayTLSPinning.swift +++ b/apps/shared/MoltbotKit/Sources/MoltbotKit/GatewayTLSPinning.swift @@ -17,17 +17,30 @@ public struct GatewayTLSParams: Sendable { } public enum GatewayTLSStore { - private static let suiteName = "com.clawdbot.shared" + private static let suiteName = "bot.molt.shared" + private static let legacySuiteName = "com.clawdbot.shared" private static let keyPrefix = "gateway.tls." private static var defaults: UserDefaults { UserDefaults(suiteName: suiteName) ?? .standard } + private static var legacyDefaults: UserDefaults? { + UserDefaults(suiteName: legacySuiteName) + } + public static func loadFingerprint(stableID: String) -> String? { let key = self.keyPrefix + stableID let raw = self.defaults.string(forKey: key)?.trimmingCharacters(in: .whitespacesAndNewlines) - return raw?.isEmpty == false ? raw : nil + if raw?.isEmpty == false { return raw } + + let legacy = self.legacyDefaults?.string(forKey: key)?.trimmingCharacters(in: .whitespacesAndNewlines) + if legacy?.isEmpty == false { + self.defaults.set(legacy, forKey: key) + return legacy + } + + return nil } public static func saveFingerprint(_ value: String, stableID: String) { diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/InstanceIdentity.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/InstanceIdentity.swift similarity index 86% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/InstanceIdentity.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/InstanceIdentity.swift index e1a52ff39..cbc824329 100644 --- a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/InstanceIdentity.swift +++ b/apps/shared/MoltbotKit/Sources/MoltbotKit/InstanceIdentity.swift @@ -5,13 +5,18 @@ import UIKit #endif public enum InstanceIdentity { - private static let suiteName = "com.clawdbot.shared" + private static let suiteName = "bot.molt.shared" + private static let legacySuiteName = "com.clawdbot.shared" private static let instanceIdKey = "instanceId" private static var defaults: UserDefaults { UserDefaults(suiteName: suiteName) ?? .standard } + private static var legacyDefaults: UserDefaults? { + UserDefaults(suiteName: legacySuiteName) + } + #if canImport(UIKit) private static func readMainActor(_ body: @MainActor () -> T) -> T { if Thread.isMainThread { @@ -32,6 +37,14 @@ public enum InstanceIdentity { return existing } + if let legacy = Self.legacyDefaults?.string(forKey: instanceIdKey)? + .trimmingCharacters(in: .whitespacesAndNewlines), + !legacy.isEmpty + { + defaults.set(legacy, forKey: instanceIdKey) + return legacy + } + let id = UUID().uuidString.lowercased() defaults.set(id, forKey: instanceIdKey) return id diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/JPEGTranscoder.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/JPEGTranscoder.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/JPEGTranscoder.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/JPEGTranscoder.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/LocationCommands.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/LocationCommands.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/LocationCommands.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/LocationCommands.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/LocationSettings.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/LocationSettings.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/LocationSettings.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/LocationSettings.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/NodeError.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/NodeError.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/NodeError.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/NodeError.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/Resources/CanvasScaffold/scaffold.html b/apps/shared/MoltbotKit/Sources/MoltbotKit/Resources/CanvasScaffold/scaffold.html similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/Resources/CanvasScaffold/scaffold.html rename to apps/shared/MoltbotKit/Sources/MoltbotKit/Resources/CanvasScaffold/scaffold.html diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/Resources/tool-display.json b/apps/shared/MoltbotKit/Sources/MoltbotKit/Resources/tool-display.json similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/Resources/tool-display.json rename to apps/shared/MoltbotKit/Sources/MoltbotKit/Resources/tool-display.json diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/ScreenCommands.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/ScreenCommands.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/ScreenCommands.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/ScreenCommands.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/StoragePaths.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/StoragePaths.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/StoragePaths.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/StoragePaths.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/SystemCommands.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/SystemCommands.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/SystemCommands.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/SystemCommands.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/TalkDirective.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/TalkDirective.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/TalkDirective.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/TalkDirective.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/TalkHistoryTimestamp.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/TalkHistoryTimestamp.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/TalkHistoryTimestamp.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/TalkHistoryTimestamp.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/TalkPromptBuilder.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/TalkPromptBuilder.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/TalkPromptBuilder.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/TalkPromptBuilder.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/TalkSystemSpeechSynthesizer.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/TalkSystemSpeechSynthesizer.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/TalkSystemSpeechSynthesizer.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/TalkSystemSpeechSynthesizer.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/ToolDisplay.swift b/apps/shared/MoltbotKit/Sources/MoltbotKit/ToolDisplay.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotKit/ToolDisplay.swift rename to apps/shared/MoltbotKit/Sources/MoltbotKit/ToolDisplay.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotProtocol/AnyCodable.swift b/apps/shared/MoltbotKit/Sources/MoltbotProtocol/AnyCodable.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotProtocol/AnyCodable.swift rename to apps/shared/MoltbotKit/Sources/MoltbotProtocol/AnyCodable.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotProtocol/GatewayModels.swift b/apps/shared/MoltbotKit/Sources/MoltbotProtocol/GatewayModels.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotProtocol/GatewayModels.swift rename to apps/shared/MoltbotKit/Sources/MoltbotProtocol/GatewayModels.swift diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotProtocol/WizardHelpers.swift b/apps/shared/MoltbotKit/Sources/MoltbotProtocol/WizardHelpers.swift similarity index 100% rename from apps/shared/ClawdbotKit/Sources/ClawdbotProtocol/WizardHelpers.swift rename to apps/shared/MoltbotKit/Sources/MoltbotProtocol/WizardHelpers.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/AssistantTextParserTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/AssistantTextParserTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/AssistantTextParserTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/AssistantTextParserTests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/BonjourEscapesTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/BonjourEscapesTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/BonjourEscapesTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/BonjourEscapesTests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/CanvasA2UIActionTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/CanvasA2UIActionTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/CanvasA2UIActionTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/CanvasA2UIActionTests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/CanvasA2UITests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/CanvasA2UITests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/CanvasA2UITests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/CanvasA2UITests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/CanvasSnapshotFormatTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/CanvasSnapshotFormatTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/CanvasSnapshotFormatTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/CanvasSnapshotFormatTests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/ChatMarkdownPreprocessorTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/ChatMarkdownPreprocessorTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/ChatMarkdownPreprocessorTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/ChatMarkdownPreprocessorTests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/ChatThemeTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/ChatThemeTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/ChatThemeTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/ChatThemeTests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/ChatViewModelTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/ChatViewModelTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/ChatViewModelTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/ChatViewModelTests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/ElevenLabsTTSValidationTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/ElevenLabsTTSValidationTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/ElevenLabsTTSValidationTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/ElevenLabsTTSValidationTests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/GatewayNodeSessionTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/GatewayNodeSessionTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/GatewayNodeSessionTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/GatewayNodeSessionTests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/JPEGTranscoderTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/JPEGTranscoderTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/JPEGTranscoderTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/JPEGTranscoderTests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/TalkDirectiveTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/TalkDirectiveTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/TalkDirectiveTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/TalkDirectiveTests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/TalkHistoryTimestampTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/TalkHistoryTimestampTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/TalkHistoryTimestampTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/TalkHistoryTimestampTests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/TalkPromptBuilderTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/TalkPromptBuilderTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/TalkPromptBuilderTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/TalkPromptBuilderTests.swift diff --git a/apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/ToolDisplayRegistryTests.swift b/apps/shared/MoltbotKit/Tests/MoltbotKitTests/ToolDisplayRegistryTests.swift similarity index 100% rename from apps/shared/ClawdbotKit/Tests/ClawdbotKitTests/ToolDisplayRegistryTests.swift rename to apps/shared/MoltbotKit/Tests/MoltbotKitTests/ToolDisplayRegistryTests.swift diff --git a/apps/shared/ClawdbotKit/Tools/CanvasA2UI/bootstrap.js b/apps/shared/MoltbotKit/Tools/CanvasA2UI/bootstrap.js similarity index 100% rename from apps/shared/ClawdbotKit/Tools/CanvasA2UI/bootstrap.js rename to apps/shared/MoltbotKit/Tools/CanvasA2UI/bootstrap.js diff --git a/apps/shared/ClawdbotKit/Tools/CanvasA2UI/rolldown.config.mjs b/apps/shared/MoltbotKit/Tools/CanvasA2UI/rolldown.config.mjs similarity index 100% rename from apps/shared/ClawdbotKit/Tools/CanvasA2UI/rolldown.config.mjs rename to apps/shared/MoltbotKit/Tools/CanvasA2UI/rolldown.config.mjs diff --git a/docs/cli/acp.md b/docs/cli/acp.md index da2de00b3..a7cb0e1d6 100644 --- a/docs/cli/acp.md +++ b/docs/cli/acp.md @@ -42,7 +42,7 @@ moltbot acp client moltbot acp client --server-args --url wss://gateway-host:18789 --token # Override the server command (default: moltbot) -moltbot acp client --server "node" --server-args dist/entry.js acp --url ws://127.0.0.1:19001 +moltbot acp client --server "node" --server-args moltbot.mjs acp --url ws://127.0.0.1:19001 ``` ## How to use this diff --git a/docs/cli/security.md b/docs/cli/security.md index 662181616..551debc99 100644 --- a/docs/cli/security.md +++ b/docs/cli/security.md @@ -20,5 +20,5 @@ moltbot security audit --deep moltbot security audit --fix ``` -The audit warns when multiple DM senders share the main session and recommends `session.dmScope="per-channel-peer"` for shared inboxes. +The audit warns when multiple DM senders share the main session and recommends `session.dmScope="per-channel-peer"` (or `per-account-channel-peer` for multi-account channels) for shared inboxes. It also warns when small models (`<=300B`) are used without sandboxing and with web/browser tools enabled. diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md index ef27fc9e3..9dbb984fc 100644 --- a/docs/concepts/model-providers.md +++ b/docs/concepts/model-providers.md @@ -130,9 +130,10 @@ Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider: - Provider: `moonshot` - Auth: `MOONSHOT_API_KEY` -- Example model: `moonshot/kimi-k2-0905-preview` +- Example model: `moonshot/kimi-k2.5` - Kimi K2 model IDs: {/* moonshot-kimi-k2-model-refs:start */} + - `moonshot/kimi-k2.5` - `moonshot/kimi-k2-0905-preview` - `moonshot/kimi-k2-turbo-preview` - `moonshot/kimi-k2-thinking` @@ -141,7 +142,7 @@ Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider: ```json5 { agents: { - defaults: { model: { primary: "moonshot/kimi-k2-0905-preview" } } + defaults: { model: { primary: "moonshot/kimi-k2.5" } } }, models: { mode: "merge", @@ -150,7 +151,7 @@ Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider: baseUrl: "https://api.moonshot.ai/v1", apiKey: "${MOONSHOT_API_KEY}", api: "openai-completions", - models: [{ id: "kimi-k2-0905-preview", name: "Kimi K2 0905 Preview" }] + models: [{ id: "kimi-k2.5", name: "Kimi K2.5" }] } } } diff --git a/docs/concepts/session.md b/docs/concepts/session.md index 58ac57145..b15b1a1ea 100644 --- a/docs/concepts/session.md +++ b/docs/concepts/session.md @@ -11,7 +11,8 @@ Use `session.dmScope` to control how **direct messages** are grouped: - `main` (default): all DMs share the main session for continuity. - `per-peer`: isolate by sender id across channels. - `per-channel-peer`: isolate by channel + sender (recommended for multi-user inboxes). -Use `session.identityLinks` to map provider-prefixed peer ids to a canonical identity so the same person shares a DM session across channels when using `per-peer` or `per-channel-peer`. +- `per-account-channel-peer`: isolate by account + channel + sender (recommended for multi-account inboxes). +Use `session.identityLinks` to map provider-prefixed peer ids to a canonical identity so the same person shares a DM session across channels when using `per-peer`, `per-channel-peer`, or `per-account-channel-peer`. ## Gateway is the source of truth All session state is **owned by the gateway** (the “master” Moltbot). UI clients (macOS app, WebChat, etc.) must query the gateway for session lists and token counts instead of reading local files. @@ -44,6 +45,7 @@ the workspace is writable. See [Memory](/concepts/memory) and - Multiple phone numbers and channels can map to the same agent main key; they act as transports into one conversation. - `per-peer`: `agent::dm:`. - `per-channel-peer`: `agent:::dm:`. + - `per-account-channel-peer`: `agent::::dm:` (accountId defaults to `default`). - If `session.identityLinks` matches a provider-prefixed peer id (for example `telegram:123`), the canonical key replaces `` so the same person shares a session across channels. - Group chats isolate state: `agent:::group:` (rooms/channels use `agent:::channel:`). - Telegram forum topics append `:topic:` to the group id for isolation. @@ -94,7 +96,7 @@ Send these as standalone messages so they register. { session: { scope: "per-sender", // keep group keys separate - dmScope: "main", // DM continuity (set per-channel-peer for shared inboxes) + dmScope: "main", // DM continuity (set per-channel-peer/per-account-channel-peer for shared inboxes) identityLinks: { alice: ["telegram:123456789", "discord:987654321012345678"] }, diff --git a/docs/concepts/typebox.md b/docs/concepts/typebox.md index 5ee4346cd..892ba1b2d 100644 --- a/docs/concepts/typebox.md +++ b/docs/concepts/typebox.md @@ -58,7 +58,7 @@ Authoritative list lives in `src/gateway/server.ts` (`METHODS`, `EVENTS`). - Server handshake + method dispatch: `src/gateway/server.ts` - Node client: `src/gateway/client.ts` - Generated JSON Schema: `dist/protocol.schema.json` -- Generated Swift models: `apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift` +- Generated Swift models: `apps/macos/Sources/MoltbotProtocol/GatewayModels.swift` ## Current pipeline diff --git a/docs/debug/node-issue.md b/docs/debug/node-issue.md index c71b903f3..a549ad51b 100644 --- a/docs/debug/node-issue.md +++ b/docs/debug/node-issue.md @@ -55,9 +55,9 @@ node --import tsx scripts/repro/tsx-name-repro.ts - Use Node + tsc watch, then run compiled output: ```bash pnpm exec tsc --watch --preserveWatchOutput - node --watch dist/entry.js status + node --watch moltbot.mjs status ``` -- Confirmed locally: `pnpm exec tsc -p tsconfig.json` + `node dist/entry.js status` works on Node 25. +- Confirmed locally: `pnpm exec tsc -p tsconfig.json` + `node moltbot.mjs status` works on Node 25. - Disable esbuild keepNames in the TS loader if possible (prevents `__name` helper insertion); tsx does not currently expose this. - Test Node LTS (22/24) with `tsx` to see if the issue is Node 25–specific. diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index f5438fb46..1d270974d 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -2396,8 +2396,8 @@ Use Moonshot's OpenAI-compatible endpoint: env: { MOONSHOT_API_KEY: "sk-..." }, agents: { defaults: { - model: { primary: "moonshot/kimi-k2-0905-preview" }, - models: { "moonshot/kimi-k2-0905-preview": { alias: "Kimi K2" } } + model: { primary: "moonshot/kimi-k2.5" }, + models: { "moonshot/kimi-k2.5": { alias: "Kimi K2.5" } } } }, models: { @@ -2409,8 +2409,8 @@ Use Moonshot's OpenAI-compatible endpoint: api: "openai-completions", models: [ { - id: "kimi-k2-0905-preview", - name: "Kimi K2 0905 Preview", + id: "kimi-k2.5", + name: "Kimi K2.5", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, @@ -2426,7 +2426,7 @@ Use Moonshot's OpenAI-compatible endpoint: Notes: - Set `MOONSHOT_API_KEY` in the environment or use `moltbot onboard --auth-choice moonshot-api-key`. -- Model ref: `moonshot/kimi-k2-0905-preview`. +- Model ref: `moonshot/kimi-k2.5`. - Use `https://api.moonshot.cn/v1` if you need the China endpoint. ### Kimi Code @@ -2657,7 +2657,8 @@ Fields: - `main`: all DMs share the main session for continuity. - `per-peer`: isolate DMs by sender id across channels. - `per-channel-peer`: isolate DMs per channel + sender (recommended for multi-user inboxes). -- `identityLinks`: map canonical ids to provider-prefixed peers so the same person shares a DM session across channels when using `per-peer` or `per-channel-peer`. + - `per-account-channel-peer`: isolate DMs per account + channel + sender (recommended for multi-account inboxes). +- `identityLinks`: map canonical ids to provider-prefixed peers so the same person shares a DM session across channels when using `per-peer`, `per-channel-peer`, or `per-account-channel-peer`. - Example: `alice: ["telegram:123456789", "discord:987654321012345678"]`. - `reset`: primary reset policy. Defaults to daily resets at 4:00 AM local time on the gateway host. - `mode`: `daily` or `idle` (default: `daily` when `reset` is present). diff --git a/docs/gateway/index.md b/docs/gateway/index.md index 65ef3d61d..8c5be0fa8 100644 --- a/docs/gateway/index.md +++ b/docs/gateway/index.md @@ -56,7 +56,7 @@ Usually unnecessary: one Gateway can serve multiple messaging channels and agent Supported if you isolate state + config and use unique ports. Full guide: [Multiple gateways](/gateway/multiple-gateways). Service names are profile-aware: -- macOS: `com.clawdbot.` +- macOS: `bot.molt.` (legacy `com.clawdbot.*` may still exist) - Linux: `moltbot-gateway-.service` - Windows: `Moltbot Gateway ()` @@ -181,8 +181,8 @@ See also: [Presence](/concepts/presence) for how presence is produced/deduped an - StandardOut/Err: file paths or `syslog` - On failure, launchd restarts; fatal misconfig should keep exiting so the operator notices. - LaunchAgents are per-user and require a logged-in session; for headless setups use a custom LaunchDaemon (not shipped). - - `moltbot gateway install` writes `~/Library/LaunchAgents/com.clawdbot.gateway.plist` - (or `com.clawdbot..plist`). + - `moltbot gateway install` writes `~/Library/LaunchAgents/bot.molt.gateway.plist` + (or `bot.molt..plist`; legacy `com.clawdbot.*` is cleaned up). - `moltbot doctor` audits the LaunchAgent config and can update it to current defaults. ## Gateway service management (CLI) @@ -213,11 +213,11 @@ Notes: Bundled mac app: - Moltbot.app can bundle a Node-based gateway relay and install a per-user LaunchAgent labeled - `com.clawdbot.gateway` (or `com.clawdbot.`). -- To stop it cleanly, use `moltbot gateway stop` (or `launchctl bootout gui/$UID/com.clawdbot.gateway`). -- To restart, use `moltbot gateway restart` (or `launchctl kickstart -k gui/$UID/com.clawdbot.gateway`). + `bot.molt.gateway` (or `bot.molt.`; legacy `com.clawdbot.*` labels still unload cleanly). +- To stop it cleanly, use `moltbot gateway stop` (or `launchctl bootout gui/$UID/bot.molt.gateway`). +- To restart, use `moltbot gateway restart` (or `launchctl kickstart -k gui/$UID/bot.molt.gateway`). - `launchctl` only works if the LaunchAgent is installed; otherwise use `moltbot gateway install` first. - - Replace the label with `com.clawdbot.` when running a named profile. + - Replace the label with `bot.molt.` when running a named profile. ## Supervision (systemd user unit) Moltbot installs a **systemd user service** by default on Linux/WSL2. We diff --git a/docs/gateway/remote-gateway-readme.md b/docs/gateway/remote-gateway-readme.md index f0923ba7c..b48a746d7 100644 --- a/docs/gateway/remote-gateway-readme.md +++ b/docs/gateway/remote-gateway-readme.md @@ -82,7 +82,7 @@ To have the SSH tunnel start automatically when you log in, create a Launch Agen ### Create the PLIST file -Save this as `~/Library/LaunchAgents/com.clawdbot.ssh-tunnel.plist`: +Save this as `~/Library/LaunchAgents/bot.molt.ssh-tunnel.plist`: ```xml @@ -90,7 +90,7 @@ Save this as `~/Library/LaunchAgents/com.clawdbot.ssh-tunnel.plist`: Label - com.clawdbot.ssh-tunnel + bot.molt.ssh-tunnel ProgramArguments /usr/bin/ssh @@ -108,7 +108,7 @@ Save this as `~/Library/LaunchAgents/com.clawdbot.ssh-tunnel.plist`: ### Load the Launch Agent ```bash -launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.clawdbot.ssh-tunnel.plist +launchctl bootstrap gui/$UID ~/Library/LaunchAgents/bot.molt.ssh-tunnel.plist ``` The tunnel will now: @@ -116,6 +116,8 @@ The tunnel will now: - Restart if it crashes - Keep running in the background +Legacy note: remove any leftover `com.clawdbot.ssh-tunnel` LaunchAgent if present. + --- ## Troubleshooting @@ -130,13 +132,13 @@ lsof -i :18789 **Restart the tunnel:** ```bash -launchctl kickstart -k gui/$UID/com.clawdbot.ssh-tunnel +launchctl kickstart -k gui/$UID/bot.molt.ssh-tunnel ``` **Stop the tunnel:** ```bash -launchctl bootout gui/$UID/com.clawdbot.ssh-tunnel +launchctl bootout gui/$UID/bot.molt.ssh-tunnel ``` --- diff --git a/docs/gateway/security/formal-verification.md b/docs/gateway/security/formal-verification.md index 3d41aed06..f5c6bbbb4 100644 --- a/docs/gateway/security/formal-verification.md +++ b/docs/gateway/security/formal-verification.md @@ -1,13 +1,15 @@ --- title: Formal Verification (Security Models) summary: Machine-checked security models for Moltbot’s highest-risk paths. -permalink: /gateway/security/formal-verification/ +permalink: /security/formal-verification/ --- # Formal Verification (Security Models) This page tracks Moltbot’s **formal security models** (TLA+/TLC today; more as needed). +> Note: some older links may refer to the previous project name. + **Goal (north star):** provide a machine-checked argument that Moltbot enforces its intended security policy (authorization, session isolation, tool gating, and misconfiguration safety), under explicit assumptions. @@ -20,7 +22,7 @@ misconfiguration safety), under explicit assumptions. ## Where the models live -Models are maintained in a separate repo: [vignesh07/moltbot-formal-models](https://github.com/vignesh07/moltbot-formal-models). +Models are maintained in a separate repo: [vignesh07/clawdbot-formal-models](https://github.com/vignesh07/clawdbot-formal-models). ## Important caveats @@ -37,8 +39,8 @@ Today, results are reproduced by cloning the models repo locally and running TLC Getting started: ```bash -git clone https://github.com/vignesh07/moltbot-formal-models -cd moltbot-formal-models +git clone https://github.com/vignesh07/clawdbot-formal-models +cd clawdbot-formal-models # Java 11+ required (TLC runs on the JVM). # The repo vendors a pinned `tla2tools.jar` (TLA+ tools) and provides `bin/tlc` + Make targets. @@ -98,10 +100,61 @@ See also: `docs/gateway-exposure-matrix.md` in the models repo. - Red (expected): - `make routing-isolation-negative` -## Roadmap -Next models to deepen fidelity: -- Pairing store concurrency/locking/idempotency -- Provider-specific ingress preflight modeling -- Routing identity-links + dmScope variants + binding precedence -- Gateway auth conformance (proxy/tailscale specifics) +## v1++: additional bounded models (concurrency, retries, trace correctness) + +These are follow-on models that tighten fidelity around real-world failure modes (non-atomic updates, retries, and message fan-out). + +### Pairing store concurrency / idempotency + +**Claim:** a pairing store should enforce `MaxPending` and idempotency even under interleavings (i.e., “check-then-write” must be atomic / locked; refresh shouldn’t create duplicates). + +What it means: +- Under concurrent requests, you can’t exceed `MaxPending` for a channel. +- Repeated requests/refreshes for the same `(channel, sender)` should not create duplicate live pending rows. + +- Green runs: + - `make pairing-race` (atomic/locked cap check) + - `make pairing-idempotency` + - `make pairing-refresh` + - `make pairing-refresh-race` +- Red (expected): + - `make pairing-race-negative` (non-atomic begin/commit cap race) + - `make pairing-idempotency-negative` + - `make pairing-refresh-negative` + - `make pairing-refresh-race-negative` + +### Ingress trace correlation / idempotency + +**Claim:** ingestion should preserve trace correlation across fan-out and be idempotent under provider retries. + +What it means: +- When one external event becomes multiple internal messages, every part keeps the same trace/event identity. +- Retries do not result in double-processing. +- If provider event IDs are missing, dedupe falls back to a safe key (e.g., trace ID) to avoid dropping distinct events. + +- Green: + - `make ingress-trace` + - `make ingress-trace2` + - `make ingress-idempotency` + - `make ingress-dedupe-fallback` +- Red (expected): + - `make ingress-trace-negative` + - `make ingress-trace2-negative` + - `make ingress-idempotency-negative` + - `make ingress-dedupe-fallback-negative` + +### Routing dmScope precedence + identityLinks + +**Claim:** routing must keep DM sessions isolated by default, and only collapse sessions when explicitly configured (channel precedence + identity links). + +What it means: +- Channel-specific dmScope overrides must win over global defaults. +- identityLinks should collapse only within explicit linked groups, not across unrelated peers. + +- Green: + - `make routing-precedence` + - `make routing-identitylinks` +- Red (expected): + - `make routing-precedence-negative` + - `make routing-identitylinks-negative` diff --git a/docs/gateway/security/index.md b/docs/gateway/security/index.md index e3c85af7f..a5d841c18 100644 --- a/docs/gateway/security/index.md +++ b/docs/gateway/security/index.md @@ -5,7 +5,7 @@ read_when: --- # Security 🔒 -## Quick check: `moltbot security audit` +## Quick check: `moltbot security audit` (formerly `clawdbot security audit`) See also: [Formal Verification (Security Models)](/security/formal-verification/) @@ -15,6 +15,8 @@ Run this regularly (especially after changing config or exposing network surface moltbot security audit moltbot security audit --deep moltbot security audit --fix + +# (On older installs, the command is `clawdbot ...`.) ``` It flags common footguns (Gateway auth exposure, browser control exposure, elevated allowlists, filesystem permissions). @@ -22,7 +24,7 @@ It flags common footguns (Gateway auth exposure, browser control exposure, eleva `--fix` applies safe guardrails: - Tighten `groupPolicy="open"` to `groupPolicy="allowlist"` (and per-account variants) for common channels. - Turn `logging.redactSensitive="off"` back to `"tools"`. -- Tighten local perms (`~/.clawdbot` → `700`, config file → `600`, plus common state files like `credentials/*.json`, `agents/*/agent/auth-profiles.json`, and `agents/*/sessions/sessions.json`). +- Tighten local perms (`~/.moltbot` → `700`, config file → `600`, plus common state files like `credentials/*.json`, `agents/*/agent/auth-profiles.json`, and `agents/*/sessions/sessions.json`). Running an AI agent with shell access on your machine is... *spicy*. Here’s how to not get pwned. @@ -49,13 +51,13 @@ If you run `--deep`, Moltbot also attempts a best-effort live Gateway probe. Use this when auditing access or deciding what to back up: -- **WhatsApp**: `~/.clawdbot/credentials/whatsapp//creds.json` +- **WhatsApp**: `~/.moltbot/credentials/whatsapp//creds.json` - **Telegram bot token**: config/env or `channels.telegram.tokenFile` - **Discord bot token**: config/env (token file not yet supported) - **Slack tokens**: config/env (`channels.slack.*`) -- **Pairing allowlists**: `~/.clawdbot/credentials/-allowFrom.json` -- **Model auth profiles**: `~/.clawdbot/agents//agent/auth-profiles.json` -- **Legacy OAuth import**: `~/.clawdbot/credentials/oauth.json` +- **Pairing allowlists**: `~/.moltbot/credentials/-allowFrom.json` +- **Model auth profiles**: `~/.moltbot/agents//agent/auth-profiles.json` +- **Legacy OAuth import**: `~/.moltbot/credentials/oauth.json` ## Security Audit Checklist @@ -100,10 +102,10 @@ When `trustedProxies` is configured, the Gateway will use `X-Forwarded-For` head ## Local session logs live on disk -Moltbot stores session transcripts on disk under `~/.clawdbot/agents//sessions/*.jsonl`. +Moltbot stores session transcripts on disk under `~/.moltbot/agents//sessions/*.jsonl`. This is required for session continuity and (optionally) session memory indexing, but it also means **any process/user with filesystem access can read those logs**. Treat disk access as the trust -boundary and lock down permissions on `~/.clawdbot` (see the audit section below). If you need +boundary and lock down permissions on `~/.moltbot` (see the audit section below). If you need stronger isolation between agents, run them under separate OS users or separate hosts. ## Node execution (system.run) @@ -163,7 +165,7 @@ Plugins run **in-process** with the Gateway. Treat them as trusted code: - Review plugin config before enabling. - Restart the Gateway after plugin changes. - If you install plugins from npm (`moltbot plugins install `), treat it like running untrusted code: - - The install path is `~/.clawdbot/extensions//` (or `$CLAWDBOT_STATE_DIR/extensions//`). + - The install path is `~/.moltbot/extensions//` (or `$CLAWDBOT_STATE_DIR/extensions//`). - Moltbot uses `npm pack` and then runs `npm install --omit=dev` in that directory (npm lifecycle scripts can execute code during install). - Prefer pinned, exact versions (`@scope/pkg@1.2.3`), and inspect the unpacked code on disk before enabling. @@ -197,14 +199,14 @@ By default, Moltbot routes **all DMs into the main session** so your assistant h } ``` -This prevents cross-user context leakage while keeping group chats isolated. If the same person contacts you on multiple channels, use `session.identityLinks` to collapse those DM sessions into one canonical identity. See [Session Management](/concepts/session) and [Configuration](/gateway/configuration). +This prevents cross-user context leakage while keeping group chats isolated. If you run multiple accounts on the same channel, use `per-account-channel-peer` instead. If the same person contacts you on multiple channels, use `session.identityLinks` to collapse those DM sessions into one canonical identity. See [Session Management](/concepts/session) and [Configuration](/gateway/configuration). ## Allowlists (DM + groups) — terminology Moltbot has two separate “who can trigger me?” layers: - **DM allowlist** (`allowFrom` / `channels.discord.dm.allowFrom` / `channels.slack.dm.allowFrom`): who is allowed to talk to the bot in direct messages. - - When `dmPolicy="pairing"`, approvals are written to `~/.clawdbot/credentials/-allowFrom.json` (merged with config allowlists). + - When `dmPolicy="pairing"`, approvals are written to `~/.moltbot/credentials/-allowFrom.json` (merged with config allowlists). - **Group allowlist** (channel-specific): which groups/channels/guilds the bot will accept messages from at all. - Common patterns: - `channels.whatsapp.groups`, `channels.telegram.groups`, `channels.imessage.groups`: per-group defaults like `requireMention`; when set, it also acts as a group allowlist (include `"*"` to keep allow-all behavior). @@ -231,7 +233,7 @@ Red flags to treat as untrusted: - “Read this file/URL and do exactly what it says.” - “Ignore your system prompt or safety rules.” - “Reveal your hidden instructions or tool outputs.” -- “Paste the full contents of ~/.clawdbot or your logs.” +- “Paste the full contents of ~/.moltbot or your logs.” ### Prompt injection does not require public DMs @@ -308,8 +310,8 @@ This is social engineering 101. Create distrust, encourage snooping. ### 0) File permissions Keep config + state private on the gateway host: -- `~/.clawdbot/moltbot.json`: `600` (user read/write only) -- `~/.clawdbot`: `700` (user only) +- `~/.moltbot/moltbot.json`: `600` (user read/write only) +- `~/.moltbot`: `700` (user only) `moltbot doctor` can warn and offer to tighten these permissions. @@ -448,7 +450,7 @@ Avoid: ### 0.7) Secrets on disk (what’s sensitive) -Assume anything under `~/.clawdbot/` (or `$CLAWDBOT_STATE_DIR/`) may contain secrets or private data: +Assume anything under `~/.moltbot/` (or `$CLAWDBOT_STATE_DIR/`) may contain secrets or private data: - `moltbot.json`: config may include tokens (gateway, remote gateway), provider settings, and allowlists. - `credentials/**`: channel credentials (example: WhatsApp creds), pairing allowlists, legacy OAuth imports. @@ -572,9 +574,6 @@ If that browser profile already contains logged-in sessions, the model can access those accounts and data. Treat browser profiles as **sensitive state**: - Prefer a dedicated profile for the agent (the default `clawd` profile). - Avoid pointing the agent at your personal daily-driver profile. -- `act:evaluate` and `wait --fn` run arbitrary JavaScript in the page context. - Prompt injection can steer the model into calling them. If you do not need - them, set `browser.evaluateEnabled=false` (see [Configuration](/gateway/configuration#browser-clawd-managed-browser)). - Keep host browser control disabled for sandboxed agents unless you trust them. - Treat browser downloads as untrusted input; prefer an isolated downloads directory. - Disable browser sync/password managers in the agent profile if possible (reduces blast radius). @@ -691,7 +690,7 @@ If your AI does something bad: ### Audit 1. Check Gateway logs: `/tmp/moltbot/moltbot-YYYY-MM-DD.log` (or `logging.file`). -2. Review the relevant transcript(s): `~/.clawdbot/agents//sessions/*.jsonl`. +2. Review the relevant transcript(s): `~/.moltbot/agents//sessions/*.jsonl`. 3. Review recent config changes (anything that could have widened access: `gateway.bind`, `gateway.auth`, dm/group policies, `tools.elevated`, plugin changes). ### Collect for a report @@ -750,7 +749,7 @@ Mario asking for find ~ Found a vulnerability in Moltbot? Please report responsibly: -1. Email: security@molt.bot +1. Email: security@clawd.bot 2. Don't post publicly until fixed 3. We'll credit you (unless you prefer anonymity) diff --git a/docs/gateway/troubleshooting.md b/docs/gateway/troubleshooting.md index 48c14318f..a4b0b151d 100644 --- a/docs/gateway/troubleshooting.md +++ b/docs/gateway/troubleshooting.md @@ -576,7 +576,7 @@ If the app disappears or shows "Abort trap 6" when you click "Allow" on a privac **Fix 1: Reset TCC Cache** ```bash -tccutil reset All com.clawdbot.mac.debug +tccutil reset All bot.molt.mac.debug ``` **Fix 2: Force New Bundle ID** @@ -591,7 +591,7 @@ If the gateway is supervised by launchd, killing the PID will just respawn it. S ```bash moltbot gateway status moltbot gateway stop -# Or: launchctl bootout gui/$UID/com.clawdbot.gateway (replace with com.clawdbot. if needed) +# Or: launchctl bootout gui/$UID/bot.molt.gateway (replace with bot.molt.; legacy com.clawdbot.* still works) ``` **Fix 2: Port is busy (find the listener)** diff --git a/docs/help/faq.md b/docs/help/faq.md index 0766f64b4..7372a4997 100644 --- a/docs/help/faq.md +++ b/docs/help/faq.md @@ -1026,7 +1026,7 @@ Docs: [Cron jobs](/automation/cron-jobs), [Cron vs Heartbeat](/automation/cron-v **Can I run Apple macOS only skills from Linux** -Not directly. macOS skills are gated by `metadata.clawdbot.os` plus required binaries, and skills only appear in the system prompt when they are eligible on the **Gateway host**. On Linux, `darwin`-only skills (like `imsg`, `apple-notes`, `apple-reminders`) will not load unless you override the gating. +Not directly. macOS skills are gated by `metadata.moltbot.os` plus required binaries, and skills only appear in the system prompt when they are eligible on the **Gateway host**. On Linux, `darwin`-only skills (like `imsg`, `apple-notes`, `apple-reminders`) will not load unless you override the gating. You have three supported patterns: @@ -2328,7 +2328,7 @@ Quick setup (recommended): - Set a unique `gateway.port` in each profile config (or pass `--port` for manual runs). - Install a per-profile service: `moltbot --profile gateway install`. -Profiles also suffix service names (`com.clawdbot.`, `moltbot-gateway-.service`, `Moltbot Gateway ()`). +Profiles also suffix service names (`bot.molt.`; legacy `com.clawdbot.*`, `moltbot-gateway-.service`, `Moltbot Gateway ()`). Full guide: [Multiple gateways](/gateway/multiple-gateways). ### What does invalid handshake code 1008 mean diff --git a/docs/hooks.md b/docs/hooks.md index fddab384c..8576146ba 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -149,7 +149,7 @@ No configuration needed. ### Metadata Fields -The `metadata.clawdbot` object supports: +The `metadata.moltbot` object supports: - **`emoji`**: Display emoji for CLI (e.g., `"💾"`) - **`events`**: Array of events to listen for (e.g., `["command:new", "command:reset"]`) diff --git a/docs/install/nix.md b/docs/install/nix.md index ee2e09997..b67677423 100644 --- a/docs/install/nix.md +++ b/docs/install/nix.md @@ -57,7 +57,7 @@ On macOS, the GUI app does not automatically inherit shell env vars. You can also enable Nix mode via defaults: ```bash -defaults write com.clawdbot.mac moltbot.nixMode -bool true +defaults write bot.molt.mac moltbot.nixMode -bool true ``` ### Config + state paths diff --git a/docs/install/uninstall.md b/docs/install/uninstall.md index 8d8be7a11..f3a180caa 100644 --- a/docs/install/uninstall.md +++ b/docs/install/uninstall.md @@ -78,14 +78,14 @@ Use this if the gateway service keeps running but `moltbot` is missing. ### macOS (launchd) -Default label is `com.clawdbot.gateway` (or `com.clawdbot.`): +Default label is `bot.molt.gateway` (or `bot.molt.`; legacy `com.clawdbot.*` may still exist): ```bash -launchctl bootout gui/$UID/com.clawdbot.gateway -rm -f ~/Library/LaunchAgents/com.clawdbot.gateway.plist +launchctl bootout gui/$UID/bot.molt.gateway +rm -f ~/Library/LaunchAgents/bot.molt.gateway.plist ``` -If you used a profile, replace the label and plist name with `com.clawdbot.`. +If you used a profile, replace the label and plist name with `bot.molt.`. Remove any legacy `com.clawdbot.*` plists if present. ### Linux (systemd user unit) diff --git a/docs/install/updating.md b/docs/install/updating.md index 8ee27f7ad..12303cb2a 100644 --- a/docs/install/updating.md +++ b/docs/install/updating.md @@ -125,7 +125,7 @@ moltbot health ``` Notes: -- `pnpm build` matters when you run the packaged `moltbot` binary ([`dist/entry.js`](https://github.com/moltbot/moltbot/blob/main/dist/entry.js)) or use Node to run `dist/`. +- `pnpm build` matters when you run the packaged `moltbot` binary ([`moltbot.mjs`](https://github.com/moltbot/moltbot/blob/main/moltbot.mjs)) or use Node to run `dist/`. - If you run from a repo checkout without a global install, use `pnpm moltbot ...` for CLI commands. - If you run directly from TypeScript (`pnpm moltbot ...`), a rebuild is usually unnecessary, but **config migrations still apply** → run doctor. - Switching between global and git installs is easy: install the other flavor, then run `moltbot doctor` so the gateway service entrypoint is rewritten to the current install. @@ -158,7 +158,7 @@ moltbot logs --follow ``` If you’re supervised: -- macOS launchd (app-bundled LaunchAgent): `launchctl kickstart -k gui/$UID/com.clawdbot.gateway` (use `com.clawdbot.` if set) +- macOS launchd (app-bundled LaunchAgent): `launchctl kickstart -k gui/$UID/bot.molt.gateway` (use `bot.molt.`; legacy `com.clawdbot.*` still works) - Linux systemd user service: `systemctl --user restart moltbot-gateway[-].service` - Windows (WSL2): `systemctl --user restart moltbot-gateway[-].service` - `launchctl`/`systemctl` only work if the service is installed; otherwise run `moltbot gateway install`. diff --git a/docs/platforms/exe-dev.md b/docs/platforms/exe-dev.md index 2e58d5dcd..796ddc374 100644 --- a/docs/platforms/exe-dev.md +++ b/docs/platforms/exe-dev.md @@ -7,40 +7,47 @@ read_when: # exe.dev -Goal: Moltbot Gateway running on an exe.dev VM, reachable from your laptop via: -- **exe.dev HTTPS proxy** (easy, no tunnel) or -- **SSH tunnel** (most secure; loopback-only Gateway) +Goal: Moltbot Gateway running on an exe.dev VM, reachable from your laptop via: `https://.exe.xyz` -This page assumes **Ubuntu/Debian**. If you picked a different distro, map packages accordingly. - -If you’re on any other Linux VPS, the same steps apply — you just won’t use the exe.dev proxy commands. +This page assumes exe.dev's default **exeuntu** image. If you picked a different distro, map packages accordingly. ## Beginner quick path -1) Create VM → install Node 22 → install Moltbot -2) Run `moltbot onboard --install-daemon` -3) Tunnel from laptop (`ssh -N -L 18789:127.0.0.1:18789 …`) -4) Open `http://127.0.0.1:18789/` and paste your token +1) [https://exe.new/moltbot](https://exe.new/moltbot) +2) Fill in your auth key/token as needed +3) Click on "Agent" next to your VM, and wait... +4) ??? +5) Profit ## What you need -- exe.dev account + `ssh exe.dev` working on your laptop -- SSH keys set up (your laptop → exe.dev) -- Model auth (OAuth or API key) you want to use -- Provider credentials (optional): WhatsApp QR scan, Telegram bot token, Discord bot token, … +- exe.dev account +- `ssh exe.dev` access to [exe.dev](https://exe.dev) virtual machines (optional) + + +## Automated Install with Shelley + +Shelley, [exe.dev](https://exe.dev)'s agent, can install Moltbot instantly with our +prompt. The prompt used is as below: + +``` +Set up Moltbot (https://docs.molt.bot/install) on this VM. Use the non-interactive and accept-risk flags for moltbot onboarding. Add the supplied auth or token as needed. Configure nginx to forward from the default port 18789 to the root location on the default enabled site config, making sure to enable Websocket support. Pairing is done by "moltbot devices list" and "moltbot device approve ". Make sure the dashboard shows that Moltbot's health is OK. exe.dev handles forwarding from port 8000 to port 80/443 and HTTPS for us, so the final "reachable" should be .exe.xyz, without port specification. +``` + +## Manual installation ## 1) Create the VM -From your laptop: +From your device: ```bash -ssh exe.dev new --name=moltbot +ssh exe.dev new ``` Then connect: ```bash -ssh moltbot.exe.xyz +ssh .exe.xyz ``` Tip: keep this VM **stateful**. Moltbot stores state under `~/.clawdbot/` and `~/clawd/`. @@ -52,130 +59,61 @@ sudo apt-get update sudo apt-get install -y git curl jq ca-certificates openssl ``` -### Node 22 - -Install Node **>= 22.12** (any method is fine). Quick check: - -```bash -node -v -``` - -If you don’t already have Node 22 on the VM, use your preferred Node manager (nvm/mise/asdf) or a distro package source that provides Node 22+. - -Common Ubuntu/Debian option (NodeSource): - -```bash -curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - -sudo apt-get install -y nodejs -``` - ## 3) Install Moltbot -Recommended on servers: npm global install. +Run the Moltbot install script: ```bash -npm i -g moltbot@latest -moltbot --version +curl -fsSL https://molt.bot/install.sh | bash ``` -If native deps fail to install (rare; usually `sharp`), add build tools: +## 4) Setup nginx to proxy Moltbot to port 8000 + +Edit `/etc/nginx/sites-enabled/default` with -```bash -sudo apt-get install -y build-essential python3 ``` +server { + listen 80 default_server; + listen [::]:80 default_server; + listen 8000; + listen [::]:8000; -## 4) First-time setup (wizard) + server_name _; -Run the onboarding wizard on the VM: + location / { + proxy_pass http://127.0.0.1:18789; + proxy_http_version 1.1; -```bash -moltbot onboard --install-daemon -``` + # WebSocket support + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; -It can set up: -- `~/clawd` workspace bootstrap -- `~/.clawdbot/moltbot.json` config -- model auth profiles -- model provider config/login -- Linux systemd **user** service (service) + # Standard proxy headers + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; -If you’re doing OAuth on a headless VM: do OAuth on a normal machine first, then copy the auth profile to the VM (see [Help](/help)). - -## 5) Remote access options - -### Option A (recommended): SSH tunnel (loopback-only) - -Keep Gateway on loopback (default) and tunnel it from your laptop: - -```bash -ssh -N -L 18789:127.0.0.1:18789 moltbot.exe.xyz -``` - -Open locally: -- `http://127.0.0.1:18789/` (Control UI) - -Runbook: [Remote access](/gateway/remote) - -### Option B: exe.dev HTTPS proxy (no tunnel) - -To let exe.dev proxy traffic to the VM, bind the Gateway to the LAN interface and set a token: - -```bash -export CLAWDBOT_GATEWAY_TOKEN="$(openssl rand -hex 32)" -moltbot gateway --bind lan --port 8080 --token "$CLAWDBOT_GATEWAY_TOKEN" -``` - -For service runs, persist it in `~/.clawdbot/moltbot.json`: - -```json5 -{ - gateway: { - mode: "local", - port: 8080, - bind: "lan", - auth: { mode: "token", token: "YOUR_TOKEN" } - } + # Timeout settings for long-lived connections + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + } } ``` -Notes: -- Non-loopback binds require `gateway.auth.token` (or `CLAWDBOT_GATEWAY_TOKEN`). -- `gateway.remote.token` is only for remote CLI calls; it does not enable local auth. +## 5) Access Moltbot and grant privileges -Then point exe.dev’s proxy at `8080` (or whatever port you chose) and open your VM’s HTTPS URL: +Access `https://.exe.xyz/?token=YOUR-TOKEN-FROM-TERMINAL`. Approve +devices with `moltbot devices list` and `moltbot device approve`. When in doubt, +use Shelley from your browser! -```bash -ssh exe.dev share port moltbot 8080 -``` +## Remote Access -Open: -- `https://moltbot.exe.xyz/` +Remote access is handled by [exe.dev](https://exe.dev)'s authentication. By +default, HTTP traffic from port 8000 is forwarded to `https://.exe.xyz` +with email auth. -In the Control UI, paste the token (UI → Settings → token). The UI sends it as `connect.params.auth.token`. - -Notes: -- Prefer a **non-default** port (like `8080`) if your proxy expects an app port. -- Treat the token like a password. - -Control UI details: [Control UI](/web/control-ui) - -## 6) Keep it running (service) - -On Linux, Moltbot uses a systemd **user** service. After `--install-daemon`, verify: - -```bash -systemctl --user status moltbot-gateway[-].service -``` - -If the service dies after logout, enable lingering: - -```bash -sudo loginctl enable-linger "$USER" -``` - -More: [Linux](/platforms/linux) - -## 7) Updates +## Updating ```bash npm i -g moltbot@latest diff --git a/docs/platforms/fly.md b/docs/platforms/fly.md index 545c4fe82..d8db124ac 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.26" + "lastTouchedVersion": "2026.1.27-beta.1" } } EOF diff --git a/docs/platforms/index.md b/docs/platforms/index.md index a4a34b4ab..65eeac2ed 100644 --- a/docs/platforms/index.md +++ b/docs/platforms/index.md @@ -46,5 +46,5 @@ Use one of these (all supported): - Repair/migrate: `moltbot doctor` (offers to install or fix the service) The service target depends on OS: -- macOS: LaunchAgent (`com.clawdbot.gateway` or `com.clawdbot.`) +- macOS: LaunchAgent (`bot.molt.gateway` or `bot.molt.`; legacy `com.clawdbot.*`) - Linux/WSL2: systemd user service (`moltbot-gateway[-].service`) diff --git a/docs/platforms/mac/bundled-gateway.md b/docs/platforms/mac/bundled-gateway.md index f8fb9179f..909fddcfc 100644 --- a/docs/platforms/mac/bundled-gateway.md +++ b/docs/platforms/mac/bundled-gateway.md @@ -26,11 +26,11 @@ The macOS app’s **Install CLI** button runs the same flow via npm/pnpm (bun no ## Launchd (Gateway as LaunchAgent) Label: -- `com.clawdbot.gateway` (or `com.clawdbot.`) +- `bot.molt.gateway` (or `bot.molt.`; legacy `com.clawdbot.*` may remain) Plist location (per‑user): -- `~/Library/LaunchAgents/com.clawdbot.gateway.plist` - (or `~/Library/LaunchAgents/com.clawdbot..plist`) +- `~/Library/LaunchAgents/bot.molt.gateway.plist` + (or `~/Library/LaunchAgents/bot.molt..plist`) Manager: - The macOS app owns LaunchAgent install/update in Local mode. diff --git a/docs/platforms/mac/child-process.md b/docs/platforms/mac/child-process.md index 6483f1df5..d8b2d8728 100644 --- a/docs/platforms/mac/child-process.md +++ b/docs/platforms/mac/child-process.md @@ -16,8 +16,8 @@ If you need tighter coupling to the UI, run the Gateway manually in a terminal. ## Default behavior (launchd) -- The app installs a per‑user LaunchAgent labeled `com.clawdbot.gateway` - (or `com.clawdbot.` when using `--profile`/`CLAWDBOT_PROFILE`). +- The app installs a per‑user LaunchAgent labeled `bot.molt.gateway` + (or `bot.molt.` when using `--profile`/`CLAWDBOT_PROFILE`; legacy `com.clawdbot.*` is supported). - When Local mode is enabled, the app ensures the LaunchAgent is loaded and starts the Gateway if needed. - Logs are written to the launchd gateway log path (visible in Debug Settings). @@ -25,11 +25,11 @@ If you need tighter coupling to the UI, run the Gateway manually in a terminal. Common commands: ```bash -launchctl kickstart -k gui/$UID/com.clawdbot.gateway -launchctl bootout gui/$UID/com.clawdbot.gateway +launchctl kickstart -k gui/$UID/bot.molt.gateway +launchctl bootout gui/$UID/bot.molt.gateway ``` -Replace the label with `com.clawdbot.` when running a named profile. +Replace the label with `bot.molt.` when running a named profile. ## Unsigned dev builds diff --git a/docs/platforms/mac/dev-setup.md b/docs/platforms/mac/dev-setup.md index 179589b26..af0883e18 100644 --- a/docs/platforms/mac/dev-setup.md +++ b/docs/platforms/mac/dev-setup.md @@ -74,7 +74,7 @@ If the app crashes when you try to allow **Speech Recognition** or **Microphone* **Fix:** 1. Reset the TCC permissions: ```bash - tccutil reset All com.clawdbot.mac.debug + tccutil reset All bot.molt.mac.debug ``` 2. If that fails, change the `BUNDLE_ID` temporarily in [`scripts/package-mac-app.sh`](https://github.com/moltbot/moltbot/blob/main/scripts/package-mac-app.sh) to force a "clean slate" from macOS. diff --git a/docs/platforms/mac/logging.md b/docs/platforms/mac/logging.md index b7a9d9d33..9a5594d7d 100644 --- a/docs/platforms/mac/logging.md +++ b/docs/platforms/mac/logging.md @@ -22,11 +22,11 @@ Notes: Unified logging redacts most payloads unless a subsystem opts into `privacy -off`. Per Peter's write-up on macOS [logging privacy shenanigans](https://steipete.me/posts/2025/logging-privacy-shenanigans) (2025) this is controlled by a plist in `/Library/Preferences/Logging/Subsystems/` keyed by the subsystem name. Only new log entries pick up the flag, so enable it before reproducing an issue. -## Enable for Moltbot (`com.clawdbot`) +## Enable for Moltbot (`bot.molt`) - Write the plist to a temp file first, then install it atomically as root: ```bash -cat <<'EOF' >/tmp/com.clawdbot.plist +cat <<'EOF' >/tmp/bot.molt.plist @@ -39,13 +39,13 @@ cat <<'EOF' >/tmp/com.clawdbot.plist EOF -sudo install -m 644 -o root -g wheel /tmp/com.clawdbot.plist /Library/Preferences/Logging/Subsystems/com.clawdbot.plist +sudo install -m 644 -o root -g wheel /tmp/bot.molt.plist /Library/Preferences/Logging/Subsystems/bot.molt.plist ``` - No reboot is required; logd notices the file quickly, but only new log lines will include private payloads. - View the richer output with the existing helper, e.g. `./scripts/clawlog.sh --category WebChat --last 5m`. ## Disable after debugging -- Remove the override: `sudo rm /Library/Preferences/Logging/Subsystems/com.clawdbot.plist`. +- Remove the override: `sudo rm /Library/Preferences/Logging/Subsystems/bot.molt.plist`. - Optionally run `sudo log config --reload` to force logd to drop the override immediately. - Remember this surface can include phone numbers and message bodies; keep the plist in place only while you actively need the extra detail. diff --git a/docs/platforms/mac/permissions.md b/docs/platforms/mac/permissions.md index bfc8f099e..d2570829c 100644 --- a/docs/platforms/mac/permissions.md +++ b/docs/platforms/mac/permissions.md @@ -31,8 +31,8 @@ grants, and prompts can disappear entirely until the stale entries are cleared. Example resets (replace bundle ID as needed): ```bash -sudo tccutil reset Accessibility com.clawdbot.mac -sudo tccutil reset ScreenCapture com.clawdbot.mac +sudo tccutil reset Accessibility bot.molt.mac +sudo tccutil reset ScreenCapture bot.molt.mac sudo tccutil reset AppleEvents ``` diff --git a/docs/platforms/mac/release.md b/docs/platforms/mac/release.md index 4e6d428da..237eac616 100644 --- a/docs/platforms/mac/release.md +++ b/docs/platforms/mac/release.md @@ -29,45 +29,45 @@ Notes: ```bash # From repo root; set release IDs so Sparkle feed is enabled. # APP_BUILD must be numeric + monotonic for Sparkle compare. -BUNDLE_ID=com.clawdbot.mac \ -APP_VERSION=2026.1.26 \ +BUNDLE_ID=bot.molt.mac \ +APP_VERSION=2026.1.27-beta.1 \ 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.26.zip +ditto -c -k --sequesterRsrc --keepParent dist/Moltbot.app dist/Moltbot-2026.1.27-beta.1.zip # Optional: also build a styled DMG for humans (drag to /Applications) -scripts/create-dmg.sh dist/Moltbot.app dist/Moltbot-2026.1.26.dmg +scripts/create-dmg.sh dist/Moltbot.app dist/Moltbot-2026.1.27-beta.1.dmg # Recommended: build + notarize/staple zip + DMG # First, create a keychain profile once: # xcrun notarytool store-credentials "moltbot-notary" \ # --apple-id "" --team-id "" --password "" NOTARIZE=1 NOTARYTOOL_PROFILE=moltbot-notary \ -BUNDLE_ID=com.clawdbot.mac \ -APP_VERSION=2026.1.26 \ +BUNDLE_ID=bot.molt.mac \ +APP_VERSION=2026.1.27-beta.1 \ 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.26.dSYM.zip +ditto -c -k --keepParent apps/macos/.build/release/Moltbot.app.dSYM dist/Moltbot-2026.1.27-beta.1.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.26.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.27-beta.1.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.26.zip` (and `Moltbot-2026.1.26.dSYM.zip`) to the GitHub release for tag `v2026.1.26`. +- 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`. - 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/platforms/mac/signing.md b/docs/platforms/mac/signing.md index cef1b359d..71f153765 100644 --- a/docs/platforms/mac/signing.md +++ b/docs/platforms/mac/signing.md @@ -7,7 +7,7 @@ read_when: This app is usually built from [`scripts/package-mac-app.sh`](https://github.com/moltbot/moltbot/blob/main/scripts/package-mac-app.sh), which now: -- sets a stable debug bundle identifier: `com.clawdbot.mac.debug` +- sets a stable debug bundle identifier: `bot.molt.mac.debug` - writes the Info.plist with that bundle id (override via `BUNDLE_ID=...`) - calls [`scripts/codesign-mac-app.sh`](https://github.com/moltbot/moltbot/blob/main/scripts/codesign-mac-app.sh) to sign the main binary and app bundle so macOS treats each rebuild as the same signed bundle and keeps TCC permissions (notifications, accessibility, screen recording, mic, speech). For stable permissions, use a real signing identity; ad-hoc is opt-in and fragile (see [macOS permissions](/platforms/mac/permissions)). - uses `CODESIGN_TIMESTAMP=auto` by default; it enables trusted timestamps for Developer ID signatures. Set `CODESIGN_TIMESTAMP=off` to skip timestamping (offline debug builds). diff --git a/docs/platforms/mac/skills.md b/docs/platforms/mac/skills.md index 5f80b9d5e..aad035d53 100644 --- a/docs/platforms/mac/skills.md +++ b/docs/platforms/mac/skills.md @@ -11,10 +11,10 @@ The macOS app surfaces Moltbot skills via the gateway; it does not parse skills ## Data source - `skills.status` (gateway) returns all skills plus eligibility and missing requirements (including allowlist blocks for bundled skills). -- Requirements are derived from `metadata.clawdbot.requires` in each `SKILL.md`. +- Requirements are derived from `metadata.moltbot.requires` in each `SKILL.md`. ## Install actions -- `metadata.clawdbot.install` defines install options (brew/node/go/uv). +- `metadata.moltbot.install` defines install options (brew/node/go/uv). - The app calls `skills.install` to run installers on the gateway host. - The gateway surfaces only one preferred installer when multiple are provided (brew when available, otherwise node manager from `skills.install`, default npm). diff --git a/docs/platforms/mac/voice-overlay.md b/docs/platforms/mac/voice-overlay.md index 5d755fe67..139445164 100644 --- a/docs/platforms/mac/voice-overlay.md +++ b/docs/platforms/mac/voice-overlay.md @@ -32,14 +32,14 @@ Audience: macOS app contributors. Goal: keep the voice overlay predictable when - Push-to-talk: no delay; wake-word: optional delay for auto-send. - Apply a short cooldown to the wake runtime after push-to-talk finishes so wake-word doesn’t immediately retrigger. 5. **Logging** - - Coordinator emits `.info` logs in subsystem `com.clawdbot`, categories `voicewake.overlay` and `voicewake.chime`. + - Coordinator emits `.info` logs in subsystem `bot.molt`, categories `voicewake.overlay` and `voicewake.chime`. - Key events: `session_started`, `adopted_by_push_to_talk`, `partial`, `finalized`, `send`, `dismiss`, `cancel`, `cooldown`. ### Debugging checklist - Stream logs while reproducing a sticky overlay: ```bash - sudo log stream --predicate 'subsystem == "com.clawdbot" AND category CONTAINS "voicewake"' --level info --style compact + sudo log stream --predicate 'subsystem == "bot.molt" AND category CONTAINS "voicewake"' --level info --style compact ``` - Verify only one active session token; stale callbacks should be dropped by the coordinator. - Ensure push-to-talk release always calls `endCapture` with the active token; if text is empty, expect `dismiss` without chime or send. diff --git a/docs/platforms/mac/webchat.md b/docs/platforms/mac/webchat.md index 80d5cfe2b..5f4e32308 100644 --- a/docs/platforms/mac/webchat.md +++ b/docs/platforms/mac/webchat.md @@ -20,7 +20,7 @@ agent (with a session switcher for other sessions). ```bash dist/Moltbot.app/Contents/MacOS/Moltbot --webchat ``` -- Logs: `./scripts/clawlog.sh` (subsystem `com.clawdbot`, category `WebChatSwiftUI`). +- Logs: `./scripts/clawlog.sh` (subsystem `bot.molt`, category `WebChatSwiftUI`). ## How it’s wired diff --git a/docs/platforms/macos.md b/docs/platforms/macos.md index 8b00dc8c9..c98fe0817 100644 --- a/docs/platforms/macos.md +++ b/docs/platforms/macos.md @@ -32,15 +32,15 @@ The app does not spawn the Gateway as a child process. ## Launchd control -The app manages a per‑user LaunchAgent labeled `com.clawdbot.gateway` -(or `com.clawdbot.` when using `--profile`/`CLAWDBOT_PROFILE`). +The app manages a per‑user LaunchAgent labeled `bot.molt.gateway` +(or `bot.molt.` when using `--profile`/`CLAWDBOT_PROFILE`; legacy `com.clawdbot.*` still unloads). ```bash -launchctl kickstart -k gui/$UID/com.clawdbot.gateway -launchctl bootout gui/$UID/com.clawdbot.gateway +launchctl kickstart -k gui/$UID/bot.molt.gateway +launchctl bootout gui/$UID/bot.molt.gateway ``` -Replace the label with `com.clawdbot.` when running a named profile. +Replace the label with `bot.molt.` when running a named profile. If the LaunchAgent isn’t installed, enable it from the app or run `moltbot gateway install`. diff --git a/docs/providers/moonshot.md b/docs/providers/moonshot.md index 7e0723f7e..a1f2d18ad 100644 --- a/docs/providers/moonshot.md +++ b/docs/providers/moonshot.md @@ -9,11 +9,12 @@ read_when: # Moonshot AI (Kimi) Moonshot provides the Kimi API with OpenAI-compatible endpoints. Configure the -provider and set the default model to `moonshot/kimi-k2-0905-preview`, or use +provider and set the default model to `moonshot/kimi-k2.5`, or use Kimi Code with `kimi-code/kimi-for-coding`. Current Kimi K2 model IDs: {/* moonshot-kimi-k2-ids:start */} +- `kimi-k2.5` - `kimi-k2-0905-preview` - `kimi-k2-turbo-preview` - `kimi-k2-thinking` @@ -39,9 +40,10 @@ Note: Moonshot and Kimi Code are separate providers. Keys are not interchangeabl env: { MOONSHOT_API_KEY: "sk-..." }, agents: { defaults: { - model: { primary: "moonshot/kimi-k2-0905-preview" }, + model: { primary: "moonshot/kimi-k2.5" }, models: { // moonshot-kimi-k2-aliases:start + "moonshot/kimi-k2.5": { alias: "Kimi K2.5" }, "moonshot/kimi-k2-0905-preview": { alias: "Kimi K2" }, "moonshot/kimi-k2-turbo-preview": { alias: "Kimi K2 Turbo" }, "moonshot/kimi-k2-thinking": { alias: "Kimi K2 Thinking" }, @@ -59,6 +61,15 @@ Note: Moonshot and Kimi Code are separate providers. Keys are not interchangeabl api: "openai-completions", models: [ // moonshot-kimi-k2-models:start + { + id: "kimi-k2.5", + name: "Kimi K2.5", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 256000, + maxTokens: 8192 + }, { id: "kimi-k2-0905-preview", name: "Kimi K2 0905 Preview", diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index 68d1c0223..e648fb33c 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -17,10 +17,10 @@ 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.26`). +- [ ] Bump `package.json` version (e.g., `2026.1.27-beta.1`). - [ ] 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 [`dist/entry.js`](https://github.com/moltbot/moltbot/blob/main/dist/entry.js) for `moltbot`. +- [ ] 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`. - [ ] If dependencies changed, run `pnpm install` so `pnpm-lock.yaml` is current. 2) **Build & artifacts** diff --git a/docs/security/formal-verification.md b/docs/security/formal-verification.md index 437fc11a6..f5c6bbbb4 100644 --- a/docs/security/formal-verification.md +++ b/docs/security/formal-verification.md @@ -8,6 +8,8 @@ permalink: /security/formal-verification/ This page tracks Moltbot’s **formal security models** (TLA+/TLC today; more as needed). +> Note: some older links may refer to the previous project name. + **Goal (north star):** provide a machine-checked argument that Moltbot enforces its intended security policy (authorization, session isolation, tool gating, and misconfiguration safety), under explicit assumptions. @@ -20,7 +22,7 @@ misconfiguration safety), under explicit assumptions. ## Where the models live -Models are maintained in a separate repo: [vignesh07/moltbot-formal-models](https://github.com/vignesh07/moltbot-formal-models). +Models are maintained in a separate repo: [vignesh07/clawdbot-formal-models](https://github.com/vignesh07/clawdbot-formal-models). ## Important caveats @@ -37,8 +39,8 @@ Today, results are reproduced by cloning the models repo locally and running TLC Getting started: ```bash -git clone https://github.com/vignesh07/moltbot-formal-models -cd moltbot-formal-models +git clone https://github.com/vignesh07/clawdbot-formal-models +cd clawdbot-formal-models # Java 11+ required (TLC runs on the JVM). # The repo vendors a pinned `tla2tools.jar` (TLA+ tools) and provides `bin/tlc` + Make targets. @@ -98,10 +100,61 @@ See also: `docs/gateway-exposure-matrix.md` in the models repo. - Red (expected): - `make routing-isolation-negative` -## Roadmap -Next models to deepen fidelity: -- Pairing store concurrency/locking/idempotency -- Provider-specific ingress preflight modeling -- Routing identity-links + dmScope variants + binding precedence -- Gateway auth conformance (proxy/tailscale specifics) +## v1++: additional bounded models (concurrency, retries, trace correctness) + +These are follow-on models that tighten fidelity around real-world failure modes (non-atomic updates, retries, and message fan-out). + +### Pairing store concurrency / idempotency + +**Claim:** a pairing store should enforce `MaxPending` and idempotency even under interleavings (i.e., “check-then-write” must be atomic / locked; refresh shouldn’t create duplicates). + +What it means: +- Under concurrent requests, you can’t exceed `MaxPending` for a channel. +- Repeated requests/refreshes for the same `(channel, sender)` should not create duplicate live pending rows. + +- Green runs: + - `make pairing-race` (atomic/locked cap check) + - `make pairing-idempotency` + - `make pairing-refresh` + - `make pairing-refresh-race` +- Red (expected): + - `make pairing-race-negative` (non-atomic begin/commit cap race) + - `make pairing-idempotency-negative` + - `make pairing-refresh-negative` + - `make pairing-refresh-race-negative` + +### Ingress trace correlation / idempotency + +**Claim:** ingestion should preserve trace correlation across fan-out and be idempotent under provider retries. + +What it means: +- When one external event becomes multiple internal messages, every part keeps the same trace/event identity. +- Retries do not result in double-processing. +- If provider event IDs are missing, dedupe falls back to a safe key (e.g., trace ID) to avoid dropping distinct events. + +- Green: + - `make ingress-trace` + - `make ingress-trace2` + - `make ingress-idempotency` + - `make ingress-dedupe-fallback` +- Red (expected): + - `make ingress-trace-negative` + - `make ingress-trace2-negative` + - `make ingress-idempotency-negative` + - `make ingress-dedupe-fallback-negative` + +### Routing dmScope precedence + identityLinks + +**Claim:** routing must keep DM sessions isolated by default, and only collapse sessions when explicitly configured (channel precedence + identity links). + +What it means: +- Channel-specific dmScope overrides must win over global defaults. +- identityLinks should collapse only within explicit linked groups, not across unrelated peers. + +- Green: + - `make routing-precedence` + - `make routing-identitylinks` +- Red (expected): + - `make routing-precedence-negative` + - `make routing-identitylinks-negative` diff --git a/docs/start/getting-started.md b/docs/start/getting-started.md index 8ba2ea3f3..239b29966 100644 --- a/docs/start/getting-started.md +++ b/docs/start/getting-started.md @@ -180,7 +180,7 @@ If you don’t have a global install yet, run the onboarding step via `pnpm molt Gateway (from this repo): ```bash -node dist/entry.js gateway --port 18789 --verbose +node moltbot.mjs gateway --port 18789 --verbose ``` ## 7) Verify end-to-end diff --git a/docs/tools/skills-config.md b/docs/tools/skills-config.md index 3667b99cd..d233e8f21 100644 --- a/docs/tools/skills-config.md +++ b/docs/tools/skills-config.md @@ -60,7 +60,7 @@ Per-skill fields: ## Notes - Keys under `entries` map to the skill name by default. If a skill defines - `metadata.clawdbot.skillKey`, use that key instead. + `metadata.moltbot.skillKey`, use that key instead. - Changes to skills are picked up on the next agent turn when the watcher is enabled. ### Sandboxed skills + env vars diff --git a/docs/tools/skills.md b/docs/tools/skills.md index b99bc5660..0f72a8036 100644 --- a/docs/tools/skills.md +++ b/docs/tools/skills.md @@ -41,7 +41,7 @@ applies: workspace wins, then managed/local, then bundled. Plugins can ship their own skills by listing `skills` directories in `moltbot.plugin.json` (paths relative to the plugin root). Plugin skills load when the plugin is enabled and participate in the normal skill precedence rules. -You can gate them via `metadata.clawdbot.requires.config` on the plugin’s config +You can gate them via `metadata.moltbot.requires.config` on the plugin’s config entry. See [Plugins](/plugin) for discovery/config and [Tools](/tools) for the tool surface those skills teach. @@ -89,7 +89,7 @@ Notes: - `metadata` should be a **single-line JSON object**. - Use `{baseDir}` in instructions to reference the skill folder path. - Optional frontmatter keys: - - `homepage` — URL surfaced as “Website” in the macOS Skills UI (also supported via `metadata.clawdbot.homepage`). + - `homepage` — URL surfaced as “Website” in the macOS Skills UI (also supported via `metadata.moltbot.homepage`). - `user-invocable` — `true|false` (default: `true`). When `true`, the skill is exposed as a user slash command. - `disable-model-invocation` — `true|false` (default: `false`). When `true`, the skill is excluded from the model prompt (still available via user invocation). - `command-dispatch` — `tool` (optional). When set to `tool`, the slash command bypasses the model and dispatches directly to a tool. @@ -111,7 +111,7 @@ metadata: {"moltbot":{"requires":{"bins":["uv"],"env":["GEMINI_API_KEY"],"config --- ``` -Fields under `metadata.clawdbot`: +Fields under `metadata.moltbot`: - `always: true` — always include the skill (skip other gates). - `emoji` — optional emoji used by the macOS Skills UI. - `homepage` — optional URL shown as “Website” in the macOS Skills UI. @@ -152,7 +152,7 @@ Notes: - Go installs: if `go` is missing and `brew` is available, the gateway installs Go via Homebrew first and sets `GOBIN` to Homebrew’s `bin` when possible. - Download installs: `url` (required), `archive` (`tar.gz` | `tar.bz2` | `zip`), `extract` (default: auto when archive detected), `stripComponents`, `targetDir` (default: `~/.clawdbot/tools/`). -If no `metadata.clawdbot` is present, the skill is always eligible (unless +If no `metadata.moltbot` is present, the skill is always eligible (unless disabled in config or blocked by `skills.allowBundled` for bundled skills). ## Config overrides (`~/.clawdbot/moltbot.json`) @@ -184,12 +184,12 @@ Bundled/managed skills can be toggled and supplied with env values: Note: if the skill name contains hyphens, quote the key (JSON5 allows quoted keys). Config keys match the **skill name** by default. If a skill defines -`metadata.clawdbot.skillKey`, use that key under `skills.entries`. +`metadata.moltbot.skillKey`, use that key under `skills.entries`. Rules: - `enabled: false` disables the skill even if it’s bundled/installed. - `env`: injected **only if** the variable isn’t already set in the process. -- `apiKey`: convenience for skills that declare `metadata.clawdbot.primaryEnv`. +- `apiKey`: convenience for skills that declare `metadata.moltbot.primaryEnv`. - `config`: optional bag for custom per-skill fields; custom keys must live here. - `allowBundled`: optional allowlist for **bundled** skills only. If set, only bundled skills in the list are eligible (managed/workspace skills unaffected). diff --git a/extensions/bluebubbles/package.json b/extensions/bluebubbles/package.json index fc81c0c23..fc1ac34ae 100644 --- a/extensions/bluebubbles/package.json +++ b/extensions/bluebubbles/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/bluebubbles", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot BlueBubbles channel plugin", "moltbot": { diff --git a/extensions/copilot-proxy/package.json b/extensions/copilot-proxy/package.json index 7093b9c6d..2d4753446 100644 --- a/extensions/copilot-proxy/package.json +++ b/extensions/copilot-proxy/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/copilot-proxy", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot Copilot Proxy provider plugin", "moltbot": { diff --git a/extensions/diagnostics-otel/package.json b/extensions/diagnostics-otel/package.json index 5f8b3643e..f6560702b 100644 --- a/extensions/diagnostics-otel/package.json +++ b/extensions/diagnostics-otel/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/diagnostics-otel", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot diagnostics OpenTelemetry exporter", "moltbot": { diff --git a/extensions/discord/package.json b/extensions/discord/package.json index c31e55e39..9921468b4 100644 --- a/extensions/discord/package.json +++ b/extensions/discord/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/discord", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "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 039b4871f..8b13861ec 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.26", + "version": "2026.1.27-beta.1", "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 0c268a773..59cbd52a9 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.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot Gemini CLI OAuth provider plugin", "moltbot": { diff --git a/extensions/googlechat/package.json b/extensions/googlechat/package.json index af3188e90..0a01621e6 100644 --- a/extensions/googlechat/package.json +++ b/extensions/googlechat/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/googlechat", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot Google Chat channel plugin", "moltbot": { diff --git a/extensions/imessage/package.json b/extensions/imessage/package.json index a298f1a1b..29ceb0631 100644 --- a/extensions/imessage/package.json +++ b/extensions/imessage/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/imessage", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot iMessage channel plugin", "moltbot": { diff --git a/extensions/line/package.json b/extensions/line/package.json index 803c7f74c..bd336b158 100644 --- a/extensions/line/package.json +++ b/extensions/line/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/line", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot LINE channel plugin", "moltbot": { diff --git a/extensions/llm-task/package.json b/extensions/llm-task/package.json index 4a2a89a75..247d126a9 100644 --- a/extensions/llm-task/package.json +++ b/extensions/llm-task/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/llm-task", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot JSON-only LLM task plugin", "moltbot": { diff --git a/extensions/lobster/package.json b/extensions/lobster/package.json index 513d83925..c95d7021a 100644 --- a/extensions/lobster/package.json +++ b/extensions/lobster/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/lobster", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "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 77aeba16c..8b7dcb62c 100644 --- a/extensions/matrix/CHANGELOG.md +++ b/extensions/matrix/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2026.1.27-beta.1 + +### Changes +- Version alignment with core Moltbot release numbers. + ## 2026.1.23 ### Changes diff --git a/extensions/matrix/package.json b/extensions/matrix/package.json index d90a399c4..abc608b5b 100644 --- a/extensions/matrix/package.json +++ b/extensions/matrix/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/matrix", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot Matrix channel plugin", "moltbot": { diff --git a/extensions/mattermost/package.json b/extensions/mattermost/package.json index 8d571ab76..6e7d3f1fc 100644 --- a/extensions/mattermost/package.json +++ b/extensions/mattermost/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/mattermost", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot Mattermost channel plugin", "moltbot": { diff --git a/extensions/memory-core/package.json b/extensions/memory-core/package.json index fc2bb36e1..e863adbd2 100644 --- a/extensions/memory-core/package.json +++ b/extensions/memory-core/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/memory-core", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot core memory search plugin", "moltbot": { @@ -10,5 +10,8 @@ }, "peerDependencies": { "moltbot": ">=2026.1.26" + }, + "devDependencies": { + "moltbot": "workspace:*" } } diff --git a/extensions/memory-lancedb/package.json b/extensions/memory-lancedb/package.json index 2b2858e02..0e79ce83a 100644 --- a/extensions/memory-lancedb/package.json +++ b/extensions/memory-lancedb/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/memory-lancedb", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "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 f9b6e8b86..09a9e92bd 100644 --- a/extensions/msteams/CHANGELOG.md +++ b/extensions/msteams/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2026.1.27-beta.1 + +### Changes +- Version alignment with core Moltbot release numbers. + ## 2026.1.23 ### Changes diff --git a/extensions/msteams/package.json b/extensions/msteams/package.json index 8cc39b5d7..29e615862 100644 --- a/extensions/msteams/package.json +++ b/extensions/msteams/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/msteams", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot Microsoft Teams channel plugin", "moltbot": { diff --git a/extensions/nextcloud-talk/package.json b/extensions/nextcloud-talk/package.json index aea8b8942..5e98956da 100644 --- a/extensions/nextcloud-talk/package.json +++ b/extensions/nextcloud-talk/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/nextcloud-talk", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot Nextcloud Talk channel plugin", "moltbot": { diff --git a/extensions/nostr/CHANGELOG.md b/extensions/nostr/CHANGELOG.md index 57f073d0e..65ac7f56e 100644 --- a/extensions/nostr/CHANGELOG.md +++ b/extensions/nostr/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2026.1.27-beta.1 + +### Changes +- Version alignment with core Moltbot release numbers. + ## 2026.1.23 ### Changes diff --git a/extensions/nostr/package.json b/extensions/nostr/package.json index b932ac998..8ba9a48d0 100644 --- a/extensions/nostr/package.json +++ b/extensions/nostr/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/nostr", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "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 4be78502d..89904fcca 100644 --- a/extensions/open-prose/package.json +++ b/extensions/open-prose/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/open-prose", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "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 db4976b49..105a4fee8 100644 --- a/extensions/signal/package.json +++ b/extensions/signal/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/signal", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot Signal channel plugin", "moltbot": { diff --git a/extensions/slack/package.json b/extensions/slack/package.json index 352f15483..8ada7de5f 100644 --- a/extensions/slack/package.json +++ b/extensions/slack/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/slack", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot Slack channel plugin", "moltbot": { diff --git a/extensions/telegram/package.json b/extensions/telegram/package.json index aff1bf081..0f485d029 100644 --- a/extensions/telegram/package.json +++ b/extensions/telegram/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/telegram", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot Telegram channel plugin", "moltbot": { diff --git a/extensions/tlon/package.json b/extensions/tlon/package.json index 85dbd2a8b..2df375b55 100644 --- a/extensions/tlon/package.json +++ b/extensions/tlon/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/tlon", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot Tlon/Urbit channel plugin", "moltbot": { diff --git a/extensions/twitch/CHANGELOG.md b/extensions/twitch/CHANGELOG.md index 2e291db10..95b5ff2c7 100644 --- a/extensions/twitch/CHANGELOG.md +++ b/extensions/twitch/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2026.1.27-beta.1 + +### Changes +- Version alignment with core Moltbot release numbers. + ## 2026.1.23 ### Features diff --git a/extensions/twitch/package.json b/extensions/twitch/package.json index cb79d2fbb..6654f9bb7 100644 --- a/extensions/twitch/package.json +++ b/extensions/twitch/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/twitch", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "description": "Moltbot Twitch channel plugin", "type": "module", "dependencies": { diff --git a/extensions/voice-call/CHANGELOG.md b/extensions/voice-call/CHANGELOG.md index 0ece35f87..312e95917 100644 --- a/extensions/voice-call/CHANGELOG.md +++ b/extensions/voice-call/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2026.1.27-beta.1 + +### Changes +- Version alignment with core Moltbot release numbers. + ## 2026.1.26 ### Changes diff --git a/extensions/voice-call/package.json b/extensions/voice-call/package.json index 3b0294733..72bfba03d 100644 --- a/extensions/voice-call/package.json +++ b/extensions/voice-call/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/voice-call", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot voice-call plugin", "dependencies": { diff --git a/extensions/whatsapp/package.json b/extensions/whatsapp/package.json index d64945784..d5139e18f 100644 --- a/extensions/whatsapp/package.json +++ b/extensions/whatsapp/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/whatsapp", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot WhatsApp channel plugin", "moltbot": { diff --git a/extensions/zalo/CHANGELOG.md b/extensions/zalo/CHANGELOG.md index 03f128c28..55766ea8e 100644 --- a/extensions/zalo/CHANGELOG.md +++ b/extensions/zalo/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2026.1.27-beta.1 + +### Changes +- Version alignment with core Moltbot release numbers. + ## 2026.1.23 ### Changes diff --git a/extensions/zalo/package.json b/extensions/zalo/package.json index ca3c321a2..2a6cf9a5f 100644 --- a/extensions/zalo/package.json +++ b/extensions/zalo/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/zalo", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot Zalo channel plugin", "moltbot": { diff --git a/extensions/zalouser/CHANGELOG.md b/extensions/zalouser/CHANGELOG.md index 35cc9026d..e189e2e45 100644 --- a/extensions/zalouser/CHANGELOG.md +++ b/extensions/zalouser/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2026.1.27-beta.1 + +### Changes +- Version alignment with core Moltbot release numbers. + ## 2026.1.23 ### Changes diff --git a/extensions/zalouser/package.json b/extensions/zalouser/package.json index fad8a582a..6bace36e8 100644 --- a/extensions/zalouser/package.json +++ b/extensions/zalouser/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/zalouser", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "type": "module", "description": "Moltbot Zalo Personal Account plugin via zca-cli", "dependencies": { diff --git a/moltbot.mjs b/moltbot.mjs new file mode 100755 index 000000000..78992f94a --- /dev/null +++ b/moltbot.mjs @@ -0,0 +1,14 @@ +#!/usr/bin/env node + +import module from "node:module"; + +// https://nodejs.org/api/module.html#module-compile-cache +if (module.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) { + try { + module.enableCompileCache(); + } catch { + // Ignore errors + } +} + +await import("./dist/entry.js"); diff --git a/package.json b/package.json index f91af8199..04322f3af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moltbot", - "version": "2026.1.26", + "version": "2026.1.27-beta.1", "description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent", "type": "module", "main": "dist/index.js", @@ -8,11 +8,11 @@ ".": "./dist/index.js", "./plugin-sdk": "./dist/plugin-sdk/index.js", "./plugin-sdk/*": "./dist/plugin-sdk/*", - "./cli-entry": "./dist/entry.js" + "./cli-entry": "./moltbot.mjs" }, "bin": { - "moltbot": "dist/entry.js", - "clawdbot": "dist/entry.js" + "moltbot": "./moltbot.mjs", + "clawdbot": "./moltbot.mjs" }, "files": [ "dist/acp/**", @@ -56,6 +56,7 @@ "docs/**", "extensions/**", "assets/**", + "moltbot.mjs", "skills/**", "patches/**", "README.md", @@ -102,10 +103,10 @@ "ios:gen": "cd apps/ios && xcodegen generate", "ios:open": "cd apps/ios && xcodegen generate && open Moltbot.xcodeproj", "ios:build": "bash -lc 'cd apps/ios && xcodegen generate && xcodebuild -project Moltbot.xcodeproj -scheme Moltbot -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build'", - "ios:run": "bash -lc 'cd apps/ios && xcodegen generate && xcodebuild -project Moltbot.xcodeproj -scheme Moltbot -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build && xcrun simctl boot \"${IOS_SIM:-iPhone 17}\" || true && xcrun simctl launch booted com.clawdbot.ios'", + "ios:run": "bash -lc 'cd apps/ios && xcodegen generate && xcodebuild -project Moltbot.xcodeproj -scheme Moltbot -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build && xcrun simctl boot \"${IOS_SIM:-iPhone 17}\" || true && xcrun simctl launch booted bot.molt.ios'", "android:assemble": "cd apps/android && ./gradlew :app:assembleDebug", "android:install": "cd apps/android && ./gradlew :app:installDebug", - "android:run": "cd apps/android && ./gradlew :app:installDebug && adb shell am start -n com.clawdbot.android/.MainActivity", + "android:run": "cd apps/android && ./gradlew :app:installDebug && adb shell am start -n bot.molt.android/.MainActivity", "android:test": "cd apps/android && ./gradlew :app:testDebugUnitTest", "mac:restart": "bash scripts/restart-mac.sh", "mac:package": "bash scripts/package-mac-app.sh", @@ -115,7 +116,7 @@ "lint:all": "pnpm lint && pnpm lint:swift", "lint:fix": "pnpm format:fix && oxlint --type-aware --fix src test", "format": "oxfmt --check src test", - "format:swift": "swiftformat --lint --config .swiftformat apps/macos/Sources apps/ios/Sources apps/shared/ClawdbotKit/Sources", + "format:swift": "swiftformat --lint --config .swiftformat apps/macos/Sources apps/ios/Sources apps/shared/MoltbotKit/Sources", "format:all": "pnpm format && pnpm format:swift", "format:fix": "oxfmt --write src test", "test": "node scripts/test-parallel.mjs", @@ -141,7 +142,7 @@ "test:install:e2e:anthropic": "CLAWDBOT_E2E_MODELS=anthropic bash scripts/test-install-sh-e2e-docker.sh", "protocol:gen": "node --import tsx scripts/protocol-gen.ts", "protocol:gen:swift": "node --import tsx scripts/protocol-gen-swift.ts", - "protocol:check": "pnpm protocol:gen && pnpm protocol:gen:swift && git diff --exit-code -- dist/protocol.schema.json apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift", + "protocol:check": "pnpm protocol:gen && pnpm protocol:gen:swift && git diff --exit-code -- dist/protocol.schema.json apps/macos/Sources/MoltbotProtocol/GatewayModels.swift", "canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh", "check:loc": "node --import tsx scripts/check-ts-max-loc.ts --max 500" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4f35c2612..9c0f99928 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -355,9 +355,9 @@ importers: extensions/mattermost: {} extensions/memory-core: - dependencies: + devDependencies: moltbot: - specifier: '>=2026.1.26' + specifier: workspace:* version: link:../.. extensions/memory-lancedb: diff --git a/scripts/bundle-a2ui.sh b/scripts/bundle-a2ui.sh index 75844ec6d..e04648090 100755 --- a/scripts/bundle-a2ui.sh +++ b/scripts/bundle-a2ui.sh @@ -11,7 +11,7 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" HASH_FILE="$ROOT_DIR/src/canvas-host/a2ui/.bundle.hash" OUTPUT_FILE="$ROOT_DIR/src/canvas-host/a2ui/a2ui.bundle.js" A2UI_RENDERER_DIR="$ROOT_DIR/vendor/a2ui/renderers/lit" -A2UI_APP_DIR="$ROOT_DIR/apps/shared/ClawdbotKit/Tools/CanvasA2UI" +A2UI_APP_DIR="$ROOT_DIR/apps/shared/MoltbotKit/Tools/CanvasA2UI" # Docker builds exclude vendor/apps via .dockerignore. # In that environment we must keep the prebuilt bundle. diff --git a/scripts/clawlog.sh b/scripts/clawlog.sh index 60e73498c..405887d85 100755 --- a/scripts/clawlog.sh +++ b/scripts/clawlog.sh @@ -6,7 +6,7 @@ set -euo pipefail # Configuration -SUBSYSTEM="com.clawdbot" +SUBSYSTEM="bot.molt" DEFAULT_LEVEL="info" # Colors for output @@ -58,7 +58,7 @@ DESCRIPTION: Requires sudo access configured for /usr/bin/log command. LOG FLOW ARCHITECTURE: - Moltbot logs flow through the macOS unified log (subsystem: com.clawdbot). + Moltbot logs flow through the macOS unified log (subsystem: bot.molt). LOG CATEGORIES (examples): • voicewake - Voice wake detection/test harness diff --git a/scripts/e2e/doctor-install-switch-docker.sh b/scripts/e2e/doctor-install-switch-docker.sh index 7c5e96a84..d5be4fa86 100755 --- a/scripts/e2e/doctor-install-switch-docker.sh +++ b/scripts/e2e/doctor-install-switch-docker.sh @@ -81,8 +81,8 @@ LOGINCTL npm install -g --prefix /tmp/npm-prefix "/app/$pkg_tgz" npm_bin="/tmp/npm-prefix/bin/moltbot" - npm_entry="/tmp/npm-prefix/lib/node_modules/moltbot/dist/entry.js" - git_entry="/app/dist/entry.js" + npm_entry="/tmp/npm-prefix/lib/node_modules/moltbot/moltbot.mjs" + git_entry="/app/moltbot.mjs" assert_entrypoint() { local unit_path="$1" diff --git a/scripts/package-mac-app.sh b/scripts/package-mac-app.sh index 62ea80481..a63ecaf4d 100755 --- a/scripts/package-mac-app.sh +++ b/scripts/package-mac-app.sh @@ -8,7 +8,7 @@ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" APP_ROOT="$ROOT_DIR/dist/Moltbot.app" BUILD_ROOT="$ROOT_DIR/apps/macos/.build" PRODUCT="Moltbot" -BUNDLE_ID="${BUNDLE_ID:-com.clawdbot.mac.debug}" +BUNDLE_ID="${BUNDLE_ID:-bot.molt.mac.debug}" PKG_VERSION="$(cd "$ROOT_DIR" && node -p "require('./package.json').version" 2>/dev/null || echo "0.0.0")" BUILD_TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ") GIT_COMMIT=$(cd "$ROOT_DIR" && git rev-parse --short HEAD 2>/dev/null || echo "unknown") diff --git a/scripts/protocol-gen-swift.ts b/scripts/protocol-gen-swift.ts index b4310d9b8..0c2ca5066 100644 --- a/scripts/protocol-gen-swift.ts +++ b/scripts/protocol-gen-swift.ts @@ -24,16 +24,16 @@ const outPaths = [ "apps", "macos", "Sources", - "ClawdbotProtocol", + "MoltbotProtocol", "GatewayModels.swift", ), path.join( repoRoot, "apps", "shared", - "ClawdbotKit", + "MoltbotKit", "Sources", - "ClawdbotProtocol", + "MoltbotProtocol", "GatewayModels.swift", ), ]; diff --git a/scripts/release-check.ts b/scripts/release-check.ts index 5895bf7f9..d73850799 100755 --- a/scripts/release-check.ts +++ b/scripts/release-check.ts @@ -23,6 +23,7 @@ function runPackDry(): PackResult[] { const raw = execSync("npm pack --dry-run --json --ignore-scripts", { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], + maxBuffer: 1024 * 1024 * 100, }); return JSON.parse(raw) as PackResult[]; } diff --git a/scripts/restart-mac.sh b/scripts/restart-mac.sh index 67ed81908..b9bf1ab86 100755 --- a/scripts/restart-mac.sh +++ b/scripts/restart-mac.sh @@ -9,7 +9,7 @@ APP_PROCESS_PATTERN="Moltbot.app/Contents/MacOS/Moltbot" DEBUG_PROCESS_PATTERN="${ROOT_DIR}/apps/macos/.build/debug/Moltbot" LOCAL_PROCESS_PATTERN="${ROOT_DIR}/apps/macos/.build-local/debug/Moltbot" RELEASE_PROCESS_PATTERN="${ROOT_DIR}/apps/macos/.build/release/Moltbot" -LAUNCH_AGENT="${HOME}/Library/LaunchAgents/com.clawdbot.mac.plist" +LAUNCH_AGENT="${HOME}/Library/LaunchAgents/bot.molt.mac.plist" LOCK_KEY="$(printf '%s' "${ROOT_DIR}" | shasum -a 256 | cut -c1-8)" LOCK_DIR="${TMPDIR:-/tmp}/moltbot-restart-${LOCK_KEY}" LOCK_PID_FILE="${LOCK_DIR}/pid" @@ -96,8 +96,8 @@ for arg in "$@"; do log " CLAWDBOT_GATEWAY_WAIT_SECONDS=0 Wait time before gateway port check (unsigned only)" log "" log "Unsigned recovery:" - log " node dist/entry.js daemon install --force --runtime node" - log " node dist/entry.js daemon restart" + log " node moltbot.mjs daemon install --force --runtime node" + log " node moltbot.mjs daemon restart" log "" log "Reset unsigned overrides:" log " rm ~/.clawdbot/disable-launchagent" @@ -145,7 +145,7 @@ kill_all_moltbot() { } stop_launch_agent() { - launchctl bootout gui/"$UID"/com.clawdbot.mac 2>/dev/null || true + launchctl bootout gui/"$UID"/bot.molt.mac 2>/dev/null || true } # 1) Kill all running instances first. @@ -217,8 +217,8 @@ fi # When unsigned, ensure the gateway LaunchAgent targets the repo CLI (before the app launches). # This reduces noisy "could not connect" errors during app startup. if [ "$NO_SIGN" -eq 1 ] && [ "$ATTACH_ONLY" -ne 1 ]; then - run_step "install gateway launch agent (unsigned)" bash -lc "cd '${ROOT_DIR}' && node dist/entry.js daemon install --force --runtime node" - run_step "restart gateway daemon (unsigned)" bash -lc "cd '${ROOT_DIR}' && node dist/entry.js daemon restart" + run_step "install gateway launch agent (unsigned)" bash -lc "cd '${ROOT_DIR}' && node moltbot.mjs daemon install --force --runtime node" + run_step "restart gateway daemon (unsigned)" bash -lc "cd '${ROOT_DIR}' && node moltbot.mjs daemon restart" if [[ "${GATEWAY_WAIT_SECONDS}" -gt 0 ]]; then run_step "wait for gateway (unsigned)" sleep "${GATEWAY_WAIT_SECONDS}" fi @@ -265,5 +265,5 @@ else fi if [ "$NO_SIGN" -eq 1 ] && [ "$ATTACH_ONLY" -ne 1 ]; then - run_step "show gateway launch agent args (unsigned)" bash -lc "/usr/bin/plutil -p '${HOME}/Library/LaunchAgents/com.clawdbot.gateway.plist' | head -n 40 || true" + run_step "show gateway launch agent args (unsigned)" bash -lc "/usr/bin/plutil -p '${HOME}/Library/LaunchAgents/bot.molt.gateway.plist' | head -n 40 || true" fi diff --git a/scripts/run-node.mjs b/scripts/run-node.mjs index 0748a5991..b26f996a6 100644 --- a/scripts/run-node.mjs +++ b/scripts/run-node.mjs @@ -86,7 +86,7 @@ const logRunner = (message) => { }; const runNode = () => { - const nodeProcess = spawn(process.execPath, ["dist/entry.js", ...args], { + const nodeProcess = spawn(process.execPath, ["moltbot.mjs", ...args], { cwd, env, stdio: "inherit", @@ -95,7 +95,6 @@ const runNode = () => { nodeProcess.on("exit", (exitCode, exitSignal) => { if (exitSignal) { process.exit(1); - return; } process.exit(exitCode ?? 1); }); @@ -128,11 +127,9 @@ if (!shouldBuild()) { build.on("exit", (code, signal) => { if (signal) { process.exit(1); - return; } if (code !== 0 && code !== null) { process.exit(code); - return; } writeBuildStamp(); runNode(); diff --git a/scripts/test-parallel.mjs b/scripts/test-parallel.mjs index e753a6e76..811d7c546 100644 --- a/scripts/test-parallel.mjs +++ b/scripts/test-parallel.mjs @@ -23,6 +23,9 @@ const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true"; const isMacOS = process.platform === "darwin" || process.env.RUNNER_OS === "macOS"; const isWindows = process.platform === "win32" || process.env.RUNNER_OS === "Windows"; const isWindowsCi = isCI && isWindows; +const shardOverride = Number.parseInt(process.env.CLAWDBOT_TEST_SHARDS ?? "", 10); +const shardCount = isWindowsCi ? (Number.isFinite(shardOverride) && shardOverride > 1 ? shardOverride : 2) : 1; +const windowsCiArgs = isWindowsCi ? ["--no-file-parallelism", "--dangerouslyIgnoreUnhandledErrors"] : []; const overrideWorkers = Number.parseInt(process.env.CLAWDBOT_TEST_WORKERS ?? "", 10); const resolvedOverride = Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null; const parallelRuns = isWindowsCi ? [] : runs.filter((entry) => entry.name !== "gateway"); @@ -41,9 +44,11 @@ const WARNING_SUPPRESSION_FLAGS = [ "--disable-warning=DEP0060", ]; -const run = (entry) => +const runOnce = (entry, extraArgs = []) => new Promise((resolve) => { - const args = maxWorkers ? [...entry.args, "--maxWorkers", String(maxWorkers)] : entry.args; + const args = maxWorkers + ? [...entry.args, "--maxWorkers", String(maxWorkers), ...windowsCiArgs, ...extraArgs] + : [...entry.args, ...windowsCiArgs, ...extraArgs]; const nodeOptions = process.env.NODE_OPTIONS ?? ""; const nextNodeOptions = WARNING_SUPPRESSION_FLAGS.reduce( (acc, flag) => (acc.includes(flag) ? acc : `${acc} ${flag}`.trim()), @@ -61,6 +66,16 @@ const run = (entry) => }); }); +const run = async (entry) => { + if (shardCount <= 1) return runOnce(entry); + for (let shardIndex = 1; shardIndex <= shardCount; shardIndex += 1) { + // eslint-disable-next-line no-await-in-loop + const code = await runOnce(entry, ["--shard", `${shardIndex}/${shardCount}`]); + if (code !== 0) return code; + } + return 0; +}; + const shutdown = (signal) => { for (const child of children) { child.kill(signal); diff --git a/scripts/watch-node.mjs b/scripts/watch-node.mjs index 7ed210853..982a8c773 100644 --- a/scripts/watch-node.mjs +++ b/scripts/watch-node.mjs @@ -29,7 +29,7 @@ const compilerProcess = spawn("pnpm", ["exec", compiler, ...watchArgs], { stdio: "inherit", }); -const nodeProcess = spawn(process.execPath, ["--watch", "dist/entry.js", ...args], { +const nodeProcess = spawn(process.execPath, ["--watch", "moltbot.mjs", ...args], { cwd, env, stdio: "inherit", diff --git a/src/agents/channel-tools.test.ts b/src/agents/channel-tools.test.ts new file mode 100644 index 000000000..05ec460a7 --- /dev/null +++ b/src/agents/channel-tools.test.ts @@ -0,0 +1,53 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import type { MoltbotConfig } from "../config/config.js"; +import type { ChannelPlugin } from "../channels/plugins/types.js"; +import { setActivePluginRegistry } from "../plugins/runtime.js"; +import { createTestRegistry } from "../test-utils/channel-plugins.js"; +import { defaultRuntime } from "../runtime.js"; +import { __testing, listAllChannelSupportedActions } from "./channel-tools.js"; + +describe("channel tools", () => { + const errorSpy = vi.spyOn(defaultRuntime, "error").mockImplementation(() => undefined); + + beforeEach(() => { + const plugin: ChannelPlugin = { + id: "test", + meta: { + id: "test", + label: "Test", + selectionLabel: "Test", + docsPath: "/channels/test", + blurb: "test plugin", + }, + capabilities: { chatTypes: ["direct"] }, + config: { + listAccountIds: () => [], + resolveAccount: () => ({}), + }, + actions: { + listActions: () => { + throw new Error("boom"); + }, + }, + }; + + __testing.resetLoggedListActionErrors(); + errorSpy.mockClear(); + setActivePluginRegistry(createTestRegistry([{ pluginId: "test", source: "test", plugin }])); + }); + + afterEach(() => { + setActivePluginRegistry(createTestRegistry([])); + errorSpy.mockClear(); + }); + + it("skips crashing plugins and logs once", () => { + const cfg = {} as MoltbotConfig; + expect(listAllChannelSupportedActions({ cfg })).toEqual([]); + expect(errorSpy).toHaveBeenCalledTimes(1); + + expect(listAllChannelSupportedActions({ cfg })).toEqual([]); + expect(errorSpy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/agents/channel-tools.ts b/src/agents/channel-tools.ts index 437d326cb..27af3c5f9 100644 --- a/src/agents/channel-tools.ts +++ b/src/agents/channel-tools.ts @@ -1,8 +1,13 @@ import { getChannelDock } from "../channels/dock.js"; import { getChannelPlugin, listChannelPlugins } from "../channels/plugins/index.js"; import { normalizeAnyChannelId } from "../channels/registry.js"; -import type { ChannelAgentTool, ChannelMessageActionName } from "../channels/plugins/types.js"; +import type { + ChannelAgentTool, + ChannelMessageActionName, + ChannelPlugin, +} from "../channels/plugins/types.js"; import type { MoltbotConfig } from "../config/config.js"; +import { defaultRuntime } from "../runtime.js"; /** * Get the list of supported message actions for a specific channel. @@ -16,7 +21,7 @@ export function listChannelSupportedActions(params: { const plugin = getChannelPlugin(params.channel as Parameters[0]); if (!plugin?.actions?.listActions) return []; const cfg = params.cfg ?? ({} as MoltbotConfig); - return plugin.actions.listActions({ cfg }); + return runPluginListActions(plugin, cfg); } /** @@ -29,7 +34,7 @@ export function listAllChannelSupportedActions(params: { for (const plugin of listChannelPlugins()) { if (!plugin.actions?.listActions) continue; const cfg = params.cfg ?? ({} as MoltbotConfig); - const channelActions = plugin.actions.listActions({ cfg }); + const channelActions = runPluginListActions(plugin, cfg); for (const action of channelActions) { actions.add(action); } @@ -64,3 +69,35 @@ export function resolveChannelMessageToolHints(params: { .map((entry) => entry.trim()) .filter(Boolean); } + +const loggedListActionErrors = new Set(); + +function runPluginListActions( + plugin: ChannelPlugin, + cfg: MoltbotConfig, +): ChannelMessageActionName[] { + if (!plugin.actions?.listActions) return []; + try { + const listed = plugin.actions.listActions({ cfg }); + return Array.isArray(listed) ? listed : []; + } catch (err) { + logListActionsError(plugin.id, err); + return []; + } +} + +function logListActionsError(pluginId: string, err: unknown) { + const message = err instanceof Error ? err.message : String(err); + const key = `${pluginId}:${message}`; + if (loggedListActionErrors.has(key)) return; + loggedListActionErrors.add(key); + const stack = err instanceof Error && err.stack ? err.stack : null; + const details = stack ?? message; + defaultRuntime.error?.(`[channel-tools] ${pluginId}.actions.listActions failed: ${details}`); +} + +export const __testing = { + resetLoggedListActionErrors() { + loggedListActionErrors.clear(); + }, +}; diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 76f1c3acd..a176dac8a 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -17,7 +17,7 @@ import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js"; type ModelsConfig = NonNullable; export type ProviderConfig = NonNullable[string]; -const MINIMAX_API_BASE_URL = "https://api.minimax.io/anthropic"; +const MINIMAX_API_BASE_URL = "https://api.minimax.chat/v1"; const MINIMAX_DEFAULT_MODEL_ID = "MiniMax-M2.1"; const MINIMAX_DEFAULT_VISION_MODEL_ID = "MiniMax-VL-01"; const MINIMAX_DEFAULT_CONTEXT_WINDOW = 200000; @@ -31,7 +31,7 @@ const MINIMAX_API_COST = { }; const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1"; -const MOONSHOT_DEFAULT_MODEL_ID = "kimi-k2-0905-preview"; +const MOONSHOT_DEFAULT_MODEL_ID = "kimi-k2.5"; const MOONSHOT_DEFAULT_CONTEXT_WINDOW = 256000; const MOONSHOT_DEFAULT_MAX_TOKENS = 8192; const MOONSHOT_DEFAULT_COST = { @@ -244,7 +244,7 @@ export function normalizeProviders(params: { function buildMinimaxProvider(): ProviderConfig { return { baseUrl: MINIMAX_API_BASE_URL, - api: "anthropic-messages", + api: "openai-completions", models: [ { id: MINIMAX_DEFAULT_MODEL_ID, @@ -275,7 +275,7 @@ function buildMoonshotProvider(): ProviderConfig { models: [ { id: MOONSHOT_DEFAULT_MODEL_ID, - name: "Kimi K2 0905 Preview", + name: "Kimi K2.5", reasoning: false, input: ["text"], cost: MOONSHOT_DEFAULT_COST, 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 270b5fb02..fef8fa6a4 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 @@ -136,7 +136,7 @@ describe("models-config", () => { } >; }; - expect(parsed.providers.minimax?.baseUrl).toBe("https://api.minimax.io/anthropic"); + expect(parsed.providers.minimax?.baseUrl).toBe("https://api.minimax.chat/v1"); expect(parsed.providers.minimax?.apiKey).toBe("MINIMAX_API_KEY"); const ids = parsed.providers.minimax?.models?.map((model) => model.id); expect(ids).toContain("MiniMax-M2.1"); diff --git a/src/agents/pi-embedded-helpers.classifyfailoverreason.test.ts b/src/agents/pi-embedded-helpers.classifyfailoverreason.test.ts index bb449a6e4..749a52414 100644 --- a/src/agents/pi-embedded-helpers.classifyfailoverreason.test.ts +++ b/src/agents/pi-embedded-helpers.classifyfailoverreason.test.ts @@ -31,6 +31,7 @@ describe("classifyFailoverReason", () => { "messages.84.content.1.image.source.base64.data: At least one of the image dimensions exceed max allowed size for many-image requests: 2000 pixels", ), ).toBeNull(); + expect(classifyFailoverReason("image exceeds 5 MB maximum")).toBeNull(); }); it("classifies OpenAI usage limit errors as rate_limit", () => { expect(classifyFailoverReason("You have hit your ChatGPT usage limit (plus plan)")).toBe( diff --git a/src/agents/pi-embedded-helpers.image-size-error.test.ts b/src/agents/pi-embedded-helpers.image-size-error.test.ts new file mode 100644 index 000000000..75b165d8d --- /dev/null +++ b/src/agents/pi-embedded-helpers.image-size-error.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from "vitest"; + +import { parseImageSizeError } from "./pi-embedded-helpers.js"; + +describe("parseImageSizeError", () => { + it("parses max MB values from error text", () => { + expect(parseImageSizeError("image exceeds 5 MB maximum")?.maxMb).toBe(5); + expect(parseImageSizeError("Image exceeds 5.5 MB limit")?.maxMb).toBe(5.5); + }); + + it("returns null for unrelated errors", () => { + expect(parseImageSizeError("context overflow")).toBeNull(); + }); +}); diff --git a/src/agents/pi-embedded-helpers.ts b/src/agents/pi-embedded-helpers.ts index 6f6bb474f..88443756f 100644 --- a/src/agents/pi-embedded-helpers.ts +++ b/src/agents/pi-embedded-helpers.ts @@ -23,12 +23,14 @@ export { isFailoverAssistantError, isFailoverErrorMessage, isImageDimensionErrorMessage, + isImageSizeError, isOverloadedErrorMessage, isRawApiErrorPayload, isRateLimitAssistantError, isRateLimitErrorMessage, isTimeoutErrorMessage, parseImageDimensionError, + parseImageSizeError, } from "./pi-embedded-helpers/errors.js"; export { isGoogleModelApi, sanitizeGoogleTurnOrdering } from "./pi-embedded-helpers/google.js"; diff --git a/src/agents/pi-embedded-helpers/errors.ts b/src/agents/pi-embedded-helpers/errors.ts index d6e33f924..849c4293e 100644 --- a/src/agents/pi-embedded-helpers/errors.ts +++ b/src/agents/pi-embedded-helpers/errors.ts @@ -401,6 +401,7 @@ const ERROR_PATTERNS = { const IMAGE_DIMENSION_ERROR_RE = /image dimensions exceed max allowed size for many-image requests:\s*(\d+)\s*pixels/i; const IMAGE_DIMENSION_PATH_RE = /messages\.(\d+)\.content\.(\d+)\.image/i; +const IMAGE_SIZE_ERROR_RE = /image exceeds\s*(\d+(?:\.\d+)?)\s*mb/i; function matchesErrorPatterns(raw: string, patterns: readonly ErrorPattern[]): boolean { if (!raw) return false; @@ -467,6 +468,25 @@ export function isImageDimensionErrorMessage(raw: string): boolean { return Boolean(parseImageDimensionError(raw)); } +export function parseImageSizeError(raw: string): { + maxMb?: number; + raw: string; +} | null { + if (!raw) return null; + const lower = raw.toLowerCase(); + if (!lower.includes("image exceeds") || !lower.includes("mb")) return null; + const match = raw.match(IMAGE_SIZE_ERROR_RE); + return { + maxMb: match?.[1] ? Number.parseFloat(match[1]) : undefined, + raw, + }; +} + +export function isImageSizeError(errorMessage?: string): boolean { + if (!errorMessage) return false; + return Boolean(parseImageSizeError(errorMessage)); +} + export function isCloudCodeAssistFormatError(raw: string): boolean { return !isImageDimensionErrorMessage(raw) && matchesErrorPatterns(raw, ERROR_PATTERNS.format); } @@ -478,6 +498,7 @@ export function isAuthAssistantError(msg: AssistantMessage | undefined): boolean export function classifyFailoverReason(raw: string): FailoverReason | null { if (isImageDimensionErrorMessage(raw)) return null; + if (isImageSizeError(raw)) return null; if (isRateLimitErrorMessage(raw)) return "rate_limit"; if (isOverloadedErrorMessage(raw)) return "rate_limit"; if (isCloudCodeAssistFormatError(raw)) return "format"; diff --git a/src/agents/pi-embedded-runner/model.test.ts b/src/agents/pi-embedded-runner/model.test.ts index b59735623..cdcb5fe8e 100644 --- a/src/agents/pi-embedded-runner/model.test.ts +++ b/src/agents/pi-embedded-runner/model.test.ts @@ -1,6 +1,12 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; -import { buildInlineProviderModels } from "./model.js"; +vi.mock("@mariozechner/pi-coding-agent", () => ({ + discoverAuthStorage: vi.fn(() => ({ mocked: true })), + discoverModels: vi.fn(() => ({ find: vi.fn(() => null) })), +})); + +import type { MoltbotConfig } from "../../config/config.js"; +import { buildInlineProviderModels, resolveModel } from "./model.js"; const makeModel = (id: string) => ({ id, @@ -15,15 +21,110 @@ const makeModel = (id: string) => ({ describe("buildInlineProviderModels", () => { it("attaches provider ids to inline models", () => { const providers = { - " alpha ": { models: [makeModel("alpha-model")] }, - beta: { models: [makeModel("beta-model")] }, + " alpha ": { baseUrl: "http://alpha.local", models: [makeModel("alpha-model")] }, + beta: { baseUrl: "http://beta.local", models: [makeModel("beta-model")] }, }; const result = buildInlineProviderModels(providers); expect(result).toEqual([ - { ...makeModel("alpha-model"), provider: "alpha" }, - { ...makeModel("beta-model"), provider: "beta" }, + { + ...makeModel("alpha-model"), + provider: "alpha", + baseUrl: "http://alpha.local", + api: undefined, + }, + { + ...makeModel("beta-model"), + provider: "beta", + baseUrl: "http://beta.local", + api: undefined, + }, ]); }); + + it("inherits baseUrl from provider when model does not specify it", () => { + const providers = { + custom: { + baseUrl: "http://localhost:8000", + models: [makeModel("custom-model")], + }, + }; + + const result = buildInlineProviderModels(providers); + + expect(result).toHaveLength(1); + expect(result[0].baseUrl).toBe("http://localhost:8000"); + }); + + it("inherits api from provider when model does not specify it", () => { + const providers = { + custom: { + baseUrl: "http://localhost:8000", + api: "anthropic-messages", + models: [makeModel("custom-model")], + }, + }; + + const result = buildInlineProviderModels(providers); + + expect(result).toHaveLength(1); + expect(result[0].api).toBe("anthropic-messages"); + }); + + it("model-level api takes precedence over provider-level api", () => { + const providers = { + custom: { + baseUrl: "http://localhost:8000", + api: "openai-responses", + models: [{ ...makeModel("custom-model"), api: "anthropic-messages" as const }], + }, + }; + + const result = buildInlineProviderModels(providers); + + expect(result).toHaveLength(1); + expect(result[0].api).toBe("anthropic-messages"); + }); + + it("inherits both baseUrl and api from provider config", () => { + const providers = { + custom: { + baseUrl: "http://localhost:10000", + api: "anthropic-messages", + models: [makeModel("claude-opus-4.5")], + }, + }; + + const result = buildInlineProviderModels(providers); + + expect(result).toHaveLength(1); + expect(result[0]).toMatchObject({ + provider: "custom", + baseUrl: "http://localhost:10000", + api: "anthropic-messages", + name: "claude-opus-4.5", + }); + }); +}); + +describe("resolveModel", () => { + it("includes provider baseUrl in fallback model", () => { + const cfg = { + models: { + providers: { + custom: { + baseUrl: "http://localhost:9000", + models: [], + }, + }, + }, + } as MoltbotConfig; + + const result = resolveModel("custom", "missing-model", "/tmp/agent", cfg); + + expect(result.model?.baseUrl).toBe("http://localhost:9000"); + expect(result.model?.provider).toBe("custom"); + expect(result.model?.id).toBe("missing-model"); + }); }); diff --git a/src/agents/pi-embedded-runner/model.ts b/src/agents/pi-embedded-runner/model.ts index 1d7201ea9..1792e6706 100644 --- a/src/agents/pi-embedded-runner/model.ts +++ b/src/agents/pi-embedded-runner/model.ts @@ -8,15 +8,25 @@ import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js"; import { normalizeModelCompat } from "../model-compat.js"; import { normalizeProviderId } from "../model-selection.js"; -type InlineModelEntry = ModelDefinitionConfig & { provider: string }; +type InlineModelEntry = ModelDefinitionConfig & { provider: string; baseUrl?: string }; +type InlineProviderConfig = { + baseUrl?: string; + api?: ModelDefinitionConfig["api"]; + models?: ModelDefinitionConfig[]; +}; export function buildInlineProviderModels( - providers: Record, + providers: Record, ): InlineModelEntry[] { return Object.entries(providers).flatMap(([providerId, entry]) => { const trimmed = providerId.trim(); if (!trimmed) return []; - return (entry?.models ?? []).map((model) => ({ ...model, provider: trimmed })); + return (entry?.models ?? []).map((model) => ({ + ...model, + provider: trimmed, + baseUrl: entry?.baseUrl, + api: model.api ?? entry?.api, + })); }); } @@ -72,6 +82,7 @@ export function resolveModel( name: modelId, api: providerCfg?.api ?? "openai-responses", provider, + baseUrl: providerCfg?.baseUrl, reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, diff --git a/src/agents/pi-embedded-runner/run.ts b/src/agents/pi-embedded-runner/run.ts index 69eb1514a..870453f38 100644 --- a/src/agents/pi-embedded-runner/run.ts +++ b/src/agents/pi-embedded-runner/run.ts @@ -34,6 +34,7 @@ import { isContextOverflowError, isFailoverAssistantError, isFailoverErrorMessage, + parseImageSizeError, parseImageDimensionError, isRateLimitAssistantError, isTimeoutErrorMessage, @@ -440,6 +441,34 @@ export async function runEmbeddedPiAgent( }, }; } + // Handle image size errors with a user-friendly message (no retry needed) + const imageSizeError = parseImageSizeError(errorText); + if (imageSizeError) { + const maxMb = imageSizeError.maxMb; + const maxMbLabel = + typeof maxMb === "number" && Number.isFinite(maxMb) ? `${maxMb}` : null; + const maxBytesHint = maxMbLabel ? ` (max ${maxMbLabel}MB)` : ""; + return { + payloads: [ + { + text: + `Image too large for the model${maxBytesHint}. ` + + "Please compress or resize the image and try again.", + isError: true, + }, + ], + meta: { + durationMs: Date.now() - started, + agentMeta: { + sessionId: sessionIdUsed, + provider, + model: model.id, + }, + systemPromptReport: attempt.systemPromptReport, + error: { kind: "image_size", message: errorText }, + }, + }; + } const promptFailoverReason = classifyFailoverReason(errorText); if (promptFailoverReason && promptFailoverReason !== "timeout" && lastProfileId) { await markAuthProfileFailure({ diff --git a/src/agents/pi-embedded-runner/types.ts b/src/agents/pi-embedded-runner/types.ts index 4be395bce..27ccfa64e 100644 --- a/src/agents/pi-embedded-runner/types.ts +++ b/src/agents/pi-embedded-runner/types.ts @@ -20,7 +20,7 @@ export type EmbeddedPiRunMeta = { aborted?: boolean; systemPromptReport?: SessionSystemPromptReport; error?: { - kind: "context_overflow" | "compaction_failure" | "role_ordering"; + kind: "context_overflow" | "compaction_failure" | "role_ordering" | "image_size"; message: string; }; /** Stop reason for the agent run (e.g., "completed", "tool_calls"). */ diff --git a/src/agents/tools/image-tool.test.ts b/src/agents/tools/image-tool.test.ts index 0e4579d6d..2b4e1aea1 100644 --- a/src/agents/tools/image-tool.test.ts +++ b/src/agents/tools/image-tool.test.ts @@ -275,7 +275,7 @@ describe("image tool MiniMax VLM routing", () => { expect(fetch).toHaveBeenCalledTimes(1); const [url, init] = fetch.mock.calls[0]; - expect(String(url)).toBe("https://api.minimax.io/v1/coding_plan/vlm"); + expect(String(url)).toBe("https://api.minimax.chat/v1/coding_plan/vlm"); expect(init?.method).toBe("POST"); expect(String((init?.headers as Record)?.Authorization)).toBe( "Bearer minimax-test", diff --git a/src/agents/tools/message-tool.ts b/src/agents/tools/message-tool.ts index 2188d737d..4ea178a54 100644 --- a/src/agents/tools/message-tool.ts +++ b/src/agents/tools/message-tool.ts @@ -60,6 +60,9 @@ function buildSendSchema(options: { includeButtons: boolean; includeCards: boole threadId: Type.Optional(Type.String()), asVoice: Type.Optional(Type.Boolean()), silent: Type.Optional(Type.Boolean()), + quoteText: Type.Optional( + Type.String({ description: "Quote text for Telegram reply_parameters" }), + ), bestEffort: Type.Optional(Type.Boolean()), gifPlayback: Type.Optional(Type.Boolean()), buttons: Type.Optional( diff --git a/src/agents/tools/telegram-actions.test.ts b/src/agents/tools/telegram-actions.test.ts index 63a55e3d0..3b78dde20 100644 --- a/src/agents/tools/telegram-actions.test.ts +++ b/src/agents/tools/telegram-actions.test.ts @@ -261,6 +261,31 @@ describe("handleTelegramAction", () => { ); }); + it("passes quoteText when provided", async () => { + const cfg = { + channels: { telegram: { botToken: "tok" } }, + } as MoltbotConfig; + await handleTelegramAction( + { + action: "sendMessage", + to: "123456", + content: "Replying now", + replyToMessageId: 144, + quoteText: "The text you want to quote", + }, + cfg, + ); + expect(sendMessageTelegram).toHaveBeenCalledWith( + "123456", + "Replying now", + expect.objectContaining({ + token: "tok", + replyToMessageId: 144, + quoteText: "The text you want to quote", + }), + ); + }); + it("allows media-only messages without content", async () => { const cfg = { channels: { telegram: { botToken: "tok" } }, diff --git a/src/agents/tools/telegram-actions.ts b/src/agents/tools/telegram-actions.ts index 3d7dc6eb2..515ff8c47 100644 --- a/src/agents/tools/telegram-actions.ts +++ b/src/agents/tools/telegram-actions.ts @@ -165,6 +165,7 @@ export async function handleTelegramAction( const messageThreadId = readNumberParam(params, "messageThreadId", { integer: true, }); + const quoteText = readStringParam(params, "quoteText"); const token = resolveTelegramToken(cfg, { accountId }).token; if (!token) { throw new Error( @@ -178,6 +179,7 @@ export async function handleTelegramAction( buttons, replyToMessageId: replyToMessageId ?? undefined, messageThreadId: messageThreadId ?? undefined, + quoteText: quoteText ?? undefined, asVoice: typeof params.asVoice === "boolean" ? params.asVoice : undefined, silent: typeof params.silent === "boolean" ? params.silent : undefined, }); diff --git a/src/agents/transcript-policy.ts b/src/agents/transcript-policy.ts index 3ea06ce88..9ae14d38f 100644 --- a/src/agents/transcript-policy.ts +++ b/src/agents/transcript-policy.ts @@ -51,7 +51,8 @@ function isOpenAiProvider(provider?: string | null): boolean { function isAnthropicApi(modelApi?: string | null, provider?: string | null): boolean { if (modelApi === "anthropic-messages") return true; const normalized = normalizeProviderId(provider ?? ""); - return normalized === "anthropic" || normalized === "minimax"; + // MiniMax now uses openai-completions API, not anthropic-messages + return normalized === "anthropic"; } function isMistralModel(params: { provider?: string | null; modelId?: string | null }): boolean { diff --git a/src/auto-reply/templating.ts b/src/auto-reply/templating.ts index 593858e64..242cee232 100644 --- a/src/auto-reply/templating.ts +++ b/src/auto-reply/templating.ts @@ -49,6 +49,7 @@ export type MsgContext = { ReplyToIdFull?: string; ReplyToBody?: string; ReplyToSender?: string; + ReplyToIsQuote?: boolean; ForwardedFrom?: string; ForwardedFromType?: string; ForwardedFromId?: string; diff --git a/src/canvas-host/a2ui/.bundle.hash b/src/canvas-host/a2ui/.bundle.hash deleted file mode 100644 index 6c9cb0299..000000000 --- a/src/canvas-host/a2ui/.bundle.hash +++ /dev/null @@ -1 +0,0 @@ -b6d3dea7c656c8a480059c32e954c4d39053ff79c4e9c69b38f4c04e3f0280d4 diff --git a/src/channels/plugins/actions/telegram.ts b/src/channels/plugins/actions/telegram.ts index 2acfaf9f1..693e94492 100644 --- a/src/channels/plugins/actions/telegram.ts +++ b/src/channels/plugins/actions/telegram.ts @@ -23,6 +23,7 @@ function readTelegramSendParams(params: Record) { const buttons = params.buttons; const asVoice = typeof params.asVoice === "boolean" ? params.asVoice : undefined; const silent = typeof params.silent === "boolean" ? params.silent : undefined; + const quoteText = readStringParam(params, "quoteText"); return { to, content, @@ -32,6 +33,7 @@ function readTelegramSendParams(params: Record) { buttons, asVoice, silent, + quoteText: quoteText ?? undefined, }; } diff --git a/src/channels/plugins/outbound/telegram.ts b/src/channels/plugins/outbound/telegram.ts index 6db7afd28..04abb77e0 100644 --- a/src/channels/plugins/outbound/telegram.ts +++ b/src/channels/plugins/outbound/telegram.ts @@ -56,8 +56,10 @@ export const telegramOutbound: ChannelOutboundAdapter = { const replyToMessageId = parseReplyToMessageId(replyToId); const messageThreadId = parseThreadId(threadId); const telegramData = payload.channelData?.telegram as - | { buttons?: Array> } + | { buttons?: Array>; quoteText?: string } | undefined; + const quoteText = + typeof telegramData?.quoteText === "string" ? telegramData.quoteText : undefined; const text = payload.text ?? ""; const mediaUrls = payload.mediaUrls?.length ? payload.mediaUrls @@ -69,6 +71,7 @@ export const telegramOutbound: ChannelOutboundAdapter = { textMode: "html" as const, messageThreadId, replyToMessageId, + quoteText, accountId: accountId ?? undefined, }; diff --git a/src/channels/targets.ts b/src/channels/targets.ts index 77ab755b7..7c9d9cf60 100644 --- a/src/channels/targets.ts +++ b/src/channels/targets.ts @@ -1,3 +1,6 @@ +export type { DirectoryConfigParams } from "./plugins/directory-config.js"; +export type { ChannelDirectoryEntry } from "./plugins/types.js"; + export type MessagingTargetKind = "user" | "channel"; export type MessagingTarget = { diff --git a/src/cli/banner.ts b/src/cli/banner.ts index 0d9c435c8..6ca7d4cbc 100644 --- a/src/cli/banner.ts +++ b/src/cli/banner.ts @@ -65,12 +65,12 @@ export function formatCliBannerLine(version: string, options: BannerOptions = {} } const LOBSTER_ASCII = [ - "░████░█░░░░░█████░█░░░█░███░░████░░████░░▀█▀", - "█░░░░░█░░░░░█░░░█░█░█░█░█░░█░█░░░█░█░░░█░░█░", - "█░░░░░█░░░░░█████░█░█░█░█░░█░████░░█░░░█░░█░", - "█░░░░░█░░░░░█░░░█░█░█░█░█░░█░█░░█░░█░░░█░░█░", - "░████░█████░█░░░█░░█░█░░███░░████░░░███░░░█░", - " 🦞 FRESH DAILY 🦞", + "▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄", + "██░▄▀▄░██░▄▄▄░██░████▄▄░▄▄██░▄▄▀██░▄▄▄░█▄▄░▄▄██", + "██░█░█░██░███░██░██████░████░▄▄▀██░███░███░████", + "██░███░██░▀▀▀░██░▀▀░███░████░▀▀░██░▀▀▀░███░████", + "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀", + " 🦞 FRESH DAILY 🦞 ", ]; export function formatCliBannerArt(options: BannerOptions = {}): string { diff --git a/src/cli/daemon-cli.coverage.test.ts b/src/cli/daemon-cli.coverage.test.ts index 6c8ff9cc9..0a6d07390 100644 --- a/src/cli/daemon-cli.coverage.test.ts +++ b/src/cli/daemon-cli.coverage.test.ts @@ -145,7 +145,7 @@ describe("daemon-cli coverage", () => { CLAWDBOT_CONFIG_PATH: "/tmp/moltbot-daemon-state/moltbot.json", CLAWDBOT_GATEWAY_PORT: "19001", }, - sourcePath: "/tmp/com.clawdbot.gateway.plist", + sourcePath: "/tmp/bot.molt.gateway.plist", }); const { registerDaemonCli } = await import("./daemon-cli.js"); diff --git a/src/cli/gateway-cli/dev.ts b/src/cli/gateway-cli/dev.ts index cc754c4dd..565df14b8 100644 --- a/src/cli/gateway-cli/dev.ts +++ b/src/cli/gateway-cli/dev.ts @@ -4,7 +4,7 @@ import path from "node:path"; import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js"; import { handleReset } from "../../commands/onboard-helpers.js"; -import { CONFIG_PATH, writeConfigFile } from "../../config/config.js"; +import { createConfigIO, writeConfigFile } from "../../config/config.js"; import { defaultRuntime } from "../../runtime.js"; import { resolveUserPath, shortenHomePath } from "../../utils.js"; @@ -89,7 +89,9 @@ export async function ensureDevGatewayConfig(opts: { reset?: boolean }) { await handleReset("full", workspace, defaultRuntime); } - const configExists = fs.existsSync(CONFIG_PATH); + const io = createConfigIO(); + const configPath = io.configPath; + const configExists = fs.existsSync(configPath); if (!opts.reset && configExists) return; await writeConfigFile({ @@ -117,6 +119,6 @@ export async function ensureDevGatewayConfig(opts: { reset?: boolean }) { }, }); await ensureDevWorkspace(workspace); - defaultRuntime.log(`Dev config ready: ${shortenHomePath(CONFIG_PATH)}`); + defaultRuntime.log(`Dev config ready: ${shortenHomePath(configPath)}`); defaultRuntime.log(`Dev workspace ready: ${shortenHomePath(resolveUserPath(workspace))}`); } diff --git a/src/cli/gateway-cli/run.ts b/src/cli/gateway-cli/run.ts index 0f4d4e9b7..cb26aa98d 100644 --- a/src/cli/gateway-cli/run.ts +++ b/src/cli/gateway-cli/run.ts @@ -157,7 +157,8 @@ async function runGatewayCommand(opts: GatewayRunOpts) { const passwordRaw = toOptionString(opts.password); const tokenRaw = toOptionString(opts.token); - const configExists = fs.existsSync(CONFIG_PATH); + const snapshot = await readConfigFileSnapshot().catch(() => null); + const configExists = snapshot?.exists ?? fs.existsSync(CONFIG_PATH); const mode = cfg.gateway?.mode; if (!opts.allowUnconfigured && mode !== "local") { if (!configExists) { @@ -187,7 +188,6 @@ async function runGatewayCommand(opts: GatewayRunOpts) { return; } - const snapshot = await readConfigFileSnapshot().catch(() => null); const miskeys = extractGatewayMiskeys(snapshot?.parsed); const authConfig = { ...cfg.gateway?.auth, diff --git a/src/commands/configure.daemon.ts b/src/commands/configure.daemon.ts index 38d8365c0..c0431c9f1 100644 --- a/src/commands/configure.daemon.ts +++ b/src/commands/configure.daemon.ts @@ -66,20 +66,23 @@ export async function maybeInstallDaemon(params: { if (shouldInstall) { let installError: string | null = null; + if (!params.daemonRuntime) { + if (GATEWAY_DAEMON_RUNTIME_OPTIONS.length === 1) { + daemonRuntime = GATEWAY_DAEMON_RUNTIME_OPTIONS[0]?.value ?? DEFAULT_GATEWAY_DAEMON_RUNTIME; + } else { + daemonRuntime = guardCancel( + await select({ + message: "Gateway service runtime", + options: GATEWAY_DAEMON_RUNTIME_OPTIONS, + initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME, + }), + params.runtime, + ) as GatewayDaemonRuntime; + } + } await withProgress( { label: "Gateway service", indeterminate: true, delayMs: 0 }, async (progress) => { - if (!params.daemonRuntime) { - daemonRuntime = guardCancel( - await select({ - message: "Gateway service runtime", - options: GATEWAY_DAEMON_RUNTIME_OPTIONS, - initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME, - }), - params.runtime, - ) as GatewayDaemonRuntime; - } - progress.setLabel("Preparing Gateway service…"); const cfg = loadConfig(); diff --git a/src/commands/doctor-config-flow.ts b/src/commands/doctor-config-flow.ts index 879c8679e..fda4673d9 100644 --- a/src/commands/doctor-config-flow.ts +++ b/src/commands/doctor-config-flow.ts @@ -1,3 +1,5 @@ +import fs from "node:fs"; +import path from "node:path"; import type { ZodIssue } from "zod"; import type { MoltbotConfig } from "../config/config.js"; @@ -12,6 +14,7 @@ import { formatCliCommand } from "../cli/command-format.js"; import { note } from "../terminal/note.js"; import { normalizeLegacyConfigValues } from "./doctor-legacy-config.js"; import type { DoctorOptions } from "./doctor-prompter.js"; +import { autoMigrateLegacyStateDir } from "./doctor-state-migrations.js"; function isRecord(value: unknown): value is Record { return Boolean(value && typeof value === "object" && !Array.isArray(value)); @@ -117,12 +120,50 @@ function noteOpencodeProviderOverrides(cfg: MoltbotConfig) { note(lines.join("\n"), "OpenCode Zen"); } +function hasExplicitConfigPath(env: NodeJS.ProcessEnv): boolean { + return Boolean(env.MOLTBOT_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim()); +} + +function moveLegacyConfigFile(legacyPath: string, canonicalPath: string) { + fs.mkdirSync(path.dirname(canonicalPath), { recursive: true, mode: 0o700 }); + try { + fs.renameSync(legacyPath, canonicalPath); + } catch { + fs.copyFileSync(legacyPath, canonicalPath); + fs.chmodSync(canonicalPath, 0o600); + try { + fs.unlinkSync(legacyPath); + } catch { + // Best-effort cleanup; we'll warn later if both files exist. + } + } +} + export async function loadAndMaybeMigrateDoctorConfig(params: { options: DoctorOptions; confirm: (p: { message: string; initialValue: boolean }) => Promise; }) { const shouldRepair = params.options.repair === true || params.options.yes === true; - const snapshot = await readConfigFileSnapshot(); + const stateDirResult = await autoMigrateLegacyStateDir({ env: process.env }); + if (stateDirResult.changes.length > 0) { + note(stateDirResult.changes.map((entry) => `- ${entry}`).join("\n"), "Doctor changes"); + } + if (stateDirResult.warnings.length > 0) { + note(stateDirResult.warnings.map((entry) => `- ${entry}`).join("\n"), "Doctor warnings"); + } + + let snapshot = await readConfigFileSnapshot(); + if (!hasExplicitConfigPath(process.env) && snapshot.exists) { + const basename = path.basename(snapshot.path); + if (basename === "clawdbot.json") { + const canonicalPath = path.join(path.dirname(snapshot.path), "moltbot.json"); + if (!fs.existsSync(canonicalPath)) { + moveLegacyConfigFile(snapshot.path, canonicalPath); + note(`- Config: ${snapshot.path} → ${canonicalPath}`, "Doctor changes"); + snapshot = await readConfigFileSnapshot(); + } + } + } const baseCfg = snapshot.config ?? {}; let cfg: MoltbotConfig = baseCfg; let candidate = structuredClone(baseCfg) as MoltbotConfig; diff --git a/src/commands/doctor-security.ts b/src/commands/doctor-security.ts index bf2c94da7..856b18bfb 100644 --- a/src/commands/doctor-security.ts +++ b/src/commands/doctor-security.ts @@ -124,7 +124,7 @@ export async function noteSecurityWarnings(cfg: MoltbotConfig) { if (dmScope === "main" && isMultiUserDm) { warnings.push( - `- ${params.label} DMs: multiple senders share the main session; set session.dmScope="per-channel-peer" to isolate sessions.`, + `- ${params.label} DMs: multiple senders share the main session; set session.dmScope="per-channel-peer" (or "per-account-channel-peer" for multi-account channels) to isolate sessions.`, ); } }; diff --git a/src/commands/doctor-state-migrations.test.ts b/src/commands/doctor-state-migrations.test.ts index 15ba11804..2ae7faf05 100644 --- a/src/commands/doctor-state-migrations.test.ts +++ b/src/commands/doctor-state-migrations.test.ts @@ -6,8 +6,10 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import type { MoltbotConfig } from "../config/config.js"; import { + autoMigrateLegacyStateDir, autoMigrateLegacyState, detectLegacyStateMigrations, + resetAutoMigrateLegacyStateDirForTest, resetAutoMigrateLegacyStateForTest, runLegacyStateMigrations, } from "./doctor-state-migrations.js"; @@ -22,6 +24,7 @@ async function makeTempRoot() { afterEach(async () => { resetAutoMigrateLegacyStateForTest(); + resetAutoMigrateLegacyStateDirForTest(); if (!tempRoot) return; await fs.promises.rm(tempRoot, { recursive: true, force: true }); tempRoot = null; @@ -323,4 +326,53 @@ describe("doctor legacy state migrations", () => { expect(store["main"]).toBeUndefined(); expect(store["agent:main:main"]?.sessionId).toBe("legacy"); }); + + it("auto-migrates legacy state dir to ~/.moltbot", async () => { + const root = await makeTempRoot(); + const legacyDir = path.join(root, ".clawdbot"); + fs.mkdirSync(legacyDir, { recursive: true }); + fs.writeFileSync(path.join(legacyDir, "foo.txt"), "legacy", "utf-8"); + + const result = await autoMigrateLegacyStateDir({ + env: {} as NodeJS.ProcessEnv, + homedir: () => root, + }); + + const targetDir = path.join(root, ".moltbot"); + expect(fs.existsSync(path.join(targetDir, "foo.txt"))).toBe(true); + const legacyStat = fs.lstatSync(legacyDir); + expect(legacyStat.isSymbolicLink()).toBe(true); + expect(fs.realpathSync(legacyDir)).toBe(fs.realpathSync(targetDir)); + expect(result.migrated).toBe(true); + }); + + it("skips state dir migration when target exists", async () => { + const root = await makeTempRoot(); + const legacyDir = path.join(root, ".clawdbot"); + const targetDir = path.join(root, ".moltbot"); + fs.mkdirSync(legacyDir, { recursive: true }); + fs.mkdirSync(targetDir, { recursive: true }); + + const result = await autoMigrateLegacyStateDir({ + env: {} as NodeJS.ProcessEnv, + homedir: () => root, + }); + + expect(result.migrated).toBe(false); + expect(result.warnings.length).toBeGreaterThan(0); + }); + + it("skips state dir migration when env override is set", async () => { + const root = await makeTempRoot(); + const legacyDir = path.join(root, ".clawdbot"); + fs.mkdirSync(legacyDir, { recursive: true }); + + const result = await autoMigrateLegacyStateDir({ + env: { MOLTBOT_STATE_DIR: "/custom/state" } as NodeJS.ProcessEnv, + homedir: () => root, + }); + + expect(result.skipped).toBe(true); + expect(result.migrated).toBe(false); + }); }); diff --git a/src/commands/doctor-state-migrations.ts b/src/commands/doctor-state-migrations.ts index 7448b8cd7..50c59a3a0 100644 --- a/src/commands/doctor-state-migrations.ts +++ b/src/commands/doctor-state-migrations.ts @@ -1,9 +1,11 @@ export type { LegacyStateDetection } from "../infra/state-migrations.js"; export { + autoMigrateLegacyStateDir, autoMigrateLegacyAgentDir, autoMigrateLegacyState, detectLegacyStateMigrations, migrateLegacyAgentDir, + resetAutoMigrateLegacyStateDirForTest, resetAutoMigrateLegacyAgentDirForTest, resetAutoMigrateLegacyStateForTest, runLegacyStateMigrations, diff --git a/src/commands/doctor.falls-back-legacy-sandbox-image-missing.test.ts b/src/commands/doctor.falls-back-legacy-sandbox-image-missing.test.ts index 08af35e90..7ddcc2049 100644 --- a/src/commands/doctor.falls-back-legacy-sandbox-image-missing.test.ts +++ b/src/commands/doctor.falls-back-legacy-sandbox-image-missing.test.ts @@ -292,6 +292,12 @@ vi.mock("./onboard-helpers.js", () => ({ })); vi.mock("./doctor-state-migrations.js", () => ({ + autoMigrateLegacyStateDir: vi.fn().mockResolvedValue({ + migrated: false, + skipped: false, + changes: [], + warnings: [], + }), detectLegacyStateMigrations: vi.fn().mockResolvedValue({ targetAgentId: "main", targetMainKey: "main", diff --git a/src/commands/doctor.migrates-routing-allowfrom-channels-whatsapp-allowfrom.test.ts b/src/commands/doctor.migrates-routing-allowfrom-channels-whatsapp-allowfrom.test.ts index b6cb0c988..4f6651251 100644 --- a/src/commands/doctor.migrates-routing-allowfrom-channels-whatsapp-allowfrom.test.ts +++ b/src/commands/doctor.migrates-routing-allowfrom-channels-whatsapp-allowfrom.test.ts @@ -291,6 +291,12 @@ vi.mock("./onboard-helpers.js", () => ({ })); vi.mock("./doctor-state-migrations.js", () => ({ + autoMigrateLegacyStateDir: vi.fn().mockResolvedValue({ + migrated: false, + skipped: false, + changes: [], + warnings: [], + }), detectLegacyStateMigrations: vi.fn().mockResolvedValue({ targetAgentId: "main", targetMainKey: "main", diff --git a/src/commands/doctor.runs-legacy-state-migrations-yes-mode-without.test.ts b/src/commands/doctor.runs-legacy-state-migrations-yes-mode-without.test.ts index 677813bc1..f36b85b29 100644 --- a/src/commands/doctor.runs-legacy-state-migrations-yes-mode-without.test.ts +++ b/src/commands/doctor.runs-legacy-state-migrations-yes-mode-without.test.ts @@ -291,6 +291,12 @@ vi.mock("./onboard-helpers.js", () => ({ })); vi.mock("./doctor-state-migrations.js", () => ({ + autoMigrateLegacyStateDir: vi.fn().mockResolvedValue({ + migrated: false, + skipped: false, + changes: [], + warnings: [], + }), detectLegacyStateMigrations: vi.fn().mockResolvedValue({ targetAgentId: "main", targetMainKey: "main", diff --git a/src/commands/doctor.warns-per-agent-sandbox-docker-browser-prune.test.ts b/src/commands/doctor.warns-per-agent-sandbox-docker-browser-prune.test.ts index 980ddc8dc..d2d232606 100644 --- a/src/commands/doctor.warns-per-agent-sandbox-docker-browser-prune.test.ts +++ b/src/commands/doctor.warns-per-agent-sandbox-docker-browser-prune.test.ts @@ -291,6 +291,12 @@ vi.mock("./onboard-helpers.js", () => ({ })); vi.mock("./doctor-state-migrations.js", () => ({ + autoMigrateLegacyStateDir: vi.fn().mockResolvedValue({ + migrated: false, + skipped: false, + changes: [], + warnings: [], + }), detectLegacyStateMigrations: vi.fn().mockResolvedValue({ targetAgentId: "main", targetMainKey: "main", diff --git a/src/commands/doctor.warns-state-directory-is-missing.test.ts b/src/commands/doctor.warns-state-directory-is-missing.test.ts index 4bbc938fc..10b9e8a67 100644 --- a/src/commands/doctor.warns-state-directory-is-missing.test.ts +++ b/src/commands/doctor.warns-state-directory-is-missing.test.ts @@ -291,6 +291,12 @@ vi.mock("./onboard-helpers.js", () => ({ })); vi.mock("./doctor-state-migrations.js", () => ({ + autoMigrateLegacyStateDir: vi.fn().mockResolvedValue({ + migrated: false, + skipped: false, + changes: [], + warnings: [], + }), detectLegacyStateMigrations: vi.fn().mockResolvedValue({ targetAgentId: "main", targetMainKey: "main", diff --git a/src/commands/onboard-channels.ts b/src/commands/onboard-channels.ts index e1f8dbe8e..27ec07de4 100644 --- a/src/commands/onboard-channels.ts +++ b/src/commands/onboard-channels.ts @@ -190,7 +190,7 @@ async function noteChannelPrimer( "DM security: default is pairing; unknown DMs get a pairing code.", `Approve with: ${formatCliCommand("moltbot pairing approve ")}`, 'Public DMs require dmPolicy="open" + allowFrom=["*"].', - 'Multi-user DMs: set session.dmScope="per-channel-peer" to isolate sessions.', + 'Multi-user DMs: set session.dmScope="per-channel-peer" (or "per-account-channel-peer" for multi-account channels) to isolate sessions.', `Docs: ${formatDocsLink("/start/pairing", "start/pairing")}`, "", ...channelLines, @@ -238,7 +238,7 @@ async function maybeConfigureDmPolicies(params: { `Approve: ${formatCliCommand(`moltbot pairing approve ${policy.channel} `)}`, `Allowlist DMs: ${policy.policyKey}="allowlist" + ${policy.allowFromKey} entries.`, `Public DMs: ${policy.policyKey}="open" + ${policy.allowFromKey} includes "*".`, - 'Multi-user DMs: set session.dmScope="per-channel-peer" to isolate sessions.', + 'Multi-user DMs: set session.dmScope="per-channel-peer" (or "per-account-channel-peer" for multi-account channels) to isolate sessions.', `Docs: ${formatDocsLink("/start/pairing", "start/pairing")}`, ].join("\n"), `${policy.label} DM access`, diff --git a/src/commands/onboard-helpers.ts b/src/commands/onboard-helpers.ts index 03fe77a27..165365bb6 100644 --- a/src/commands/onboard-helpers.ts +++ b/src/commands/onboard-helpers.ts @@ -64,12 +64,12 @@ export function randomToken(): string { export function printWizardHeader(runtime: RuntimeEnv) { const header = [ - "░████░█░░░░░█████░█░░░█░███░░████░░████░░▀█▀", - "█░░░░░█░░░░░█░░░█░█░█░█░█░░█░█░░░█░█░░░█░░█░", - "█░░░░░█░░░░░█████░█░█░█░█░░█░████░░█░░░█░░█░", - "█░░░░░█░░░░░█░░░█░█░█░█░█░░█░█░░█░░█░░░█░░█░", - "░████░█████░█░░░█░░█░█░░███░░████░░░███░░░█░", - " 🦞 FRESH DAILY 🦞", + "▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄", + "██░▄▀▄░██░▄▄▄░██░████▄▄░▄▄██░▄▄▀██░▄▄▄░█▄▄░▄▄██", + "██░█░█░██░███░██░██████░████░▄▄▀██░███░███░████", + "██░███░██░▀▀▀░██░▀▀░███░████░▀▀░██░▀▀▀░███░████", + "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀", + " 🦞 FRESH DAILY 🦞 ", ].join("\n"); runtime.log(header); } diff --git a/src/commands/setup.ts b/src/commands/setup.ts index ad1d4ec38..2f3ea90c7 100644 --- a/src/commands/setup.ts +++ b/src/commands/setup.ts @@ -3,19 +3,19 @@ import fs from "node:fs/promises"; import JSON5 from "json5"; import { DEFAULT_AGENT_WORKSPACE_DIR, ensureAgentWorkspace } from "../agents/workspace.js"; -import { type MoltbotConfig, CONFIG_PATH, writeConfigFile } from "../config/config.js"; +import { type MoltbotConfig, createConfigIO, writeConfigFile } from "../config/config.js"; import { formatConfigPath, logConfigUpdated } from "../config/logging.js"; import { resolveSessionTranscriptsDir } from "../config/sessions.js"; import type { RuntimeEnv } from "../runtime.js"; import { defaultRuntime } from "../runtime.js"; import { shortenHomePath } from "../utils.js"; -async function readConfigFileRaw(): Promise<{ +async function readConfigFileRaw(configPath: string): Promise<{ exists: boolean; parsed: MoltbotConfig; }> { try { - const raw = await fs.readFile(CONFIG_PATH, "utf-8"); + const raw = await fs.readFile(configPath, "utf-8"); const parsed = JSON5.parse(raw); if (parsed && typeof parsed === "object") { return { exists: true, parsed: parsed as MoltbotConfig }; @@ -35,7 +35,9 @@ export async function setupCommand( ? opts.workspace.trim() : undefined; - const existingRaw = await readConfigFileRaw(); + const io = createConfigIO(); + const configPath = io.configPath; + const existingRaw = await readConfigFileRaw(configPath); const cfg = existingRaw.parsed; const defaults = cfg.agents?.defaults ?? {}; @@ -55,12 +57,12 @@ export async function setupCommand( if (!existingRaw.exists || defaults.workspace !== workspace) { await writeConfigFile(next); if (!existingRaw.exists) { - runtime.log(`Wrote ${formatConfigPath()}`); + runtime.log(`Wrote ${formatConfigPath(configPath)}`); } else { - logConfigUpdated(runtime, { suffix: "(set agents.defaults.workspace)" }); + logConfigUpdated(runtime, { path: configPath, suffix: "(set agents.defaults.workspace)" }); } } else { - runtime.log(`Config OK: ${formatConfigPath()}`); + runtime.log(`Config OK: ${formatConfigPath(configPath)}`); } const ws = await ensureAgentWorkspace({ diff --git a/src/commands/status.test.ts b/src/commands/status.test.ts index a425df6ad..06d07d476 100644 --- a/src/commands/status.test.ts +++ b/src/commands/status.test.ts @@ -255,7 +255,7 @@ vi.mock("../daemon/service.js", () => ({ readRuntime: async () => ({ status: "running", pid: 1234 }), readCommand: async () => ({ programArguments: ["node", "dist/entry.js", "gateway"], - sourcePath: "/tmp/Library/LaunchAgents/com.clawdbot.gateway.plist", + sourcePath: "/tmp/Library/LaunchAgents/bot.molt.gateway.plist", }), }), })); @@ -268,7 +268,7 @@ vi.mock("../daemon/node-service.js", () => ({ readRuntime: async () => ({ status: "running", pid: 4321 }), readCommand: async () => ({ programArguments: ["node", "dist/entry.js", "node-host"], - sourcePath: "/tmp/Library/LaunchAgents/com.clawdbot.node.plist", + sourcePath: "/tmp/Library/LaunchAgents/bot.molt.node.plist", }), }), })); diff --git a/src/compat/legacy-names.ts b/src/compat/legacy-names.ts index f2ff6993d..e57b6b688 100644 --- a/src/compat/legacy-names.ts +++ b/src/compat/legacy-names.ts @@ -7,3 +7,5 @@ export const LEGACY_PLUGIN_MANIFEST_FILENAME = `${LEGACY_PROJECT_NAME}.plugin.js export const LEGACY_CANVAS_HANDLER_NAME = `${LEGACY_PROJECT_NAME}CanvasA2UIAction` as const; export const LEGACY_MACOS_APP_SOURCES_DIR = "apps/macos/Sources/Clawdbot" as const; + +export const MACOS_APP_SOURCES_DIR = "apps/macos/Sources/Moltbot" as const; diff --git a/src/config/io.compat.test.ts b/src/config/io.compat.test.ts index 4a32658ae..fd98f2650 100644 --- a/src/config/io.compat.test.ts +++ b/src/config/io.compat.test.ts @@ -14,10 +14,15 @@ async function withTempHome(run: (home: string) => Promise): Promise } } -async function writeConfig(home: string, dirname: ".moltbot" | ".clawdbot", port: number) { +async function writeConfig( + home: string, + dirname: ".moltbot" | ".clawdbot", + port: number, + filename: "moltbot.json" | "clawdbot.json" = "moltbot.json", +) { const dir = path.join(home, dirname); await fs.mkdir(dir, { recursive: true }); - const configPath = path.join(dir, "moltbot.json"); + const configPath = path.join(dir, filename); await fs.writeFile(configPath, JSON.stringify({ gateway: { port } }, null, 2)); return configPath; } @@ -51,6 +56,35 @@ describe("config io compat (new + legacy folders)", () => { }); }); + it("falls back to ~/.clawdbot/clawdbot.json when only legacy filename exists", async () => { + await withTempHome(async (home) => { + const legacyConfigPath = await writeConfig(home, ".clawdbot", 20002, "clawdbot.json"); + + const io = createConfigIO({ + env: {} as NodeJS.ProcessEnv, + homedir: () => home, + }); + + expect(io.configPath).toBe(legacyConfigPath); + expect(io.loadConfig().gateway?.port).toBe(20002); + }); + }); + + it("prefers moltbot.json over legacy filename in the same dir", async () => { + await withTempHome(async (home) => { + const preferred = await writeConfig(home, ".clawdbot", 20003, "moltbot.json"); + await writeConfig(home, ".clawdbot", 20004, "clawdbot.json"); + + const io = createConfigIO({ + env: {} as NodeJS.ProcessEnv, + homedir: () => home, + }); + + expect(io.configPath).toBe(preferred); + expect(io.loadConfig().gateway?.port).toBe(20003); + }); + }); + it("honors explicit legacy config path env override", async () => { await withTempHome(async (home) => { const newConfigPath = await writeConfig(home, ".moltbot", 19002); diff --git a/src/config/io.ts b/src/config/io.ts index ef8ffba86..50f1edb82 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -555,7 +555,8 @@ function clearConfigCache(): void { } export function loadConfig(): MoltbotConfig { - const configPath = resolveConfigPath(); + const io = createConfigIO(); + const configPath = io.configPath; const now = Date.now(); if (shouldUseConfigCache(process.env)) { const cached = configCache; @@ -563,7 +564,7 @@ export function loadConfig(): MoltbotConfig { return cached.config; } } - const config = createConfigIO({ configPath }).loadConfig(); + const config = io.loadConfig(); if (shouldUseConfigCache(process.env)) { const cacheMs = resolveConfigCacheMs(process.env); if (cacheMs > 0) { @@ -578,12 +579,10 @@ export function loadConfig(): MoltbotConfig { } export async function readConfigFileSnapshot(): Promise { - return await createConfigIO({ - configPath: resolveConfigPath(), - }).readConfigFileSnapshot(); + return await createConfigIO().readConfigFileSnapshot(); } export async function writeConfigFile(cfg: MoltbotConfig): Promise { clearConfigCache(); - await createConfigIO({ configPath: resolveConfigPath() }).writeConfigFile(cfg); + await createConfigIO().writeConfigFile(cfg); } diff --git a/src/config/paths.test.ts b/src/config/paths.test.ts index f99e88513..806d29f92 100644 --- a/src/config/paths.test.ts +++ b/src/config/paths.test.ts @@ -1,8 +1,11 @@ +import fs from "node:fs/promises"; +import os from "node:os"; import path from "node:path"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { resolveDefaultConfigCandidates, + resolveConfigPath, resolveOAuthDir, resolveOAuthPath, resolveStateDir, @@ -47,6 +50,93 @@ describe("state + config path candidates", () => { const home = "/home/test"; const candidates = resolveDefaultConfigCandidates({} as NodeJS.ProcessEnv, () => home); expect(candidates[0]).toBe(path.join(home, ".moltbot", "moltbot.json")); - expect(candidates[1]).toBe(path.join(home, ".clawdbot", "moltbot.json")); + expect(candidates[1]).toBe(path.join(home, ".moltbot", "clawdbot.json")); + expect(candidates[2]).toBe(path.join(home, ".clawdbot", "moltbot.json")); + expect(candidates[3]).toBe(path.join(home, ".clawdbot", "clawdbot.json")); + }); + + it("prefers ~/.moltbot when it exists and legacy dir is missing", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-state-")); + try { + const newDir = path.join(root, ".moltbot"); + await fs.mkdir(newDir, { recursive: true }); + const resolved = resolveStateDir({} as NodeJS.ProcessEnv, () => root); + expect(resolved).toBe(newDir); + } finally { + await fs.rm(root, { recursive: true, force: true }); + } + }); + + it("CONFIG_PATH prefers existing legacy filename when present", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-config-")); + const previousHome = process.env.HOME; + const previousUserProfile = process.env.USERPROFILE; + const previousHomeDrive = process.env.HOMEDRIVE; + const previousHomePath = process.env.HOMEPATH; + const previousMoltbotConfig = process.env.MOLTBOT_CONFIG_PATH; + const previousClawdbotConfig = process.env.CLAWDBOT_CONFIG_PATH; + const previousMoltbotState = process.env.MOLTBOT_STATE_DIR; + const previousClawdbotState = process.env.CLAWDBOT_STATE_DIR; + try { + const legacyDir = path.join(root, ".clawdbot"); + await fs.mkdir(legacyDir, { recursive: true }); + const legacyPath = path.join(legacyDir, "clawdbot.json"); + await fs.writeFile(legacyPath, "{}", "utf-8"); + + process.env.HOME = root; + if (process.platform === "win32") { + process.env.USERPROFILE = root; + const parsed = path.win32.parse(root); + process.env.HOMEDRIVE = parsed.root.replace(/\\$/, ""); + process.env.HOMEPATH = root.slice(parsed.root.length - 1); + } + delete process.env.MOLTBOT_CONFIG_PATH; + delete process.env.CLAWDBOT_CONFIG_PATH; + delete process.env.MOLTBOT_STATE_DIR; + delete process.env.CLAWDBOT_STATE_DIR; + + vi.resetModules(); + const { CONFIG_PATH } = await import("./paths.js"); + expect(CONFIG_PATH).toBe(legacyPath); + } finally { + if (previousHome === undefined) { + delete process.env.HOME; + } else { + process.env.HOME = previousHome; + } + if (previousUserProfile === undefined) delete process.env.USERPROFILE; + else process.env.USERPROFILE = previousUserProfile; + if (previousHomeDrive === undefined) delete process.env.HOMEDRIVE; + else process.env.HOMEDRIVE = previousHomeDrive; + if (previousHomePath === undefined) delete process.env.HOMEPATH; + else process.env.HOMEPATH = previousHomePath; + if (previousMoltbotConfig === undefined) delete process.env.MOLTBOT_CONFIG_PATH; + else process.env.MOLTBOT_CONFIG_PATH = previousMoltbotConfig; + if (previousClawdbotConfig === undefined) delete process.env.CLAWDBOT_CONFIG_PATH; + else process.env.CLAWDBOT_CONFIG_PATH = previousClawdbotConfig; + if (previousMoltbotState === undefined) delete process.env.MOLTBOT_STATE_DIR; + else process.env.MOLTBOT_STATE_DIR = previousMoltbotState; + if (previousClawdbotState === undefined) delete process.env.CLAWDBOT_STATE_DIR; + else process.env.CLAWDBOT_STATE_DIR = previousClawdbotState; + await fs.rm(root, { recursive: true, force: true }); + vi.resetModules(); + } + }); + + it("respects state dir overrides when config is missing", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-config-override-")); + try { + const legacyDir = path.join(root, ".clawdbot"); + await fs.mkdir(legacyDir, { recursive: true }); + const legacyConfig = path.join(legacyDir, "moltbot.json"); + await fs.writeFile(legacyConfig, "{}", "utf-8"); + + const overrideDir = path.join(root, "override"); + const env = { MOLTBOT_STATE_DIR: overrideDir } as NodeJS.ProcessEnv; + const resolved = resolveConfigPath(env, overrideDir, () => root); + expect(resolved).toBe(path.join(overrideDir, "moltbot.json")); + } finally { + await fs.rm(root, { recursive: true, force: true }); + } }); }); diff --git a/src/config/paths.ts b/src/config/paths.ts index 2fc3937c4..f6e451596 100644 --- a/src/config/paths.ts +++ b/src/config/paths.ts @@ -1,3 +1,4 @@ +import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import type { MoltbotConfig } from "./types.js"; @@ -18,6 +19,7 @@ export const isNixMode = resolveIsNixMode(); const LEGACY_STATE_DIRNAME = ".clawdbot"; const NEW_STATE_DIRNAME = ".moltbot"; const CONFIG_FILENAME = "moltbot.json"; +const LEGACY_CONFIG_FILENAME = "clawdbot.json"; function legacyStateDir(homedir: () => string = os.homedir): string { return path.join(homedir(), LEGACY_STATE_DIRNAME); @@ -27,10 +29,19 @@ function newStateDir(homedir: () => string = os.homedir): string { return path.join(homedir(), NEW_STATE_DIRNAME); } +export function resolveLegacyStateDir(homedir: () => string = os.homedir): string { + return legacyStateDir(homedir); +} + +export function resolveNewStateDir(homedir: () => string = os.homedir): string { + return newStateDir(homedir); +} + /** * State directory for mutable data (sessions, logs, caches). * Can be overridden via MOLTBOT_STATE_DIR (preferred) or CLAWDBOT_STATE_DIR (legacy). * Default: ~/.clawdbot (legacy default for compatibility) + * If ~/.moltbot exists and ~/.clawdbot does not, prefer ~/.moltbot. */ export function resolveStateDir( env: NodeJS.ProcessEnv = process.env, @@ -38,7 +49,12 @@ export function resolveStateDir( ): string { const override = env.MOLTBOT_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim(); if (override) return resolveUserPath(override); - return legacyStateDir(homedir); + const legacyDir = legacyStateDir(homedir); + const newDir = newStateDir(homedir); + const hasLegacy = fs.existsSync(legacyDir); + const hasNew = fs.existsSync(newDir); + if (!hasLegacy && hasNew) return newDir; + return legacyDir; } function resolveUserPath(input: string): string { @@ -58,7 +74,7 @@ export const STATE_DIR = resolveStateDir(); * Can be overridden via MOLTBOT_CONFIG_PATH (preferred) or CLAWDBOT_CONFIG_PATH (legacy). * Default: ~/.clawdbot/moltbot.json (or $*_STATE_DIR/moltbot.json) */ -export function resolveConfigPath( +export function resolveCanonicalConfigPath( env: NodeJS.ProcessEnv = process.env, stateDir: string = resolveStateDir(env, os.homedir), ): string { @@ -67,7 +83,58 @@ export function resolveConfigPath( return path.join(stateDir, CONFIG_FILENAME); } -export const CONFIG_PATH = resolveConfigPath(); +/** + * Resolve the active config path by preferring existing config candidates + * (new/legacy filenames) before falling back to the canonical path. + */ +export function resolveConfigPathCandidate( + env: NodeJS.ProcessEnv = process.env, + homedir: () => string = os.homedir, +): string { + const candidates = resolveDefaultConfigCandidates(env, homedir); + const existing = candidates.find((candidate) => { + try { + return fs.existsSync(candidate); + } catch { + return false; + } + }); + if (existing) return existing; + return resolveCanonicalConfigPath(env, resolveStateDir(env, homedir)); +} + +/** + * Active config path (prefers existing legacy/new config files). + */ +export function resolveConfigPath( + env: NodeJS.ProcessEnv = process.env, + stateDir: string = resolveStateDir(env, os.homedir), + homedir: () => string = os.homedir, +): string { + const override = env.MOLTBOT_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim(); + if (override) return resolveUserPath(override); + const stateOverride = env.MOLTBOT_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim(); + const candidates = [ + path.join(stateDir, CONFIG_FILENAME), + path.join(stateDir, LEGACY_CONFIG_FILENAME), + ]; + const existing = candidates.find((candidate) => { + try { + return fs.existsSync(candidate); + } catch { + return false; + } + }); + if (existing) return existing; + if (stateOverride) return path.join(stateDir, CONFIG_FILENAME); + const defaultStateDir = resolveStateDir(env, homedir); + if (path.resolve(stateDir) === path.resolve(defaultStateDir)) { + return resolveConfigPathCandidate(env, homedir); + } + return path.join(stateDir, CONFIG_FILENAME); +} + +export const CONFIG_PATH = resolveConfigPathCandidate(); /** * Resolve default config path candidates across new + legacy locations. @@ -84,14 +151,18 @@ export function resolveDefaultConfigCandidates( const moltbotStateDir = env.MOLTBOT_STATE_DIR?.trim(); if (moltbotStateDir) { candidates.push(path.join(resolveUserPath(moltbotStateDir), CONFIG_FILENAME)); + candidates.push(path.join(resolveUserPath(moltbotStateDir), LEGACY_CONFIG_FILENAME)); } const legacyStateDirOverride = env.CLAWDBOT_STATE_DIR?.trim(); if (legacyStateDirOverride) { candidates.push(path.join(resolveUserPath(legacyStateDirOverride), CONFIG_FILENAME)); + candidates.push(path.join(resolveUserPath(legacyStateDirOverride), LEGACY_CONFIG_FILENAME)); } candidates.push(path.join(newStateDir(homedir), CONFIG_FILENAME)); + candidates.push(path.join(newStateDir(homedir), LEGACY_CONFIG_FILENAME)); candidates.push(path.join(legacyStateDir(homedir), CONFIG_FILENAME)); + candidates.push(path.join(legacyStateDir(homedir), LEGACY_CONFIG_FILENAME)); return candidates; } diff --git a/src/config/schema.ts b/src/config/schema.ts index 9b5ad8be6..b4ec8723b 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -591,7 +591,7 @@ const FIELD_HELP: Record = { "commands.restart": "Allow /restart and gateway restart tool actions (default: false).", "commands.useAccessGroups": "Enforce access-group allowlists/policies for commands.", "session.dmScope": - 'DM session scoping: "main" keeps continuity; "per-peer" or "per-channel-peer" isolates DM history (recommended for shared inboxes).', + 'DM session scoping: "main" keeps continuity; "per-peer", "per-channel-peer", or "per-account-channel-peer" isolates DM history (recommended for shared inboxes/multi-account).', "session.identityLinks": "Map canonical identities to provider-prefixed peer IDs for DM session linking (example: telegram:123456).", "channels.telegram.configWrites": diff --git a/src/config/types.base.ts b/src/config/types.base.ts index cc805e8ec..e7da1ecd8 100644 --- a/src/config/types.base.ts +++ b/src/config/types.base.ts @@ -3,7 +3,7 @@ import type { NormalizedChatType } from "../channels/chat-type.js"; export type ReplyMode = "text" | "command"; export type TypingMode = "never" | "instant" | "thinking" | "message"; export type SessionScope = "per-sender" | "global"; -export type DmScope = "main" | "per-peer" | "per-channel-peer"; +export type DmScope = "main" | "per-peer" | "per-channel-peer" | "per-account-channel-peer"; export type ReplyToMode = "off" | "first" | "all"; export type GroupPolicy = "open" | "disabled" | "allowlist"; export type DmPolicy = "pairing" | "allowlist" | "open" | "disabled"; diff --git a/src/config/zod-schema.session.ts b/src/config/zod-schema.session.ts index b9e7b42cc..4412f5515 100644 --- a/src/config/zod-schema.session.ts +++ b/src/config/zod-schema.session.ts @@ -20,7 +20,12 @@ export const SessionSchema = z .object({ scope: z.union([z.literal("per-sender"), z.literal("global")]).optional(), dmScope: z - .union([z.literal("main"), z.literal("per-peer"), z.literal("per-channel-peer")]) + .union([ + z.literal("main"), + z.literal("per-peer"), + z.literal("per-channel-peer"), + z.literal("per-account-channel-peer"), + ]) .optional(), identityLinks: z.record(z.string(), z.array(z.string())).optional(), resetTriggers: z.array(z.string()).optional(), diff --git a/src/cron/cron-protocol-conformance.test.ts b/src/cron/cron-protocol-conformance.test.ts index 9cdf07c31..3eebfa290 100644 --- a/src/cron/cron-protocol-conformance.test.ts +++ b/src/cron/cron-protocol-conformance.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { describe, expect, it } from "vitest"; -import { LEGACY_MACOS_APP_SOURCES_DIR } from "../compat/legacy-names.js"; +import { LEGACY_MACOS_APP_SOURCES_DIR, MACOS_APP_SOURCES_DIR } from "../compat/legacy-names.js"; import { CronPayloadSchema } from "../gateway/protocol/schema.js"; type SchemaLike = { @@ -30,7 +30,26 @@ function extractCronChannels(schema: SchemaLike): string[] { const UI_FILES = ["ui/src/ui/types.ts", "ui/src/ui/ui-types.ts", "ui/src/ui/views/cron.ts"]; -const SWIFT_FILES = [`${LEGACY_MACOS_APP_SOURCES_DIR}/GatewayConnection.swift`]; +const SWIFT_FILE_CANDIDATES = [ + `${MACOS_APP_SOURCES_DIR}/GatewayConnection.swift`, + `${LEGACY_MACOS_APP_SOURCES_DIR}/GatewayConnection.swift`, +]; + +async function resolveSwiftFiles(cwd: string): Promise { + const matches: string[] = []; + for (const relPath of SWIFT_FILE_CANDIDATES) { + try { + await fs.access(path.join(cwd, relPath)); + matches.push(relPath); + } catch { + // ignore missing path + } + } + if (matches.length === 0) { + throw new Error(`Missing Swift cron definition. Tried: ${SWIFT_FILE_CANDIDATES.join(", ")}`); + } + return matches; +} describe("cron protocol conformance", () => { it("ui + swift include all cron providers from gateway schema", async () => { @@ -45,7 +64,8 @@ describe("cron protocol conformance", () => { } } - for (const relPath of SWIFT_FILES) { + const swiftFiles = await resolveSwiftFiles(cwd); + for (const relPath of swiftFiles) { const content = await fs.readFile(path.join(cwd, relPath), "utf-8"); for (const channel of channels) { const pattern = new RegExp(`\\bcase\\s+${channel}\\b`); @@ -61,7 +81,8 @@ describe("cron protocol conformance", () => { expect(uiTypes.includes("jobs:")).toBe(true); expect(uiTypes.includes("jobCount")).toBe(false); - const swiftPath = path.join(cwd, SWIFT_FILES[0]); + const [swiftRelPath] = await resolveSwiftFiles(cwd); + const swiftPath = path.join(cwd, swiftRelPath); const swift = await fs.readFile(swiftPath, "utf-8"); expect(swift.includes("struct CronSchedulerStatus")).toBe(true); expect(swift.includes("let jobs:")).toBe(true); diff --git a/src/daemon/constants.test.ts b/src/daemon/constants.test.ts index 4a82e4b7a..854c527ea 100644 --- a/src/daemon/constants.test.ts +++ b/src/daemon/constants.test.ts @@ -14,7 +14,7 @@ describe("resolveGatewayLaunchAgentLabel", () => { it("returns default label when no profile is set", () => { const result = resolveGatewayLaunchAgentLabel(); expect(result).toBe(GATEWAY_LAUNCH_AGENT_LABEL); - expect(result).toBe("com.clawdbot.gateway"); + expect(result).toBe("bot.molt.gateway"); }); it("returns default label when profile is undefined", () => { @@ -34,17 +34,17 @@ describe("resolveGatewayLaunchAgentLabel", () => { it("returns profile-specific label when profile is set", () => { const result = resolveGatewayLaunchAgentLabel("dev"); - expect(result).toBe("com.clawdbot.dev"); + expect(result).toBe("bot.molt.dev"); }); it("returns profile-specific label for custom profile", () => { const result = resolveGatewayLaunchAgentLabel("work"); - expect(result).toBe("com.clawdbot.work"); + expect(result).toBe("bot.molt.work"); }); it("trims whitespace from profile", () => { const result = resolveGatewayLaunchAgentLabel(" staging "); - expect(result).toBe("com.clawdbot.staging"); + expect(result).toBe("bot.molt.staging"); }); it("returns default label for empty string profile", () => { diff --git a/src/daemon/constants.ts b/src/daemon/constants.ts index 3a0325baa..b46a69817 100644 --- a/src/daemon/constants.ts +++ b/src/daemon/constants.ts @@ -1,16 +1,19 @@ // Default service labels (for backward compatibility and when no profile specified) -export const GATEWAY_LAUNCH_AGENT_LABEL = "com.clawdbot.gateway"; +export const GATEWAY_LAUNCH_AGENT_LABEL = "bot.molt.gateway"; export const GATEWAY_SYSTEMD_SERVICE_NAME = "moltbot-gateway"; export const GATEWAY_WINDOWS_TASK_NAME = "Moltbot Gateway"; export const GATEWAY_SERVICE_MARKER = "moltbot"; export const GATEWAY_SERVICE_KIND = "gateway"; -export const NODE_LAUNCH_AGENT_LABEL = "com.clawdbot.node"; +export const NODE_LAUNCH_AGENT_LABEL = "bot.molt.node"; export const NODE_SYSTEMD_SERVICE_NAME = "moltbot-node"; export const NODE_WINDOWS_TASK_NAME = "Moltbot Node"; export const NODE_SERVICE_MARKER = "moltbot"; export const NODE_SERVICE_KIND = "node"; export const NODE_WINDOWS_TASK_SCRIPT_NAME = "node.cmd"; -export const LEGACY_GATEWAY_LAUNCH_AGENT_LABELS = ["com.steipete.clawdbot.gateway"]; +export const LEGACY_GATEWAY_LAUNCH_AGENT_LABELS = [ + "com.clawdbot.gateway", + "com.steipete.clawdbot.gateway", +]; export const LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES: string[] = []; export const LEGACY_GATEWAY_WINDOWS_TASK_NAMES: string[] = []; @@ -30,7 +33,15 @@ export function resolveGatewayLaunchAgentLabel(profile?: string): string { if (!normalized) { return GATEWAY_LAUNCH_AGENT_LABEL; } - return `com.clawdbot.${normalized}`; + return `bot.molt.${normalized}`; +} + +export function resolveLegacyGatewayLaunchAgentLabels(profile?: string): string[] { + const normalized = normalizeGatewayProfile(profile); + if (!normalized) { + return [...LEGACY_GATEWAY_LAUNCH_AGENT_LABELS]; + } + return [...LEGACY_GATEWAY_LAUNCH_AGENT_LABELS, `com.clawdbot.${normalized}`]; } export function resolveGatewaySystemdServiceName(profile?: string): string { diff --git a/src/daemon/inspect.ts b/src/daemon/inspect.ts index 1296a62d6..46318956d 100644 --- a/src/daemon/inspect.ts +++ b/src/daemon/inspect.ts @@ -6,12 +6,12 @@ import { promisify } from "node:util"; import { GATEWAY_SERVICE_KIND, GATEWAY_SERVICE_MARKER, - LEGACY_GATEWAY_LAUNCH_AGENT_LABELS, LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES, LEGACY_GATEWAY_WINDOWS_TASK_NAMES, resolveGatewayLaunchAgentLabel, resolveGatewaySystemdServiceName, resolveGatewayWindowsTaskName, + resolveLegacyGatewayLaunchAgentLabels, } from "./constants.js"; export type ExtraGatewayService = { @@ -78,7 +78,7 @@ function isMoltbotGatewayLaunchdService(label: string, contents: string): boolea if (hasGatewayServiceMarker(contents)) return true; const lowerContents = contents.toLowerCase(); if (!lowerContents.includes("gateway")) return false; - return label.startsWith("com.clawdbot."); + return label.startsWith("bot.molt.") || label.startsWith("com.clawdbot."); } function isMoltbotGatewaySystemdService(name: string, contents: string): boolean { @@ -102,7 +102,8 @@ function tryExtractPlistLabel(contents: string): string | null { function isIgnoredLaunchdLabel(label: string): boolean { return ( - label === resolveGatewayLaunchAgentLabel() || LEGACY_GATEWAY_LAUNCH_AGENT_LABELS.includes(label) + label === resolveGatewayLaunchAgentLabel() || + resolveLegacyGatewayLaunchAgentLabels(process.env.CLAWDBOT_PROFILE).includes(label) ); } diff --git a/src/daemon/launchd.test.ts b/src/daemon/launchd.test.ts index 4954b6b15..1052cb9b9 100644 --- a/src/daemon/launchd.test.ts +++ b/src/daemon/launchd.test.ts @@ -107,7 +107,7 @@ describe("launchd runtime parsing", () => { describe("launchctl list detection", () => { it("detects the resolved label in launchctl list", async () => { - await withLaunchctlStub({ listOutput: "123 0 com.clawdbot.gateway\n" }, async ({ env }) => { + await withLaunchctlStub({ listOutput: "123 0 bot.molt.gateway\n" }, async ({ env }) => { const listed = await isLaunchAgentListed({ env }); expect(listed).toBe(true); }); @@ -133,7 +133,7 @@ describe("launchd bootstrap repair", () => { .map((line) => JSON.parse(line) as string[]); const domain = typeof process.getuid === "function" ? `gui/${process.getuid()}` : "gui/501"; - const label = "com.clawdbot.gateway"; + const label = "bot.molt.gateway"; const plistPath = resolveLaunchAgentPlistPath(env); expect(calls).toContainEqual(["bootstrap", domain, plistPath]); @@ -201,7 +201,7 @@ describe("launchd install", () => { .map((line) => JSON.parse(line) as string[]); const domain = typeof process.getuid === "function" ? `gui/${process.getuid()}` : "gui/501"; - const label = "com.clawdbot.gateway"; + const label = "bot.molt.gateway"; const plistPath = resolveLaunchAgentPlistPath(env); const serviceId = `${domain}/${label}`; @@ -231,21 +231,21 @@ describe("resolveLaunchAgentPlistPath", () => { it("uses default label when CLAWDBOT_PROFILE is default", () => { const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "default" }; expect(resolveLaunchAgentPlistPath(env)).toBe( - "/Users/test/Library/LaunchAgents/com.clawdbot.gateway.plist", + "/Users/test/Library/LaunchAgents/bot.molt.gateway.plist", ); }); it("uses default label when CLAWDBOT_PROFILE is unset", () => { const env = { HOME: "/Users/test" }; expect(resolveLaunchAgentPlistPath(env)).toBe( - "/Users/test/Library/LaunchAgents/com.clawdbot.gateway.plist", + "/Users/test/Library/LaunchAgents/bot.molt.gateway.plist", ); }); it("uses profile-specific label when CLAWDBOT_PROFILE is set to a custom value", () => { const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "jbphoenix" }; expect(resolveLaunchAgentPlistPath(env)).toBe( - "/Users/test/Library/LaunchAgents/com.clawdbot.jbphoenix.plist", + "/Users/test/Library/LaunchAgents/bot.molt.jbphoenix.plist", ); }); @@ -277,28 +277,28 @@ describe("resolveLaunchAgentPlistPath", () => { CLAWDBOT_LAUNCHD_LABEL: " ", }; expect(resolveLaunchAgentPlistPath(env)).toBe( - "/Users/test/Library/LaunchAgents/com.clawdbot.myprofile.plist", + "/Users/test/Library/LaunchAgents/bot.molt.myprofile.plist", ); }); it("handles case-insensitive 'Default' profile", () => { const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "Default" }; expect(resolveLaunchAgentPlistPath(env)).toBe( - "/Users/test/Library/LaunchAgents/com.clawdbot.gateway.plist", + "/Users/test/Library/LaunchAgents/bot.molt.gateway.plist", ); }); it("handles case-insensitive 'DEFAULT' profile", () => { const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "DEFAULT" }; expect(resolveLaunchAgentPlistPath(env)).toBe( - "/Users/test/Library/LaunchAgents/com.clawdbot.gateway.plist", + "/Users/test/Library/LaunchAgents/bot.molt.gateway.plist", ); }); it("trims whitespace from CLAWDBOT_PROFILE", () => { const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: " myprofile " }; expect(resolveLaunchAgentPlistPath(env)).toBe( - "/Users/test/Library/LaunchAgents/com.clawdbot.myprofile.plist", + "/Users/test/Library/LaunchAgents/bot.molt.myprofile.plist", ); }); }); diff --git a/src/daemon/launchd.ts b/src/daemon/launchd.ts index 529cfdc1a..747494bf7 100644 --- a/src/daemon/launchd.ts +++ b/src/daemon/launchd.ts @@ -7,8 +7,8 @@ import { colorize, isRich, theme } from "../terminal/theme.js"; import { formatGatewayServiceDescription, GATEWAY_LAUNCH_AGENT_LABEL, - LEGACY_GATEWAY_LAUNCH_AGENT_LABELS, resolveGatewayLaunchAgentLabel, + resolveLegacyGatewayLaunchAgentLabels, } from "./constants.js"; import { buildLaunchAgentPlist as buildLaunchAgentPlistImpl, @@ -248,7 +248,7 @@ export async function findLegacyLaunchAgents( ): Promise { const domain = resolveGuiDomain(); const results: LegacyLaunchAgent[] = []; - for (const label of LEGACY_GATEWAY_LAUNCH_AGENT_LABELS) { + for (const label of resolveLegacyGatewayLaunchAgentLabels(env.CLAWDBOT_PROFILE)) { const plistPath = resolveLaunchAgentPlistPathForLabel(env, label); const res = await execLaunchctl(["print", `${domain}/${label}`]); const loaded = res.code === 0; @@ -384,7 +384,7 @@ export async function installLaunchAgent({ const domain = resolveGuiDomain(); const label = resolveLaunchAgentLabel({ env }); - for (const legacyLabel of LEGACY_GATEWAY_LAUNCH_AGENT_LABELS) { + for (const legacyLabel of resolveLegacyGatewayLaunchAgentLabels(env.CLAWDBOT_PROFILE)) { const legacyPlistPath = resolveLaunchAgentPlistPathForLabel(env, legacyLabel); await execLaunchctl(["bootout", domain, legacyPlistPath]); await execLaunchctl(["unload", legacyPlistPath]); diff --git a/src/daemon/service-env.test.ts b/src/daemon/service-env.test.ts index 73e2fc564..8a5cc6072 100644 --- a/src/daemon/service-env.test.ts +++ b/src/daemon/service-env.test.ts @@ -230,7 +230,7 @@ describe("buildServiceEnvironment", () => { expect(typeof env.CLAWDBOT_SERVICE_VERSION).toBe("string"); expect(env.CLAWDBOT_SYSTEMD_UNIT).toBe("moltbot-gateway.service"); if (process.platform === "darwin") { - expect(env.CLAWDBOT_LAUNCHD_LABEL).toBe("com.clawdbot.gateway"); + expect(env.CLAWDBOT_LAUNCHD_LABEL).toBe("bot.molt.gateway"); } }); @@ -241,7 +241,7 @@ describe("buildServiceEnvironment", () => { }); expect(env.CLAWDBOT_SYSTEMD_UNIT).toBe("moltbot-gateway-work.service"); if (process.platform === "darwin") { - expect(env.CLAWDBOT_LAUNCHD_LABEL).toBe("com.clawdbot.work"); + expect(env.CLAWDBOT_LAUNCHD_LABEL).toBe("bot.molt.work"); } }); }); diff --git a/src/discord/send.outbound.ts b/src/discord/send.outbound.ts index a47d0f4f1..22b402ae3 100644 --- a/src/discord/send.outbound.ts +++ b/src/discord/send.outbound.ts @@ -13,7 +13,7 @@ import { createDiscordClient, normalizeDiscordPollInput, normalizeStickerIds, - parseRecipient, + parseAndResolveRecipient, resolveChannelId, sendDiscordMedia, sendDiscordText, @@ -49,7 +49,7 @@ export async function sendMessageDiscord( const chunkMode = resolveChunkMode(cfg, "discord", accountInfo.accountId); const textWithTables = convertMarkdownTables(text ?? "", tableMode); const { token, rest, request } = createDiscordClient(opts, cfg); - const recipient = parseRecipient(to); + const recipient = await parseAndResolveRecipient(to, opts.accountId); const { channelId } = await resolveChannelId(rest, recipient, request); let result: { id: string; channel_id: string } | { id: string | null; channel_id: string }; try { @@ -104,7 +104,7 @@ export async function sendStickerDiscord( ): Promise { const cfg = loadConfig(); const { rest, request } = createDiscordClient(opts, cfg); - const recipient = parseRecipient(to); + const recipient = await parseAndResolveRecipient(to, opts.accountId); const { channelId } = await resolveChannelId(rest, recipient, request); const content = opts.content?.trim(); const stickers = normalizeStickerIds(stickerIds); @@ -131,7 +131,7 @@ export async function sendPollDiscord( ): Promise { const cfg = loadConfig(); const { rest, request } = createDiscordClient(opts, cfg); - const recipient = parseRecipient(to); + const recipient = await parseAndResolveRecipient(to, opts.accountId); const { channelId } = await resolveChannelId(rest, recipient, request); const content = opts.content?.trim(); const payload = normalizeDiscordPollInput(poll); diff --git a/src/discord/send.shared.ts b/src/discord/send.shared.ts index 4919be29d..e247300ee 100644 --- a/src/discord/send.shared.ts +++ b/src/discord/send.shared.ts @@ -13,7 +13,7 @@ import type { ChunkMode } from "../auto-reply/chunk.js"; import { chunkDiscordTextWithMode } from "./chunk.js"; import { fetchChannelPermissionsDiscord, isThreadChannelType } from "./send.permissions.js"; import { DiscordSendError } from "./send.types.js"; -import { parseDiscordTarget } from "./targets.js"; +import { parseDiscordTarget, resolveDiscordTarget } from "./targets.js"; import { normalizeDiscordToken } from "./token.js"; const DISCORD_TEXT_LIMIT = 2000; @@ -101,6 +101,51 @@ function parseRecipient(raw: string): DiscordRecipient { return { kind: target.kind, id: target.id }; } +/** + * Parse and resolve Discord recipient, including username lookup. + * This enables sending DMs by username (e.g., "john.doe") by querying + * the Discord directory to resolve usernames to user IDs. + * + * @param raw - The recipient string (username, ID, or known format) + * @param accountId - Discord account ID to use for directory lookup + * @returns Parsed DiscordRecipient with resolved user ID if applicable + */ +export async function parseAndResolveRecipient( + raw: string, + accountId?: string, +): Promise { + const cfg = loadConfig(); + const accountInfo = resolveDiscordAccount({ cfg, accountId }); + + // First try to resolve using directory lookup (handles usernames) + const trimmed = raw.trim(); + const parseOptions = { + ambiguousMessage: `Ambiguous Discord recipient "${trimmed}". Use "user:${trimmed}" for DMs or "channel:${trimmed}" for channel messages.`, + }; + + const resolved = await resolveDiscordTarget( + raw, + { + cfg, + accountId: accountInfo.accountId, + }, + parseOptions, + ); + + if (resolved) { + return { kind: resolved.kind, id: resolved.id }; + } + + // Fallback to standard parsing (for channels, etc.) + const parsed = parseDiscordTarget(raw, parseOptions); + + if (!parsed) { + throw new Error("Recipient is required for Discord sends"); + } + + return { kind: parsed.kind, id: parsed.id }; +} + function normalizeStickerIds(raw: string[]) { const ids = raw.map((entry) => entry.trim()).filter(Boolean); if (ids.length === 0) { diff --git a/src/discord/targets.test.ts b/src/discord/targets.test.ts index 3eee1eb1e..7ac39450b 100644 --- a/src/discord/targets.test.ts +++ b/src/discord/targets.test.ts @@ -1,7 +1,13 @@ -import { describe, expect, it } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { ClawdbotConfig } from "../config/config.js"; import { normalizeDiscordMessagingTarget } from "../channels/plugins/normalize/discord.js"; -import { parseDiscordTarget, resolveDiscordChannelId } from "./targets.js"; +import { listDiscordDirectoryPeersLive } from "./directory-live.js"; +import { parseDiscordTarget, resolveDiscordChannelId, resolveDiscordTarget } from "./targets.js"; + +vi.mock("./directory-live.js", () => ({ + listDiscordDirectoryPeersLive: vi.fn(), +})); describe("parseDiscordTarget", () => { it("parses user mention and prefixes", () => { @@ -68,6 +74,38 @@ describe("resolveDiscordChannelId", () => { }); }); +describe("resolveDiscordTarget", () => { + const cfg = { channels: { discord: {} } } as ClawdbotConfig; + const listPeers = vi.mocked(listDiscordDirectoryPeersLive); + + beforeEach(() => { + listPeers.mockReset(); + }); + + it("returns a resolved user for usernames", async () => { + listPeers.mockResolvedValueOnce([{ kind: "user", id: "user:999", name: "Jane" } as const]); + + await expect( + resolveDiscordTarget("jane", { cfg, accountId: "default" }), + ).resolves.toMatchObject({ kind: "user", id: "999", normalized: "user:999" }); + }); + + it("falls back to parsing when lookup misses", async () => { + listPeers.mockResolvedValueOnce([]); + await expect( + resolveDiscordTarget("general", { cfg, accountId: "default" }), + ).resolves.toMatchObject({ kind: "channel", id: "general" }); + }); + + it("does not call directory lookup for explicit user ids", async () => { + listPeers.mockResolvedValueOnce([]); + await expect( + resolveDiscordTarget("user:123", { cfg, accountId: "default" }), + ).resolves.toMatchObject({ kind: "user", id: "123" }); + expect(listPeers).not.toHaveBeenCalled(); + }); +}); + describe("normalizeDiscordMessagingTarget", () => { it("defaults raw numeric ids to channels", () => { expect(normalizeDiscordMessagingTarget("123")).toBe("channel:123"); diff --git a/src/discord/targets.ts b/src/discord/targets.ts index 3a3c93ec8..5ea6f5b1b 100644 --- a/src/discord/targets.ts +++ b/src/discord/targets.ts @@ -7,6 +7,10 @@ import { type MessagingTargetParseOptions, } from "../channels/targets.js"; +import type { DirectoryConfigParams } from "../channels/plugins/directory-config.js"; + +import { listDiscordDirectoryPeersLive } from "./directory-live.js"; + export type DiscordTargetKind = MessagingTargetKind; export type DiscordTarget = MessagingTarget; @@ -60,3 +64,93 @@ export function resolveDiscordChannelId(raw: string): string { const target = parseDiscordTarget(raw, { defaultKind: "channel" }); return requireTargetKind({ platform: "Discord", target, kind: "channel" }); } + +/** + * Resolve a Discord username to user ID using the directory lookup. + * This enables sending DMs by username instead of requiring explicit user IDs. + * + * @param raw - The username or raw target string (e.g., "john.doe") + * @param options - Directory configuration params (cfg, accountId, limit) + * @param parseOptions - Messaging target parsing options (defaults, ambiguity message) + * @returns Parsed MessagingTarget with user ID, or undefined if not found + */ +export async function resolveDiscordTarget( + raw: string, + options: DirectoryConfigParams, + parseOptions: DiscordTargetParseOptions = {}, +): Promise { + const trimmed = raw.trim(); + if (!trimmed) return undefined; + + const likelyUsername = isLikelyUsername(trimmed); + const shouldLookup = isExplicitUserLookup(trimmed, parseOptions) || likelyUsername; + const directParse = safeParseDiscordTarget(trimmed, parseOptions); + if (directParse && directParse.kind !== "channel" && !likelyUsername) { + return directParse; + } + if (!shouldLookup) { + return directParse ?? parseDiscordTarget(trimmed, parseOptions); + } + + // Try to resolve as a username via directory lookup + try { + const directoryEntries = await listDiscordDirectoryPeersLive({ + ...options, + query: trimmed, + limit: 1, + }); + + const match = directoryEntries[0]; + if (match && match.kind === "user") { + // Extract user ID from the directory entry (format: "user:") + const userId = match.id.replace(/^user:/, ""); + return buildMessagingTarget("user", userId, trimmed); + } + } catch { + // Directory lookup failed - fall through to parse as-is + // This preserves existing behavior for channel names + } + + // Fallback to original parsing (for channels, etc.) + return parseDiscordTarget(trimmed, parseOptions); +} + +function safeParseDiscordTarget( + input: string, + options: DiscordTargetParseOptions, +): MessagingTarget | undefined { + try { + return parseDiscordTarget(input, options); + } catch { + return undefined; + } +} + +function isExplicitUserLookup(input: string, options: DiscordTargetParseOptions): boolean { + if (/^<@!?(\d+)>$/.test(input)) { + return true; + } + if (/^(user:|discord:)/.test(input)) { + return true; + } + if (input.startsWith("@")) { + return true; + } + if (/^\d+$/.test(input)) { + return options.defaultKind === "user"; + } + return false; +} + +/** + * Check if a string looks like a Discord username (not a mention, prefix, or ID). + * Usernames typically don't start with special characters except underscore. + */ +function isLikelyUsername(input: string): boolean { + // Skip if it's already a known format + if (/^(user:|channel:|discord:|@|<@!?)|[\d]+$/.test(input)) { + return false; + } + // Likely a username if it doesn't match known patterns + return true; +} diff --git a/src/infra/gateway-lock.ts b/src/infra/gateway-lock.ts index a3c4d1290..aa65e7d81 100644 --- a/src/infra/gateway-lock.ts +++ b/src/infra/gateway-lock.ts @@ -72,6 +72,7 @@ function isGatewayArgv(args: string[]): boolean { "dist/index.js", "dist/index.mjs", "dist/entry.js", + "moltbot.mjs", "dist/entry.mjs", "scripts/run-node.mjs", "src/index.ts", diff --git a/src/infra/outbound/outbound-session.ts b/src/infra/outbound/outbound-session.ts index c74abc509..9c12fab96 100644 --- a/src/infra/outbound/outbound-session.ts +++ b/src/infra/outbound/outbound-session.ts @@ -103,11 +103,13 @@ function buildBaseSessionKey(params: { cfg: MoltbotConfig; agentId: string; channel: ChannelId; + accountId?: string | null; peer: RoutePeer; }): string { return buildAgentSessionKey({ agentId: params.agentId, channel: params.channel, + accountId: params.accountId, peer: params.peer, dmScope: params.cfg.session?.dmScope ?? "main", identityLinks: params.cfg.session?.identityLinks, @@ -200,6 +202,7 @@ async function resolveSlackSession( cfg: params.cfg, agentId: params.agentId, channel: "slack", + accountId: params.accountId, peer, }); const threadId = normalizeThreadId(params.threadId ?? params.replyToId); @@ -237,6 +240,7 @@ function resolveDiscordSession( cfg: params.cfg, agentId: params.agentId, channel: "discord", + accountId: params.accountId, peer, }); const explicitThreadId = normalizeThreadId(params.threadId); @@ -285,6 +289,7 @@ function resolveTelegramSession( cfg: params.cfg, agentId: params.agentId, channel: "telegram", + accountId: params.accountId, peer, }); return { @@ -312,6 +317,7 @@ function resolveWhatsAppSession( cfg: params.cfg, agentId: params.agentId, channel: "whatsapp", + accountId: params.accountId, peer, }); return { @@ -337,6 +343,7 @@ function resolveSignalSession( cfg: params.cfg, agentId: params.agentId, channel: "signal", + accountId: params.accountId, peer, }); return { @@ -371,6 +378,7 @@ function resolveSignalSession( cfg: params.cfg, agentId: params.agentId, channel: "signal", + accountId: params.accountId, peer, }); return { @@ -395,6 +403,7 @@ function resolveIMessageSession( cfg: params.cfg, agentId: params.agentId, channel: "imessage", + accountId: params.accountId, peer, }); return { @@ -419,6 +428,7 @@ function resolveIMessageSession( cfg: params.cfg, agentId: params.agentId, channel: "imessage", + accountId: params.accountId, peer, }); const toPrefix = @@ -450,6 +460,7 @@ function resolveMatrixSession( cfg: params.cfg, agentId: params.agentId, channel: "matrix", + accountId: params.accountId, peer, }); return { @@ -483,6 +494,7 @@ function resolveMSTeamsSession( cfg: params.cfg, agentId: params.agentId, channel: "msteams", + accountId: params.accountId, peer, }); return { @@ -517,6 +529,7 @@ function resolveMattermostSession( cfg: params.cfg, agentId: params.agentId, channel: "mattermost", + accountId: params.accountId, peer, }); const threadId = normalizeThreadId(params.replyToId ?? params.threadId); @@ -561,6 +574,7 @@ function resolveBlueBubblesSession( cfg: params.cfg, agentId: params.agentId, channel: "bluebubbles", + accountId: params.accountId, peer, }); return { @@ -586,6 +600,7 @@ function resolveNextcloudTalkSession( cfg: params.cfg, agentId: params.agentId, channel: "nextcloud-talk", + accountId: params.accountId, peer, }); return { @@ -612,6 +627,7 @@ function resolveZaloSession( cfg: params.cfg, agentId: params.agentId, channel: "zalo", + accountId: params.accountId, peer, }); return { @@ -639,6 +655,7 @@ function resolveZalouserSession( cfg: params.cfg, agentId: params.agentId, channel: "zalouser", + accountId: params.accountId, peer, }); return { @@ -661,6 +678,7 @@ function resolveNostrSession( cfg: params.cfg, agentId: params.agentId, channel: "nostr", + accountId: params.accountId, peer, }); return { @@ -719,6 +737,7 @@ function resolveTlonSession( cfg: params.cfg, agentId: params.agentId, channel: "tlon", + accountId: params.accountId, peer, }); return { diff --git a/src/infra/state-migrations.ts b/src/infra/state-migrations.ts index cb3d5f333..f5e50740e 100644 --- a/src/infra/state-migrations.ts +++ b/src/infra/state-migrations.ts @@ -4,7 +4,12 @@ import path from "node:path"; import { resolveDefaultAgentId } from "../agents/agent-scope.js"; import type { MoltbotConfig } from "../config/config.js"; -import { resolveOAuthDir, resolveStateDir } from "../config/paths.js"; +import { + resolveLegacyStateDir, + resolveNewStateDir, + resolveOAuthDir, + resolveStateDir, +} from "../config/paths.js"; import type { SessionEntry } from "../config/sessions.js"; import type { SessionScope } from "../config/sessions/types.js"; import { saveSessionStore } from "../config/sessions.js"; @@ -59,6 +64,7 @@ type MigrationLogger = { }; let autoMigrateChecked = false; +let autoMigrateStateDirChecked = false; function isSurfaceGroupKey(key: string): boolean { return key.includes(":group:") || key.includes(":channel:"); @@ -267,6 +273,131 @@ export function resetAutoMigrateLegacyAgentDirForTest() { resetAutoMigrateLegacyStateForTest(); } +export function resetAutoMigrateLegacyStateDirForTest() { + autoMigrateStateDirChecked = false; +} + +type StateDirMigrationResult = { + migrated: boolean; + skipped: boolean; + changes: string[]; + warnings: string[]; +}; + +function resolveSymlinkTarget(linkPath: string): string | null { + try { + const target = fs.readlinkSync(linkPath); + return path.resolve(path.dirname(linkPath), target); + } catch { + return null; + } +} + +function formatStateDirMigration(legacyDir: string, targetDir: string): string { + return `State dir: ${legacyDir} → ${targetDir} (legacy path now symlinked)`; +} + +function isDirPath(filePath: string): boolean { + try { + return fs.statSync(filePath).isDirectory(); + } catch { + return false; + } +} + +export async function autoMigrateLegacyStateDir(params: { + env?: NodeJS.ProcessEnv; + homedir?: () => string; + log?: MigrationLogger; +}): Promise { + if (autoMigrateStateDirChecked) { + return { migrated: false, skipped: true, changes: [], warnings: [] }; + } + autoMigrateStateDirChecked = true; + + const env = params.env ?? process.env; + if (env.MOLTBOT_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim()) { + return { migrated: false, skipped: true, changes: [], warnings: [] }; + } + + const homedir = params.homedir ?? os.homedir; + const legacyDir = resolveLegacyStateDir(homedir); + const targetDir = resolveNewStateDir(homedir); + const warnings: string[] = []; + const changes: string[] = []; + + let legacyStat: fs.Stats | null = null; + try { + legacyStat = fs.lstatSync(legacyDir); + } catch { + legacyStat = null; + } + if (!legacyStat) { + return { migrated: false, skipped: false, changes, warnings }; + } + if (!legacyStat.isDirectory() && !legacyStat.isSymbolicLink()) { + warnings.push(`Legacy state path is not a directory: ${legacyDir}`); + return { migrated: false, skipped: false, changes, warnings }; + } + + if (legacyStat.isSymbolicLink()) { + const legacyTarget = resolveSymlinkTarget(legacyDir); + if (legacyTarget && path.resolve(legacyTarget) === path.resolve(targetDir)) { + return { migrated: false, skipped: false, changes, warnings }; + } + warnings.push( + `Legacy state dir is a symlink (${legacyDir} → ${legacyTarget ?? "unknown"}); skipping auto-migration.`, + ); + return { migrated: false, skipped: false, changes, warnings }; + } + + if (isDirPath(targetDir)) { + warnings.push( + `State dir migration skipped: target already exists (${targetDir}). Remove or merge manually.`, + ); + return { migrated: false, skipped: false, changes, warnings }; + } + + try { + fs.renameSync(legacyDir, targetDir); + } catch (err) { + warnings.push(`Failed to move legacy state dir (${legacyDir} → ${targetDir}): ${String(err)}`); + return { migrated: false, skipped: false, changes, warnings }; + } + + try { + fs.symlinkSync(targetDir, legacyDir, "dir"); + changes.push(formatStateDirMigration(legacyDir, targetDir)); + } catch (err) { + try { + if (process.platform === "win32") { + fs.symlinkSync(targetDir, legacyDir, "junction"); + changes.push(formatStateDirMigration(legacyDir, targetDir)); + } else { + throw err; + } + } catch (fallbackErr) { + try { + fs.renameSync(targetDir, legacyDir); + warnings.push( + `State dir migration rolled back (failed to link legacy path): ${String(fallbackErr)}`, + ); + return { migrated: false, skipped: false, changes: [], warnings }; + } catch (rollbackErr) { + warnings.push( + `State dir moved but failed to link legacy path (${legacyDir} → ${targetDir}): ${String(fallbackErr)}`, + ); + warnings.push( + `Rollback failed; set MOLTBOT_STATE_DIR=${targetDir} to avoid split state: ${String(rollbackErr)}`, + ); + changes.push(`State dir: ${legacyDir} → ${targetDir}`); + } + } + } + + return { migrated: changes.length > 0, skipped: false, changes, warnings }; +} + export async function detectLegacyStateMigrations(params: { cfg: MoltbotConfig; env?: NodeJS.ProcessEnv; @@ -591,8 +722,18 @@ export async function autoMigrateLegacyState(params: { autoMigrateChecked = true; const env = params.env ?? process.env; + const stateDirResult = await autoMigrateLegacyStateDir({ + env, + homedir: params.homedir, + log: params.log, + }); if (env.CLAWDBOT_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim()) { - return { migrated: false, skipped: true, changes: [], warnings: [] }; + return { + migrated: stateDirResult.migrated, + skipped: true, + changes: stateDirResult.changes, + warnings: stateDirResult.warnings, + }; } const detected = await detectLegacyStateMigrations({ @@ -601,14 +742,19 @@ export async function autoMigrateLegacyState(params: { homedir: params.homedir, }); if (!detected.sessions.hasLegacy && !detected.agentDir.hasLegacy) { - return { migrated: false, skipped: false, changes: [], warnings: [] }; + return { + migrated: stateDirResult.migrated, + skipped: false, + changes: stateDirResult.changes, + warnings: stateDirResult.warnings, + }; } const now = params.now ?? (() => Date.now()); const sessions = await migrateLegacySessions(detected, now); const agentDir = await migrateLegacyAgentDir(detected, now); - const changes = [...sessions.changes, ...agentDir.changes]; - const warnings = [...sessions.warnings, ...agentDir.warnings]; + const changes = [...stateDirResult.changes, ...sessions.changes, ...agentDir.changes]; + const warnings = [...stateDirResult.warnings, ...sessions.warnings, ...agentDir.warnings]; const logger = params.log ?? createSubsystemLogger("state-migrations"); if (changes.length > 0) { diff --git a/src/infra/unhandled-rejections.fatal-detection.test.ts b/src/infra/unhandled-rejections.fatal-detection.test.ts new file mode 100644 index 000000000..7944a1e73 --- /dev/null +++ b/src/infra/unhandled-rejections.fatal-detection.test.ts @@ -0,0 +1,162 @@ +import { describe, it, expect, vi, beforeAll, afterAll, beforeEach, afterEach } from "vitest"; +import process from "node:process"; + +import { installUnhandledRejectionHandler } from "./unhandled-rejections.js"; + +describe("installUnhandledRejectionHandler - fatal detection", () => { + let exitCalls: Array = []; + let consoleErrorSpy: ReturnType; + let consoleWarnSpy: ReturnType; + let originalExit: typeof process.exit; + + beforeAll(() => { + originalExit = process.exit.bind(process); + installUnhandledRejectionHandler(); + }); + + beforeEach(() => { + exitCalls = []; + + vi.spyOn(process, "exit").mockImplementation((code: string | number | null | undefined) => { + if (code !== undefined && code !== null) { + exitCalls.push(code); + } + }); + + consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.clearAllMocks(); + consoleErrorSpy.mockRestore(); + consoleWarnSpy.mockRestore(); + }); + + afterAll(() => { + process.exit = originalExit; + }); + + describe("fatal errors", () => { + it("exits on ERR_OUT_OF_MEMORY", () => { + const oomErr = Object.assign(new Error("Out of memory"), { + code: "ERR_OUT_OF_MEMORY", + }); + + process.emit("unhandledRejection", oomErr, Promise.resolve()); + + expect(exitCalls).toEqual([1]); + expect(consoleErrorSpy).toHaveBeenCalledWith( + "[moltbot] FATAL unhandled rejection:", + expect.stringContaining("Out of memory"), + ); + }); + + it("exits on ERR_SCRIPT_EXECUTION_TIMEOUT", () => { + const timeoutErr = Object.assign(new Error("Script execution timeout"), { + code: "ERR_SCRIPT_EXECUTION_TIMEOUT", + }); + + process.emit("unhandledRejection", timeoutErr, Promise.resolve()); + + expect(exitCalls).toEqual([1]); + }); + + it("exits on ERR_WORKER_OUT_OF_MEMORY", () => { + const workerOomErr = Object.assign(new Error("Worker out of memory"), { + code: "ERR_WORKER_OUT_OF_MEMORY", + }); + + process.emit("unhandledRejection", workerOomErr, Promise.resolve()); + + expect(exitCalls).toEqual([1]); + }); + }); + + describe("configuration errors", () => { + it("exits on INVALID_CONFIG", () => { + const configErr = Object.assign(new Error("Invalid config"), { + code: "INVALID_CONFIG", + }); + + process.emit("unhandledRejection", configErr, Promise.resolve()); + + expect(exitCalls).toEqual([1]); + expect(consoleErrorSpy).toHaveBeenCalledWith( + "[moltbot] CONFIGURATION ERROR - requires fix:", + expect.stringContaining("Invalid config"), + ); + }); + + it("exits on MISSING_API_KEY", () => { + const missingKeyErr = Object.assign(new Error("Missing API key"), { + code: "MISSING_API_KEY", + }); + + process.emit("unhandledRejection", missingKeyErr, Promise.resolve()); + + expect(exitCalls).toEqual([1]); + }); + }); + + describe("non-fatal errors", () => { + it("does NOT exit on undici fetch failures", () => { + const fetchErr = Object.assign(new TypeError("fetch failed"), { + cause: { code: "UND_ERR_CONNECT_TIMEOUT", syscall: "connect" }, + }); + + process.emit("unhandledRejection", fetchErr, Promise.resolve()); + + expect(exitCalls).toEqual([]); + expect(consoleWarnSpy).toHaveBeenCalledWith( + "[moltbot] Non-fatal unhandled rejection (continuing):", + expect.stringContaining("fetch failed"), + ); + }); + + it("does NOT exit on DNS resolution failures", () => { + const dnsErr = Object.assign(new Error("DNS resolve failed"), { + code: "UND_ERR_DNS_RESOLVE_FAILED", + }); + + process.emit("unhandledRejection", dnsErr, Promise.resolve()); + + expect(exitCalls).toEqual([]); + expect(consoleWarnSpy).toHaveBeenCalled(); + }); + + it("exits on generic errors without code", () => { + const genericErr = new Error("Something went wrong"); + + process.emit("unhandledRejection", genericErr, Promise.resolve()); + + expect(exitCalls).toEqual([1]); + expect(consoleErrorSpy).toHaveBeenCalledWith( + "[moltbot] Unhandled promise rejection:", + expect.stringContaining("Something went wrong"), + ); + }); + + it("does NOT exit on connection reset errors", () => { + const connResetErr = Object.assign(new Error("Connection reset"), { + code: "ECONNRESET", + }); + + process.emit("unhandledRejection", connResetErr, Promise.resolve()); + + expect(exitCalls).toEqual([]); + expect(consoleWarnSpy).toHaveBeenCalled(); + }); + + it("does NOT exit on timeout errors", () => { + const timeoutErr = Object.assign(new Error("Timeout"), { + code: "ETIMEDOUT", + }); + + process.emit("unhandledRejection", timeoutErr, Promise.resolve()); + + expect(exitCalls).toEqual([]); + expect(consoleWarnSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/infra/unhandled-rejections.ts b/src/infra/unhandled-rejections.ts index 108b6c016..d186c6a78 100644 --- a/src/infra/unhandled-rejections.ts +++ b/src/infra/unhandled-rejections.ts @@ -1,11 +1,52 @@ import process from "node:process"; -import { formatUncaughtError } from "./errors.js"; +import { extractErrorCode, formatUncaughtError } from "./errors.js"; type UnhandledRejectionHandler = (reason: unknown) => boolean; const handlers = new Set(); +const FATAL_ERROR_CODES = new Set([ + "ERR_OUT_OF_MEMORY", + "ERR_SCRIPT_EXECUTION_TIMEOUT", + "ERR_WORKER_OUT_OF_MEMORY", + "ERR_WORKER_UNCAUGHT_EXCEPTION", + "ERR_WORKER_INITIALIZATION_FAILED", +]); + +const CONFIG_ERROR_CODES = new Set(["INVALID_CONFIG", "MISSING_API_KEY", "MISSING_CREDENTIALS"]); + +// Network error codes that indicate transient failures (shouldn't crash the gateway) +const TRANSIENT_NETWORK_CODES = new Set([ + "ECONNRESET", + "ECONNREFUSED", + "ENOTFOUND", + "ETIMEDOUT", + "ESOCKETTIMEDOUT", + "ECONNABORTED", + "EPIPE", + "EHOSTUNREACH", + "ENETUNREACH", + "EAI_AGAIN", + "UND_ERR_CONNECT_TIMEOUT", + "UND_ERR_DNS_RESOLVE_FAILED", + "UND_ERR_CONNECT", + "UND_ERR_SOCKET", + "UND_ERR_HEADERS_TIMEOUT", + "UND_ERR_BODY_TIMEOUT", +]); + +function getErrorCause(err: unknown): unknown { + if (!err || typeof err !== "object") return undefined; + return (err as { cause?: unknown }).cause; +} + +function extractErrorCodeWithCause(err: unknown): string | undefined { + const direct = extractErrorCode(err); + if (direct) return direct; + return extractErrorCode(getErrorCause(err)); +} + /** * Checks if an error is an AbortError. * These are typically intentional cancellations (e.g., during shutdown) and shouldn't crash. @@ -20,33 +61,14 @@ export function isAbortError(err: unknown): boolean { return false; } -// Network error codes that indicate transient failures (shouldn't crash the gateway) -const TRANSIENT_NETWORK_CODES = new Set([ - "ECONNRESET", - "ECONNREFUSED", - "ENOTFOUND", - "ETIMEDOUT", - "ESOCKETTIMEDOUT", - "ECONNABORTED", - "EPIPE", - "EHOSTUNREACH", - "ENETUNREACH", - "EAI_AGAIN", - "UND_ERR_CONNECT_TIMEOUT", - "UND_ERR_SOCKET", - "UND_ERR_HEADERS_TIMEOUT", - "UND_ERR_BODY_TIMEOUT", -]); - -function getErrorCode(err: unknown): string | undefined { - if (!err || typeof err !== "object") return undefined; - const code = (err as { code?: unknown }).code; - return typeof code === "string" ? code : undefined; +function isFatalError(err: unknown): boolean { + const code = extractErrorCodeWithCause(err); + return code !== undefined && FATAL_ERROR_CODES.has(code); } -function getErrorCause(err: unknown): unknown { - if (!err || typeof err !== "object") return undefined; - return (err as { cause?: unknown }).cause; +function isConfigError(err: unknown): boolean { + const code = extractErrorCodeWithCause(err); + return code !== undefined && CONFIG_ERROR_CODES.has(code); } /** @@ -56,16 +78,13 @@ function getErrorCause(err: unknown): unknown { export function isTransientNetworkError(err: unknown): boolean { if (!err) return false; - // Check the error itself - const code = getErrorCode(err); + const code = extractErrorCodeWithCause(err); if (code && TRANSIENT_NETWORK_CODES.has(code)) return true; // "fetch failed" TypeError from undici (Node's native fetch) if (err instanceof TypeError && err.message === "fetch failed") { const cause = getErrorCause(err); - // The cause often contains the actual network error if (cause) return isTransientNetworkError(cause); - // Even without a cause, "fetch failed" is typically a network issue return true; } @@ -115,10 +134,23 @@ export function installUnhandledRejectionHandler(): void { return; } - // Transient network errors (fetch failed, connection reset, etc.) shouldn't crash - // These are temporary connectivity issues that will resolve on their own + if (isFatalError(reason)) { + console.error("[moltbot] FATAL unhandled rejection:", formatUncaughtError(reason)); + process.exit(1); + return; + } + + if (isConfigError(reason)) { + console.error("[moltbot] CONFIGURATION ERROR - requires fix:", formatUncaughtError(reason)); + process.exit(1); + return; + } + if (isTransientNetworkError(reason)) { - console.error("[moltbot] Network error (non-fatal):", formatUncaughtError(reason)); + console.warn( + "[moltbot] Non-fatal unhandled rejection (continuing):", + formatUncaughtError(reason), + ); return; } diff --git a/src/routing/resolve-route.test.ts b/src/routing/resolve-route.test.ts index 6a3366e97..aed0fa755 100644 --- a/src/routing/resolve-route.test.ts +++ b/src/routing/resolve-route.test.ts @@ -227,3 +227,29 @@ describe("resolveAgentRoute", () => { expect(route.sessionKey).toBe("agent:home:main"); }); }); + +test("dmScope=per-account-channel-peer isolates DM sessions per account, channel and sender", () => { + const cfg: MoltbotConfig = { + session: { dmScope: "per-account-channel-peer" }, + }; + const route = resolveAgentRoute({ + cfg, + channel: "telegram", + accountId: "tasks", + peer: { kind: "dm", id: "7550356539" }, + }); + expect(route.sessionKey).toBe("agent:main:telegram:tasks:dm:7550356539"); +}); + +test("dmScope=per-account-channel-peer uses default accountId when not provided", () => { + const cfg: MoltbotConfig = { + session: { dmScope: "per-account-channel-peer" }, + }; + const route = resolveAgentRoute({ + cfg, + channel: "telegram", + accountId: null, + peer: { kind: "dm", id: "7550356539" }, + }); + expect(route.sessionKey).toBe("agent:main:telegram:default:dm:7550356539"); +}); diff --git a/src/routing/resolve-route.ts b/src/routing/resolve-route.ts index 473dc61f2..0c63f77c8 100644 --- a/src/routing/resolve-route.ts +++ b/src/routing/resolve-route.ts @@ -69,9 +69,10 @@ function matchesAccountId(match: string | undefined, actual: string): boolean { export function buildAgentSessionKey(params: { agentId: string; channel: string; + accountId?: string | null; peer?: RoutePeer | null; /** DM session scope. */ - dmScope?: "main" | "per-peer" | "per-channel-peer"; + dmScope?: "main" | "per-peer" | "per-channel-peer" | "per-account-channel-peer"; identityLinks?: Record; }): string { const channel = normalizeToken(params.channel) || "unknown"; @@ -80,6 +81,7 @@ export function buildAgentSessionKey(params: { agentId: params.agentId, mainKey: DEFAULT_MAIN_KEY, channel, + accountId: params.accountId, peerKind: peer?.kind ?? "dm", peerId: peer ? normalizeId(peer.id) || "unknown" : null, dmScope: params.dmScope, @@ -160,6 +162,7 @@ export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentR const sessionKey = buildAgentSessionKey({ agentId: resolvedAgentId, channel, + accountId, peer, dmScope, identityLinks, diff --git a/src/routing/session-key.ts b/src/routing/session-key.ts index 7f9f209ed..320ffeb83 100644 --- a/src/routing/session-key.ts +++ b/src/routing/session-key.ts @@ -111,11 +111,12 @@ export function buildAgentPeerSessionKey(params: { agentId: string; mainKey?: string | undefined; channel: string; + accountId?: string | null; peerKind?: "dm" | "group" | "channel" | null; peerId?: string | null; identityLinks?: Record; /** DM session scope. */ - dmScope?: "main" | "per-peer" | "per-channel-peer"; + dmScope?: "main" | "per-peer" | "per-channel-peer" | "per-account-channel-peer"; }): string { const peerKind = params.peerKind ?? "dm"; if (peerKind === "dm") { @@ -131,6 +132,11 @@ export function buildAgentPeerSessionKey(params: { }); if (linkedPeerId) peerId = linkedPeerId; peerId = peerId.toLowerCase(); + if (dmScope === "per-account-channel-peer" && peerId) { + const channel = (params.channel ?? "").trim().toLowerCase() || "unknown"; + const accountId = normalizeAccountId(params.accountId); + return `agent:${normalizeAgentId(params.agentId)}:${channel}:${accountId}:dm:${peerId}`; + } if (dmScope === "per-channel-peer" && peerId) { const channel = (params.channel ?? "").trim().toLowerCase() || "unknown"; return `agent:${normalizeAgentId(params.agentId)}:${channel}:dm:${peerId}`; diff --git a/src/security/audit-extra.ts b/src/security/audit-extra.ts index 0943196da..3a92a30a8 100644 --- a/src/security/audit-extra.ts +++ b/src/security/audit-extra.ts @@ -280,7 +280,10 @@ function isClaudeModel(id: string): boolean { } function isClaude45OrHigher(id: string): boolean { - return /\bclaude-[^\s/]*?(?:-4-5\b|4\.5\b)/i.test(id); + // Match claude-*-4-5, claude-*-45, claude-*4.5, or opus-4-5/opus-45 variants + // Examples that should match: + // claude-opus-4-5, claude-opus-45, claude-4.5, venice/claude-opus-45 + return /\bclaude-[^\s/]*?(?:-4-?5\b|4\.5\b)/i.test(id); } export function collectModelHygieneFindings(cfg: MoltbotConfig): SecurityAuditFinding[] { diff --git a/src/security/audit.test.ts b/src/security/audit.test.ts index 4bbfd21e3..40124d39a 100644 --- a/src/security/audit.test.ts +++ b/src/security/audit.test.ts @@ -730,6 +730,23 @@ describe("security audit", () => { ); }); + it("does not warn on Venice-style opus-45 model names", async () => { + // Venice uses "claude-opus-45" format (no dash between 4 and 5) + const cfg: ClawdbotConfig = { + agents: { defaults: { model: { primary: "venice/claude-opus-45" } } }, + }; + + const res = await runSecurityAudit({ + config: cfg, + includeFilesystem: false, + includeChannelSecurity: false, + }); + + // Should NOT contain weak_tier warning for opus-45 + const weakTierFinding = res.findings.find((f) => f.checkId === "models.weak_tier"); + expect(weakTierFinding).toBeUndefined(); + }); + it("warns when hooks token looks short", async () => { const cfg: MoltbotConfig = { hooks: { enabled: true, token: "short" }, diff --git a/src/security/audit.ts b/src/security/audit.ts index 7aebd6928..681d14c1d 100644 --- a/src/security/audit.ts +++ b/src/security/audit.ts @@ -519,7 +519,8 @@ async function collectChannelSecurityFindings(params: { title: `${input.label} DMs share the main session`, detail: "Multiple DM senders currently share the main session, which can leak context across users.", - remediation: 'Set session.dmScope="per-channel-peer" to isolate DM sessions per sender.', + remediation: + 'Set session.dmScope="per-channel-peer" (or "per-account-channel-peer" for multi-account channels) to isolate DM sessions per sender.', }); } }; diff --git a/src/telegram/bot-message-context.dm-threads.test.ts b/src/telegram/bot-message-context.dm-threads.test.ts index ff6a8a837..d710e0b1b 100644 --- a/src/telegram/bot-message-context.dm-threads.test.ts +++ b/src/telegram/bot-message-context.dm-threads.test.ts @@ -70,3 +70,102 @@ describe("buildTelegramMessageContext dm thread sessions", () => { expect(ctx?.ctxPayload?.SessionKey).toBe("agent:main:main"); }); }); + +describe("buildTelegramMessageContext group sessions without forum", () => { + const baseConfig = { + agents: { defaults: { model: "anthropic/claude-opus-4-5", workspace: "/tmp/clawd" } }, + channels: { telegram: {} }, + messages: { groupChat: { mentionPatterns: [] } }, + } as never; + + const buildContext = async (message: Record) => + await buildTelegramMessageContext({ + primaryCtx: { + message, + me: { id: 7, username: "bot" }, + } as never, + allMedia: [], + storeAllowFrom: [], + options: { forceWasMentioned: true }, + bot: { + api: { + sendChatAction: vi.fn(), + setMessageReaction: vi.fn(), + }, + } as never, + cfg: baseConfig, + account: { accountId: "default" } as never, + historyLimit: 0, + groupHistories: new Map(), + dmPolicy: "open", + allowFrom: [], + groupAllowFrom: [], + ackReactionScope: "off", + logger: { info: vi.fn() }, + resolveGroupActivation: () => true, + resolveGroupRequireMention: () => false, + resolveTelegramGroupConfig: () => ({ + groupConfig: { requireMention: false }, + topicConfig: undefined, + }), + }); + + it("ignores message_thread_id for regular groups (not forums)", async () => { + // When someone replies to a message in a non-forum group, Telegram sends + // message_thread_id but this should NOT create a separate session + const ctx = await buildContext({ + message_id: 1, + chat: { id: -1001234567890, type: "supergroup", title: "Test Group" }, + date: 1700000000, + text: "@bot hello", + message_thread_id: 42, // This is a reply thread, NOT a forum topic + from: { id: 42, first_name: "Alice" }, + }); + + expect(ctx).not.toBeNull(); + // Session key should NOT include :topic:42 + expect(ctx?.ctxPayload?.SessionKey).toBe("agent:main:telegram:group:-1001234567890"); + // MessageThreadId should be undefined (not a forum) + expect(ctx?.ctxPayload?.MessageThreadId).toBeUndefined(); + }); + + it("keeps same session for regular group with and without message_thread_id", async () => { + const ctxWithThread = await buildContext({ + message_id: 1, + chat: { id: -1001234567890, type: "supergroup", title: "Test Group" }, + date: 1700000000, + text: "@bot hello", + message_thread_id: 42, + from: { id: 42, first_name: "Alice" }, + }); + + const ctxWithoutThread = await buildContext({ + message_id: 2, + chat: { id: -1001234567890, type: "supergroup", title: "Test Group" }, + date: 1700000001, + text: "@bot world", + from: { id: 42, first_name: "Alice" }, + }); + + expect(ctxWithThread).not.toBeNull(); + expect(ctxWithoutThread).not.toBeNull(); + // Both messages should use the same session key + expect(ctxWithThread?.ctxPayload?.SessionKey).toBe(ctxWithoutThread?.ctxPayload?.SessionKey); + }); + + it("uses topic session for forum groups with message_thread_id", async () => { + const ctx = await buildContext({ + message_id: 1, + chat: { id: -1001234567890, type: "supergroup", title: "Test Forum", is_forum: true }, + date: 1700000000, + text: "@bot hello", + message_thread_id: 99, + from: { id: 42, first_name: "Alice" }, + }); + + expect(ctx).not.toBeNull(); + // Session key SHOULD include :topic:99 for forums + expect(ctx?.ctxPayload?.SessionKey).toBe("agent:main:telegram:group:-1001234567890:topic:99"); + expect(ctx?.ctxPayload?.MessageThreadId).toBe(99); + }); +}); diff --git a/src/telegram/bot-message-context.ts b/src/telegram/bot-message-context.ts index 98b42fd10..832a4413d 100644 --- a/src/telegram/bot-message-context.ts +++ b/src/telegram/bot-message-context.ts @@ -173,7 +173,8 @@ export const buildTelegramMessageContext = async ({ }, }); const baseSessionKey = route.sessionKey; - const dmThreadId = !isGroup ? resolvedThreadId : undefined; + // DMs: use raw messageThreadId for thread sessions (not resolvedThreadId which is for forums) + const dmThreadId = !isGroup ? messageThreadId : undefined; const threadKeys = dmThreadId != null ? resolveThreadSessionKeys({ baseSessionKey, threadId: String(dmThreadId) }) @@ -480,9 +481,13 @@ export const buildTelegramMessageContext = async ({ const replyTarget = describeReplyTarget(msg); const forwardOrigin = normalizeForwardedContext(msg); const replySuffix = replyTarget - ? `\n\n[Replying to ${replyTarget.sender}${ - replyTarget.id ? ` id:${replyTarget.id}` : "" - }]\n${replyTarget.body}\n[/Replying]` + ? replyTarget.kind === "quote" + ? `\n\n[Quoting ${replyTarget.sender}${ + replyTarget.id ? ` id:${replyTarget.id}` : "" + }]\n"${replyTarget.body}"\n[/Quoting]` + : `\n\n[Replying to ${replyTarget.sender}${ + replyTarget.id ? ` id:${replyTarget.id}` : "" + }]\n${replyTarget.body}\n[/Replying]` : ""; const forwardPrefix = forwardOrigin ? `[Forwarded from ${forwardOrigin.from}${ @@ -565,6 +570,7 @@ export const buildTelegramMessageContext = async ({ ReplyToId: replyTarget?.id, ReplyToBody: replyTarget?.body, ReplyToSender: replyTarget?.sender, + ReplyToIsQuote: replyTarget?.kind === "quote" ? true : undefined, ForwardedFrom: forwardOrigin?.from, ForwardedFromType: forwardOrigin?.fromType, ForwardedFromId: forwardOrigin?.fromId, @@ -596,7 +602,8 @@ export const buildTelegramMessageContext = async ({ Sticker: allMedia[0]?.stickerMetadata, ...(locationData ? toLocationContext(locationData) : undefined), CommandAuthorized: commandAuthorized, - MessageThreadId: resolvedThreadId, + // For groups: use resolvedThreadId (forum topics only); for DMs: use raw messageThreadId + MessageThreadId: isGroup ? resolvedThreadId : messageThreadId, IsForum: isForum, // Originating channel for reply routing. OriginatingChannel: "telegram" as const, diff --git a/src/telegram/bot-message-dispatch.ts b/src/telegram/bot-message-dispatch.ts index 27c6a3bfa..cead0628a 100644 --- a/src/telegram/bot-message-dispatch.ts +++ b/src/telegram/bot-message-dispatch.ts @@ -210,6 +210,10 @@ export const dispatchTelegramMessage = async ({ draftStream?.stop(); } + const replyQuoteText = + ctxPayload.ReplyToIsQuote && ctxPayload.ReplyToBody + ? ctxPayload.ReplyToBody.trim() || undefined + : undefined; await deliverReplies({ replies: [payload], chatId: String(chatId), @@ -223,6 +227,7 @@ export const dispatchTelegramMessage = async ({ chunkMode, onVoiceRecording: sendRecordVoice, linkPreview: telegramCfg.linkPreview, + replyQuoteText, }); }, onError: (err, info) => { diff --git a/src/telegram/bot-native-commands.ts b/src/telegram/bot-native-commands.ts index 4cca71d14..3415ea927 100644 --- a/src/telegram/bot-native-commands.ts +++ b/src/telegram/bot-native-commands.ts @@ -322,7 +322,7 @@ export const registerTelegramNativeCommands = ({ ]; if (allCommands.length > 0) { - void withTelegramApiErrorLogging({ + withTelegramApiErrorLogging({ operation: "setMyCommands", runtime, fn: () => bot.api.setMyCommands(allCommands), @@ -360,6 +360,8 @@ export const registerTelegramNativeCommands = ({ topicConfig, commandAuthorized, } = auth; + const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id; + const threadIdForSend = isGroup ? resolvedThreadId : messageThreadId; const commandDefinition = findCommandByNativeName(command.name, "telegram"); const rawText = ctx.match?.trim() ?? ""; @@ -406,7 +408,7 @@ export const registerTelegramNativeCommands = ({ fn: () => bot.api.sendMessage(chatId, title, { ...(replyMarkup ? { reply_markup: replyMarkup } : {}), - ...(resolvedThreadId != null ? { message_thread_id: resolvedThreadId } : {}), + ...(threadIdForSend != null ? { message_thread_id: threadIdForSend } : {}), }), }); return; @@ -421,7 +423,8 @@ export const registerTelegramNativeCommands = ({ }, }); const baseSessionKey = route.sessionKey; - const dmThreadId = !isGroup ? resolvedThreadId : undefined; + // DMs: use raw messageThreadId for thread sessions (not resolvedThreadId which is for forums) + const dmThreadId = !isGroup ? messageThreadId : undefined; const threadKeys = dmThreadId != null ? resolveThreadSessionKeys({ baseSessionKey, threadId: String(dmThreadId) }) @@ -466,7 +469,7 @@ export const registerTelegramNativeCommands = ({ CommandSource: "native" as const, SessionKey: `telegram:slash:${senderId || chatId}`, CommandTargetSessionKey: sessionKey, - MessageThreadId: resolvedThreadId, + MessageThreadId: threadIdForSend, IsForum: isForum, // Originating context for sub-agent announce routing OriginatingChannel: "telegram" as const, @@ -493,7 +496,7 @@ export const registerTelegramNativeCommands = ({ bot, replyToMode, textLimit, - messageThreadId: resolvedThreadId, + messageThreadId: threadIdForSend, tableMode, chunkMode, linkPreview: telegramCfg.linkPreview, @@ -541,7 +544,9 @@ export const registerTelegramNativeCommands = ({ requireAuth: match.command.requireAuth !== false, }); if (!auth) return; - const { resolvedThreadId, senderId, commandAuthorized } = auth; + const { resolvedThreadId, senderId, commandAuthorized, isGroup } = auth; + const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id; + const threadIdForSend = isGroup ? resolvedThreadId : messageThreadId; const result = await executePluginCommand({ command: match.command, @@ -567,7 +572,7 @@ export const registerTelegramNativeCommands = ({ bot, replyToMode, textLimit, - messageThreadId: resolvedThreadId, + messageThreadId: threadIdForSend, tableMode, chunkMode, linkPreview: telegramCfg.linkPreview, @@ -576,7 +581,7 @@ export const registerTelegramNativeCommands = ({ } } } else if (nativeDisabledExplicit) { - void withTelegramApiErrorLogging({ + withTelegramApiErrorLogging({ operation: "setMyCommands", runtime, fn: () => bot.api.setMyCommands([]), diff --git a/src/telegram/bot.create-telegram-bot.installs-grammy-throttler.test.ts b/src/telegram/bot.create-telegram-bot.installs-grammy-throttler.test.ts index bf94e4f6f..c3844ac88 100644 --- a/src/telegram/bot.create-telegram-bot.installs-grammy-throttler.test.ts +++ b/src/telegram/bot.create-telegram-bot.installs-grammy-throttler.test.ts @@ -238,12 +238,17 @@ describe("createTelegramBot", () => { expect(getTelegramSequentialKey({ message: { chat: { id: 123 } } })).toBe("telegram:123"); expect( getTelegramSequentialKey({ - message: { chat: { id: 123 }, message_thread_id: 9 }, + message: { chat: { id: 123, type: "private" }, message_thread_id: 9 }, }), ).toBe("telegram:123:topic:9"); expect( getTelegramSequentialKey({ - message: { chat: { id: 123, is_forum: true } }, + message: { chat: { id: 123, type: "supergroup" }, message_thread_id: 9 }, + }), + ).toBe("telegram:123"); + expect( + getTelegramSequentialKey({ + message: { chat: { id: 123, type: "supergroup", is_forum: true } }, }), ).toBe("telegram:123:topic:1"); expect( diff --git a/src/telegram/bot.test.ts b/src/telegram/bot.test.ts index 72ee418bb..c075174fb 100644 --- a/src/telegram/bot.test.ts +++ b/src/telegram/bot.test.ts @@ -340,12 +340,17 @@ describe("createTelegramBot", () => { expect(getTelegramSequentialKey({ message: { chat: { id: 123 } } })).toBe("telegram:123"); expect( getTelegramSequentialKey({ - message: { chat: { id: 123 }, message_thread_id: 9 }, + message: { chat: { id: 123, type: "private" }, message_thread_id: 9 }, }), ).toBe("telegram:123:topic:9"); expect( getTelegramSequentialKey({ - message: { chat: { id: 123, is_forum: true } }, + message: { chat: { id: 123, type: "supergroup" }, message_thread_id: 9 }, + }), + ).toBe("telegram:123"); + expect( + getTelegramSequentialKey({ + message: { chat: { id: 123, type: "supergroup", is_forum: true } }, }), ).toBe("telegram:123:topic:1"); expect( @@ -894,6 +899,73 @@ describe("createTelegramBot", () => { expect(payload.ReplyToSender).toBe("Ada"); }); + it("uses quote text when a Telegram partial reply is received", async () => { + onSpy.mockReset(); + sendMessageSpy.mockReset(); + const replySpy = replyModule.__replySpy as unknown as ReturnType; + replySpy.mockReset(); + + createTelegramBot({ token: "tok" }); + const handler = getOnHandler("message") as (ctx: Record) => Promise; + + await handler({ + message: { + chat: { id: 7, type: "private" }, + text: "Sure, see below", + date: 1736380800, + reply_to_message: { + message_id: 9001, + text: "Can you summarize this?", + from: { first_name: "Ada" }, + }, + quote: { + text: "summarize this", + }, + }, + me: { username: "moltbot_bot" }, + getFile: async () => ({ download: async () => new Uint8Array() }), + }); + + expect(replySpy).toHaveBeenCalledTimes(1); + const payload = replySpy.mock.calls[0][0]; + expect(payload.Body).toContain("[Quoting Ada id:9001]"); + expect(payload.Body).toContain('"summarize this"'); + expect(payload.ReplyToId).toBe("9001"); + expect(payload.ReplyToBody).toBe("summarize this"); + expect(payload.ReplyToSender).toBe("Ada"); + }); + + it("handles quote-only replies without reply metadata", async () => { + onSpy.mockReset(); + sendMessageSpy.mockReset(); + const replySpy = replyModule.__replySpy as unknown as ReturnType; + replySpy.mockReset(); + + createTelegramBot({ token: "tok" }); + const handler = getOnHandler("message") as (ctx: Record) => Promise; + + await handler({ + message: { + chat: { id: 7, type: "private" }, + text: "Sure, see below", + date: 1736380800, + quote: { + text: "summarize this", + }, + }, + me: { username: "moltbot_bot" }, + getFile: async () => ({ download: async () => new Uint8Array() }), + }); + + expect(replySpy).toHaveBeenCalledTimes(1); + const payload = replySpy.mock.calls[0][0]; + expect(payload.Body).toContain("[Quoting unknown sender]"); + expect(payload.Body).toContain('"summarize this"'); + expect(payload.ReplyToId).toBeUndefined(); + expect(payload.ReplyToBody).toBe("summarize this"); + expect(payload.ReplyToSender).toBe("unknown sender"); + }); + it("sends replies without native reply threading", async () => { onSpy.mockReset(); sendMessageSpy.mockReset(); diff --git a/src/telegram/bot.ts b/src/telegram/bot.ts index 655e1b427..ae21d10da 100644 --- a/src/telegram/bot.ts +++ b/src/telegram/bot.ts @@ -94,11 +94,12 @@ export function getTelegramSequentialKey(ctx: { if (typeof chatId === "number") return `telegram:${chatId}:control`; return "telegram:control"; } + const isGroup = msg?.chat?.type === "group" || msg?.chat?.type === "supergroup"; + const messageThreadId = msg?.message_thread_id; const isForum = (msg?.chat as { is_forum?: boolean } | undefined)?.is_forum; - const threadId = resolveTelegramForumThreadId({ - isForum, - messageThreadId: msg?.message_thread_id, - }); + const threadId = isGroup + ? resolveTelegramForumThreadId({ isForum, messageThreadId }) + : messageThreadId; if (typeof chatId === "number") { return threadId != null ? `telegram:${chatId}:topic:${threadId}` : `telegram:${chatId}`; } @@ -427,7 +428,8 @@ export function createTelegramBot(opts: TelegramBotOptions) { peer: { kind: isGroup ? "group" : "dm", id: peerId }, }); const baseSessionKey = route.sessionKey; - const dmThreadId = !isGroup ? resolvedThreadId : undefined; + // DMs: use raw messageThreadId for thread sessions (not resolvedThreadId which is for forums) + const dmThreadId = !isGroup ? messageThreadId : undefined; const threadKeys = dmThreadId != null ? resolveThreadSessionKeys({ baseSessionKey, threadId: String(dmThreadId) }) diff --git a/src/telegram/bot/delivery.test.ts b/src/telegram/bot/delivery.test.ts index 404cc2fc2..3cf1b2534 100644 --- a/src/telegram/bot/delivery.test.ts +++ b/src/telegram/bot/delivery.test.ts @@ -168,6 +168,37 @@ describe("deliverReplies", () => { ); }); + it("uses reply_parameters when quote text is provided", async () => { + const runtime = { error: vi.fn(), log: vi.fn() }; + const sendMessage = vi.fn().mockResolvedValue({ + message_id: 10, + chat: { id: "123" }, + }); + const bot = { api: { sendMessage } } as unknown as Bot; + + await deliverReplies({ + replies: [{ text: "Hello there", replyToId: "500" }], + chatId: "123", + token: "tok", + runtime, + bot, + replyToMode: "all", + textLimit: 4000, + replyQuoteText: "quoted text", + }); + + expect(sendMessage).toHaveBeenCalledWith( + "123", + expect.any(String), + expect.objectContaining({ + reply_parameters: { + message_id: 500, + quote: "quoted text", + }, + }), + ); + }); + it("falls back to text when sendVoice fails with VOICE_MESSAGES_FORBIDDEN", async () => { const runtime = { error: vi.fn(), log: vi.fn() }; const sendVoice = vi diff --git a/src/telegram/bot/delivery.ts b/src/telegram/bot/delivery.ts index 779c0c026..4f45f9997 100644 --- a/src/telegram/bot/delivery.ts +++ b/src/telegram/bot/delivery.ts @@ -42,11 +42,21 @@ export async function deliverReplies(params: { onVoiceRecording?: () => Promise | void; /** Controls whether link previews are shown. Default: true (previews enabled). */ linkPreview?: boolean; + /** Optional quote text for Telegram reply_parameters. */ + replyQuoteText?: string; }) { - const { replies, chatId, runtime, bot, replyToMode, textLimit, messageThreadId, linkPreview } = - params; + const { + replies, + chatId, + runtime, + bot, + replyToMode, + textLimit, + messageThreadId, + linkPreview, + replyQuoteText, + } = params; const chunkMode = params.chunkMode ?? "length"; - const threadParams = buildTelegramThreadParams(messageThreadId); let hasReplied = false; const chunkText = (markdown: string) => { const markdownChunks = @@ -97,6 +107,7 @@ export async function deliverReplies(params: { await sendTelegramText(bot, chatId, chunk.html, runtime, { replyToMessageId: replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined, + replyQuoteText, messageThreadId, textMode: "html", plainText: chunk.text, @@ -140,13 +151,14 @@ export async function deliverReplies(params: { const shouldAttachButtonsToMedia = isFirstMedia && replyMarkup && !followUpText; const mediaParams: Record = { caption: htmlCaption, - reply_to_message_id: replyToMessageId, ...(htmlCaption ? { parse_mode: "HTML" } : {}), ...(shouldAttachButtonsToMedia ? { reply_markup: replyMarkup } : {}), + ...buildTelegramSendParams({ + replyToMessageId, + messageThreadId, + replyQuoteText, + }), }; - if (threadParams) { - mediaParams.message_thread_id = threadParams.message_thread_id; - } if (isGif) { await withTelegramApiErrorLogging({ operation: "sendAnimation", @@ -207,6 +219,7 @@ export async function deliverReplies(params: { messageThreadId, linkPreview, replyMarkup, + replyQuoteText, }); // Skip this media item; continue with next. continue; @@ -391,6 +404,7 @@ async function sendTelegramVoiceFallbackText(opts: { messageThreadId?: number; linkPreview?: boolean; replyMarkup?: ReturnType; + replyQuoteText?: string; }): Promise { const chunks = opts.chunkText(opts.text); let hasReplied = opts.hasReplied; @@ -399,6 +413,7 @@ async function sendTelegramVoiceFallbackText(opts: { await sendTelegramText(opts.bot, opts.chatId, chunk.html, opts.runtime, { replyToMessageId: opts.replyToId && (opts.replyToMode === "all" || !hasReplied) ? opts.replyToId : undefined, + replyQuoteText: opts.replyQuoteText, messageThreadId: opts.messageThreadId, textMode: "html", plainText: chunk.text, @@ -415,11 +430,20 @@ async function sendTelegramVoiceFallbackText(opts: { function buildTelegramSendParams(opts?: { replyToMessageId?: number; messageThreadId?: number; + replyQuoteText?: string; }): Record { const threadParams = buildTelegramThreadParams(opts?.messageThreadId); const params: Record = {}; + const quoteText = opts?.replyQuoteText?.trim(); if (opts?.replyToMessageId) { - params.reply_to_message_id = opts.replyToMessageId; + if (quoteText) { + params.reply_parameters = { + message_id: Math.trunc(opts.replyToMessageId), + quote: quoteText, + }; + } else { + params.reply_to_message_id = opts.replyToMessageId; + } } if (threadParams) { params.message_thread_id = threadParams.message_thread_id; @@ -434,6 +458,7 @@ async function sendTelegramText( runtime: RuntimeEnv, opts?: { replyToMessageId?: number; + replyQuoteText?: string; messageThreadId?: number; textMode?: "markdown" | "html"; plainText?: string; @@ -443,6 +468,7 @@ async function sendTelegramText( ): Promise { const baseParams = buildTelegramSendParams({ replyToMessageId: opts?.replyToMessageId, + replyQuoteText: opts?.replyQuoteText, messageThreadId: opts?.messageThreadId, }); // Add link_preview_options when link preview is disabled. diff --git a/src/telegram/bot/helpers.test.ts b/src/telegram/bot/helpers.test.ts index 60fbba0dc..8e90bb520 100644 --- a/src/telegram/bot/helpers.test.ts +++ b/src/telegram/bot/helpers.test.ts @@ -3,8 +3,34 @@ import { buildTelegramThreadParams, buildTypingThreadParams, normalizeForwardedContext, + resolveTelegramForumThreadId, } from "./helpers.js"; +describe("resolveTelegramForumThreadId", () => { + it("returns undefined for non-forum groups even with messageThreadId", () => { + // Reply threads in regular groups should not create separate sessions + expect(resolveTelegramForumThreadId({ isForum: false, messageThreadId: 42 })).toBeUndefined(); + }); + + it("returns undefined for non-forum groups without messageThreadId", () => { + expect( + resolveTelegramForumThreadId({ isForum: false, messageThreadId: undefined }), + ).toBeUndefined(); + expect( + resolveTelegramForumThreadId({ isForum: undefined, messageThreadId: 99 }), + ).toBeUndefined(); + }); + + it("returns General topic (1) for forum groups without messageThreadId", () => { + expect(resolveTelegramForumThreadId({ isForum: true, messageThreadId: undefined })).toBe(1); + expect(resolveTelegramForumThreadId({ isForum: true, messageThreadId: null })).toBe(1); + }); + + it("returns the topic id for forum groups with messageThreadId", () => { + expect(resolveTelegramForumThreadId({ isForum: true, messageThreadId: 99 })).toBe(99); + }); +}); + describe("buildTelegramThreadParams", () => { it("omits General topic thread id for message sends", () => { expect(buildTelegramThreadParams(1)).toBeUndefined(); diff --git a/src/telegram/bot/helpers.ts b/src/telegram/bot/helpers.ts index f2e1eff24..cd57392c0 100644 --- a/src/telegram/bot/helpers.ts +++ b/src/telegram/bot/helpers.ts @@ -13,14 +13,25 @@ import type { const TELEGRAM_GENERAL_TOPIC_ID = 1; +/** + * Resolve the thread ID for Telegram forum topics. + * For non-forum groups, returns undefined even if messageThreadId is present + * (reply threads in regular groups should not create separate sessions). + * For forum groups, returns the topic ID (or General topic ID=1 if unspecified). + */ export function resolveTelegramForumThreadId(params: { isForum?: boolean; messageThreadId?: number | null; }) { - if (params.isForum && params.messageThreadId == null) { + // Non-forum groups: ignore message_thread_id (reply threads are not real topics) + if (!params.isForum) { + return undefined; + } + // Forum groups: use the topic ID, defaulting to General topic + if (params.messageThreadId == null) { return TELEGRAM_GENERAL_TOPIC_ID; } - return params.messageThreadId ?? undefined; + return params.messageThreadId; } /** @@ -150,28 +161,49 @@ export function resolveTelegramReplyId(raw?: string): number | undefined { return parsed; } -export function describeReplyTarget(msg: TelegramMessage) { +export type TelegramReplyTarget = { + id?: string; + sender: string; + body: string; + kind: "reply" | "quote"; +}; + +export function describeReplyTarget(msg: TelegramMessage): TelegramReplyTarget | null { const reply = msg.reply_to_message; - if (!reply) return null; - const replyBody = (reply.text ?? reply.caption ?? "").trim(); - let body = replyBody; - if (!body) { - if (reply.photo) body = ""; - else if (reply.video) body = ""; - else if (reply.audio || reply.voice) body = ""; - else if (reply.document) body = ""; - else { - const locationData = extractTelegramLocation(reply); - if (locationData) body = formatLocationText(locationData); + const quote = msg.quote; + let body = ""; + let kind: TelegramReplyTarget["kind"] = "reply"; + + if (quote?.text) { + body = quote.text.trim(); + if (body) { + kind = "quote"; + } + } + + if (!body && reply) { + const replyBody = (reply.text ?? reply.caption ?? "").trim(); + body = replyBody; + if (!body) { + if (reply.photo) body = ""; + else if (reply.video) body = ""; + else if (reply.audio || reply.voice) body = ""; + else if (reply.document) body = ""; + else { + const locationData = extractTelegramLocation(reply); + if (locationData) body = formatLocationText(locationData); + } } } if (!body) return null; - const sender = buildSenderName(reply); + const sender = reply ? buildSenderName(reply) : undefined; const senderLabel = sender ? `${sender}` : "unknown sender"; + return { - id: reply.message_id ? String(reply.message_id) : undefined, + id: reply?.message_id ? String(reply.message_id) : undefined, sender: senderLabel, body, + kind, }; } diff --git a/src/telegram/bot/types.ts b/src/telegram/bot/types.ts index 3e106b885..df3dba6d3 100644 --- a/src/telegram/bot/types.ts +++ b/src/telegram/bot/types.ts @@ -1,6 +1,12 @@ import type { Message } from "@grammyjs/types"; -export type TelegramMessage = Message; +export type TelegramQuote = { + text?: string; +}; + +export type TelegramMessage = Message & { + quote?: TelegramQuote; +}; export type TelegramStreamMode = "off" | "partial" | "block"; diff --git a/src/telegram/monitor.ts b/src/telegram/monitor.ts index 59df7098d..c3b3a5a2f 100644 --- a/src/telegram/monitor.ts +++ b/src/telegram/monitor.ts @@ -74,6 +74,23 @@ const isGetUpdatesConflict = (err: unknown) => { return haystack.includes("getupdates"); }; +const NETWORK_ERROR_SNIPPETS = [ + "fetch failed", + "network", + "timeout", + "socket", + "econnreset", + "econnrefused", + "undici", +]; + +const isNetworkRelatedError = (err: unknown) => { + if (!err) return false; + const message = formatErrorMessage(err).toLowerCase(); + if (!message) return false; + return NETWORK_ERROR_SNIPPETS.some((snippet) => message.includes(snippet)); +}; + export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) { const cfg = opts.config ?? loadConfig(); const account = resolveTelegramAccount({ @@ -158,7 +175,8 @@ export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) { } const isConflict = isGetUpdatesConflict(err); const isRecoverable = isRecoverableTelegramNetworkError(err, { context: "polling" }); - if (!isConflict && !isRecoverable) { + const isNetworkError = isNetworkRelatedError(err); + if (!isConflict && !isRecoverable && !isNetworkError) { throw err; } restartAttempts += 1; diff --git a/src/telegram/network-errors.test.ts b/src/telegram/network-errors.test.ts index ae42cbb97..db582355f 100644 --- a/src/telegram/network-errors.test.ts +++ b/src/telegram/network-errors.test.ts @@ -8,6 +8,13 @@ describe("isRecoverableTelegramNetworkError", () => { expect(isRecoverableTelegramNetworkError(err)).toBe(true); }); + it("detects additional recoverable error codes", () => { + const aborted = Object.assign(new Error("aborted"), { code: "ECONNABORTED" }); + const network = Object.assign(new Error("network"), { code: "ERR_NETWORK" }); + expect(isRecoverableTelegramNetworkError(aborted)).toBe(true); + expect(isRecoverableTelegramNetworkError(network)).toBe(true); + }); + it("detects AbortError names", () => { const err = Object.assign(new Error("The operation was aborted"), { name: "AbortError" }); expect(isRecoverableTelegramNetworkError(err)).toBe(true); @@ -19,6 +26,11 @@ describe("isRecoverableTelegramNetworkError", () => { expect(isRecoverableTelegramNetworkError(err)).toBe(true); }); + it("detects expanded message patterns", () => { + expect(isRecoverableTelegramNetworkError(new Error("TypeError: fetch failed"))).toBe(true); + expect(isRecoverableTelegramNetworkError(new Error("Undici: socket failure"))).toBe(true); + }); + it("skips message matches for send context", () => { const err = new TypeError("fetch failed"); expect(isRecoverableTelegramNetworkError(err, { context: "send" })).toBe(false); diff --git a/src/telegram/network-errors.ts b/src/telegram/network-errors.ts index 70cd81994..bb3432432 100644 --- a/src/telegram/network-errors.ts +++ b/src/telegram/network-errors.ts @@ -15,6 +15,8 @@ const RECOVERABLE_ERROR_CODES = new Set([ "UND_ERR_BODY_TIMEOUT", "UND_ERR_SOCKET", "UND_ERR_ABORTED", + "ECONNABORTED", + "ERR_NETWORK", ]); const RECOVERABLE_ERROR_NAMES = new Set([ @@ -27,6 +29,8 @@ const RECOVERABLE_ERROR_NAMES = new Set([ const RECOVERABLE_MESSAGE_SNIPPETS = [ "fetch failed", + "typeerror: fetch failed", + "undici", "network error", "network request", "client network socket disconnected", diff --git a/src/telegram/send.ts b/src/telegram/send.ts index 7dd79dd1f..e3f3ac30e 100644 --- a/src/telegram/send.ts +++ b/src/telegram/send.ts @@ -46,6 +46,8 @@ type TelegramSendOpts = { silent?: boolean; /** Message ID to reply to (for threading) */ replyToMessageId?: number; + /** Quote text for Telegram reply_parameters. */ + quoteText?: string; /** Forum topic thread ID (for forum supergroups) */ messageThreadId?: number; /** Inline keyboard buttons (reply markup). */ @@ -198,9 +200,17 @@ export async function sendMessageTelegram( const messageThreadId = opts.messageThreadId != null ? opts.messageThreadId : target.messageThreadId; const threadIdParams = buildTelegramThreadParams(messageThreadId); - const threadParams: Record = threadIdParams ? { ...threadIdParams } : {}; + const threadParams: Record = threadIdParams ? { ...threadIdParams } : {}; + const quoteText = opts.quoteText?.trim(); if (opts.replyToMessageId != null) { - threadParams.reply_to_message_id = Math.trunc(opts.replyToMessageId); + if (quoteText) { + threadParams.reply_parameters = { + message_id: Math.trunc(opts.replyToMessageId), + quote: quoteText, + }; + } else { + threadParams.reply_to_message_id = Math.trunc(opts.replyToMessageId); + } } const hasThreadParams = Object.keys(threadParams).length > 0; const request = createTelegramRetryRunner({ diff --git a/src/utils.test.ts b/src/utils.test.ts index 686808a46..769c98a4f 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -9,6 +9,7 @@ import { jidToE164, normalizeE164, normalizePath, + resolveConfigDir, resolveJidToE164, resolveUserPath, sleep, @@ -120,6 +121,20 @@ describe("jidToE164", () => { }); }); +describe("resolveConfigDir", () => { + it("prefers ~/.moltbot when legacy dir is missing", async () => { + const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "moltbot-config-dir-")); + try { + const newDir = path.join(root, ".moltbot"); + await fs.promises.mkdir(newDir, { recursive: true }); + const resolved = resolveConfigDir({} as NodeJS.ProcessEnv, () => root); + expect(resolved).toBe(newDir); + } finally { + await fs.promises.rm(root, { recursive: true, force: true }); + } + }); +}); + describe("resolveJidToE164", () => { it("resolves @lid via lidLookup when mapping file is missing", async () => { const lidLookup = { diff --git a/src/utils.ts b/src/utils.ts index cdb56c7ee..7c441f4f1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -215,9 +215,18 @@ export function resolveConfigDir( env: NodeJS.ProcessEnv = process.env, homedir: () => string = os.homedir, ): string { - const override = env.CLAWDBOT_STATE_DIR?.trim(); + const override = env.MOLTBOT_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim(); if (override) return resolveUserPath(override); - return path.join(homedir(), ".clawdbot"); + const legacyDir = path.join(homedir(), ".clawdbot"); + const newDir = path.join(homedir(), ".moltbot"); + try { + const hasLegacy = fs.existsSync(legacyDir); + const hasNew = fs.existsSync(newDir); + if (!hasLegacy && hasNew) return newDir; + } catch { + // best-effort + } + return legacyDir; } export function resolveHomeDir(): string | undefined { diff --git a/src/web/auto-reply/monitor/broadcast.ts b/src/web/auto-reply/monitor/broadcast.ts index ef76ce3b0..c8f84a048 100644 --- a/src/web/auto-reply/monitor/broadcast.ts +++ b/src/web/auto-reply/monitor/broadcast.ts @@ -54,11 +54,13 @@ export async function maybeBroadcastMessage(params: { sessionKey: buildAgentSessionKey({ agentId: normalizedAgentId, channel: "whatsapp", + accountId: params.route.accountId, peer: { kind: params.msg.chatType === "group" ? "group" : "dm", id: params.peerId, }, dmScope: params.cfg.session?.dmScope, + identityLinks: params.cfg.session?.identityLinks, }), mainSessionKey: buildAgentMainSessionKey({ agentId: normalizedAgentId, diff --git a/ui/src/styles/chat/layout.css b/ui/src/styles/chat/layout.css index e11fedb71..589b0b62d 100644 --- a/ui/src/styles/chat/layout.css +++ b/ui/src/styles/chat/layout.css @@ -250,7 +250,8 @@ max-height: 150px; padding: 9px 12px; border-radius: 8px; - resize: vertical; + overflow-y: auto; + resize: none; white-space: pre-wrap; font-family: var(--font-body); font-size: 14px; diff --git a/ui/src/ui/views/chat.ts b/ui/src/ui/views/chat.ts index a9b4da572..f5fb6e80b 100644 --- a/ui/src/ui/views/chat.ts +++ b/ui/src/ui/views/chat.ts @@ -1,4 +1,5 @@ import { html, nothing } from "lit"; +import { ref } from "lit/directives/ref.js"; import { repeat } from "lit/directives/repeat.js"; import type { SessionsListResult } from "../types"; import type { ChatAttachment, ChatQueueItem } from "../ui-types"; @@ -71,6 +72,11 @@ export type ChatProps = { const COMPACTION_TOAST_DURATION_MS = 5000; +function adjustTextareaHeight(el: HTMLTextAreaElement) { + el.style.height = "auto"; + el.style.height = `${el.scrollHeight}px`; +} + function renderCompactionIndicator(status: CompactionIndicatorStatus | null | undefined) { if (!status) return nothing; @@ -327,6 +333,7 @@ export function renderChat(props: ChatProps) {