@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.
- package/LICENSE +21 -0
- package/README.md +226 -0
- package/dist/src/bin/crabline.d.ts +2 -0
- package/dist/src/bin/crabline.js +7 -0
- package/dist/src/bin/crabline.js.map +1 -0
- package/dist/src/cli/program.d.ts +5 -0
- package/dist/src/cli/program.js +295 -0
- package/dist/src/cli/program.js.map +1 -0
- package/dist/src/config/load.d.ts +6 -0
- package/dist/src/config/load.js +41 -0
- package/dist/src/config/load.js.map +1 -0
- package/dist/src/config/schema.d.ts +1571 -0
- package/dist/src/config/schema.js +528 -0
- package/dist/src/config/schema.js.map +1 -0
- package/dist/src/core/errors.d.ts +12 -0
- package/dist/src/core/errors.js +28 -0
- package/dist/src/core/errors.js.map +1 -0
- package/dist/src/core/exit-codes.d.ts +13 -0
- package/dist/src/core/exit-codes.js +13 -0
- package/dist/src/core/exit-codes.js.map +1 -0
- package/dist/src/core/matcher.d.ts +2 -0
- package/dist/src/core/matcher.js +30 -0
- package/dist/src/core/matcher.js.map +1 -0
- package/dist/src/core/message-template.d.ts +2 -0
- package/dist/src/core/message-template.js +20 -0
- package/dist/src/core/message-template.js.map +1 -0
- package/dist/src/core/nonces.d.ts +2 -0
- package/dist/src/core/nonces.js +11 -0
- package/dist/src/core/nonces.js.map +1 -0
- package/dist/src/core/reporters.d.ts +3 -0
- package/dist/src/core/reporters.js +23 -0
- package/dist/src/core/reporters.js.map +1 -0
- package/dist/src/core/run.d.ts +30 -0
- package/dist/src/core/run.js +240 -0
- package/dist/src/core/run.js.map +1 -0
- package/dist/src/fake-servers/index.d.ts +10 -0
- package/dist/src/fake-servers/index.js +12 -0
- package/dist/src/fake-servers/index.js.map +1 -0
- package/dist/src/fake-servers/telegram.d.ts +29 -0
- package/dist/src/fake-servers/telegram.js +346 -0
- package/dist/src/fake-servers/telegram.js.map +1 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/openclaw.d.ts +102 -0
- package/dist/src/openclaw.js +289 -0
- package/dist/src/openclaw.js.map +1 -0
- package/dist/src/providers/builtin/discord.d.ts +15 -0
- package/dist/src/providers/builtin/discord.js +92 -0
- package/dist/src/providers/builtin/discord.js.map +1 -0
- package/dist/src/providers/builtin/feishu.d.ts +10 -0
- package/dist/src/providers/builtin/feishu.js +95 -0
- package/dist/src/providers/builtin/feishu.js.map +1 -0
- package/dist/src/providers/builtin/googlechat.d.ts +11 -0
- package/dist/src/providers/builtin/googlechat.js +83 -0
- package/dist/src/providers/builtin/googlechat.js.map +1 -0
- package/dist/src/providers/builtin/imessage.d.ts +11 -0
- package/dist/src/providers/builtin/imessage.js +67 -0
- package/dist/src/providers/builtin/imessage.js.map +1 -0
- package/dist/src/providers/builtin/loopback.d.ts +63 -0
- package/dist/src/providers/builtin/loopback.js +174 -0
- package/dist/src/providers/builtin/loopback.js.map +1 -0
- package/dist/src/providers/builtin/matrix.d.ts +21 -0
- package/dist/src/providers/builtin/matrix.js +84 -0
- package/dist/src/providers/builtin/matrix.js.map +1 -0
- package/dist/src/providers/builtin/mattermost.d.ts +11 -0
- package/dist/src/providers/builtin/mattermost.js +67 -0
- package/dist/src/providers/builtin/mattermost.js.map +1 -0
- package/dist/src/providers/builtin/msteams.d.ts +13 -0
- package/dist/src/providers/builtin/msteams.js +72 -0
- package/dist/src/providers/builtin/msteams.js.map +1 -0
- package/dist/src/providers/builtin/native-local-mock.d.ts +48 -0
- package/dist/src/providers/builtin/native-local-mock.js +119 -0
- package/dist/src/providers/builtin/native-local-mock.js.map +1 -0
- package/dist/src/providers/builtin/script.d.ts +39 -0
- package/dist/src/providers/builtin/script.js +206 -0
- package/dist/src/providers/builtin/script.js.map +1 -0
- package/dist/src/providers/builtin/slack.d.ts +6 -0
- package/dist/src/providers/builtin/slack.js +106 -0
- package/dist/src/providers/builtin/slack.js.map +1 -0
- package/dist/src/providers/builtin/telegram.d.ts +21 -0
- package/dist/src/providers/builtin/telegram.js +123 -0
- package/dist/src/providers/builtin/telegram.js.map +1 -0
- package/dist/src/providers/builtin/whatsapp.d.ts +19 -0
- package/dist/src/providers/builtin/whatsapp.js +90 -0
- package/dist/src/providers/builtin/whatsapp.js.map +1 -0
- package/dist/src/providers/builtin/zalo.d.ts +17 -0
- package/dist/src/providers/builtin/zalo.js +70 -0
- package/dist/src/providers/builtin/zalo.js.map +1 -0
- package/dist/src/providers/catalog.d.ts +69 -0
- package/dist/src/providers/catalog.js +95 -0
- package/dist/src/providers/catalog.js.map +1 -0
- package/dist/src/providers/local-mock.d.ts +41 -0
- package/dist/src/providers/local-mock.js +243 -0
- package/dist/src/providers/local-mock.js.map +1 -0
- package/dist/src/providers/recorder.d.ts +19 -0
- package/dist/src/providers/recorder.js +160 -0
- package/dist/src/providers/recorder.js.map +1 -0
- package/dist/src/providers/registry.d.ts +8 -0
- package/dist/src/providers/registry.js +171 -0
- package/dist/src/providers/registry.js.map +1 -0
- package/dist/src/providers/types.d.ts +94 -0
- package/dist/src/providers/types.js +2 -0
- package/dist/src/providers/types.js.map +1 -0
- package/dist/src/providers/webhook-server.d.ts +12 -0
- package/dist/src/providers/webhook-server.js +118 -0
- package/dist/src/providers/webhook-server.js.map +1 -0
- package/docs/channel-setup.md +181 -0
- package/fixtures/examples/crabline.example.yaml +195 -0
- package/fixtures/examples/openclaw-bridge.yaml +125 -0
- 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 @@
|
|
|
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
|