@openclaw/crabline 0.1.10 → 0.1.12

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 (197) hide show
  1. package/README.md +99 -12
  2. package/dist/src/bin/crabline.js +3 -1
  3. package/dist/src/bin/crabline.js.map +1 -1
  4. package/dist/src/cli/program.d.ts +5 -1
  5. package/dist/src/cli/program.js +264 -50
  6. package/dist/src/cli/program.js.map +1 -1
  7. package/dist/src/config/load.js +39 -3
  8. package/dist/src/config/load.js.map +1 -1
  9. package/dist/src/config/schema.d.ts +20 -0
  10. package/dist/src/config/schema.js +163 -35
  11. package/dist/src/config/schema.js.map +1 -1
  12. package/dist/src/core/errors.d.ts +1 -0
  13. package/dist/src/core/errors.js +6 -3
  14. package/dist/src/core/errors.js.map +1 -1
  15. package/dist/src/core/http-path.d.ts +1 -0
  16. package/dist/src/core/http-path.js +14 -0
  17. package/dist/src/core/http-path.js.map +1 -0
  18. package/dist/src/core/matcher.d.ts +3 -1
  19. package/dist/src/core/matcher.js +58 -2
  20. package/dist/src/core/matcher.js.map +1 -1
  21. package/dist/src/core/nonces.d.ts +2 -0
  22. package/dist/src/core/nonces.js +11 -1
  23. package/dist/src/core/nonces.js.map +1 -1
  24. package/dist/src/core/reporters.d.ts +5 -0
  25. package/dist/src/core/reporters.js +26 -2
  26. package/dist/src/core/reporters.js.map +1 -1
  27. package/dist/src/core/run.d.ts +3 -1
  28. package/dist/src/core/run.js +178 -66
  29. package/dist/src/core/run.js.map +1 -1
  30. package/dist/src/matrix-ids.d.ts +5 -0
  31. package/dist/src/matrix-ids.js +83 -0
  32. package/dist/src/matrix-ids.js.map +1 -0
  33. package/dist/src/openclaw/artifact-generation.d.ts +12 -2
  34. package/dist/src/openclaw/artifact-generation.js +436 -15
  35. package/dist/src/openclaw/artifact-generation.js.map +1 -1
  36. package/dist/src/openclaw/bridges/matrix.js +21 -4
  37. package/dist/src/openclaw/bridges/matrix.js.map +1 -1
  38. package/dist/src/openclaw/bridges/mattermost.js +13 -8
  39. package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
  40. package/dist/src/openclaw/bridges/probe-response.d.ts +1 -0
  41. package/dist/src/openclaw/bridges/probe-response.js +5 -0
  42. package/dist/src/openclaw/bridges/probe-response.js.map +1 -0
  43. package/dist/src/openclaw/bridges/signal.js +15 -6
  44. package/dist/src/openclaw/bridges/signal.js.map +1 -1
  45. package/dist/src/openclaw/bridges/slack.js +116 -23
  46. package/dist/src/openclaw/bridges/slack.js.map +1 -1
  47. package/dist/src/openclaw/bridges/telegram.js +45 -19
  48. package/dist/src/openclaw/bridges/telegram.js.map +1 -1
  49. package/dist/src/openclaw/bridges/whatsapp.js +32 -16
  50. package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
  51. package/dist/src/openclaw/bridges/zalo.js +8 -3
  52. package/dist/src/openclaw/bridges/zalo.js.map +1 -1
  53. package/dist/src/openclaw/private-file.d.ts +40 -3
  54. package/dist/src/openclaw/private-file.js +2070 -95
  55. package/dist/src/openclaw/private-file.js.map +1 -1
  56. package/dist/src/openclaw/shared.d.ts +1 -0
  57. package/dist/src/openclaw/shared.js +48 -13
  58. package/dist/src/openclaw/shared.js.map +1 -1
  59. package/dist/src/openclaw/smoke-lock.d.ts +9 -0
  60. package/dist/src/openclaw/smoke-lock.js +696 -187
  61. package/dist/src/openclaw/smoke-lock.js.map +1 -1
  62. package/dist/src/openclaw.js +352 -29
  63. package/dist/src/openclaw.js.map +1 -1
  64. package/dist/src/platform/process-owned-lock.d.ts +11 -0
  65. package/dist/src/platform/process-owned-lock.js +1698 -0
  66. package/dist/src/platform/process-owned-lock.js.map +1 -0
  67. package/dist/src/platform/recorder-directory.d.ts +1 -0
  68. package/dist/src/platform/recorder-directory.js +14 -0
  69. package/dist/src/platform/recorder-directory.js.map +1 -0
  70. package/dist/src/platform/windows-acl.d.ts +18 -0
  71. package/dist/src/platform/windows-acl.js +1367 -0
  72. package/dist/src/platform/windows-acl.js.map +1 -0
  73. package/dist/src/platform/windows-lock-root.d.ts +13 -0
  74. package/dist/src/platform/windows-lock-root.js +98 -0
  75. package/dist/src/platform/windows-lock-root.js.map +1 -0
  76. package/dist/src/providers/builtin/discord.d.ts +4 -0
  77. package/dist/src/providers/builtin/discord.js +52 -9
  78. package/dist/src/providers/builtin/discord.js.map +1 -1
  79. package/dist/src/providers/builtin/external-webhook-auth.d.ts +1 -0
  80. package/dist/src/providers/builtin/external-webhook-auth.js +38 -1
  81. package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -1
  82. package/dist/src/providers/builtin/feishu.d.ts +1 -0
  83. package/dist/src/providers/builtin/feishu.js +96 -26
  84. package/dist/src/providers/builtin/feishu.js.map +1 -1
  85. package/dist/src/providers/builtin/googlechat.d.ts +1 -0
  86. package/dist/src/providers/builtin/googlechat.js +95 -26
  87. package/dist/src/providers/builtin/googlechat.js.map +1 -1
  88. package/dist/src/providers/builtin/imessage.js +60 -17
  89. package/dist/src/providers/builtin/imessage.js.map +1 -1
  90. package/dist/src/providers/builtin/loopback.js +88 -35
  91. package/dist/src/providers/builtin/loopback.js.map +1 -1
  92. package/dist/src/providers/builtin/matrix.d.ts +2 -2
  93. package/dist/src/providers/builtin/matrix.js +78 -21
  94. package/dist/src/providers/builtin/matrix.js.map +1 -1
  95. package/dist/src/providers/builtin/mattermost.d.ts +11 -7
  96. package/dist/src/providers/builtin/mattermost.js +138 -24
  97. package/dist/src/providers/builtin/mattermost.js.map +1 -1
  98. package/dist/src/providers/builtin/msteams.d.ts +1 -0
  99. package/dist/src/providers/builtin/msteams.js +118 -38
  100. package/dist/src/providers/builtin/msteams.js.map +1 -1
  101. package/dist/src/providers/builtin/native-local-mock.js +4 -4
  102. package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
  103. package/dist/src/providers/builtin/script.js +1057 -146
  104. package/dist/src/providers/builtin/script.js.map +1 -1
  105. package/dist/src/providers/builtin/slack.d.ts +8 -1
  106. package/dist/src/providers/builtin/slack.js +327 -24
  107. package/dist/src/providers/builtin/slack.js.map +1 -1
  108. package/dist/src/providers/builtin/telegram.d.ts +1 -1
  109. package/dist/src/providers/builtin/telegram.js +55 -14
  110. package/dist/src/providers/builtin/telegram.js.map +1 -1
  111. package/dist/src/providers/builtin/whatsapp.d.ts +4 -3
  112. package/dist/src/providers/builtin/whatsapp.js +157 -29
  113. package/dist/src/providers/builtin/whatsapp.js.map +1 -1
  114. package/dist/src/providers/builtin/zalo.d.ts +5 -2
  115. package/dist/src/providers/builtin/zalo.js +31 -7
  116. package/dist/src/providers/builtin/zalo.js.map +1 -1
  117. package/dist/src/providers/catalog.d.ts +1 -1
  118. package/dist/src/providers/catalog.js +1 -1
  119. package/dist/src/providers/catalog.js.map +1 -1
  120. package/dist/src/providers/local-mock.d.ts +4 -0
  121. package/dist/src/providers/local-mock.js +162 -35
  122. package/dist/src/providers/local-mock.js.map +1 -1
  123. package/dist/src/providers/native-ids.d.ts +2 -0
  124. package/dist/src/providers/native-ids.js +3 -0
  125. package/dist/src/providers/native-ids.js.map +1 -1
  126. package/dist/src/providers/recorder.d.ts +14 -3
  127. package/dist/src/providers/recorder.js +532 -42
  128. package/dist/src/providers/recorder.js.map +1 -1
  129. package/dist/src/providers/registry.d.ts +1 -0
  130. package/dist/src/providers/registry.js +65 -20
  131. package/dist/src/providers/registry.js.map +1 -1
  132. package/dist/src/providers/signed-jwt.d.ts +6 -1
  133. package/dist/src/providers/signed-jwt.js +241 -50
  134. package/dist/src/providers/signed-jwt.js.map +1 -1
  135. package/dist/src/providers/slack-ids.d.ts +1 -0
  136. package/dist/src/providers/slack-ids.js +5 -0
  137. package/dist/src/providers/slack-ids.js.map +1 -1
  138. package/dist/src/providers/target-normalizers.d.ts +9 -1
  139. package/dist/src/providers/target-normalizers.js +177 -58
  140. package/dist/src/providers/target-normalizers.js.map +1 -1
  141. package/dist/src/providers/webhook-server.d.ts +3 -0
  142. package/dist/src/providers/webhook-server.js +268 -47
  143. package/dist/src/providers/webhook-server.js.map +1 -1
  144. package/dist/src/servers/http.d.ts +14 -2
  145. package/dist/src/servers/http.js +229 -58
  146. package/dist/src/servers/http.js.map +1 -1
  147. package/dist/src/servers/matrix.d.ts +3 -0
  148. package/dist/src/servers/matrix.js +268 -110
  149. package/dist/src/servers/matrix.js.map +1 -1
  150. package/dist/src/servers/mattermost.d.ts +5 -0
  151. package/dist/src/servers/mattermost.js +389 -91
  152. package/dist/src/servers/mattermost.js.map +1 -1
  153. package/dist/src/servers/recorder.d.ts +11 -0
  154. package/dist/src/servers/recorder.js +1241 -22
  155. package/dist/src/servers/recorder.js.map +1 -1
  156. package/dist/src/servers/signal.d.ts +1 -0
  157. package/dist/src/servers/signal.js +279 -44
  158. package/dist/src/servers/signal.js.map +1 -1
  159. package/dist/src/servers/slack.d.ts +15 -0
  160. package/dist/src/servers/slack.js +259 -51
  161. package/dist/src/servers/slack.js.map +1 -1
  162. package/dist/src/servers/telegram-identity.d.ts +6 -0
  163. package/dist/src/servers/telegram-identity.js +27 -0
  164. package/dist/src/servers/telegram-identity.js.map +1 -0
  165. package/dist/src/servers/telegram.d.ts +17 -6
  166. package/dist/src/servers/telegram.js +1193 -118
  167. package/dist/src/servers/telegram.js.map +1 -1
  168. package/dist/src/servers/webhook-target.d.ts +12 -4
  169. package/dist/src/servers/webhook-target.js +226 -28
  170. package/dist/src/servers/webhook-target.js.map +1 -1
  171. package/dist/src/servers/whatsapp-baileys-websocket.d.ts +60 -1
  172. package/dist/src/servers/whatsapp-baileys-websocket.js +782 -112
  173. package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
  174. package/dist/src/servers/whatsapp-jid.d.ts +1 -0
  175. package/dist/src/servers/whatsapp-jid.js +9 -5
  176. package/dist/src/servers/whatsapp-jid.js.map +1 -1
  177. package/dist/src/servers/whatsapp-wire/binary-node.d.ts +1 -0
  178. package/dist/src/servers/whatsapp-wire/binary-node.js +37 -13
  179. package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
  180. package/dist/src/servers/whatsapp-wire/crypto.d.ts +3 -0
  181. package/dist/src/servers/whatsapp-wire/crypto.js +23 -8
  182. package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
  183. package/dist/src/servers/whatsapp-wire/handshake.d.ts +9 -3
  184. package/dist/src/servers/whatsapp-wire/handshake.js +173 -12
  185. package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
  186. package/dist/src/servers/whatsapp-wire/signal.js +3 -3
  187. package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
  188. package/dist/src/servers/whatsapp.d.ts +7 -0
  189. package/dist/src/servers/whatsapp.js +149 -24
  190. package/dist/src/servers/whatsapp.js.map +1 -1
  191. package/dist/src/servers/zalo.d.ts +1 -0
  192. package/dist/src/servers/zalo.js +193 -82
  193. package/dist/src/servers/zalo.js.map +1 -1
  194. package/docs/channel-setup.md +185 -43
  195. package/fixtures/examples/crabline.example.yaml +3 -2
  196. package/package.json +18 -10
  197. package/fixtures/examples/openclaw-bridge.yaml +0 -125
@@ -56,6 +56,37 @@ remove stale config blocks or split them into separate provider ids.
56
56
  `loopback` has no externally meaningful webhook surface and is primarily useful
57
57
  for local direct adapter checks.
58
58
 
59
+ ## OpenClaw Support Matrix
60
+
61
+ `ready` platforms have built-in local mock adapters. `bridge` platforms use the
62
+ `script` adapter with commands supplied by the manifest owner.
63
+
64
+ | Platform | Status |
65
+ | --------------- | -------- |
66
+ | `bluebubbles` | `bridge` |
67
+ | `discord` | `ready` |
68
+ | `feishu` | `ready` |
69
+ | `googlechat` | `ready` |
70
+ | `imessage` | `ready` |
71
+ | `irc` | `bridge` |
72
+ | `line` | `bridge` |
73
+ | `loopback` | `ready` |
74
+ | `matrix` | `ready` |
75
+ | `mattermost` | `ready` |
76
+ | `msteams` | `ready` |
77
+ | `nextcloudtalk` | `bridge` |
78
+ | `nostr` | `bridge` |
79
+ | `signal` | `bridge` |
80
+ | `slack` | `ready` |
81
+ | `synologychat` | `bridge` |
82
+ | `telegram` | `ready` |
83
+ | `tlon` | `bridge` |
84
+ | `twitch` | `bridge` |
85
+ | `webchat` | `bridge` |
86
+ | `whatsapp` | `ready` |
87
+ | `zalo` | `ready` |
88
+ | `zalouser` | `bridge` |
89
+
59
90
  ## Mock Config
60
91
 
61
92
  ```yaml
@@ -72,12 +103,18 @@ providers:
72
103
  ```
73
104
 
74
105
  Provider credential fields such as `botToken`, `accessToken`, `baseURL`, or
75
- `serverUrl` are optional mock metadata. They are not required for local mock
76
- execution.
106
+ `serverUrl` are optional mock metadata for loopback execution. Externally
107
+ reachable Discord webhooks require `publicKey` or `DISCORD_PUBLIC_KEY`.
77
108
 
78
109
  Optional webhook credentials enforce each provider's native authentication
79
110
  header before JSON parsing or recorder writes:
80
111
 
112
+ - Authenticated external ingress must set `webhook.publicUrl` to an HTTPS URL;
113
+ Google Chat may use its HTTPS `endpointUrl` as the signed public callback
114
+ instead. A non-loopback bind without a public HTTPS endpoint is rejected even
115
+ when request credentials are configured. Plain HTTP is reserved for local
116
+ tests where both the listener host and advertised URL host are loopback; that
117
+ loopback-only case does not require provider callback authentication.
81
118
  - Discord `publicKey` or `DISCORD_PUBLIC_KEY` verifies
82
119
  `X-Signature-Ed25519` over `X-Signature-Timestamp` plus the raw request body.
83
120
  - Google Chat `endpointUrl` verifies Google ID tokens for the configured HTTP
@@ -87,33 +124,45 @@ header before JSON parsing or recorder writes:
87
124
  when omitted, and `credentials.client_email` remains the service-account
88
125
  email fallback when inline credentials are configured. Wrapped Pub/Sub
89
126
  `message.data` must be canonical base64 before Google Chat event
90
- normalization.
127
+ normalization. Direct and wrapped Pub/Sub callbacks are POST-only. Successful
128
+ direct callbacks receive an empty `200` acknowledgement instead of a
129
+ synthetic chat message.
91
130
  Google Workspace add-on `chat.messagePayload` events are rejected until the
92
131
  adapter can bind the verified request to a configured add-on deployment
93
132
  identity.
94
133
  - Microsoft Teams `appId` or `TEAMS_APP_ID` verifies Bot Connector bearer
95
134
  tokens, including the activity channel and exact `serviceUrl`. An `appId` is
96
- required when the webhook host is non-loopback or `publicUrl` is set; an
97
- unauthenticated webhook remains available only on loopback.
135
+ required when the webhook host or advertised callback is non-loopback.
98
136
  - Matrix webhook ingress currently has no provider-native authentication mode,
99
- so it is restricted to loopback hosts and cannot set `publicUrl`.
100
- - Mattermost and iMessage webhook ingress currently have no provider-native
101
- authentication mode, so those adapter webhooks are restricted to loopback
102
- hosts and cannot set `publicUrl`. Their API credentials do not authenticate
103
- inbound callbacks.
137
+ so its listener and any advertised callback must remain loopback-only.
138
+ - Mattermost `webhookToken` or `MATTERMOST_TOKEN` verifies the `token` field in
139
+ outgoing webhook form or JSON bodies. The token is removed before recorder
140
+ persistence. Externally reachable callbacks require this token and an HTTPS
141
+ `webhook.publicUrl`. Outgoing webhook ingress is POST-only.
142
+ - iMessage webhook ingress currently has no provider-native authentication
143
+ mode, so its listener and any advertised callback must remain loopback-only.
104
144
  - Feishu `verificationToken` or `FEISHU_VERIFICATION_TOKEN` verifies plaintext
105
145
  callback tokens on loopback and remains an additional check when configured
106
146
  with encryption. Externally reachable webhooks require `encryptKey` or
107
- `FEISHU_ENCRYPT_KEY` to verify `X-Lark-Signature` and decrypt encrypted event
108
- envelopes before challenge handling or event normalization.
147
+ `FEISHU_ENCRYPT_KEY` to verify `X-Lark-Signature` on event callbacks and
148
+ decrypt encrypted envelopes before normalization. Initial encrypted
149
+ `url_verification` challenges may omit signature headers and are accepted
150
+ only when their decrypted token matches the configured verification token.
151
+ Text events must carry `message.content` as valid JSON containing a string
152
+ `text` field; malformed JSON is rejected instead of treated as plaintext.
109
153
  - Slack `signingSecret` or `SLACK_SIGNING_SECRET` verifies
110
154
  `X-Slack-Request-Timestamp` and `X-Slack-Signature`; it is required when the
111
- webhook host is non-loopback or `publicUrl` is set.
155
+ webhook host or advertised callback is non-loopback.
112
156
  - Telegram `secretToken` or `TELEGRAM_WEBHOOK_SECRET_TOKEN` verifies
113
- `X-Telegram-Bot-Api-Secret-Token`.
157
+ `X-Telegram-Bot-Api-Secret-Token`. The value must contain 1-256 letters,
158
+ digits, underscores, or hyphens; control characters are rejected.
114
159
  - Zalo `webhookSecret` or `ZALO_WEBHOOK_SECRET` verifies
115
160
  `X-Bot-Api-Secret-Token`.
116
161
 
162
+ Webhook `path` values must already be canonical URL pathnames. Authority-form
163
+ paths, dot-segment or backslash traversal, query strings, fragments, spaces,
164
+ and other values changed by URL normalization are rejected.
165
+
117
166
  The built-in `whatsapp` adapter implements Meta's GET verification challenge
118
167
  and requires `X-Hub-Signature-256` on POST requests. Set `whatsapp.appSecret`
119
168
  and `whatsapp.verifyToken` (or `WHATSAPP_APP_SECRET` and
@@ -130,6 +179,9 @@ Treat manifests containing script providers as executable, trusted code.
130
179
  Crabline runs their declared commands with the configured environment, so load
131
180
  them only from sources you trust and review changes before use.
132
181
 
182
+ Crabline does not ship a generic OpenClaw gateway command bridge. Supply and
183
+ version the command implementations with the manifest that uses them:
184
+
133
185
  ```yaml
134
186
  providers:
135
187
  slack-openclaw:
@@ -141,19 +193,16 @@ providers:
141
193
  - OPENCLAW_TOKEN
142
194
  script:
143
195
  commands:
144
- probe: node ./scripts/openclaw-bridge-probe.mjs
145
- send: node ./scripts/openclaw-bridge-send.mjs
146
- waitForInbound: node ./scripts/openclaw-bridge-wait.mjs
196
+ probe: node ./bridge/probe.mjs
197
+ send: node ./bridge/send.mjs
198
+ waitForInbound: node ./bridge/wait-for-inbound.mjs
147
199
  ```
148
200
 
149
- The complete multi-channel script fixture is available in
150
- `fixtures/examples/openclaw-bridge.yaml`.
151
-
152
201
  Each command receives one JSON document on stdin. Every payload contains the
153
202
  parsed `fixture` plus `provider.config`, `provider.id`, and
154
203
  `provider.manifestPath`. `send` adds `outbound` with `mode`, `nonce`, normalized
155
204
  `target`, and `text`; `waitForInbound` adds `wait` with `excludeIds`, `nonce`,
156
- `since`, normalized `target`, and `timeoutMs`. A stateless wait bridge must
205
+ `since`, `threadId`, normalized `target`, and `timeoutMs`. A stateless wait bridge must
157
206
  exclude messages whose IDs are listed in `excludeIds` while retaining later
158
207
  messages with the same timestamp. Crabline retains at most 1024 unmatched IDs
159
208
  per wait. `watch` adds `watch` with optional `since` and normalized `target`.
@@ -178,12 +227,42 @@ watch commands are terminated when cancellation fires.
178
227
  Server-backed channels currently include Mattermost, Matrix, Signal, Slack,
179
228
  Telegram, WhatsApp, and Zalo. Loopback binds retain stable local credentials for
180
229
  fixture compatibility. Non-loopback binds generate fresh provider-shaped
181
- credentials unless the corresponding token or secret option is supplied.
230
+ credentials unless the corresponding credential is supplied.
231
+ WhatsApp is loopback-only because its HTTP and WebSocket endpoints carry bearer
232
+ credentials over cleartext and the built-in server does not terminate TLS.
233
+
234
+ Commands in this section use the installed-package form. In a source checkout,
235
+ replace `crabline` with `pnpm dev`; `pnpm exec crabline` is not available.
236
+
237
+ `serve` rejects credential values in command-line arguments because argv and
238
+ shell history are not secret-safe. Use the `CRABLINE_ADMIN_TOKEN`,
239
+ `CRABLINE_ACCESS_TOKEN`, `CRABLINE_BOT_TOKEN`, and
240
+ `CRABLINE_SIGNING_SECRET` environment fallbacks, or pass a JSON object through
241
+ a file descriptor. Package runners should use stdin:
242
+
243
+ ```bash
244
+ pnpm dev --json serve slack --credentials-fd 0 < .crabline/serve-credentials.json
245
+ ```
182
246
 
183
- Serve credential flags take precedence over their optional environment
184
- fallbacks: `--admin-token` over `CRABLINE_ADMIN_TOKEN`, `--access-token` over
185
- `CRABLINE_ACCESS_TOKEN`, `--bot-token` over `CRABLINE_BOT_TOKEN`, and
186
- `--signing-secret` over `CRABLINE_SIGNING_SECRET`.
247
+ The installed CLI can also use an inherited descriptor when its launcher
248
+ preserves descriptors above 2:
249
+
250
+ ```bash
251
+ crabline --json serve slack --credentials-fd 3 3< .crabline/serve-credentials.json
252
+ ```
253
+
254
+ The accepted JSON fields are `adminToken`, `accessToken`, `botToken`, and
255
+ `signingSecret`. The input is bounded to 64 KiB, must contain only string
256
+ values, and overrides environment fallbacks field by field. Do not pass
257
+ descriptors above 2 through package runners such as `pnpm`; use fd 0 instead.
258
+ Keep credential files owner-readable or pipe the JSON directly from a secret
259
+ manager.
260
+
261
+ Ready files contain generated provider and admin credentials. Crabline creates
262
+ or replaces them with POSIX mode `0600`, but the parent directory still needs
263
+ to be private. Exclude ready files from version control and CI artifact
264
+ collection, and delete them after use. On non-POSIX or shared filesystems,
265
+ verify the effective ACLs before publishing one.
187
266
 
188
267
  ### Mattermost
189
268
 
@@ -198,11 +277,16 @@ and credential. Because the server is loopback HTTP, trusted QA configuration
198
277
  must also set `channels.mattermost.network.dangerouslyAllowPrivateNetwork` to
199
278
  `true`. The OpenClaw bridge does this automatically.
200
279
 
201
- Admin inbound accepts `channelId`, `senderId`, `text`, optional `senderName`,
202
- `channelType`, and `rootId`. It emits the message through Mattermost's native
203
- `/api/v4/websocket` `posted` event. Text sends through `POST /api/v4/posts` are
204
- written to the manifest's recorder. QA agent delivery currently supports DM and
205
- channel targets; thread targets require the later OpenClaw QA wiring step.
280
+ Admin inbound accepts 26-character lowercase alphanumeric Mattermost
281
+ `channelId` and `senderId` values, `text`, optional `senderName`, `channelType`,
282
+ `rootId`, `channelName`, and `channelDisplayName`; `rootId`, when present, uses
283
+ the same strict 26-character format. It emits the message through Mattermost's
284
+ native `/api/v4/websocket` channel-scoped `posted` event, including the
285
+ configured channel names. Text sends through `POST /api/v4/posts` require a
286
+ JSON media type and are written to the manifest's recorder. Fixture and QA
287
+ channel/user targets must use provider-native Mattermost IDs rather than local
288
+ labels. QA agent delivery currently supports DM and channel targets; thread
289
+ targets require the later OpenClaw QA wiring step.
206
290
 
207
291
  The server itself is provider-shaped and has no OpenClaw runtime dependency. It
208
292
  implements Mattermost REST error/status behavior plus WebSocket authentication,
@@ -225,10 +309,17 @@ because the server is loopback HTTP, enable
225
309
  applies those settings automatically.
226
310
 
227
311
  Admin inbound accepts native `roomId`, `senderId`, and `text` fields plus
228
- optional `senderName` and `threadId`. It queues a native `m.room.message` event
229
- for delivery through Matrix `/sync`. Outbound room sends through
230
- `PUT /_matrix/client/v3/rooms/:roomId/send/:eventType/:txnId` are written to the
231
- manifest recorder.
312
+ optional `senderName`, `roomName`, `direct`, and `threadId`. `roomName` names a
313
+ newly created room, while `direct: true` marks membership as direct and
314
+ publishes the room through `m.direct` account data. `threadId` is the raw Matrix
315
+ root event ID, such as `$eventid:matrix.org`; Crabline emits it as both the
316
+ `m.thread` relation target and `m.in_reply_to` root. The resulting native
317
+ `m.room.message` event is delivered through Matrix `/sync`. Outbound room sends
318
+ through `PUT /_matrix/client/v3/rooms/:roomId/send/:eventType/:txnId` are
319
+ written to the manifest recorder.
320
+
321
+ Post admin inbound payloads to the manifest's `endpoints.adminInboundUrl` with
322
+ its `adminToken` in the `X-Crabline-Admin-Token` header.
232
323
 
233
324
  The provider server implements the unencrypted Client-Server API subset needed
234
325
  by the normal Matrix SDK: versions, `whoami`, filters, push rules, joined rooms
@@ -245,6 +336,17 @@ outcome together with the provider's exact send method and path. Rejected
245
336
  requests and lookalike route suffixes can remain diagnostic recorder entries,
246
337
  but they are never exposed as successful outbound deliveries.
247
338
 
339
+ Use one filesystem name per recorder file. When multiple server processes must
340
+ run without one shared writable OS account home or write through hardlinks to
341
+ the same recorder inode, set
342
+ `CRABLINE_RECORDER_LOCK_DIR` to one absolute writable directory shared by every
343
+ writer and pre-create its ownership, group, or ACLs. Hardlinked server-recorder
344
+ writes fail closed when this shared lock namespace is absent. The path must be
345
+ canonical and contain no symlink components. Use a separate lock directory per
346
+ recorder filesystem; lock identities omit device numbers so containers that
347
+ mount the same inode under different device IDs still coordinate. Otherwise,
348
+ Unix writers use `~/.cache/crabline/locks/server-recorder`.
349
+
248
350
  Slack:
249
351
 
250
352
  ```bash
@@ -273,7 +375,10 @@ The admin ingress accepts JSON like:
273
375
  ```
274
376
 
275
377
  OpenClaw consumes that message through Slack Events API shape; outbound adapter
276
- sends are recorded through Slack `chat.postMessage`.
378
+ sends are recorded through Slack `chat.postMessage`. Outbound direct sends may
379
+ use native user IDs beginning with `U` or `W`; Crabline opens the corresponding
380
+ DM conversation before posting. Admin inbound still requires a conversation ID
381
+ beginning with `C`, `D`, or `G` plus a sender user ID beginning with `U` or `W`.
277
382
 
278
383
  Signal:
279
384
 
@@ -321,8 +426,9 @@ Manifest fields:
321
426
  - `endpoints.adminInboundUrl`: authenticated admin ingress for test user messages
322
427
  - `recorderPath`: JSONL provider traffic recorder
323
428
 
324
- The admin token is generated randomly unless `--admin-token <token>` is
325
- provided. Requests may also use `Authorization: Bearer <token>`.
429
+ The admin token is generated randomly unless `adminToken` is supplied through
430
+ the credential ingress above. Requests may also use
431
+ `Authorization: Bearer <token>`.
326
432
 
327
433
  The admin ingress accepts JSON like:
328
434
 
@@ -338,6 +444,16 @@ The admin ingress accepts JSON like:
338
444
  OpenClaw consumes that message through Telegram `getUpdates`; outbound adapter
339
445
  sends are recorded through Telegram `sendMessage` and the media send endpoints
340
446
  `sendPhoto`, `sendDocument`, `sendVideo`, `sendAudio`, and `sendAnimation`.
447
+ Compatible clients may switch to webhook delivery with `setWebhook`, including
448
+ an optional `secret_token` that Crabline returns as
449
+ `X-Telegram-Bot-Api-Secret-Token`. `deleteWebhook` restores polling. While a
450
+ webhook is configured, `getUpdates` returns Telegram's native conflict error
451
+ instead of consuming updates. The secret accepts only 1-256 letters, digits,
452
+ underscores, or hyphens. Bot API text fields are string-only and limited to
453
+ 4,096 UTF-16 code units; media captions are string-only and limited to 1,024.
454
+ Admin-injected message entities preserve native `url`, `user`, `language`, and
455
+ `custom_emoji_id` metadata. Calls addressed to `@username` return a numeric
456
+ `Chat.id`, as Telegram does.
341
457
 
342
458
  WhatsApp:
343
459
 
@@ -378,7 +494,8 @@ Group outbound uses sender-key `skmsg` encryption and is outside this supported
378
494
  subset, so OpenClaw Crabline outbound targets are direct users only. Group
379
495
  inbound injection remains supported. The WebSocket endpoint rejects clients
380
496
  that do not present the access token embedded in the manifest URL. The admin
381
- token is generated randomly unless `--admin-token <token>` is provided.
497
+ token is generated randomly unless `adminToken` is supplied through the
498
+ credential ingress above.
382
499
 
383
500
  OpenClaw bridge callers should post injected user messages with the
384
501
  `providerUrl`, `providerHeaders`, and `providerBody` returned by
@@ -410,6 +527,10 @@ Set trusted `ZALO_API_URL` to `endpoints.apiRoot` and configure the emitted
410
527
  does this mapping without adding provider-server-specific behavior to the Zalo
411
528
  adapter.
412
529
 
530
+ For admin ingress, POST to the manifest's `endpoints.adminInboundUrl`
531
+ (`/crabline/zalo/inbound`) and send the manifest's `adminToken` in
532
+ `X-Crabline-Admin-Token`. `Authorization: Bearer <adminToken>` is also accepted.
533
+
413
534
  The server accepts the provider-native `/bot<TOKEN>/<METHOD>` API shape over
414
535
  GET or POST. It implements bot identity, single-update long polling, text and
415
536
  photo sends, chat actions, and webhook lifecycle calls. A configured webhook
@@ -418,6 +539,10 @@ receives the native Zalo `{ event_name, message }` update directly with
418
539
  otherwise injected messages are returned by `getUpdates`. Provider errors use
419
540
  Zalo's `{ ok, error_code, description }` shape.
420
541
 
542
+ Programmatic `startZaloServer()` callers can tune the retained polling queue
543
+ with `maxPendingInboundEvents` and `maxPendingInboundBytes`; the byte limit
544
+ defaults to 64 MiB and remains reserved until the polling response commits.
545
+
421
546
  `setWebhook` requires HTTPS, matching Zalo's public API. A loopback-bound
422
547
  Crabline server also accepts a loopback HTTP URL for independent local client
423
548
  tests. Non-loopback binds reject webhook destinations that resolve to private,
@@ -475,11 +600,17 @@ Targets use native channel identifiers. Crabline does not add local prefixes suc
475
600
  as `telegram:`, `discord:`, or `slack:`.
476
601
 
477
602
  - Slack conversations: `C1234567890`, `G1234567890`, or `D1234567890`
603
+ - Slack direct sends may also target user IDs such as `U1234567890` or
604
+ `W1234567890`
478
605
  - Slack threads: `1700000000.000100`
479
606
  - Telegram chats: `-1001234567890` or `@channelusername`
480
607
  - Telegram topics: `42`
608
+ - Telegram username targets require `@`, contain 5-32 letters, digits, or
609
+ underscores, and normalize to lowercase. Numeric chat IDs must be nonzero and
610
+ have an absolute value no greater than `2^52 - 1`.
481
611
  - Built-in WhatsApp Cloud API users: digits-only `wa_id` values such as
482
612
  `15551234567`
613
+ - Built-in WhatsApp Cloud API fixtures do not support `threadId` targets.
483
614
  - OpenClaw WhatsApp bridge users: `15551234567@s.whatsapp.net`
484
615
  (legacy `15551234567@c.us` inputs are accepted and canonicalized)
485
616
  - OpenClaw WhatsApp bridge groups: `120363001234567890@g.us`
@@ -487,8 +618,16 @@ as `telegram:`, `discord:`, or `slack:`.
487
618
  `123456789012345678`
488
619
  - Google Chat spaces: `spaces/AAAABbbbCCC`
489
620
  - Google Chat threads: `spaces/AAAABbbbCCC/threads/BBBBccccDDD`
621
+ - iMessage recipients: E.164 phone numbers such as `+15551234567`, email
622
+ addresses such as `user@example.com`, or chat GUIDs such as
623
+ `iMessage;-;chat-guid` and `SMS;+;chat-guid`
490
624
  - Matrix rooms: scoped ids such as `!abcdef:matrix.org` or Matrix v12
491
625
  domainless room ids
626
+ - Matrix threads: raw root event ids such as `$eventid:matrix.org`
627
+ - Mattermost channels, users, and root posts: exactly 26 lowercase alphanumeric
628
+ characters, such as `abcdefghijklmnopqrstuvwx12`
629
+ - Microsoft Teams: non-empty opaque Bot Connector `conversation.id` values such
630
+ as `a:opaque-conversation-id` or `19:conversation@thread.v2`
492
631
  - Zalo users, OAs, and chats: provider-native non-whitespace string ids such as
493
632
  `user-1` or `group-1`
494
633
 
@@ -529,10 +668,13 @@ inside one owner-only generation directory under the legacy
529
668
  directory, and then atomically switches the single `current.json` pointer.
530
669
  Readers therefore see either the prior complete generation or the next complete
531
670
  generation, never per-file mixtures. Setup, probe, cleanup, staging, or
532
- ownership failures leave the prior pointer unchanged. Crash-leftover staging
533
- directories and installed-but-uncommitted generations remain owner-only and are
534
- not removed automatically: a lease that can expire cannot safely authorize a
535
- resumed stale process to delete another publisher's generation.
671
+ ownership failures leave the prior pointer unchanged. A successful probe must
672
+ also produce at least one valid JSONL recorder evidence record before
673
+ publication. Crash-leftover staging
674
+ directories and installed-but-uncommitted generations remain owner-only.
675
+ Publication rollback removes them when possible, and the next lock-owning
676
+ publisher prunes any leftovers before staging a new generation. Post-commit
677
+ cleanup retains only the current and previous pointer generations.
536
678
 
537
679
  POSIX generation directories use mode `0700` and files use mode `0600`. Windows
538
680
  hosts require `powershell.exe` with `Set-Acl`; Crabline resolves it from the
@@ -89,6 +89,7 @@ providers:
89
89
  mattermost:
90
90
  adapter: mattermost
91
91
  mattermost:
92
+ # webhookToken: sample
92
93
  recorder:
93
94
  path: ./.crabline/recorders/mattermost.jsonl
94
95
  webhook:
@@ -184,7 +185,7 @@ fixtures:
184
185
  provider: mattermost
185
186
  mode: roundtrip
186
187
  target:
187
- id: channel-id
188
+ id: aaaaaaaaaaaaaaaaaaaaaaaaaa
188
189
 
189
190
  - id: zalo-chat
190
191
  provider: zalo
@@ -202,4 +203,4 @@ fixtures:
202
203
  provider: imessage
203
204
  mode: roundtrip
204
205
  target:
205
- id: chat-guid
206
+ id: iMessage;-;chat-guid
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/crabline",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Standalone CLI for deterministic messaging provider E2E tests",
5
5
  "homepage": "https://github.com/openclaw/crabline#readme",
6
6
  "bugs": {
@@ -19,7 +19,7 @@
19
19
  "dist/src",
20
20
  "README.md",
21
21
  "docs/channel-setup.md",
22
- "fixtures"
22
+ "fixtures/examples/crabline.example.yaml"
23
23
  ],
24
24
  "type": "module",
25
25
  "main": "./dist/src/index.js",
@@ -39,14 +39,15 @@
39
39
  "fixtures": "tsx src/bin/crabline.ts fixtures",
40
40
  "format": "oxfmt --write .",
41
41
  "format:check": "oxfmt --check .",
42
- "lint": "pnpm format:check && pnpm typecheck && oxlint --deny-warnings --allow vitest/require-mock-type-parameters --type-aware --tsconfig tsconfig.test.json src test",
42
+ "lint": "pnpm format:check && pnpm typecheck && oxlint --deny-warnings --allow vitest/require-mock-type-parameters --type-aware --tsconfig tsconfig.test.json src test tools vitest.config.ts",
43
43
  "prepare": "npm run build",
44
44
  "run": "tsx src/bin/crabline.ts run",
45
45
  "test": "vitest run",
46
+ "test:autoreview": "vitest run test/autoreview-tooling.test.ts && node tools/run-autoreview-tests.mjs",
46
47
  "test:coverage": "vitest run --coverage",
47
48
  "test:watch": "vitest",
48
49
  "typecheck": "tsc -p tsconfig.test.json",
49
- "verify": "pnpm lint && pnpm test:coverage",
50
+ "verify": "pnpm lint && pnpm test:autoreview && pnpm build && pnpm test:coverage",
50
51
  "check": "pnpm verify"
51
52
  },
52
53
  "dependencies": {
@@ -57,7 +58,7 @@
57
58
  "picocolors": "^1.1.1",
58
59
  "proper-lockfile": "4.1.2",
59
60
  "re2js": "2.8.6",
60
- "ws": "^8.21.0",
61
+ "ws": "^8.21.1",
61
62
  "yaml": "^2.9.0",
62
63
  "zod": "^4.4.3"
63
64
  },
@@ -68,15 +69,22 @@
68
69
  "@vitest/coverage-v8": "^4.1.10",
69
70
  "baileys": "7.0.0-rc13",
70
71
  "matrix-js-sdk": "41.9.0",
71
- "oxfmt": "^0.58.0",
72
- "oxlint": "^1.73.0",
73
- "oxlint-tsgolint": "^0.24.0",
74
- "tsx": "^4.23.0",
72
+ "oxfmt": "^0.60.0",
73
+ "oxlint": "^1.75.0",
74
+ "oxlint-tsgolint": "^7.0.2001",
75
+ "tsx": "^4.23.1",
75
76
  "typescript": "npm:@typescript/typescript6@^6.0.2",
76
77
  "vitest": "^4.1.10"
77
78
  },
79
+ "devEngines": {
80
+ "runtime": {
81
+ "name": "node",
82
+ "onFail": "error",
83
+ "version": ">=22.13.0"
84
+ }
85
+ },
78
86
  "engines": {
79
87
  "node": ">=22"
80
88
  },
81
- "packageManager": "pnpm@10.32.1"
89
+ "packageManager": "pnpm@11.17.0"
82
90
  }
@@ -1,125 +0,0 @@
1
- configVersion: 1
2
- userName: crabline
3
-
4
- x-openclaw-bridge: &openclaw-bridge
5
- adapter: script
6
- env:
7
- - OPENCLAW_URL
8
- - OPENCLAW_TOKEN
9
- script:
10
- commands:
11
- probe: node ./scripts/openclaw-bridge-probe.mjs
12
- send: node ./scripts/openclaw-bridge-send.mjs
13
- waitForInbound: node ./scripts/openclaw-bridge-wait.mjs
14
- watch: node ./scripts/openclaw-bridge-watch.mjs
15
-
16
- providers:
17
- bluebubbles-openclaw:
18
- <<: *openclaw-bridge
19
- platform: bluebubbles
20
- discord-openclaw:
21
- <<: *openclaw-bridge
22
- platform: discord
23
- feishu-openclaw:
24
- <<: *openclaw-bridge
25
- platform: feishu
26
- googlechat-openclaw:
27
- <<: *openclaw-bridge
28
- platform: googlechat
29
- imessage-openclaw:
30
- <<: *openclaw-bridge
31
- platform: imessage
32
- irc-openclaw:
33
- <<: *openclaw-bridge
34
- platform: irc
35
- line-openclaw:
36
- <<: *openclaw-bridge
37
- platform: line
38
- matrix-openclaw:
39
- <<: *openclaw-bridge
40
- platform: matrix
41
- mattermost-openclaw:
42
- <<: *openclaw-bridge
43
- platform: mattermost
44
- msteams-openclaw:
45
- <<: *openclaw-bridge
46
- platform: msteams
47
- nextcloudtalk-openclaw:
48
- <<: *openclaw-bridge
49
- platform: nextcloudtalk
50
- nostr-openclaw:
51
- <<: *openclaw-bridge
52
- platform: nostr
53
- signal-openclaw:
54
- <<: *openclaw-bridge
55
- platform: signal
56
- slack-openclaw:
57
- <<: *openclaw-bridge
58
- platform: slack
59
- synologychat-openclaw:
60
- <<: *openclaw-bridge
61
- platform: synologychat
62
- telegram-openclaw:
63
- <<: *openclaw-bridge
64
- platform: telegram
65
- tlon-openclaw:
66
- <<: *openclaw-bridge
67
- platform: tlon
68
- twitch-openclaw:
69
- <<: *openclaw-bridge
70
- platform: twitch
71
- webchat-openclaw:
72
- <<: *openclaw-bridge
73
- platform: webchat
74
- whatsapp-openclaw:
75
- <<: *openclaw-bridge
76
- platform: whatsapp
77
- zalo-openclaw:
78
- <<: *openclaw-bridge
79
- platform: zalo
80
- zalouser-openclaw:
81
- <<: *openclaw-bridge
82
- platform: zalouser
83
-
84
- fixtures:
85
- - id: slack-openclaw-agent
86
- provider: slack-openclaw
87
- mode: agent
88
- target:
89
- id: REPLACE_ME
90
- channelId: REPLACE_ME
91
- inboundMatch:
92
- author: assistant
93
- strategy: contains
94
- nonce: contains
95
-
96
- - id: discord-openclaw-roundtrip
97
- provider: discord-openclaw
98
- mode: roundtrip
99
- target:
100
- id: REPLACE_ME
101
- channelId: REPLACE_ME
102
- inboundMatch:
103
- author: assistant
104
- strategy: contains
105
- nonce: contains
106
-
107
- - id: telegram-openclaw-roundtrip
108
- provider: telegram-openclaw
109
- mode: roundtrip
110
- target:
111
- id: REPLACE_ME # Telegram user id; use a dedicated human/MTProto test account, not another bot
112
- inboundMatch:
113
- author: assistant
114
- strategy: contains
115
- nonce: contains
116
-
117
- - id: whatsapp-openclaw-roundtrip
118
- provider: whatsapp-openclaw
119
- mode: roundtrip
120
- target:
121
- id: REPLACE_ME
122
- inboundMatch:
123
- author: assistant
124
- strategy: contains
125
- nonce: contains