Standalone CLI for deterministic messaging-provider tests. multipass is config-driven, CI-friendly, and deliberately has no openclaw dependency. It now models the full OpenClaw messaging matrix: bluebubbles, discord, feishu, googlechat, imessage, irc, line, matrix, mattermost, msteams, nextcloudtalk, nostr, signal, slack, synologychat, telegram, tlon, twitch, webchat, whatsapp, zalo, zalouser.
The v1 shape is:
- built-in
loopbackprovider for local development and contract tests - native
discordprovider backed by the Chat SDK Discord adapter - native
slackprovider backed by the Chat SDK Slack adapter - native community adapters for
matrixandimessage scriptbridge for the remaining OpenClaw-supported messaging channels- webhook-backed recorder mode for Slack
watch/webhook - interactions-webhook + gateway-backed recorder mode for Discord
- recorder-backed watch mode for Matrix and iMessage
- nonce-based
send,roundtrip,agent,probe,run,watch,doctor - text output by default, stable
--jsonfor automation - core provider model aligned with Vercel Chat SDK concepts
pnpm install
pnpm build
pnpm verifyRun locally:
pnpm dev fixtures --config fixtures/examples/multipass.example.yaml
pnpm dev roundtrip loopback-roundtrip --config fixtures/examples/multipass.example.yamlLocal and CI use the same gate:
pnpm verifyThat enforces:
oxlintwith strict correctness/suspicious rules plus import and Vitest checkstsc --noEmitunderstrict,noUncheckedIndexedAccess,exactOptionalPropertyTypes- Vitest coverage with global thresholds of 80% for statements, lines, and functions
oxfmt --checkformatting
GitHub Actions runs the same pnpm verify flow on pushes to main and pull requests, and uploads the coverage artifact.
Config file search order:
--config <path>./multipass.yaml./multipass.yml./multipass.json
Top-level shape:
configVersion: 1
userName: multipass
providers:
local:
adapter: loopback | script | slack | discord | matrix | imessage
platform: see support matrix below
fixtures:
- id: string
provider: string
accountId: string?
mode: probe | send | roundtrip | agent
target:
id: string
channelId: string?
threadId: string?
behavior: echo | agent | sink?
inboundMatch:
author: assistant | user | system | any
strategy: contains | exact | regex
nonce: contains | exact | ignore
pattern: string?
timeoutMs: number
retries: number
tags: string[]
env: string[]
notes: string?Credentials stay in env, never in fixtures.
ready:loopback, nativeslack, nativediscord, native-communitymatrix, native-communityimessagebridge:bluebubbles,feishu,googlechat,irc,line,mattermost,msteams,nextcloudtalk,nostr,signal,synologychat,telegram,tlon,twitch,webchat,whatsapp,zalo,zalouser- Plugin-backed in OpenClaw, still supported through the bridge:
feishu,line,mattermost,msteams,nextcloudtalk,nostr,synologychat,tlon,twitch,zalo,zalouser - Recommended bridge-only path today:
bluebubbles,googlechat,irc,signal,telegram,webchat,whatsapp
Native Discord provider options:
providers:
discord-native:
adapter: discord
platform: discord
env:
- DISCORD_BOT_TOKEN
- DISCORD_PUBLIC_KEY
- DISCORD_APPLICATION_ID
discord:
recorder:
path: ./.multipass/recorders/discord-native.jsonl
webhook:
host: 127.0.0.1
port: 8788
path: /discord/interactions
publicUrl: https://example.ngrok.app/discord/interactions # optionalDiscord fixture targeting rules:
- Guild channels: set
target.metadata.guildIdand either a raw channel id or a fully encodeddiscord:guild:channel[:thread]id. - DMs: omit
target.metadata.guildId;target.idis treated as the user id. - Quote Discord snowflakes in YAML so they stay strings.
Discord watch and roundtrip start the local interactions server plus a Discord Gateway listener. publicUrl is optional for local gateway-driven receive tests, but needed if you want Discord itself to hit your interactions endpoint from outside your machine.
Native Slack provider options:
providers:
slack-native:
adapter: slack
platform: slack
slack:
recorder:
path: ./.multipass/recorders/slack-native.jsonl
webhook:
host: 127.0.0.1
port: 8787
path: /slack/events
publicUrl: https://example.ngrok.app/slack/events # optional but usefulwatch (alias: webhook) starts the local Slack webhook listener and tails the recorded inbound JSONL stream. roundtrip and agent also start the webhook listener on demand, and will reuse an already-running listener on the configured port.
Native Matrix provider options:
providers:
matrix-native:
adapter: matrix
platform: matrix
env:
- MATRIX_BASE_URL
- MATRIX_ACCESS_TOKEN
matrix:
baseURL: https://matrix.example.com
recorder:
path: ./.multipass/recorders/matrix-native.jsonlNative iMessage provider options:
providers:
imessage-native:
adapter: imessage
platform: imessage
env:
- IMESSAGE_API_KEY
- IMESSAGE_SERVER_URL
imessage:
local: false
serverUrl: https://imessage-gateway.example.com
recorder:
path: ./.multipass/recorders/imessage-native.jsonlMatrix and iMessage watch tail the local recorder stream. There is no webhook listener for Matrix; iMessage uses the adapter gateway listener under the hood.
See fixtures/examples/multipass.example.yaml.
Full OpenClaw bridge matrix example:
Loopback:
pnpm dev roundtrip loopback-roundtrip --config fixtures/examples/multipass.example.yaml
pnpm dev agent loopback-agent --config fixtures/examples/multipass.example.yamlNative Slack:
SLACK_BOT_TOKEN=xoxb-... \
SLACK_SIGNING_SECRET=... \
pnpm dev probe slack-native-agent --config fixtures/examples/multipass.example.yaml
SLACK_BOT_TOKEN=xoxb-... \
SLACK_SIGNING_SECRET=... \
pnpm dev watch slack-native-agent --config fixtures/examples/multipass.example.yamlNative Discord:
DISCORD_BOT_TOKEN=... \
DISCORD_PUBLIC_KEY=... \
DISCORD_APPLICATION_ID=... \
pnpm dev probe discord-native-agent --config fixtures/examples/multipass.example.yaml
DISCORD_BOT_TOKEN=... \
DISCORD_PUBLIC_KEY=... \
DISCORD_APPLICATION_ID=... \
pnpm dev watch discord-native-agent --config fixtures/examples/multipass.example.yamlNative Matrix:
MATRIX_BASE_URL=https://matrix.example.com \
MATRIX_ACCESS_TOKEN=... \
pnpm dev probe matrix-native-agent --config fixtures/examples/multipass.example.yaml
MATRIX_BASE_URL=https://matrix.example.com \
MATRIX_ACCESS_TOKEN=... \
pnpm dev watch matrix-native-agent --config fixtures/examples/multipass.example.yamlNative iMessage:
IMESSAGE_SERVER_URL=https://imessage-gateway.example.com \
IMESSAGE_API_KEY=... \
pnpm dev probe imessage-native-agent --config fixtures/examples/multipass.example.yaml
IMESSAGE_SERVER_URL=https://imessage-gateway.example.com \
IMESSAGE_API_KEY=... \
pnpm dev watch imessage-native-agent --config fixtures/examples/multipass.example.yamlScript bridge:
OPENCLAW_URL=http://127.0.0.1:18789 \
OPENCLAW_TOKEN=secret \
pnpm dev probe slack-openclaw-demo --config fixtures/examples/multipass.example.yamlFull bridge matrix bootstrap:
OPENCLAW_URL=http://127.0.0.1:18789 \
OPENCLAW_TOKEN=secret \
pnpm dev providers --config fixtures/examples/openclaw-supported.yamlmultipass providers
multipass fixtures
multipass probe <fixture|provider>
multipass send <fixture>
multipass roundtrip <fixture>
multipass agent <fixture>
multipass run <fixture...>
multipass watch <fixture>
multipass webhook <fixture> # alias of watch
multipass doctorscript providers receive JSON on stdin and must emit JSON on stdout.
probe input:
{
"fixture": { "...": "fixture config" },
"provider": {
"id": "slack-openclaw",
"manifestPath": "...",
"config": { "...": "provider config" }
}
}probe output:
{
"healthy": true,
"details": ["token ok", "channel reachable"]
}send output:
{
"accepted": true,
"messageId": "123",
"threadId": "slack:C123:thread"
}waitForInbound output:
{
"message": {
"id": "456",
"author": "assistant",
"sentAt": "2026-03-13T21:00:00.000Z",
"text": "ACK mp-demo-...",
"threadId": "slack:C123:thread"
}
}or:
{ "timeout": true }- Add a configured provider instance under
providers. - Set
platformto one of the supported OpenClaw channel ids from the support matrix. - Use
adapter: slackfor native Slack, otherwiseadapter: scriptfor bridge-based real E2E. - Add one or more fixtures that point at stable demo accounts/targets.
- Run
multipass doctor,multipass probe, thenmultipass run ....
- Real built-in providers:
loopback, nativeslack - Real external bridge:
scriptfor the full OpenClaw channel matrix - Not implemented yet: native adapters beyond Slack, richer inbound event storage, rich media/cards, recorder compaction/query tooling