@openclaw/crabline 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +226 -0
  3. package/dist/src/bin/crabline.d.ts +2 -0
  4. package/dist/src/bin/crabline.js +7 -0
  5. package/dist/src/bin/crabline.js.map +1 -0
  6. package/dist/src/cli/program.d.ts +5 -0
  7. package/dist/src/cli/program.js +295 -0
  8. package/dist/src/cli/program.js.map +1 -0
  9. package/dist/src/config/load.d.ts +6 -0
  10. package/dist/src/config/load.js +41 -0
  11. package/dist/src/config/load.js.map +1 -0
  12. package/dist/src/config/schema.d.ts +1571 -0
  13. package/dist/src/config/schema.js +528 -0
  14. package/dist/src/config/schema.js.map +1 -0
  15. package/dist/src/core/errors.d.ts +12 -0
  16. package/dist/src/core/errors.js +28 -0
  17. package/dist/src/core/errors.js.map +1 -0
  18. package/dist/src/core/exit-codes.d.ts +13 -0
  19. package/dist/src/core/exit-codes.js +13 -0
  20. package/dist/src/core/exit-codes.js.map +1 -0
  21. package/dist/src/core/matcher.d.ts +2 -0
  22. package/dist/src/core/matcher.js +30 -0
  23. package/dist/src/core/matcher.js.map +1 -0
  24. package/dist/src/core/message-template.d.ts +2 -0
  25. package/dist/src/core/message-template.js +20 -0
  26. package/dist/src/core/message-template.js.map +1 -0
  27. package/dist/src/core/nonces.d.ts +2 -0
  28. package/dist/src/core/nonces.js +11 -0
  29. package/dist/src/core/nonces.js.map +1 -0
  30. package/dist/src/core/reporters.d.ts +3 -0
  31. package/dist/src/core/reporters.js +23 -0
  32. package/dist/src/core/reporters.js.map +1 -0
  33. package/dist/src/core/run.d.ts +30 -0
  34. package/dist/src/core/run.js +240 -0
  35. package/dist/src/core/run.js.map +1 -0
  36. package/dist/src/fake-servers/index.d.ts +10 -0
  37. package/dist/src/fake-servers/index.js +12 -0
  38. package/dist/src/fake-servers/index.js.map +1 -0
  39. package/dist/src/fake-servers/telegram.d.ts +29 -0
  40. package/dist/src/fake-servers/telegram.js +346 -0
  41. package/dist/src/fake-servers/telegram.js.map +1 -0
  42. package/dist/src/index.d.ts +15 -0
  43. package/dist/src/index.js +9 -0
  44. package/dist/src/index.js.map +1 -0
  45. package/dist/src/openclaw.d.ts +102 -0
  46. package/dist/src/openclaw.js +289 -0
  47. package/dist/src/openclaw.js.map +1 -0
  48. package/dist/src/providers/builtin/discord.d.ts +15 -0
  49. package/dist/src/providers/builtin/discord.js +92 -0
  50. package/dist/src/providers/builtin/discord.js.map +1 -0
  51. package/dist/src/providers/builtin/feishu.d.ts +10 -0
  52. package/dist/src/providers/builtin/feishu.js +95 -0
  53. package/dist/src/providers/builtin/feishu.js.map +1 -0
  54. package/dist/src/providers/builtin/googlechat.d.ts +11 -0
  55. package/dist/src/providers/builtin/googlechat.js +83 -0
  56. package/dist/src/providers/builtin/googlechat.js.map +1 -0
  57. package/dist/src/providers/builtin/imessage.d.ts +11 -0
  58. package/dist/src/providers/builtin/imessage.js +67 -0
  59. package/dist/src/providers/builtin/imessage.js.map +1 -0
  60. package/dist/src/providers/builtin/loopback.d.ts +63 -0
  61. package/dist/src/providers/builtin/loopback.js +174 -0
  62. package/dist/src/providers/builtin/loopback.js.map +1 -0
  63. package/dist/src/providers/builtin/matrix.d.ts +21 -0
  64. package/dist/src/providers/builtin/matrix.js +84 -0
  65. package/dist/src/providers/builtin/matrix.js.map +1 -0
  66. package/dist/src/providers/builtin/mattermost.d.ts +11 -0
  67. package/dist/src/providers/builtin/mattermost.js +67 -0
  68. package/dist/src/providers/builtin/mattermost.js.map +1 -0
  69. package/dist/src/providers/builtin/msteams.d.ts +13 -0
  70. package/dist/src/providers/builtin/msteams.js +72 -0
  71. package/dist/src/providers/builtin/msteams.js.map +1 -0
  72. package/dist/src/providers/builtin/native-local-mock.d.ts +48 -0
  73. package/dist/src/providers/builtin/native-local-mock.js +119 -0
  74. package/dist/src/providers/builtin/native-local-mock.js.map +1 -0
  75. package/dist/src/providers/builtin/script.d.ts +39 -0
  76. package/dist/src/providers/builtin/script.js +206 -0
  77. package/dist/src/providers/builtin/script.js.map +1 -0
  78. package/dist/src/providers/builtin/slack.d.ts +6 -0
  79. package/dist/src/providers/builtin/slack.js +106 -0
  80. package/dist/src/providers/builtin/slack.js.map +1 -0
  81. package/dist/src/providers/builtin/telegram.d.ts +21 -0
  82. package/dist/src/providers/builtin/telegram.js +123 -0
  83. package/dist/src/providers/builtin/telegram.js.map +1 -0
  84. package/dist/src/providers/builtin/whatsapp.d.ts +19 -0
  85. package/dist/src/providers/builtin/whatsapp.js +90 -0
  86. package/dist/src/providers/builtin/whatsapp.js.map +1 -0
  87. package/dist/src/providers/builtin/zalo.d.ts +17 -0
  88. package/dist/src/providers/builtin/zalo.js +70 -0
  89. package/dist/src/providers/builtin/zalo.js.map +1 -0
  90. package/dist/src/providers/catalog.d.ts +69 -0
  91. package/dist/src/providers/catalog.js +95 -0
  92. package/dist/src/providers/catalog.js.map +1 -0
  93. package/dist/src/providers/local-mock.d.ts +41 -0
  94. package/dist/src/providers/local-mock.js +243 -0
  95. package/dist/src/providers/local-mock.js.map +1 -0
  96. package/dist/src/providers/recorder.d.ts +19 -0
  97. package/dist/src/providers/recorder.js +160 -0
  98. package/dist/src/providers/recorder.js.map +1 -0
  99. package/dist/src/providers/registry.d.ts +8 -0
  100. package/dist/src/providers/registry.js +171 -0
  101. package/dist/src/providers/registry.js.map +1 -0
  102. package/dist/src/providers/types.d.ts +94 -0
  103. package/dist/src/providers/types.js +2 -0
  104. package/dist/src/providers/types.js.map +1 -0
  105. package/dist/src/providers/webhook-server.d.ts +12 -0
  106. package/dist/src/providers/webhook-server.js +118 -0
  107. package/dist/src/providers/webhook-server.js.map +1 -0
  108. package/docs/channel-setup.md +181 -0
  109. package/fixtures/examples/crabline.example.yaml +195 -0
  110. package/fixtures/examples/openclaw-bridge.yaml +125 -0
  111. package/package.json +64 -0
@@ -0,0 +1,94 @@
1
+ import type { FixtureDefinition, FixtureMode, InboundAuthor, ProviderConfig, ProviderPlatform } from "../config/schema.js";
2
+ export type ProviderSupportStatus = "bridge" | "planned" | "ready";
3
+ export type InboundMatchConfig = {
4
+ author: InboundAuthor;
5
+ nonce: "contains" | "exact" | "ignore";
6
+ pattern?: string | undefined;
7
+ strategy: "contains" | "exact" | "regex";
8
+ };
9
+ export type NormalizedTarget = {
10
+ channelId?: string | undefined;
11
+ id: string;
12
+ metadata: Record<string, string>;
13
+ threadId?: string | undefined;
14
+ };
15
+ export type InboundEnvelope = {
16
+ author: Exclude<InboundAuthor, "any">;
17
+ id: string;
18
+ provider: string;
19
+ raw?: unknown;
20
+ sentAt: string;
21
+ text: string;
22
+ threadId: string;
23
+ };
24
+ export type ProbeResult = {
25
+ details: string[];
26
+ healthy: boolean;
27
+ };
28
+ export type SendResult = {
29
+ accepted: boolean;
30
+ messageId: string;
31
+ threadId: string;
32
+ };
33
+ export type ProviderContext = {
34
+ config: ProviderConfig;
35
+ fixture: FixtureDefinition;
36
+ manifestPath: string;
37
+ providerId: string;
38
+ userName: string;
39
+ };
40
+ export type SendContext = ProviderContext & {
41
+ mode: Extract<FixtureMode, "agent" | "roundtrip" | "send">;
42
+ nonce: string;
43
+ text: string;
44
+ };
45
+ export type WaitContext = ProviderContext & {
46
+ nonce: string;
47
+ since: string;
48
+ threadId?: string | undefined;
49
+ timeoutMs: number;
50
+ };
51
+ export type WatchContext = ProviderContext & {
52
+ since?: string;
53
+ };
54
+ export interface ProviderAdapter {
55
+ readonly id: string;
56
+ readonly platform: ProviderPlatform;
57
+ readonly status: ProviderSupportStatus;
58
+ readonly supports: readonly FixtureMode[];
59
+ normalizeTarget(target: FixtureDefinition["target"]): NormalizedTarget;
60
+ probe(context: ProviderContext): Promise<ProbeResult>;
61
+ send(context: SendContext): Promise<SendResult>;
62
+ waitForInbound(context: WaitContext): Promise<InboundEnvelope | null>;
63
+ watch?(context: WatchContext): AsyncIterable<InboundEnvelope>;
64
+ cleanup?(): Promise<void>;
65
+ }
66
+ export type LoopbackMessage = {
67
+ author: {
68
+ isMe: boolean;
69
+ userName: string;
70
+ };
71
+ formatted: string;
72
+ id: string;
73
+ metadata: {
74
+ dateSent: Date;
75
+ edited: boolean;
76
+ editedAt?: Date;
77
+ };
78
+ raw: {
79
+ author: "assistant" | "user";
80
+ id: string;
81
+ text: string;
82
+ threadId: string;
83
+ timestamp: string;
84
+ };
85
+ text: string;
86
+ threadId: string;
87
+ };
88
+ export type LoopbackRawMessage = {
89
+ author: "assistant" | "user";
90
+ id: string;
91
+ text: string;
92
+ threadId: string;
93
+ timestamp: string;
94
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/providers/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ export type StartedWebhookServer = {
2
+ close(): Promise<void>;
3
+ endpointUrl: string;
4
+ };
5
+ export declare function startWebhookServer(params: {
6
+ handle(request: Request): Promise<Response>;
7
+ host: string;
8
+ maxBodyBytes?: number;
9
+ methods?: readonly string[] | undefined;
10
+ path: string;
11
+ port: number;
12
+ }): Promise<StartedWebhookServer>;
@@ -0,0 +1,118 @@
1
+ import { createServer } from "node:http";
2
+ const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
3
+ class RequestBodyTooLargeError extends Error {
4
+ }
5
+ async function readRequestBody(request, maxBodyBytes) {
6
+ return await new Promise((resolve, reject) => {
7
+ const chunks = [];
8
+ let bodyBytes = 0;
9
+ let settled = false;
10
+ request.on("data", (chunk) => {
11
+ if (settled) {
12
+ return;
13
+ }
14
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
15
+ bodyBytes += buffer.length;
16
+ if (bodyBytes > maxBodyBytes) {
17
+ settled = true;
18
+ request.resume();
19
+ reject(new RequestBodyTooLargeError());
20
+ return;
21
+ }
22
+ chunks.push(buffer);
23
+ });
24
+ request.once("end", () => {
25
+ if (settled) {
26
+ return;
27
+ }
28
+ settled = true;
29
+ resolve(Buffer.concat(chunks));
30
+ });
31
+ request.once("error", (error) => {
32
+ if (settled) {
33
+ return;
34
+ }
35
+ settled = true;
36
+ reject(error);
37
+ });
38
+ });
39
+ }
40
+ async function toFetchRequest(request, maxBodyBytes) {
41
+ const host = request.headers.host ?? "127.0.0.1";
42
+ const url = new URL(request.url ?? "/", `http://${host}`);
43
+ const body = request.method === "GET" || request.method === "HEAD"
44
+ ? undefined
45
+ : await readRequestBody(request, maxBodyBytes);
46
+ const init = {
47
+ headers: request.headers,
48
+ };
49
+ if (request.method) {
50
+ init.method = request.method;
51
+ }
52
+ if (body) {
53
+ init.body = body;
54
+ init.duplex = "half";
55
+ }
56
+ return new Request(url, init);
57
+ }
58
+ async function writeFetchResponse(response, fetchResponse) {
59
+ response.statusCode = fetchResponse.status;
60
+ for (const [name, value] of fetchResponse.headers) {
61
+ response.setHeader(name, value);
62
+ }
63
+ if (!fetchResponse.body) {
64
+ response.end();
65
+ return;
66
+ }
67
+ const body = Buffer.from(await fetchResponse.arrayBuffer());
68
+ response.end(body);
69
+ }
70
+ function closeServer(server) {
71
+ return new Promise((resolve, reject) => {
72
+ server.close((error) => {
73
+ if (error) {
74
+ reject(error);
75
+ return;
76
+ }
77
+ resolve();
78
+ });
79
+ });
80
+ }
81
+ export async function startWebhookServer(params) {
82
+ const methods = new Set(params.methods ?? ["POST"]);
83
+ const maxBodyBytes = params.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
84
+ const server = createServer(async (request, response) => {
85
+ try {
86
+ const fetchRequest = await toFetchRequest(request, maxBodyBytes);
87
+ const pathname = new URL(fetchRequest.url).pathname;
88
+ if (!methods.has(fetchRequest.method) || pathname !== params.path) {
89
+ await writeFetchResponse(response, new Response("not found", { status: 404 }));
90
+ return;
91
+ }
92
+ await writeFetchResponse(response, await params.handle(fetchRequest));
93
+ }
94
+ catch (error) {
95
+ const message = error instanceof Error ? error.message : String(error);
96
+ const status = error instanceof RequestBodyTooLargeError ? 413 : 500;
97
+ await writeFetchResponse(response, new Response(status === 413 ? "request body too large" : message, { status }));
98
+ }
99
+ });
100
+ await new Promise((resolve, reject) => {
101
+ server.once("error", reject);
102
+ server.listen(params.port, params.host, () => {
103
+ server.off("error", reject);
104
+ resolve();
105
+ });
106
+ });
107
+ const address = server.address();
108
+ if (!address || typeof address === "string") {
109
+ throw new Error("Unable to resolve webhook server address.");
110
+ }
111
+ return {
112
+ async close() {
113
+ await closeServer(server);
114
+ },
115
+ endpointUrl: `http://${params.host}:${address.port}${params.path}`,
116
+ };
117
+ }
118
+ //# sourceMappingURL=webhook-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-server.js","sourceRoot":"","sources":["../../../src/providers/webhook-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,181 @@
1
+ # Channel Setup
2
+
3
+ Crabline is a local mock service for OpenClaw channel contracts. It has two
4
+ surfaces:
5
+
6
+ - fixture-level local mocks used directly by the Crabline CLI
7
+ - fake provider servers that OpenClaw live adapters can target
8
+
9
+ Live channel testing belongs in OpenClaw's live channel adapters. Crabline
10
+ belongs in deterministic smoke CI and local QA where the test needs
11
+ provider-shaped behavior without external services.
12
+
13
+ ## Provider Shape
14
+
15
+ Provider ids are local profile names:
16
+
17
+ ```yaml
18
+ providers:
19
+ telegram:
20
+ adapter: telegram
21
+ ```
22
+
23
+ Fixtures reference provider ids:
24
+
25
+ ```yaml
26
+ fixtures:
27
+ - id: telegram-dm
28
+ provider: telegram
29
+ ```
30
+
31
+ Built-in adapters infer `platform` from `adapter`. `platform` is required only
32
+ for `adapter: script`, where Crabline needs to know which OpenClaw channel the
33
+ bridge profile represents.
34
+
35
+ ## Built-In Local Mocks
36
+
37
+ | Adapter | Default Webhook Path | Default Port |
38
+ | ------------ | ----------------------- | ------------ |
39
+ | `discord` | `/discord/interactions` | `8788` |
40
+ | `feishu` | `/feishu/webhook` | `8795` |
41
+ | `googlechat` | `/googlechat/webhook` | `8792` |
42
+ | `imessage` | `/imessage/webhook` | `8796` |
43
+ | `matrix` | `/matrix/webhook` | `8797` |
44
+ | `mattermost` | `/mattermost/webhook` | `8793` |
45
+ | `msteams` | `/msteams/webhook` | `8791` |
46
+ | `slack` | `/slack/events` | `8787` |
47
+ | `telegram` | `/telegram/webhook` | `8790` |
48
+ | `whatsapp` | `/whatsapp/webhook` | `8789` |
49
+ | `zalo` | `/zalo/webhook` | `8794` |
50
+
51
+ `loopback` has no externally meaningful webhook surface and is primarily useful
52
+ for local direct adapter checks.
53
+
54
+ ## Mock Config
55
+
56
+ ```yaml
57
+ providers:
58
+ slack:
59
+ adapter: slack
60
+ slack:
61
+ recorder:
62
+ path: ./.crabline/recorders/slack.jsonl
63
+ webhook:
64
+ host: 127.0.0.1
65
+ port: 8787
66
+ path: /slack/events
67
+ ```
68
+
69
+ Provider credential fields such as `botToken`, `accessToken`, `baseURL`, or
70
+ `serverUrl` are optional mock metadata. They are not required for local mock
71
+ execution.
72
+
73
+ ## Fake Provider Servers
74
+
75
+ Fake provider servers sit below OpenClaw's normal channel adapters. QA starts the
76
+ server, writes the emitted runtime manifest into OpenClaw config/env, and then
77
+ OpenClaw talks to the local fake provider instead of the public provider.
78
+
79
+ Telegram is currently implemented:
80
+
81
+ ```bash
82
+ crabline --json serve telegram --ready-file .crabline/telegram-server.json
83
+ ```
84
+
85
+ Manifest fields:
86
+
87
+ - `endpoints.apiRoot`: OpenClaw `channels.telegram.apiRoot`
88
+ - `botToken`: OpenClaw `channels.telegram.botToken`
89
+ - `adminToken`: value for the `X-Crabline-Admin-Token` header on admin ingress
90
+ - `endpoints.adminInboundUrl`: authenticated admin ingress for test user messages
91
+ - `recorderPath`: JSONL provider traffic recorder
92
+
93
+ The admin token is generated randomly unless `--admin-token <token>` is
94
+ provided. Requests may also use `Authorization: Bearer <token>`.
95
+
96
+ The admin ingress accepts JSON like:
97
+
98
+ ```json
99
+ {
100
+ "chatId": "-1001234567890",
101
+ "messageThreadId": 42,
102
+ "fromId": 100001,
103
+ "text": "user nonce-123"
104
+ }
105
+ ```
106
+
107
+ OpenClaw consumes that message through Telegram `getUpdates`; outbound adapter
108
+ sends are recorded through Telegram `sendMessage`.
109
+
110
+ ## Webhook Payload
111
+
112
+ Mock webhooks accept provider-native event payloads where Crabline has a built-in
113
+ adapter for the channel. They also accept this simple JSON shape with native
114
+ thread ids:
115
+
116
+ ```json
117
+ {
118
+ "id": "inbound-1",
119
+ "threadId": "C1234567890",
120
+ "text": "reply nonce-123",
121
+ "author": "assistant"
122
+ }
123
+ ```
124
+
125
+ The nested form is also accepted:
126
+
127
+ ```json
128
+ {
129
+ "message": {
130
+ "id": "inbound-1",
131
+ "threadId": "C1234567890",
132
+ "text": "reply nonce-123"
133
+ }
134
+ }
135
+ ```
136
+
137
+ Missing `threadId` or `text` returns `400`. Non-JSON requests return `415`.
138
+
139
+ ## Target IDs
140
+
141
+ Targets use native channel identifiers. Crabline does not add local prefixes such
142
+ as `telegram:`, `discord:`, or `slack:`.
143
+
144
+ - Slack conversations: `C1234567890`, `G1234567890`, or `D1234567890`
145
+ - Slack threads: `1700000000.000100`
146
+ - Telegram chats: `-1001234567890` or `@channelusername`
147
+ - Telegram topics: `42`
148
+ - Discord channels and threads: Discord snowflake ids such as
149
+ `123456789012345678`
150
+ - Google Chat spaces: `spaces/AAAABbbbCCC`
151
+ - Google Chat threads: `spaces/AAAABbbbCCC/threads/BBBBccccDDD`
152
+
153
+ ## Smoke CI Guidance
154
+
155
+ For deterministic CI, use Crabline through a mock channel driver:
156
+
157
+ ```yaml
158
+ profile: smoke-ci
159
+ channelDriver: crabline
160
+ ```
161
+
162
+ The scenario channel should remain the real channel contract:
163
+
164
+ ```yaml
165
+ execution:
166
+ channel: telegram
167
+ ```
168
+
169
+ That means "run Telegram-shaped behavior through the mock Telegram backend."
170
+ It does not mean "connect to live Telegram."
171
+
172
+ For release or live verification, use OpenClaw's live driver:
173
+
174
+ ```yaml
175
+ profile: release
176
+ channelDriver: live
177
+ ```
178
+
179
+ If a live driver does not support a requested channel, the QA run should report
180
+ unsupported coverage. Crabline should not be used as a substitute for live
181
+ transport coverage.
@@ -0,0 +1,195 @@
1
+ configVersion: 1
2
+ userName: crabline
3
+
4
+ providers:
5
+ local:
6
+ adapter: loopback
7
+ loopback:
8
+ delayMs: 0
9
+
10
+ telegram:
11
+ adapter: telegram
12
+ telegram:
13
+ recorder:
14
+ path: ./.crabline/recorders/telegram.jsonl
15
+ webhook:
16
+ host: 127.0.0.1
17
+ port: 8790
18
+ path: /telegram/webhook
19
+
20
+ discord:
21
+ adapter: discord
22
+ discord:
23
+ recorder:
24
+ path: ./.crabline/recorders/discord.jsonl
25
+ webhook:
26
+ host: 127.0.0.1
27
+ port: 8788
28
+ path: /discord/interactions
29
+
30
+ slack:
31
+ adapter: slack
32
+ slack:
33
+ recorder:
34
+ path: ./.crabline/recorders/slack.jsonl
35
+ webhook:
36
+ host: 127.0.0.1
37
+ port: 8787
38
+ path: /slack/events
39
+
40
+ whatsapp:
41
+ adapter: whatsapp
42
+ whatsapp:
43
+ recorder:
44
+ path: ./.crabline/recorders/whatsapp.jsonl
45
+ webhook:
46
+ host: 127.0.0.1
47
+ port: 8789
48
+ path: /whatsapp/webhook
49
+
50
+ msteams:
51
+ adapter: msteams
52
+ msteams:
53
+ recorder:
54
+ path: ./.crabline/recorders/msteams.jsonl
55
+ webhook:
56
+ host: 127.0.0.1
57
+ port: 8791
58
+ path: /msteams/webhook
59
+
60
+ googlechat:
61
+ adapter: googlechat
62
+ googlechat:
63
+ recorder:
64
+ path: ./.crabline/recorders/googlechat.jsonl
65
+ webhook:
66
+ host: 127.0.0.1
67
+ port: 8792
68
+ path: /googlechat/webhook
69
+
70
+ feishu:
71
+ adapter: feishu
72
+ feishu:
73
+ recorder:
74
+ path: ./.crabline/recorders/feishu.jsonl
75
+ webhook:
76
+ host: 127.0.0.1
77
+ port: 8795
78
+ path: /feishu/webhook
79
+
80
+ mattermost:
81
+ adapter: mattermost
82
+ mattermost:
83
+ recorder:
84
+ path: ./.crabline/recorders/mattermost.jsonl
85
+ webhook:
86
+ host: 127.0.0.1
87
+ port: 8793
88
+ path: /mattermost/webhook
89
+
90
+ zalo:
91
+ adapter: zalo
92
+ zalo:
93
+ recorder:
94
+ path: ./.crabline/recorders/zalo.jsonl
95
+ webhook:
96
+ host: 127.0.0.1
97
+ port: 8794
98
+ path: /zalo/webhook
99
+
100
+ matrix:
101
+ adapter: matrix
102
+ matrix:
103
+ recorder:
104
+ path: ./.crabline/recorders/matrix.jsonl
105
+ webhook:
106
+ host: 127.0.0.1
107
+ port: 8797
108
+ path: /matrix/webhook
109
+
110
+ imessage:
111
+ adapter: imessage
112
+ imessage:
113
+ recorder:
114
+ path: ./.crabline/recorders/imessage.jsonl
115
+ webhook:
116
+ host: 127.0.0.1
117
+ port: 8796
118
+ path: /imessage/webhook
119
+
120
+ fixtures:
121
+ - id: loopback-roundtrip
122
+ provider: local
123
+ mode: roundtrip
124
+ target:
125
+ id: echo-bot
126
+ behavior: echo
127
+
128
+ - id: telegram-dm
129
+ provider: telegram
130
+ mode: roundtrip
131
+ target:
132
+ id: "100000001"
133
+ behavior: agent
134
+
135
+ - id: discord-channel
136
+ provider: discord
137
+ mode: roundtrip
138
+ target:
139
+ id: "123456789012345678"
140
+ metadata:
141
+ guildId: "987654321098765432"
142
+
143
+ - id: slack-channel
144
+ provider: slack
145
+ mode: roundtrip
146
+ target:
147
+ id: C1234567890
148
+
149
+ - id: whatsapp-dm
150
+ provider: whatsapp
151
+ mode: roundtrip
152
+ target:
153
+ id: "15551234567"
154
+
155
+ - id: msteams-channel
156
+ provider: msteams
157
+ mode: roundtrip
158
+ target:
159
+ id: "19:conversation@thread.v2"
160
+
161
+ - id: googlechat-space
162
+ provider: googlechat
163
+ mode: roundtrip
164
+ target:
165
+ id: spaces/AAAA1234567
166
+
167
+ - id: feishu-chat
168
+ provider: feishu
169
+ mode: roundtrip
170
+ target:
171
+ id: oc_123
172
+
173
+ - id: mattermost-channel
174
+ provider: mattermost
175
+ mode: roundtrip
176
+ target:
177
+ id: channel-id
178
+
179
+ - id: zalo-chat
180
+ provider: zalo
181
+ mode: roundtrip
182
+ target:
183
+ id: chat-123
184
+
185
+ - id: matrix-room
186
+ provider: matrix
187
+ mode: roundtrip
188
+ target:
189
+ id: "!room:example.com"
190
+
191
+ - id: imessage-chat
192
+ provider: imessage
193
+ mode: roundtrip
194
+ target:
195
+ id: chat-guid