Deterministic local messaging-channel mocks for OpenClaw QA.
crabline is config-driven, CI-friendly, and deliberately has no openclaw
dependency. It can run fixture-level local mocks, and it can also serve fake
provider APIs that OpenClaw live adapters can target during deterministic QA.
- local mock providers for
discord,feishu,googlechat,imessage,loopback,matrix,mattermost,msteams,slack,telegram,whatsapp, andzalo - a
scriptbridge for channels that are still exercised by external commands - per-provider local webhook endpoints for inbound events
- fake provider servers for live-adapter smoke tests, starting with Telegram
- JSONL recorder files for deterministic wait/watch behavior
- nonce-based
send,roundtrip,agent,probe,run,watch, anddoctorcommands - text output by default and stable
--jsonoutput for automation
Crabline fake servers are not live-provider coverage. They let OpenClaw run its
normal channel adapter code against a local provider-shaped API. Release lanes
still need the live driver and real provider credentials.
pnpm install
pnpm build
pnpm verifyRun locally:
pnpm dev fixtures --config fixtures/examples/crabline.example.yaml
pnpm dev roundtrip telegram-dm --config fixtures/examples/crabline.example.yamlpnpm verifyThat enforces formatting, typecheck, type-aware lint, and Vitest coverage.
Config file search order:
--config <path>./crabline.yaml./crabline.yml./crabline.json
Top-level shape:
configVersion: 1
userName: crabline
providers:
telegram:
adapter: telegram
telegram:
recorder:
path: ./.crabline/recorders/telegram.jsonl
webhook:
host: 127.0.0.1
port: 8790
path: /telegram/webhook
fixtures:
- id: telegram-dm
provider: telegram
mode: roundtrip
target:
id: "100000001"
behavior: agentProvider ids are local profile names. Fixtures reference them through
provider. Built-in adapters infer their platform from adapter; platform is
required only for adapter: script.
Built-in provider credentials are optional metadata only. doctor checks
explicit env declarations, script command availability, and config shape; it
does not require live Slack, Discord, Telegram, WhatsApp, Matrix, iMessage, or
other platform secrets for local mocks.
All built-in mock providers support:
probesendroundtripagentwatch
The built-in providers are:
discordfeishugooglechatimessageloopbackmatrixmattermostmsteamsslacktelegramwhatsappzalo
The script adapter can bridge any other OpenClaw channel by running local
commands for probe, send, waitForInbound, or watch.
serve starts provider-shaped HTTP APIs for OpenClaw live adapters. This is the
preferred Smoke CI path because OpenClaw still uses its normal channel adapter,
but the provider endpoint is local and deterministic.
Telegram:
crabline --json serve telegram --ready-file .crabline/telegram-server.jsonThe JSON manifest contains:
endpoints.apiRoot: set OpenClawchannels.telegram.apiRootto this valuebotToken: set OpenClawchannels.telegram.botTokento this valueadminToken: send this as theX-Crabline-Admin-Tokenheader when posting test user messagesendpoints.adminInboundUrl: authenticated POST endpoint for test user messages; OpenClaw reads them through TelegramgetUpdatesrecorderPath: JSONL file of fake provider API/admin traffic
The admin token is generated randomly unless --admin-token <token> is
provided. The inbound endpoint rejects requests without the matching admin
header (or Authorization: Bearer <token>).
Implemented Telegram Bot API endpoints include getMe, sendMessage,
editMessageText, deleteMessage, setMessageReaction, createForumTopic,
editForumTopic, pinChatMessage, unpinChatMessage, getUpdates,
deleteWebhook, setWebhook, setMyCommands, deleteMyCommands,
sendChatAction, and answerCallbackQuery.
Built-in providers accept native channel identifiers. Crabline does not add
telegram:, discord:, slack:, or other local prefixes.
target:
id: "C1234567890"Thread targets use the platform's native thread identifier:
target:
channelId: "C1234567890"
threadId: "1700000000.000100"Examples:
- Slack conversations:
C1234567890,G1234567890, orD1234567890 - Slack threads:
1700000000.000100 - Telegram chats:
-1001234567890or@channelusername - Telegram topics:
42 - Discord channels and threads: Discord snowflake ids such as
123456789012345678
Each built-in provider starts a local webhook during probe, waitForInbound,
or watch. Webhook requests can use the provider's native event shape, or this
simple JSON shape with native thread ids:
{
"id": "slack-inbound-1",
"threadId": "C1234567890",
"text": "reply nonce-123",
"author": "assistant"
}Nested message payloads are also accepted:
{
"message": {
"id": "slack-inbound-1",
"threadId": "C1234567890",
"text": "reply nonce-123"
}
}Malformed webhooks return 400, and non-JSON requests return 415.
send records an outbound user event in the provider recorder. For roundtrip
and agent modes, the local mock also records a deterministic assistant reply:
[telegram mock] hello nonce-123
waitForInbound reads the recorder until it finds a matching non-user event.
watch streams matching recorder events. This gives CI channel coverage without
live service latency, external credentials, webhooks exposed to the internet, or
provider SDK state.
See Channel Setup for the provider matrix, webhook paths, and OpenClaw live-vs-mock guidance.