@openclaw/crabline 0.1.11 → 0.1.13

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 (214) hide show
  1. package/README.md +137 -23
  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 +281 -52
  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/index.d.ts +12 -10
  31. package/dist/src/index.js +6 -5
  32. package/dist/src/index.js.map +1 -1
  33. package/dist/src/matrix-ids.d.ts +5 -0
  34. package/dist/src/matrix-ids.js +83 -0
  35. package/dist/src/matrix-ids.js.map +1 -0
  36. package/dist/src/openclaw/artifact-generation.d.ts +7 -6
  37. package/dist/src/openclaw/artifact-generation.js +385 -41
  38. package/dist/src/openclaw/artifact-generation.js.map +1 -1
  39. package/dist/src/openclaw/bridges/discord.d.ts +1 -0
  40. package/dist/src/openclaw/bridges/discord.js +149 -0
  41. package/dist/src/openclaw/bridges/discord.js.map +1 -0
  42. package/dist/src/openclaw/bridges/matrix.js +22 -5
  43. package/dist/src/openclaw/bridges/matrix.js.map +1 -1
  44. package/dist/src/openclaw/bridges/mattermost.js +14 -9
  45. package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
  46. package/dist/src/openclaw/bridges/probe-response.d.ts +1 -0
  47. package/dist/src/openclaw/bridges/probe-response.js +5 -0
  48. package/dist/src/openclaw/bridges/probe-response.js.map +1 -0
  49. package/dist/src/openclaw/bridges/signal.js +16 -7
  50. package/dist/src/openclaw/bridges/signal.js.map +1 -1
  51. package/dist/src/openclaw/bridges/slack.js +117 -24
  52. package/dist/src/openclaw/bridges/slack.js.map +1 -1
  53. package/dist/src/openclaw/bridges/telegram.js +46 -20
  54. package/dist/src/openclaw/bridges/telegram.js.map +1 -1
  55. package/dist/src/openclaw/bridges/whatsapp.js +33 -17
  56. package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
  57. package/dist/src/openclaw/bridges/zalo.js +9 -4
  58. package/dist/src/openclaw/bridges/zalo.js.map +1 -1
  59. package/dist/src/openclaw/outbound-contract.js +3 -0
  60. package/dist/src/openclaw/outbound-contract.js.map +1 -1
  61. package/dist/src/openclaw/private-file.d.ts +38 -3
  62. package/dist/src/openclaw/private-file.js +2008 -87
  63. package/dist/src/openclaw/private-file.js.map +1 -1
  64. package/dist/src/openclaw/{smoke-lock.d.ts → provider-readiness-lock.d.ts} +14 -5
  65. package/dist/src/openclaw/provider-readiness-lock.js +1320 -0
  66. package/dist/src/openclaw/provider-readiness-lock.js.map +1 -0
  67. package/dist/src/openclaw/shared.d.ts +10 -17
  68. package/dist/src/openclaw/shared.js +53 -19
  69. package/dist/src/openclaw/shared.js.map +1 -1
  70. package/dist/src/openclaw.d.ts +3 -5
  71. package/dist/src/openclaw.js +245 -60
  72. package/dist/src/openclaw.js.map +1 -1
  73. package/dist/src/platform/process-owned-lock.d.ts +11 -0
  74. package/dist/src/platform/process-owned-lock.js +1698 -0
  75. package/dist/src/platform/process-owned-lock.js.map +1 -0
  76. package/dist/src/platform/recorder-directory.d.ts +1 -0
  77. package/dist/src/platform/recorder-directory.js +14 -0
  78. package/dist/src/platform/recorder-directory.js.map +1 -0
  79. package/dist/src/platform/windows-acl.d.ts +18 -0
  80. package/dist/src/platform/windows-acl.js +1367 -0
  81. package/dist/src/platform/windows-acl.js.map +1 -0
  82. package/dist/src/platform/windows-lock-root.d.ts +13 -0
  83. package/dist/src/platform/windows-lock-root.js +98 -0
  84. package/dist/src/platform/windows-lock-root.js.map +1 -0
  85. package/dist/src/providers/builtin/discord.d.ts +4 -0
  86. package/dist/src/providers/builtin/discord.js +52 -9
  87. package/dist/src/providers/builtin/discord.js.map +1 -1
  88. package/dist/src/providers/builtin/external-webhook-auth.d.ts +1 -0
  89. package/dist/src/providers/builtin/external-webhook-auth.js +38 -1
  90. package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -1
  91. package/dist/src/providers/builtin/feishu.d.ts +1 -0
  92. package/dist/src/providers/builtin/feishu.js +96 -26
  93. package/dist/src/providers/builtin/feishu.js.map +1 -1
  94. package/dist/src/providers/builtin/googlechat.d.ts +1 -0
  95. package/dist/src/providers/builtin/googlechat.js +95 -26
  96. package/dist/src/providers/builtin/googlechat.js.map +1 -1
  97. package/dist/src/providers/builtin/imessage.js +60 -17
  98. package/dist/src/providers/builtin/imessage.js.map +1 -1
  99. package/dist/src/providers/builtin/loopback.js +88 -35
  100. package/dist/src/providers/builtin/loopback.js.map +1 -1
  101. package/dist/src/providers/builtin/matrix.d.ts +2 -2
  102. package/dist/src/providers/builtin/matrix.js +78 -21
  103. package/dist/src/providers/builtin/matrix.js.map +1 -1
  104. package/dist/src/providers/builtin/mattermost.d.ts +11 -7
  105. package/dist/src/providers/builtin/mattermost.js +138 -24
  106. package/dist/src/providers/builtin/mattermost.js.map +1 -1
  107. package/dist/src/providers/builtin/msteams.d.ts +1 -0
  108. package/dist/src/providers/builtin/msteams.js +118 -38
  109. package/dist/src/providers/builtin/msteams.js.map +1 -1
  110. package/dist/src/providers/builtin/native-local-mock.js +4 -4
  111. package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
  112. package/dist/src/providers/builtin/script.js +1057 -146
  113. package/dist/src/providers/builtin/script.js.map +1 -1
  114. package/dist/src/providers/builtin/slack.d.ts +8 -1
  115. package/dist/src/providers/builtin/slack.js +327 -24
  116. package/dist/src/providers/builtin/slack.js.map +1 -1
  117. package/dist/src/providers/builtin/telegram.d.ts +1 -1
  118. package/dist/src/providers/builtin/telegram.js +55 -14
  119. package/dist/src/providers/builtin/telegram.js.map +1 -1
  120. package/dist/src/providers/builtin/whatsapp.d.ts +4 -3
  121. package/dist/src/providers/builtin/whatsapp.js +157 -29
  122. package/dist/src/providers/builtin/whatsapp.js.map +1 -1
  123. package/dist/src/providers/builtin/zalo.d.ts +5 -2
  124. package/dist/src/providers/builtin/zalo.js +31 -7
  125. package/dist/src/providers/builtin/zalo.js.map +1 -1
  126. package/dist/src/providers/catalog.d.ts +1 -1
  127. package/dist/src/providers/catalog.js +1 -1
  128. package/dist/src/providers/catalog.js.map +1 -1
  129. package/dist/src/providers/local-mock.d.ts +4 -0
  130. package/dist/src/providers/local-mock.js +162 -35
  131. package/dist/src/providers/local-mock.js.map +1 -1
  132. package/dist/src/providers/native-ids.d.ts +2 -0
  133. package/dist/src/providers/native-ids.js +3 -0
  134. package/dist/src/providers/native-ids.js.map +1 -1
  135. package/dist/src/providers/recorder.d.ts +14 -3
  136. package/dist/src/providers/recorder.js +521 -41
  137. package/dist/src/providers/recorder.js.map +1 -1
  138. package/dist/src/providers/registry.d.ts +1 -0
  139. package/dist/src/providers/registry.js +65 -20
  140. package/dist/src/providers/registry.js.map +1 -1
  141. package/dist/src/providers/signed-jwt.d.ts +6 -1
  142. package/dist/src/providers/signed-jwt.js +241 -50
  143. package/dist/src/providers/signed-jwt.js.map +1 -1
  144. package/dist/src/providers/slack-ids.d.ts +1 -0
  145. package/dist/src/providers/slack-ids.js +5 -0
  146. package/dist/src/providers/slack-ids.js.map +1 -1
  147. package/dist/src/providers/target-normalizers.d.ts +9 -1
  148. package/dist/src/providers/target-normalizers.js +177 -58
  149. package/dist/src/providers/target-normalizers.js.map +1 -1
  150. package/dist/src/providers/webhook-server.d.ts +3 -0
  151. package/dist/src/providers/webhook-server.js +268 -47
  152. package/dist/src/providers/webhook-server.js.map +1 -1
  153. package/dist/src/servers/discord.d.ts +40 -0
  154. package/dist/src/servers/discord.js +842 -0
  155. package/dist/src/servers/discord.js.map +1 -0
  156. package/dist/src/servers/http.d.ts +14 -2
  157. package/dist/src/servers/http.js +229 -58
  158. package/dist/src/servers/http.js.map +1 -1
  159. package/dist/src/servers/index.d.ts +10 -4
  160. package/dist/src/servers/index.js +4 -0
  161. package/dist/src/servers/index.js.map +1 -1
  162. package/dist/src/servers/matrix.d.ts +3 -0
  163. package/dist/src/servers/matrix.js +268 -110
  164. package/dist/src/servers/matrix.js.map +1 -1
  165. package/dist/src/servers/mattermost.d.ts +5 -0
  166. package/dist/src/servers/mattermost.js +389 -91
  167. package/dist/src/servers/mattermost.js.map +1 -1
  168. package/dist/src/servers/recorder.d.ts +11 -0
  169. package/dist/src/servers/recorder.js +1072 -69
  170. package/dist/src/servers/recorder.js.map +1 -1
  171. package/dist/src/servers/signal.d.ts +1 -0
  172. package/dist/src/servers/signal.js +279 -44
  173. package/dist/src/servers/signal.js.map +1 -1
  174. package/dist/src/servers/slack.d.ts +15 -0
  175. package/dist/src/servers/slack.js +270 -54
  176. package/dist/src/servers/slack.js.map +1 -1
  177. package/dist/src/servers/telegram-identity.d.ts +6 -0
  178. package/dist/src/servers/telegram-identity.js +27 -0
  179. package/dist/src/servers/telegram-identity.js.map +1 -0
  180. package/dist/src/servers/telegram.d.ts +17 -6
  181. package/dist/src/servers/telegram.js +1218 -107
  182. package/dist/src/servers/telegram.js.map +1 -1
  183. package/dist/src/servers/webhook-target.d.ts +12 -4
  184. package/dist/src/servers/webhook-target.js +226 -28
  185. package/dist/src/servers/webhook-target.js.map +1 -1
  186. package/dist/src/servers/whatsapp-baileys-websocket.d.ts +60 -1
  187. package/dist/src/servers/whatsapp-baileys-websocket.js +782 -112
  188. package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
  189. package/dist/src/servers/whatsapp-jid.d.ts +1 -0
  190. package/dist/src/servers/whatsapp-jid.js +9 -5
  191. package/dist/src/servers/whatsapp-jid.js.map +1 -1
  192. package/dist/src/servers/whatsapp-wire/binary-node.d.ts +1 -0
  193. package/dist/src/servers/whatsapp-wire/binary-node.js +37 -13
  194. package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
  195. package/dist/src/servers/whatsapp-wire/crypto.d.ts +3 -0
  196. package/dist/src/servers/whatsapp-wire/crypto.js +23 -8
  197. package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
  198. package/dist/src/servers/whatsapp-wire/handshake.d.ts +9 -3
  199. package/dist/src/servers/whatsapp-wire/handshake.js +173 -12
  200. package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
  201. package/dist/src/servers/whatsapp-wire/signal.js +3 -3
  202. package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
  203. package/dist/src/servers/whatsapp.d.ts +7 -1
  204. package/dist/src/servers/whatsapp.js +149 -25
  205. package/dist/src/servers/whatsapp.js.map +1 -1
  206. package/dist/src/servers/zalo.d.ts +1 -0
  207. package/dist/src/servers/zalo.js +193 -82
  208. package/dist/src/servers/zalo.js.map +1 -1
  209. package/docs/channel-setup.md +251 -64
  210. package/fixtures/examples/crabline.example.yaml +3 -2
  211. package/package.json +18 -10
  212. package/dist/src/openclaw/smoke-lock.js +0 -1004
  213. package/dist/src/openclaw/smoke-lock.js.map +0 -1
  214. package/fixtures/examples/openclaw-bridge.yaml +0 -125
@@ -4,7 +4,7 @@ Crabline is a local mock service for OpenClaw channel contracts. It has two
4
4
  surfaces:
5
5
 
6
6
  - fixture-level local mocks used directly by the Crabline CLI
7
- - local provider servers that OpenClaw live adapters can target
7
+ - local provider servers that real OpenClaw channel plugins can target
8
8
 
9
9
  Live channel testing belongs in OpenClaw's live channel adapters. Crabline
10
10
  belongs in deterministic smoke CI and local QA where the test needs
@@ -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`.
@@ -175,15 +224,92 @@ watch commands are terminated when cancellation fires.
175
224
 
176
225
  ## Local Provider Servers
177
226
 
178
- Server-backed channels currently include Mattermost, Matrix, Signal, Slack,
179
- Telegram, WhatsApp, and Zalo. Loopback binds retain stable local credentials for
180
- fixture compatibility. Non-loopback binds generate fresh provider-shaped
181
- credentials unless the corresponding token or secret option is supplied.
227
+ Server-backed channels currently include Discord, Mattermost, Matrix, Signal,
228
+ Slack, Telegram, WhatsApp, and Zalo. Loopback binds retain stable local
229
+ credentials for deterministic tests. Non-loopback binds generate fresh provider-shaped
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
+ ```
246
+
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.
182
266
 
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`.
267
+ ### Discord
268
+
269
+ ```bash
270
+ crabline --json serve discord --ready-file .crabline/discord-server.json
271
+ ```
272
+
273
+ The ready manifest exposes `applicationId`, `botUserId`, `botToken`,
274
+ `endpoints.apiRoot`, `endpoints.gatewayBotUrl`, `endpoints.gatewayUrl`,
275
+ `endpoints.adminInboundUrl`, `adminToken`, and `recorderPath`. OpenClaw-specific
276
+ endpoint injection and target translation belong to the Crabline OpenClaw
277
+ bridge and the OpenClaw Discord plugin; the provider server itself implements
278
+ Discord's public protocol subset and contains no OpenClaw behavior.
279
+
280
+ Post an inbound user message to `endpoints.adminInboundUrl` with the manifest's
281
+ admin token:
282
+
283
+ ```json
284
+ {
285
+ "channelId": "135000000000000010",
286
+ "guildId": "135000000000000011",
287
+ "senderId": "135000000000000012",
288
+ "senderName": "Alice",
289
+ "content": "hello"
290
+ }
291
+ ```
292
+
293
+ The control plane creates provider state and emits a normal Discord
294
+ `MESSAGE_CREATE` dispatch on an identified Gateway session. `parentChannelId`
295
+ turns `channelId` into a public thread channel. Supported client boundaries are
296
+ REST v10 bot/application identity, Gateway metadata, guild/channel/member
297
+ lookups, DM creation, text message create/read/reply, typing, application
298
+ command registration, and a v10 JSON Gateway with HELLO, IDENTIFY, READY,
299
+ heartbeat acknowledgement, resume, and deterministic shutdown.
300
+
301
+ Current fidelity limits are one Gateway shard, no transport compression, and no
302
+ missed-dispatch replay: resume succeeds only from the retained session's latest
303
+ sequence. The server also omits voice, interaction dispatch,
304
+ attachment/multipart upload, and Discord's distributed permission or
305
+ bucket-allocation systems.
306
+ The server still returns provider-native authentication, JSON error, and basic
307
+ rate-limit headers for its supported routes.
308
+
309
+ This is not the fixture-level Discord local mock. The fixture adapter's
310
+ interactions webhook remains available to Crabline fixture commands; the
311
+ Discord provider server exists so the real OpenClaw Discord plugin can use
312
+ normal REST and Gateway transports without contacting Discord.
187
313
 
188
314
  ### Mattermost
189
315
 
@@ -198,11 +324,16 @@ and credential. Because the server is loopback HTTP, trusted QA configuration
198
324
  must also set `channels.mattermost.network.dangerouslyAllowPrivateNetwork` to
199
325
  `true`. The OpenClaw bridge does this automatically.
200
326
 
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.
327
+ Admin inbound accepts 26-character lowercase alphanumeric Mattermost
328
+ `channelId` and `senderId` values, `text`, optional `senderName`, `channelType`,
329
+ `rootId`, `channelName`, and `channelDisplayName`; `rootId`, when present, uses
330
+ the same strict 26-character format. It emits the message through Mattermost's
331
+ native `/api/v4/websocket` channel-scoped `posted` event, including the
332
+ configured channel names. Text sends through `POST /api/v4/posts` require a
333
+ JSON media type and are written to the manifest's recorder. Fixture and QA
334
+ channel/user targets must use provider-native Mattermost IDs rather than local
335
+ labels. QA agent delivery currently supports DM and channel targets; thread
336
+ targets require the later OpenClaw QA wiring step.
206
337
 
207
338
  The server itself is provider-shaped and has no OpenClaw runtime dependency. It
208
339
  implements Mattermost REST error/status behavior plus WebSocket authentication,
@@ -225,10 +356,17 @@ because the server is loopback HTTP, enable
225
356
  applies those settings automatically.
226
357
 
227
358
  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.
359
+ optional `senderName`, `roomName`, `direct`, and `threadId`. `roomName` names a
360
+ newly created room, while `direct: true` marks membership as direct and
361
+ publishes the room through `m.direct` account data. `threadId` is the raw Matrix
362
+ root event ID, such as `$eventid:matrix.org`; Crabline emits it as both the
363
+ `m.thread` relation target and `m.in_reply_to` root. The resulting native
364
+ `m.room.message` event is delivered through Matrix `/sync`. Outbound room sends
365
+ through `PUT /_matrix/client/v3/rooms/:roomId/send/:eventType/:txnId` are
366
+ written to the manifest recorder.
367
+
368
+ Post admin inbound payloads to the manifest's `endpoints.adminInboundUrl` with
369
+ its `adminToken` in the `X-Crabline-Admin-Token` header.
232
370
 
233
371
  The provider server implements the unencrypted Client-Server API subset needed
234
372
  by the normal Matrix SDK: versions, `whoami`, filters, push rules, joined rooms
@@ -245,6 +383,17 @@ outcome together with the provider's exact send method and path. Rejected
245
383
  requests and lookalike route suffixes can remain diagnostic recorder entries,
246
384
  but they are never exposed as successful outbound deliveries.
247
385
 
386
+ Use one filesystem name per recorder file. When multiple server processes must
387
+ run without one shared writable OS account home or write through hardlinks to
388
+ the same recorder inode, set
389
+ `CRABLINE_RECORDER_LOCK_DIR` to one absolute writable directory shared by every
390
+ writer and pre-create its ownership, group, or ACLs. Hardlinked server-recorder
391
+ writes fail closed when this shared lock namespace is absent. The path must be
392
+ canonical and contain no symlink components. Use a separate lock directory per
393
+ recorder filesystem; lock identities omit device numbers so containers that
394
+ mount the same inode under different device IDs still coordinate. Otherwise,
395
+ Unix writers use `~/.cache/crabline/locks/server-recorder`.
396
+
248
397
  Slack:
249
398
 
250
399
  ```bash
@@ -273,7 +422,10 @@ The admin ingress accepts JSON like:
273
422
  ```
274
423
 
275
424
  OpenClaw consumes that message through Slack Events API shape; outbound adapter
276
- sends are recorded through Slack `chat.postMessage`.
425
+ sends are recorded through Slack `chat.postMessage`. Outbound direct sends may
426
+ use native user IDs beginning with `U` or `W`; Crabline opens the corresponding
427
+ DM conversation before posting. Admin inbound still requires a conversation ID
428
+ beginning with `C`, `D`, or `G` plus a sender user ID beginning with `U` or `W`.
277
429
 
278
430
  Signal:
279
431
 
@@ -321,8 +473,9 @@ Manifest fields:
321
473
  - `endpoints.adminInboundUrl`: authenticated admin ingress for test user messages
322
474
  - `recorderPath`: JSONL provider traffic recorder
323
475
 
324
- The admin token is generated randomly unless `--admin-token <token>` is
325
- provided. Requests may also use `Authorization: Bearer <token>`.
476
+ The admin token is generated randomly unless `adminToken` is supplied through
477
+ the credential ingress above. Requests may also use
478
+ `Authorization: Bearer <token>`.
326
479
 
327
480
  The admin ingress accepts JSON like:
328
481
 
@@ -338,6 +491,16 @@ The admin ingress accepts JSON like:
338
491
  OpenClaw consumes that message through Telegram `getUpdates`; outbound adapter
339
492
  sends are recorded through Telegram `sendMessage` and the media send endpoints
340
493
  `sendPhoto`, `sendDocument`, `sendVideo`, `sendAudio`, and `sendAnimation`.
494
+ Compatible clients may switch to webhook delivery with `setWebhook`, including
495
+ an optional `secret_token` that Crabline returns as
496
+ `X-Telegram-Bot-Api-Secret-Token`. `deleteWebhook` restores polling. While a
497
+ webhook is configured, `getUpdates` returns Telegram's native conflict error
498
+ instead of consuming updates. The secret accepts only 1-256 letters, digits,
499
+ underscores, or hyphens. Bot API text fields are string-only and limited to
500
+ 4,096 UTF-16 code units; media captions are string-only and limited to 1,024.
501
+ Admin-injected message entities preserve native `url`, `user`, `language`, and
502
+ `custom_emoji_id` metadata. Calls addressed to `@username` return a numeric
503
+ `Chat.id`, as Telegram does.
341
504
 
342
505
  WhatsApp:
343
506
 
@@ -362,7 +525,6 @@ Manifest fields:
362
525
  - `endpoints.adminInboundUrl`: authenticated admin ingress for test user
363
526
  messages using the WhatsApp Business webhook payload shape
364
527
  - `endpoints.messagesUrl`: provider-native Cloud API message and status endpoint
365
- - `endpoints.statusUrl`: alias for the same provider-native status endpoint
366
528
  - `recorderPath`: JSONL provider traffic recorder for HTTP traffic and Baileys
367
529
  WebSocket stanzas. Multi-message webhook deliveries use one versioned batch
368
530
  line that Crabline's recorder APIs flatten into individual events.
@@ -378,7 +540,8 @@ Group outbound uses sender-key `skmsg` encryption and is outside this supported
378
540
  subset, so OpenClaw Crabline outbound targets are direct users only. Group
379
541
  inbound injection remains supported. The WebSocket endpoint rejects clients
380
542
  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.
543
+ token is generated randomly unless `adminToken` is supplied through the
544
+ credential ingress above.
382
545
 
383
546
  OpenClaw bridge callers should post injected user messages with the
384
547
  `providerUrl`, `providerHeaders`, and `providerBody` returned by
@@ -394,8 +557,8 @@ The admin ingress accepts JSON like:
394
557
  }
395
558
  ```
396
559
 
397
- Outbound text sends and composing presence are recorded through the fake
398
- provider messages and presence endpoints.
560
+ Outbound text sends and composing presence are recorded through the local
561
+ provider server's messages and presence endpoints.
399
562
 
400
563
  ### Zalo Server
401
564
 
@@ -410,6 +573,10 @@ Set trusted `ZALO_API_URL` to `endpoints.apiRoot` and configure the emitted
410
573
  does this mapping without adding provider-server-specific behavior to the Zalo
411
574
  adapter.
412
575
 
576
+ For admin ingress, POST to the manifest's `endpoints.adminInboundUrl`
577
+ (`/crabline/zalo/inbound`) and send the manifest's `adminToken` in
578
+ `X-Crabline-Admin-Token`. `Authorization: Bearer <adminToken>` is also accepted.
579
+
413
580
  The server accepts the provider-native `/bot<TOKEN>/<METHOD>` API shape over
414
581
  GET or POST. It implements bot identity, single-update long polling, text and
415
582
  photo sends, chat actions, and webhook lifecycle calls. A configured webhook
@@ -418,6 +585,10 @@ receives the native Zalo `{ event_name, message }` update directly with
418
585
  otherwise injected messages are returned by `getUpdates`. Provider errors use
419
586
  Zalo's `{ ok, error_code, description }` shape.
420
587
 
588
+ Programmatic `startZaloServer()` callers can tune the retained polling queue
589
+ with `maxPendingInboundEvents` and `maxPendingInboundBytes`; the byte limit
590
+ defaults to 64 MiB and remains reserved until the polling response commits.
591
+
421
592
  `setWebhook` requires HTTPS, matching Zalo's public API. A loopback-bound
422
593
  Crabline server also accepts a loopback HTTP URL for independent local client
423
594
  tests. Non-loopback binds reject webhook destinations that resolve to private,
@@ -475,11 +646,17 @@ Targets use native channel identifiers. Crabline does not add local prefixes suc
475
646
  as `telegram:`, `discord:`, or `slack:`.
476
647
 
477
648
  - Slack conversations: `C1234567890`, `G1234567890`, or `D1234567890`
649
+ - Slack direct sends may also target user IDs such as `U1234567890` or
650
+ `W1234567890`
478
651
  - Slack threads: `1700000000.000100`
479
652
  - Telegram chats: `-1001234567890` or `@channelusername`
480
653
  - Telegram topics: `42`
654
+ - Telegram username targets require `@`, contain 5-32 letters, digits, or
655
+ underscores, and normalize to lowercase. Numeric chat IDs must be nonzero and
656
+ have an absolute value no greater than `2^52 - 1`.
481
657
  - Built-in WhatsApp Cloud API users: digits-only `wa_id` values such as
482
658
  `15551234567`
659
+ - Built-in WhatsApp Cloud API fixtures do not support `threadId` targets.
483
660
  - OpenClaw WhatsApp bridge users: `15551234567@s.whatsapp.net`
484
661
  (legacy `15551234567@c.us` inputs are accepted and canonicalized)
485
662
  - OpenClaw WhatsApp bridge groups: `120363001234567890@g.us`
@@ -487,8 +664,16 @@ as `telegram:`, `discord:`, or `slack:`.
487
664
  `123456789012345678`
488
665
  - Google Chat spaces: `spaces/AAAABbbbCCC`
489
666
  - Google Chat threads: `spaces/AAAABbbbCCC/threads/BBBBccccDDD`
667
+ - iMessage recipients: E.164 phone numbers such as `+15551234567`, email
668
+ addresses such as `user@example.com`, or chat GUIDs such as
669
+ `iMessage;-;chat-guid` and `SMS;+;chat-guid`
490
670
  - Matrix rooms: scoped ids such as `!abcdef:matrix.org` or Matrix v12
491
671
  domainless room ids
672
+ - Matrix threads: raw root event ids such as `$eventid:matrix.org`
673
+ - Mattermost channels, users, and root posts: exactly 26 lowercase alphanumeric
674
+ characters, such as `abcdefghijklmnopqrstuvwx12`
675
+ - Microsoft Teams: non-empty opaque Bot Connector `conversation.id` values such
676
+ as `a:opaque-conversation-id` or `19:conversation@thread.v2`
492
677
  - Zalo users, OAs, and chats: provider-native non-whitespace string ids such as
493
678
  `user-1` or `group-1`
494
679
 
@@ -500,7 +685,7 @@ chat IDs must be negative. Zero is invalid for every Telegram target kind.
500
685
 
501
686
  ## Smoke CI Guidance
502
687
 
503
- For deterministic CI, use Crabline through a mock channel driver:
688
+ For deterministic CI, use the Crabline channel driver:
504
689
 
505
690
  ```yaml
506
691
  profile: smoke-ci
@@ -524,15 +709,18 @@ roundtrip. Real OpenClaw channel proof comes from QA scenarios that launch the
524
709
  gateway and run its normal channel adapter against the local provider.
525
710
 
526
711
  Crabline stages the manifest, capability report, and provider-readiness report
527
- inside one owner-only generation directory under the legacy
528
- `.crabline-smoke-artifacts/` store name, atomically installs the complete
712
+ inside one owner-only generation directory under the
713
+ `.crabline-channel-driver-artifacts/` store name, atomically installs the complete
529
714
  directory, and then atomically switches the single `current.json` pointer.
530
715
  Readers therefore see either the prior complete generation or the next complete
531
716
  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.
717
+ ownership failures leave the prior pointer unchanged. A successful probe must
718
+ also produce at least one valid JSONL recorder evidence record before
719
+ publication. Crash-leftover staging
720
+ directories and installed-but-uncommitted generations remain owner-only.
721
+ Publication rollback removes them when possible, and the next lock-owning
722
+ publisher prunes any leftovers before staging a new generation. Post-commit
723
+ cleanup retains only the current and previous pointer generations.
536
724
 
537
725
  POSIX generation directories use mode `0700` and files use mode `0600`. Windows
538
726
  hosts require `powershell.exe` with `Set-Acl`; Crabline resolves it from the
@@ -545,17 +733,16 @@ contain `capabilityReport` and `providerReadiness` payloads while their manifest
545
733
  capability, and provider-readiness paths identify the authoritative immutable
546
734
  generation.
547
735
 
548
- Lock owners record both PID and process-start identity. Dead owners, and stale
549
- locks whose PID was reused by the next Crabline process, are reclaimed on the
550
- next run. New lock owners renew a 10-minute lease while the readiness run
551
- remains active, so a live run retains exclusive ownership beyond the initial
552
- lease. A heartbeat failure or lost ownership aborts publication before the
553
- generation is committed. Recovery first atomically moves a stale candidate away
554
- from the heartbeat path, then revalidates its token-specific lease before
555
- deletion; a renewal that wins the rename race is restored rather than reclaimed.
556
- The lease also bounds stale locks when an unrelated live process has inherited
557
- the abandoned PID. Older owner records remain PID-protected for compatibility
558
- and are reclaimed only after their recorded process exits.
736
+ Lock owners use one versioned format containing the PID, process-start identity,
737
+ and token-specific lease. Both the acquisition reservation and owner claim renew
738
+ their 10-minute leases while the readiness run remains active, so a live run
739
+ retains exclusive ownership beyond the initial lease. A heartbeat failure or
740
+ lost ownership aborts publication before the generation is committed. Recovery
741
+ first atomically moves a stale reservation into a uniquely named recovery claim,
742
+ then revalidates its token-specific lease before deletion; a renewal that wins
743
+ the rename race is restored rather than reclaimed. The lease also bounds stale
744
+ locks when an unrelated live process has inherited the abandoned PID. Malformed
745
+ or unversioned owner records fail closed and are not migrated or recovered.
559
746
 
560
747
  For release or live verification, use OpenClaw's live driver:
561
748
 
@@ -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.11",
3
+ "version": "0.1.13",
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
  }