Merge branch 'main' into patch-4

This commit is contained in:
Harshal Jain 2026-01-28 00:13:23 +05:30 committed by GitHub
commit b9d39cf414
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 224 additions and 114 deletions

View File

@ -76,7 +76,7 @@ jobs:
command: pnpm lint command: pnpm lint
- runtime: node - runtime: node
task: test task: test
command: pnpm test command: pnpm canvas:a2ui:bundle && pnpm test
- runtime: node - runtime: node
task: build task: build
command: pnpm build command: pnpm build
@ -88,7 +88,7 @@ jobs:
command: pnpm format command: pnpm format
- runtime: bun - runtime: bun
task: test task: test
command: bunx vitest run command: pnpm canvas:a2ui:bundle && bunx vitest run
- runtime: bun - runtime: bun
task: build task: build
command: bunx tsc -p tsconfig.json command: bunx tsc -p tsconfig.json
@ -188,6 +188,7 @@ jobs:
runs-on: blacksmith-4vcpu-windows-2025 runs-on: blacksmith-4vcpu-windows-2025
env: env:
NODE_OPTIONS: --max-old-space-size=4096 NODE_OPTIONS: --max-old-space-size=4096
CLAWDBOT_TEST_WORKERS: 1
defaults: defaults:
run: run:
shell: bash shell: bash
@ -200,7 +201,7 @@ jobs:
command: pnpm lint command: pnpm lint
- runtime: node - runtime: node
task: test task: test
command: pnpm test command: pnpm canvas:a2ui:bundle && pnpm test
- runtime: node - runtime: node
task: build task: build
command: pnpm build command: pnpm build

View File

@ -37,5 +37,5 @@ jobs:
CLAWDBOT_NO_ONBOARD: "1" CLAWDBOT_NO_ONBOARD: "1"
CLAWDBOT_INSTALL_SMOKE_SKIP_CLI: "1" CLAWDBOT_INSTALL_SMOKE_SKIP_CLI: "1"
CLAWDBOT_INSTALL_SMOKE_SKIP_NONROOT: ${{ github.event_name == 'pull_request' && '1' || '0' }} CLAWDBOT_INSTALL_SMOKE_SKIP_NONROOT: ${{ github.event_name == 'pull_request' && '1' || '0' }}
CLAWDBOT_INSTALL_SMOKE_PREVIOUS: "2026.1.11-4" CLAWDBOT_INSTALL_SMOKE_SKIP_PREVIOUS: "1"
run: pnpm test:install:smoke run: pnpm test:install:smoke

View File

@ -72,6 +72,7 @@ Status: unreleased.
- Security: pin npm overrides to keep tar@7.5.4 for install toolchains. - 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. - 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: 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. - BlueBubbles: coalesce inbound URL link preview messages. (#1981) Thanks @tyler6204.
- Cron: allow payloads containing "heartbeat" in event filter. (#2219) Thanks @dwfinkelstein. - 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. - CLI: avoid loading config for global help/version while registering plugin commands. (#2212) Thanks @dial481.

View File

@ -24,7 +24,7 @@ COPY scripts ./scripts
RUN pnpm install --frozen-lockfile RUN pnpm install --frozen-lockfile
COPY . . COPY . .
RUN pnpm build RUN CLAWDBOT_A2UI_SKIP_MISSING=1 pnpm build
# Force pnpm for UI build (Bun may fail on ARM/Synology architectures) # Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
ENV CLAWDBOT_PREFER_PNPM=1 ENV CLAWDBOT_PREFER_PNPM=1
RUN pnpm ui:install RUN pnpm ui:install

View File

@ -1,7 +1,7 @@
# 🦞 Clawdbot — Personal AI Assistant # 🦞 Moltbot — Personal AI Assistant
<p align="center"> <p align="center">
<img src="https://raw.githubusercontent.com/clawdbot/clawdbot/main/docs/whatsapp-clawd.jpg" alt="Clawdbot" width="400"> <img src="https://raw.githubusercontent.com/moltbot/moltbot/main/docs/whatsapp-clawd.jpg" alt="Clawdbot" width="400">
</p> </p>
<p align="center"> <p align="center">
@ -9,21 +9,21 @@
</p> </p>
<p align="center"> <p align="center">
<a href="https://github.com/clawdbot/clawdbot/actions/workflows/ci.yml?branch=main"><img src="https://img.shields.io/github/actions/workflow/status/clawdbot/clawdbot/ci.yml?branch=main&style=for-the-badge" alt="CI status"></a> <a href="https://github.com/moltbot/moltbot/actions/workflows/ci.yml?branch=main"><img src="https://img.shields.io/github/actions/workflow/status/moltbot/moltbot/ci.yml?branch=main&style=for-the-badge" alt="CI status"></a>
<a href="https://github.com/clawdbot/clawdbot/releases"><img src="https://img.shields.io/github/v/release/clawdbot/clawdbot?include_prereleases&style=for-the-badge" alt="GitHub release"></a> <a href="https://github.com/moltbot/moltbot/releases"><img src="https://img.shields.io/github/v/release/moltbot/moltbot?include_prereleases&style=for-the-badge" alt="GitHub release"></a>
<a href="https://deepwiki.com/clawdbot/clawdbot"><img src="https://img.shields.io/badge/DeepWiki-clawdbot-111111?style=for-the-badge" alt="DeepWiki"></a> <a href="https://deepwiki.com/moltbot/moltbot"><img src="https://img.shields.io/badge/DeepWiki-moltbot-111111?style=for-the-badge" alt="DeepWiki"></a>
<a href="https://discord.gg/clawd"><img src="https://img.shields.io/discord/1456350064065904867?label=Discord&logo=discord&logoColor=white&color=5865F2&style=for-the-badge" alt="Discord"></a> <a href="https://discord.gg/clawd"><img src="https://img.shields.io/discord/1456350064065904867?label=Discord&logo=discord&logoColor=white&color=5865F2&style=for-the-badge" alt="Discord"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="MIT License"></a> <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="MIT License"></a>
</p> </p>
**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. 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. 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. Works with npm, pnpm, or bun.
New install? Start here: [Getting started](https://docs.molt.bot/start/getting-started) 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). - **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). - **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). Details: [Development channels](https://docs.molt.bot/install/development-channels).
## From source (development) ## 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. Prefer `pnpm` for builds from source. Bun is optional for running TypeScript directly.
```bash ```bash
git clone https://github.com/clawdbot/clawdbot.git git clone https://github.com/moltbot/moltbot.git
cd clawdbot cd moltbot
pnpm install pnpm install
pnpm ui:build # auto-installs UI deps on first run 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) ## 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) Full security guide: [Security](https://docs.molt.bot/gateway/security)
Default behavior on Telegram/WhatsApp/Signal/iMessage/Microsoft Teams/Discord/Google Chat/Slack: 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. - **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 <channel> <code>` (then the sender is added to a local allowlist store). - Approve with: `moltbot pairing approve <channel> <code>` (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`). - 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 ## Highlights
@ -127,7 +127,7 @@ Run `clawdbot doctor` to surface risky/misconfigured DM policies.
## Star History ## 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 ## 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. - [macOS node mode](https://docs.molt.bot/nodes): system.run/notify + canvas/camera exposure.
### Tools + automation ### 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. - [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. - [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). - [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) ├─ Pi agent (RPC)
├─ CLI (clawdbot …) ├─ CLI (moltbot …)
├─ WebChat UI ├─ WebChat UI
├─ macOS app ├─ macOS app
└─ iOS / Android nodes └─ 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)). - **[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)). - **[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)** — clawdmanaged Chrome/Chromium with CDP control. - **[Browser control](https://docs.molt.bot/tools/browser)** — moltbotmanaged Chrome/Chromium with CDP control.
- **[Canvas + A2UI](https://docs.molt.bot/platforms/mac/canvas)** — agentdriven visual workspace (A2UI host: [Canvas/A2UI](https://docs.molt.bot/platforms/mac/canvas#canvas-a2ui)). - **[Canvas + A2UI](https://docs.molt.bot/platforms/mac/canvas)** — agentdriven 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)** — alwayson speech and continuous conversation. - **[Voice Wake](https://docs.molt.bot/nodes/voicewake) + [Talk Mode](https://docs.molt.bot/nodes/talk)** — alwayson speech and continuous conversation.
- **[Nodes](https://docs.molt.bot/nodes)** — Canvas, camera snap/clip, screen record, `location.get`, notifications, plus macOSonly `system.run`/`system.notify`. - **[Nodes](https://docs.molt.bot/nodes)** — Canvas, camera snap/clip, screen record, `location.get`, notifications, plus macOSonly `system.run`/`system.notify`.
## Tailscale access (Gateway dashboard) ## 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). - `off`: no Tailscale automation (default).
- `serve`: tailnet-only HTTPS via `tailscale serve` (uses Tailscale identity headers by default). - `serve`: tailnet-only HTTPS via `tailscale serve` (uses Tailscale identity headers by default).
- `funnel`: public HTTPS via `tailscale funnel` (requires shared password auth). - `funnel`: public HTTPS via `tailscale funnel` (requires shared password auth).
Notes: 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`. - 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. - Funnel refuses to start unless `gateway.auth.mode: "password"` is set.
- Optional: `gateway.tailscale.resetOnExit` to undo Serve/Funnel on shutdown. - 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. 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. - Menu bar control for the Gateway and health.
- Voice Wake + push-to-talk overlay. - 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. - Pairs as a node via the Bridge.
- Voice trigger forwarding + Canvas surface. - Voice trigger forwarding + Canvas surface.
- Controlled via `clawdbot nodes …`. - Controlled via `moltbot nodes …`.
Runbook: [iOS connect](https://docs.molt.bot/platforms/ios). Runbook: [iOS connect](https://docs.molt.bot/platforms/ios).
@ -301,7 +301,7 @@ Runbook: [iOS connect](https://docs.molt.bot/platforms/ios).
## Configuration ## Configuration
Minimal `~/.clawdbot/clawdbot.json` (model + defaults): Minimal `~/.clawdbot/moltbot.json` (model + defaults):
```json5 ```json5
{ {
@ -323,7 +323,7 @@ Details: [Security guide](https://docs.molt.bot/gateway/security) · [Docker + s
### [WhatsApp](https://docs.molt.bot/channels/whatsapp) ### [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`. - 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. - If `channels.whatsapp.groups` is set, it becomes a group allowlist; include `"*"` to allow all.
@ -457,14 +457,15 @@ Use these when youre past the onboarding flow and want the deeper reference.
- [docs.molt.bot/gmail-pubsub](https://docs.molt.bot/automation/gmail-pubsub) - [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. by Peter Steinberger and the community.
- [clawd.me](https://clawd.me) - [clawd.me](https://clawd.me)
- [soul.md](https://soul.md) - [soul.md](https://soul.md)
- [steipete.me](https://steipete.me) - [steipete.me](https://steipete.me)
- [@moltbot](https://x.com/moltbot)
## Community ## Community

View File

@ -11,7 +11,8 @@
"./cli-entry": "./dist/entry.js" "./cli-entry": "./dist/entry.js"
}, },
"bin": { "bin": {
"moltbot": "dist/entry.js" "moltbot": "dist/entry.js",
"clawdbot": "dist/entry.js"
}, },
"files": [ "files": [
"dist/acp/**", "dist/acp/**",
@ -22,6 +23,7 @@
"dist/cli/**", "dist/cli/**",
"dist/commands/**", "dist/commands/**",
"dist/config/**", "dist/config/**",
"dist/compat/**",
"dist/control-ui/**", "dist/control-ui/**",
"dist/cron/**", "dist/cron/**",
"dist/channels/**", "dist/channels/**",

38
pnpm-lock.yaml generated
View File

@ -314,17 +314,17 @@ importers:
specifier: ^10.5.0 specifier: ^10.5.0
version: 10.5.0 version: 10.5.0
devDependencies: devDependencies:
clawdbot: moltbot:
specifier: workspace:* specifier: workspace:*
version: link:../../packages/clawdbot version: link:../..
extensions/imessage: {} extensions/imessage: {}
extensions/line: extensions/line:
devDependencies: devDependencies:
clawdbot: moltbot:
specifier: workspace:* specifier: workspace:*
version: link:../../packages/clawdbot version: link:../..
extensions/llm-task: {} extensions/llm-task: {}
@ -348,17 +348,17 @@ importers:
specifier: ^4.3.6 specifier: ^4.3.6
version: 4.3.6 version: 4.3.6
devDependencies: devDependencies:
clawdbot: moltbot:
specifier: workspace:* specifier: workspace:*
version: link:../../packages/clawdbot version: link:../..
extensions/mattermost: {} extensions/mattermost: {}
extensions/memory-core: extensions/memory-core:
dependencies: dependencies:
clawdbot: moltbot:
specifier: '>=2026.1.24-3' specifier: '>=2026.1.26'
version: 2026.1.24-3(@types/express@5.0.6)(audio-decode@2.2.3)(devtools-protocol@0.0.1561482)(typescript@5.9.3) version: link:../..
extensions/memory-lancedb: extensions/memory-lancedb:
dependencies: dependencies:
@ -383,9 +383,9 @@ importers:
'@microsoft/agents-hosting-extensions-teams': '@microsoft/agents-hosting-extensions-teams':
specifier: ^1.2.2 specifier: ^1.2.2
version: 1.2.2 version: 1.2.2
clawdbot: moltbot:
specifier: workspace:* specifier: workspace:*
version: link:../../packages/clawdbot version: link:../..
express: express:
specifier: ^5.2.1 specifier: ^5.2.1
version: 5.2.1 version: 5.2.1
@ -397,9 +397,9 @@ importers:
extensions/nostr: extensions/nostr:
dependencies: dependencies:
clawdbot: moltbot:
specifier: workspace:* specifier: workspace:*
version: link:../../packages/clawdbot version: link:../..
nostr-tools: nostr-tools:
specifier: ^2.20.0 specifier: ^2.20.0
version: 2.20.0(typescript@5.9.3) version: 2.20.0(typescript@5.9.3)
@ -439,9 +439,9 @@ importers:
specifier: ^4.3.5 specifier: ^4.3.5
version: 4.3.6 version: 4.3.6
devDependencies: devDependencies:
clawdbot: moltbot:
specifier: workspace:* specifier: workspace:*
version: link:../../packages/clawdbot version: link:../..
extensions/voice-call: extensions/voice-call:
dependencies: dependencies:
@ -459,9 +459,9 @@ importers:
extensions/zalo: extensions/zalo:
dependencies: dependencies:
clawdbot: moltbot:
specifier: workspace:* specifier: workspace:*
version: link:../../packages/clawdbot version: link:../..
undici: undici:
specifier: 7.19.0 specifier: 7.19.0
version: 7.19.0 version: 7.19.0
@ -471,9 +471,9 @@ importers:
'@sinclair/typebox': '@sinclair/typebox':
specifier: 0.34.47 specifier: 0.34.47
version: 0.34.47 version: 0.34.47
clawdbot: moltbot:
specifier: workspace:* specifier: workspace:*
version: link:../../packages/clawdbot version: link:../..
packages/clawdbot: packages/clawdbot:
dependencies: dependencies:

View File

@ -10,31 +10,66 @@ trap on_error ERR
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
HASH_FILE="$ROOT_DIR/src/canvas-host/a2ui/.bundle.hash" HASH_FILE="$ROOT_DIR/src/canvas-host/a2ui/.bundle.hash"
OUTPUT_FILE="$ROOT_DIR/src/canvas-host/a2ui/a2ui.bundle.js" 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"
# Docker builds exclude vendor/apps via .dockerignore.
# In that environment we must keep the prebuilt bundle.
if [[ ! -d "$A2UI_RENDERER_DIR" || ! -d "$A2UI_APP_DIR" ]]; then
echo "A2UI sources missing; keeping prebuilt bundle."
exit 0
fi
INPUT_PATHS=( INPUT_PATHS=(
"$ROOT_DIR/package.json" "$ROOT_DIR/package.json"
"$ROOT_DIR/pnpm-lock.yaml" "$ROOT_DIR/pnpm-lock.yaml"
"$ROOT_DIR/vendor/a2ui/renderers/lit" "$A2UI_RENDERER_DIR"
"$ROOT_DIR/apps/shared/ClawdbotKit/Tools/CanvasA2UI" "$A2UI_APP_DIR"
) )
collect_files() { compute_hash() {
local path ROOT_DIR="$ROOT_DIR" node --input-type=module - "${INPUT_PATHS[@]}" <<'NODE'
for path in "${INPUT_PATHS[@]}"; do import { createHash } from "node:crypto";
if [[ -d "$path" ]]; then import { promises as fs } from "node:fs";
find "$path" -type f -print0 import path from "node:path";
else
printf '%s\0' "$path" const rootDir = process.env.ROOT_DIR ?? process.cwd();
fi const inputs = process.argv.slice(2);
done const files = [];
async function walk(entryPath) {
const st = await fs.stat(entryPath);
if (st.isDirectory()) {
const entries = await fs.readdir(entryPath);
for (const entry of entries) {
await walk(path.join(entryPath, entry));
}
return;
}
files.push(entryPath);
} }
compute_hash() { for (const input of inputs) {
collect_files \ await walk(input);
| LC_ALL=C sort -z \ }
| xargs -0 shasum -a 256 \
| shasum -a 256 \ function normalize(p) {
| awk '{print $1}' return p.split(path.sep).join("/");
}
files.sort((a, b) => normalize(a).localeCompare(normalize(b)));
const hash = createHash("sha256");
for (const filePath of files) {
const rel = normalize(path.relative(rootDir, filePath));
hash.update(rel);
hash.update("\0");
hash.update(await fs.readFile(filePath));
hash.update("\0");
}
process.stdout.write(hash.digest("hex"));
NODE
} }
current_hash="$(compute_hash)" current_hash="$(compute_hash)"
@ -46,7 +81,7 @@ if [[ -f "$HASH_FILE" ]]; then
fi fi
fi fi
pnpm -s exec tsc -p vendor/a2ui/renderers/lit/tsconfig.json pnpm -s exec tsc -p "$A2UI_RENDERER_DIR/tsconfig.json"
rolldown -c apps/shared/ClawdbotKit/Tools/CanvasA2UI/rolldown.config.mjs rolldown -c "$A2UI_APP_DIR/rolldown.config.mjs"
echo "$current_hash" > "$HASH_FILE" echo "$current_hash" > "$HASH_FILE"

View File

@ -19,12 +19,17 @@ export async function copyA2uiAssets({
srcDir: string; srcDir: string;
outDir: string; outDir: string;
}) { }) {
const skipMissing = process.env.CLAWDBOT_A2UI_SKIP_MISSING === "1";
try { try {
await fs.stat(path.join(srcDir, "index.html")); await fs.stat(path.join(srcDir, "index.html"));
await fs.stat(path.join(srcDir, "a2ui.bundle.js")); await fs.stat(path.join(srcDir, "a2ui.bundle.js"));
} catch (err) { } catch (err) {
const message = const message =
'Missing A2UI bundle assets. Run "pnpm canvas:a2ui:bundle" and retry.'; 'Missing A2UI bundle assets. Run "pnpm canvas:a2ui:bundle" and retry.';
if (skipMissing) {
console.warn(`${message} Skipping copy (CLAWDBOT_A2UI_SKIP_MISSING=1).`);
return;
}
throw new Error(message, { cause: err }); throw new Error(message, { cause: err });
} }
await fs.mkdir(path.dirname(outDir), { recursive: true }); await fs.mkdir(path.dirname(outDir), { recursive: true });

View File

@ -2,6 +2,16 @@
set -euo pipefail set -euo pipefail
INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://molt.bot/install.sh}" INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://molt.bot/install.sh}"
DEFAULT_PACKAGE="moltbot"
if [[ -z "${CLAWDBOT_INSTALL_PACKAGE:-}" && "$INSTALL_URL" == *"clawd.bot"* ]]; then
DEFAULT_PACKAGE="clawdbot"
fi
PACKAGE_NAME="${CLAWDBOT_INSTALL_PACKAGE:-$DEFAULT_PACKAGE}"
if [[ "$PACKAGE_NAME" == "moltbot" ]]; then
ALT_PACKAGE_NAME="clawdbot"
else
ALT_PACKAGE_NAME="moltbot"
fi
echo "==> Pre-flight: ensure git absent" echo "==> Pre-flight: ensure git absent"
if command -v git >/dev/null; then if command -v git >/dev/null; then
@ -18,26 +28,39 @@ export PATH="$HOME/.npm-global/bin:$PATH"
echo "==> Verify git installed" echo "==> Verify git installed"
command -v git >/dev/null command -v git >/dev/null
echo "==> Verify moltbot installed"
EXPECTED_VERSION="${CLAWDBOT_INSTALL_EXPECT_VERSION:-}" EXPECTED_VERSION="${CLAWDBOT_INSTALL_EXPECT_VERSION:-}"
if [[ -n "$EXPECTED_VERSION" ]]; then if [[ -n "$EXPECTED_VERSION" ]]; then
LATEST_VERSION="$EXPECTED_VERSION" LATEST_VERSION="$EXPECTED_VERSION"
else else
LATEST_VERSION="$(npm view moltbot version)" LATEST_VERSION="$(npm view "$PACKAGE_NAME" version)"
fi fi
CMD_PATH="$(command -v moltbot || true)" CLI_NAME="$PACKAGE_NAME"
if [[ -z "$CMD_PATH" && -x "$HOME/.npm-global/bin/moltbot" ]]; then CMD_PATH="$(command -v "$CLI_NAME" || true)"
CMD_PATH="$HOME/.npm-global/bin/moltbot" if [[ -z "$CMD_PATH" ]]; then
CLI_NAME="$ALT_PACKAGE_NAME"
CMD_PATH="$(command -v "$CLI_NAME" || true)"
fi
if [[ -z "$CMD_PATH" && -x "$HOME/.npm-global/bin/$PACKAGE_NAME" ]]; then
CLI_NAME="$PACKAGE_NAME"
CMD_PATH="$HOME/.npm-global/bin/$PACKAGE_NAME"
fi
if [[ -z "$CMD_PATH" && -x "$HOME/.npm-global/bin/$ALT_PACKAGE_NAME" ]]; then
CLI_NAME="$ALT_PACKAGE_NAME"
CMD_PATH="$HOME/.npm-global/bin/$ALT_PACKAGE_NAME"
fi fi
if [[ -z "$CMD_PATH" ]]; then if [[ -z "$CMD_PATH" ]]; then
echo "moltbot not on PATH" >&2 echo "Neither $PACKAGE_NAME nor $ALT_PACKAGE_NAME is on PATH" >&2
exit 1 exit 1
fi fi
if [[ -z "$EXPECTED_VERSION" && "$CLI_NAME" != "$PACKAGE_NAME" ]]; then
LATEST_VERSION="$(npm view "$CLI_NAME" version)"
fi
echo "==> Verify CLI installed: $CLI_NAME"
INSTALLED_VERSION="$("$CMD_PATH" --version 2>/dev/null | head -n 1 | tr -d '\r')" INSTALLED_VERSION="$("$CMD_PATH" --version 2>/dev/null | head -n 1 | tr -d '\r')"
echo "installed=$INSTALLED_VERSION expected=$LATEST_VERSION" echo "cli=$CLI_NAME installed=$INSTALLED_VERSION expected=$LATEST_VERSION"
if [[ "$INSTALLED_VERSION" != "$LATEST_VERSION" ]]; then if [[ "$INSTALLED_VERSION" != "$LATEST_VERSION" ]]; then
echo "ERROR: expected moltbot@$LATEST_VERSION, got @$INSTALLED_VERSION" >&2 echo "ERROR: expected ${CLI_NAME}@${LATEST_VERSION}, got ${CLI_NAME}@${INSTALLED_VERSION}" >&2
exit 1 exit 1
fi fi

View File

@ -4,15 +4,26 @@ set -euo pipefail
INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://molt.bot/install.sh}" INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://molt.bot/install.sh}"
SMOKE_PREVIOUS_VERSION="${CLAWDBOT_INSTALL_SMOKE_PREVIOUS:-}" SMOKE_PREVIOUS_VERSION="${CLAWDBOT_INSTALL_SMOKE_PREVIOUS:-}"
SKIP_PREVIOUS="${CLAWDBOT_INSTALL_SMOKE_SKIP_PREVIOUS:-0}" SKIP_PREVIOUS="${CLAWDBOT_INSTALL_SMOKE_SKIP_PREVIOUS:-0}"
DEFAULT_PACKAGE="moltbot"
if [[ -z "${CLAWDBOT_INSTALL_PACKAGE:-}" && "$INSTALL_URL" == *"clawd.bot"* ]]; then
DEFAULT_PACKAGE="clawdbot"
fi
PACKAGE_NAME="${CLAWDBOT_INSTALL_PACKAGE:-$DEFAULT_PACKAGE}"
if [[ "$PACKAGE_NAME" == "moltbot" ]]; then
ALT_PACKAGE_NAME="clawdbot"
else
ALT_PACKAGE_NAME="moltbot"
fi
echo "==> Resolve npm versions" echo "==> Resolve npm versions"
LATEST_VERSION="$(npm view "$PACKAGE_NAME" version)"
if [[ -n "$SMOKE_PREVIOUS_VERSION" ]]; then if [[ -n "$SMOKE_PREVIOUS_VERSION" ]]; then
LATEST_VERSION="$(npm view moltbot version)"
PREVIOUS_VERSION="$SMOKE_PREVIOUS_VERSION" PREVIOUS_VERSION="$SMOKE_PREVIOUS_VERSION"
else else
VERSIONS_JSON="$(npm view moltbot versions --json)" VERSIONS_JSON="$(npm view "$PACKAGE_NAME" versions --json)"
versions_line="$(node - <<'NODE' PREVIOUS_VERSION="$(VERSIONS_JSON="$VERSIONS_JSON" LATEST_VERSION="$LATEST_VERSION" node - <<'NODE'
const raw = process.env.VERSIONS_JSON || "[]"; const raw = process.env.VERSIONS_JSON || "[]";
const latest = process.env.LATEST_VERSION || "";
let versions; let versions;
try { try {
versions = JSON.parse(raw); versions = JSON.parse(raw);
@ -25,41 +36,52 @@ if (!Array.isArray(versions)) {
if (versions.length === 0) { if (versions.length === 0) {
process.exit(1); process.exit(1);
} }
const latest = versions[versions.length - 1]; const latestIndex = latest ? versions.lastIndexOf(latest) : -1;
const previous = versions.length >= 2 ? versions[versions.length - 2] : latest; if (latestIndex > 0) {
process.stdout.write(`${latest} ${previous}`); process.stdout.write(String(versions[latestIndex - 1]));
process.exit(0);
}
process.stdout.write(String(latest || versions[versions.length - 1]));
NODE NODE
)" )"
LATEST_VERSION="${versions_line%% *}"
PREVIOUS_VERSION="${versions_line#* }"
fi fi
if [[ -n "${CLAWDBOT_INSTALL_LATEST_OUT:-}" ]]; then echo "package=$PACKAGE_NAME latest=$LATEST_VERSION previous=$PREVIOUS_VERSION"
printf "%s" "$LATEST_VERSION" > "$CLAWDBOT_INSTALL_LATEST_OUT"
fi
echo "latest=$LATEST_VERSION previous=$PREVIOUS_VERSION"
if [[ "$SKIP_PREVIOUS" == "1" ]]; then if [[ "$SKIP_PREVIOUS" == "1" ]]; then
echo "==> Skip preinstall previous (CLAWDBOT_INSTALL_SMOKE_SKIP_PREVIOUS=1)" echo "==> Skip preinstall previous (CLAWDBOT_INSTALL_SMOKE_SKIP_PREVIOUS=1)"
else else
echo "==> Preinstall previous (forces installer upgrade path)" echo "==> Preinstall previous (forces installer upgrade path)"
npm install -g "moltbot@${PREVIOUS_VERSION}" npm install -g "${PACKAGE_NAME}@${PREVIOUS_VERSION}"
fi fi
echo "==> Run official installer one-liner" echo "==> Run official installer one-liner"
curl -fsSL "$INSTALL_URL" | bash curl -fsSL "$INSTALL_URL" | bash
echo "==> Verify installed version" echo "==> Verify installed version"
INSTALLED_VERSION="$(moltbot --version 2>/dev/null | head -n 1 | tr -d '\r')" CLI_NAME="$PACKAGE_NAME"
echo "installed=$INSTALLED_VERSION expected=$LATEST_VERSION" if ! command -v "$CLI_NAME" >/dev/null 2>&1; then
if command -v "$ALT_PACKAGE_NAME" >/dev/null 2>&1; then
CLI_NAME="$ALT_PACKAGE_NAME"
LATEST_VERSION="$(npm view "$CLI_NAME" version)"
echo "==> Detected alternate CLI: $CLI_NAME"
else
echo "ERROR: neither $PACKAGE_NAME nor $ALT_PACKAGE_NAME is on PATH" >&2
exit 1
fi
fi
if [[ -n "${CLAWDBOT_INSTALL_LATEST_OUT:-}" ]]; then
printf "%s" "$LATEST_VERSION" > "$CLAWDBOT_INSTALL_LATEST_OUT"
fi
INSTALLED_VERSION="$("$CLI_NAME" --version 2>/dev/null | head -n 1 | tr -d '\r')"
echo "cli=$CLI_NAME installed=$INSTALLED_VERSION expected=$LATEST_VERSION"
if [[ "$INSTALLED_VERSION" != "$LATEST_VERSION" ]]; then if [[ "$INSTALLED_VERSION" != "$LATEST_VERSION" ]]; then
echo "ERROR: expected moltbot@$LATEST_VERSION, got moltbot@$INSTALLED_VERSION" >&2 echo "ERROR: expected ${CLI_NAME}@${LATEST_VERSION}, got ${CLI_NAME}@${INSTALLED_VERSION}" >&2
exit 1 exit 1
fi fi
echo "==> Sanity: CLI runs" echo "==> Sanity: CLI runs"
moltbot --help >/dev/null "$CLI_NAME" --help >/dev/null
echo "OK" echo "OK"

View File

@ -18,16 +18,21 @@ const runs = [
}, },
]; ];
const parallelRuns = runs.filter((entry) => entry.name !== "gateway");
const serialRuns = runs.filter((entry) => entry.name === "gateway");
const children = new Set(); const children = new Set();
const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true"; const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
const isMacOS = process.platform === "darwin" || process.env.RUNNER_OS === "macOS"; 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 overrideWorkers = Number.parseInt(process.env.CLAWDBOT_TEST_WORKERS ?? "", 10);
const resolvedOverride = Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null; const resolvedOverride = Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
const parallelRuns = isWindowsCi ? [] : runs.filter((entry) => entry.name !== "gateway");
const serialRuns = isWindowsCi ? runs : runs.filter((entry) => entry.name === "gateway");
const localWorkers = Math.max(4, Math.min(16, os.cpus().length)); const localWorkers = Math.max(4, Math.min(16, os.cpus().length));
const perRunWorkers = Math.max(1, Math.floor(localWorkers / parallelRuns.length)); const parallelCount = Math.max(1, parallelRuns.length);
const perRunWorkers = Math.max(1, Math.floor(localWorkers / parallelCount));
const macCiWorkers = isCI && isMacOS ? 1 : perRunWorkers; const macCiWorkers = isCI && isMacOS ? 1 : perRunWorkers;
// Keep worker counts predictable for local runs; trim macOS CI workers to avoid worker crashes/OOM. // Keep worker counts predictable for local runs; trim macOS CI workers to avoid worker crashes/OOM.
// In CI on linux/windows, prefer Vitest defaults to avoid cross-test interference from lower worker counts. // In CI on linux/windows, prefer Vitest defaults to avoid cross-test interference from lower worker counts.
@ -39,9 +44,11 @@ const WARNING_SUPPRESSION_FLAGS = [
"--disable-warning=DEP0060", "--disable-warning=DEP0060",
]; ];
const run = (entry) => const runOnce = (entry, extraArgs = []) =>
new Promise((resolve) => { 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 nodeOptions = process.env.NODE_OPTIONS ?? "";
const nextNodeOptions = WARNING_SUPPRESSION_FLAGS.reduce( const nextNodeOptions = WARNING_SUPPRESSION_FLAGS.reduce(
(acc, flag) => (acc.includes(flag) ? acc : `${acc} ${flag}`.trim()), (acc, flag) => (acc.includes(flag) ? acc : `${acc} ${flag}`.trim()),
@ -59,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) => { const shutdown = (signal) => {
for (const child of children) { for (const child of children) {
child.kill(signal); child.kill(signal);

View File

@ -1 +1 @@
1376c2e99ad07193d9ab1719200675d84ffb40db417d05128cf07c3b8283581e b6d3dea7c656c8a480059c32e954c4d39053ff79c4e9c69b38f4c04e3f0280d4

View File

@ -66,20 +66,23 @@ export async function maybeInstallDaemon(params: {
if (shouldInstall) { if (shouldInstall) {
let installError: string | null = null; 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( await withProgress(
{ label: "Gateway service", indeterminate: true, delayMs: 0 }, { label: "Gateway service", indeterminate: true, delayMs: 0 },
async (progress) => { 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…"); progress.setLabel("Preparing Gateway service…");
const cfg = loadConfig(); const cfg = loadConfig();