@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
@@ -1,8 +1,20 @@
1
+ import { validateHeaderValue } from "node:http";
2
+ import { BlockList, isIP } from "node:net";
1
3
  import { z } from "zod";
4
+ import { isCanonicalHttpPath } from "../core/http-path.js";
5
+ import { isValidNonceFixtureId, NONCE_FIXTURE_ID_ERROR } from "../core/nonces.js";
2
6
  import { inboundRegexSafetyError } from "../core/safe-regex.js";
3
7
  export const FIXTURE_MODES = ["probe", "send", "roundtrip", "agent"];
4
- const FIXTURE_ID_PATTERN = /^[a-z0-9-]+$/i;
8
+ const LOOPBACK_ADDRESSES = new BlockList();
9
+ LOOPBACK_ADDRESSES.addSubnet("127.0.0.0", 8, "ipv4");
10
+ LOOPBACK_ADDRESSES.addAddress("::1", "ipv6");
11
+ LOOPBACK_ADDRESSES.addSubnet("::ffff:127.0.0.0", 104, "ipv6");
12
+ const MAX_FIXTURE_RETRIES = 10;
5
13
  const MAX_TIMER_MS = 2_147_483_647;
14
+ const TimerMsSchema = z
15
+ .number()
16
+ .int()
17
+ .max(MAX_TIMER_MS, "timer duration must be at most 2147483647ms");
6
18
  export const INBOUND_AUTHORS = ["assistant", "user", "system", "any"];
7
19
  export const INBOUND_STRATEGIES = ["contains", "exact", "regex"];
8
20
  export const INBOUND_NONCE_MODES = ["contains", "exact", "ignore"];
@@ -46,6 +58,25 @@ export const PROVIDER_PLATFORMS = [
46
58
  "zalo",
47
59
  "zalouser",
48
60
  ];
61
+ export function fixtureModeValidationIssue(value) {
62
+ if ((value.mode === "roundtrip" || value.mode === "agent") &&
63
+ value.retries > 0 &&
64
+ value.inboundMatch.nonce === "ignore") {
65
+ return {
66
+ message: `${value.mode} mode cannot retry when inboundMatch.nonce=ignore because a late reply from an earlier attempt could be accepted`,
67
+ path: ["inboundMatch", "nonce"],
68
+ };
69
+ }
70
+ if (value.mode === "agent" &&
71
+ value.inboundMatch.strategy === "exact" &&
72
+ value.inboundMatch.pattern) {
73
+ return {
74
+ message: "agent mode cannot use inboundMatch.strategy=exact with a static pattern because replies must include the generated ACK nonce",
75
+ path: ["inboundMatch", "strategy"],
76
+ };
77
+ }
78
+ return undefined;
79
+ }
49
80
  const HttpUrlSchema = z
50
81
  .string()
51
82
  .url()
@@ -58,6 +89,53 @@ const HttpUrlSchema = z
58
89
  return false;
59
90
  }
60
91
  }, "URL must use http or https");
92
+ const HttpsUrlSchema = HttpUrlSchema.refine((value) => {
93
+ try {
94
+ return new URL(value).protocol === "https:";
95
+ }
96
+ catch {
97
+ return false;
98
+ }
99
+ }, "URL must use https");
100
+ const NonBlankSecretSchema = z
101
+ .string()
102
+ .min(1)
103
+ .refine((value) => value.trim().length > 0, "secret must not be blank");
104
+ const HeaderValueSchema = z
105
+ .string()
106
+ .min(1)
107
+ .refine((value) => value.trim().length > 0, "secret must not be blank")
108
+ .refine((value) => value === value.trim(), "secret must not contain surrounding whitespace")
109
+ .refine((value) => {
110
+ try {
111
+ validateHeaderValue("x-crabline-secret", value);
112
+ return true;
113
+ }
114
+ catch {
115
+ return false;
116
+ }
117
+ }, "secret must be a valid HTTP header value");
118
+ const WebhookPathSchema = z
119
+ .string()
120
+ .min(1)
121
+ .startsWith("/", "webhook path must start with /")
122
+ .refine(isCanonicalHttpPath, "webhook path must be a canonical URL pathname");
123
+ function isLoopbackHost(host) {
124
+ const normalized = host
125
+ .trim()
126
+ .replace(/^\[(.*)\]$/u, "$1")
127
+ .toLowerCase()
128
+ .replace(/\.$/u, "");
129
+ if (normalized === "localhost" || normalized.endsWith(".localhost")) {
130
+ return true;
131
+ }
132
+ const family = isIP(normalized);
133
+ return family === 4
134
+ ? LOOPBACK_ADDRESSES.check(normalized, "ipv4")
135
+ : family === 6
136
+ ? LOOPBACK_ADDRESSES.check(normalized, "ipv6")
137
+ : false;
138
+ }
61
139
  function inferProviderPlatform(adapter) {
62
140
  if (adapter === "script") {
63
141
  return undefined;
@@ -89,6 +167,15 @@ const InboundMatchSchema = z
89
167
  if (value.strategy !== "regex" || !value.pattern) {
90
168
  return;
91
169
  }
170
+ const safetyError = inboundRegexSafetyError(value.pattern);
171
+ if (safetyError?.startsWith("must contain at most ")) {
172
+ ctx.addIssue({
173
+ code: z.ZodIssueCode.custom,
174
+ message: `inboundMatch.pattern ${safetyError}`,
175
+ path: ["pattern"],
176
+ });
177
+ return;
178
+ }
92
179
  try {
93
180
  RegExp(value.pattern, "u");
94
181
  }
@@ -100,7 +187,6 @@ const InboundMatchSchema = z
100
187
  });
101
188
  return;
102
189
  }
103
- const safetyError = inboundRegexSafetyError(value.pattern);
104
190
  if (safetyError) {
105
191
  ctx.addIssue({
106
192
  code: z.ZodIssueCode.custom,
@@ -120,7 +206,7 @@ const ScriptCommandsSchema = z.strictObject({
120
206
  watch: ScriptCommandSchema.optional(),
121
207
  });
122
208
  const LoopbackConfigSchema = z.strictObject({
123
- delayMs: z.number().int().min(0).max(MAX_TIMER_MS).default(25),
209
+ delayMs: TimerMsSchema.min(0).default(25),
124
210
  });
125
211
  const ScriptConfigSchema = z.strictObject({
126
212
  commands: ScriptCommandsSchema,
@@ -132,13 +218,13 @@ const SlackRecorderSchema = z.strictObject({
132
218
  });
133
219
  const SlackWebhookSchema = z.strictObject({
134
220
  host: z.string().min(1).default("127.0.0.1"),
135
- path: z.string().min(1).default("/slack/events"),
221
+ path: WebhookPathSchema.default("/slack/events"),
136
222
  port: z.number().int().min(0).max(65_535).default(8787),
137
223
  publicUrl: HttpUrlSchema.optional(),
138
224
  });
139
225
  const SlackConfigSchema = z.strictObject({
140
226
  recorder: SlackRecorderSchema.default({}),
141
- signingSecret: z.string().min(1).optional(),
227
+ signingSecret: NonBlankSecretSchema.optional(),
142
228
  webhook: SlackWebhookSchema.default({
143
229
  host: "127.0.0.1",
144
230
  path: "/slack/events",
@@ -150,14 +236,14 @@ const DiscordRecorderSchema = z.strictObject({
150
236
  });
151
237
  const DiscordWebhookSchema = z.strictObject({
152
238
  host: z.string().min(1).default("127.0.0.1"),
153
- path: z.string().min(1).default("/discord/interactions"),
239
+ path: WebhookPathSchema.default("/discord/interactions"),
154
240
  port: z.number().int().min(0).max(65_535).default(8788),
155
241
  publicUrl: HttpUrlSchema.optional(),
156
242
  });
157
243
  const DiscordConfigSchema = z.strictObject({
158
244
  applicationId: z.string().min(1).optional(),
159
245
  botToken: z.string().min(1).optional(),
160
- gatewayDurationMs: z.number().int().min(1000).default(180_000),
246
+ gatewayDurationMs: TimerMsSchema.min(1000).default(180_000),
161
247
  mentionRoleIds: z.array(z.string().min(1)).optional(),
162
248
  publicKey: z.string().min(1).optional(),
163
249
  recorder: DiscordRecorderSchema.default({}),
@@ -172,7 +258,7 @@ const WhatsAppRecorderSchema = z.strictObject({
172
258
  });
173
259
  const WhatsAppWebhookSchema = z.strictObject({
174
260
  host: z.string().min(1).default("127.0.0.1"),
175
- path: z.string().min(1).default("/whatsapp/webhook"),
261
+ path: WebhookPathSchema.default("/whatsapp/webhook"),
176
262
  port: z.number().int().min(0).max(65_535).default(8789),
177
263
  publicUrl: HttpUrlSchema.optional(),
178
264
  });
@@ -200,9 +286,9 @@ const MsTeamsRecorderSchema = z.strictObject({
200
286
  });
201
287
  const MsTeamsWebhookSchema = z.strictObject({
202
288
  host: z.string().min(1).default("127.0.0.1"),
203
- path: z.string().min(1).default("/msteams/webhook"),
289
+ path: WebhookPathSchema.default("/msteams/webhook"),
204
290
  port: z.number().int().min(0).max(65_535).default(8791),
205
- publicUrl: HttpUrlSchema.optional(),
291
+ publicUrl: HttpsUrlSchema.optional(),
206
292
  });
207
293
  const MsTeamsConfigSchema = z.strictObject({
208
294
  apiUrl: HttpUrlSchema.optional(),
@@ -210,7 +296,7 @@ const MsTeamsConfigSchema = z.strictObject({
210
296
  appPassword: z.string().min(1).optional(),
211
297
  appTenantId: z.string().min(1).optional(),
212
298
  appType: z.enum(["MultiTenant", "SingleTenant"]).optional(),
213
- dialogOpenTimeoutMs: z.number().int().min(0).optional(),
299
+ dialogOpenTimeoutMs: TimerMsSchema.min(0).optional(),
214
300
  federated: MsTeamsFederatedSchema.optional(),
215
301
  recorder: MsTeamsRecorderSchema.default({}),
216
302
  userName: z.string().min(1).optional(),
@@ -232,11 +318,12 @@ const GoogleChatRecorderSchema = z.strictObject({
232
318
  });
233
319
  const GoogleChatWebhookSchema = z.strictObject({
234
320
  host: z.string().min(1).default("127.0.0.1"),
235
- path: z.string().min(1).default("/googlechat/webhook"),
321
+ path: WebhookPathSchema.default("/googlechat/webhook"),
236
322
  port: z.number().int().min(0).max(65_535).default(8792),
237
323
  publicUrl: HttpUrlSchema.optional(),
238
324
  });
239
- const GoogleChatConfigSchema = z.strictObject({
325
+ const GoogleChatConfigSchema = z
326
+ .strictObject({
240
327
  apiUrl: HttpUrlSchema.optional(),
241
328
  credentials: GoogleChatCredentialsSchema.optional(),
242
329
  disableSignatureVerification: z.boolean().optional(),
@@ -254,13 +341,25 @@ const GoogleChatConfigSchema = z.strictObject({
254
341
  path: "/googlechat/webhook",
255
342
  port: 8792,
256
343
  }),
344
+ })
345
+ .superRefine((value, ctx) => {
346
+ if (value.pubsubAudience &&
347
+ !value.disableSignatureVerification &&
348
+ !value.pubsubServiceAccountEmail &&
349
+ !value.credentials?.client_email) {
350
+ ctx.addIssue({
351
+ code: z.ZodIssueCode.custom,
352
+ message: "pubsubAudience requires a Pub/Sub service-account identity",
353
+ path: ["pubsubAudience"],
354
+ });
355
+ }
257
356
  });
258
357
  const TelegramLongPollingSchema = z.strictObject({
259
358
  allowedUpdates: z.array(z.string().min(1)).optional(),
260
359
  deleteWebhook: z.boolean().optional(),
261
360
  dropPendingUpdates: z.boolean().optional(),
262
361
  limit: z.number().int().min(1).max(100).optional(),
263
- retryDelayMs: z.number().int().min(0).optional(),
362
+ retryDelayMs: TimerMsSchema.min(0).optional(),
264
363
  timeout: z.number().int().min(0).optional(),
265
364
  });
266
365
  const TelegramRecorderSchema = z.strictObject({
@@ -268,7 +367,7 @@ const TelegramRecorderSchema = z.strictObject({
268
367
  });
269
368
  const TelegramWebhookSchema = z.strictObject({
270
369
  host: z.string().min(1).default("127.0.0.1"),
271
- path: z.string().min(1).default("/telegram/webhook"),
370
+ path: WebhookPathSchema.default("/telegram/webhook"),
272
371
  port: z.number().int().min(0).max(65_535).default(8790),
273
372
  publicUrl: HttpUrlSchema.optional(),
274
373
  });
@@ -278,7 +377,10 @@ const TelegramConfigSchema = z.strictObject({
278
377
  longPolling: TelegramLongPollingSchema.optional(),
279
378
  mode: z.enum(["auto", "polling", "webhook"]).default("auto"),
280
379
  recorder: TelegramRecorderSchema.default({}),
281
- secretToken: z.string().min(1).optional(),
380
+ secretToken: z
381
+ .string()
382
+ .regex(/^[A-Za-z0-9_-]{1,256}(?![\s\S])/u, "Telegram secretToken must use 1-256 letters, digits, underscores, or hyphens")
383
+ .optional(),
282
384
  userName: z.string().min(1).optional(),
283
385
  webhook: TelegramWebhookSchema.default({
284
386
  host: "127.0.0.1",
@@ -289,14 +391,14 @@ const TelegramConfigSchema = z.strictObject({
289
391
  const FeishuConfigSchema = z.strictObject({
290
392
  appId: z.string().min(1).optional(),
291
393
  appSecret: z.string().min(1).optional(),
292
- encryptKey: z.string().min(1).optional(),
394
+ encryptKey: NonBlankSecretSchema.optional(),
293
395
  recorder: z.strictObject({ path: z.string().min(1).optional() }).default({}),
294
396
  userName: z.string().min(1).optional(),
295
- verificationToken: z.string().min(1).optional(),
397
+ verificationToken: NonBlankSecretSchema.optional(),
296
398
  webhook: z
297
399
  .strictObject({
298
400
  host: z.string().min(1).default("127.0.0.1"),
299
- path: z.string().min(1).default("/feishu/webhook"),
401
+ path: WebhookPathSchema.default("/feishu/webhook"),
300
402
  port: z.number().int().min(0).max(65_535).default(8795),
301
403
  publicUrl: HttpUrlSchema.optional(),
302
404
  })
@@ -311,14 +413,14 @@ const MattermostRecorderSchema = z.strictObject({
311
413
  });
312
414
  const MattermostWebhookSchema = z.strictObject({
313
415
  host: z.string().min(1).default("127.0.0.1"),
314
- path: z.string().min(1).default("/mattermost/webhook"),
416
+ path: WebhookPathSchema.default("/mattermost/webhook"),
315
417
  port: z.number().int().min(0).max(65_535).default(8793),
316
418
  publicUrl: HttpUrlSchema.optional(),
317
419
  });
318
420
  const MattermostWebsocketSchema = z.strictObject({
319
421
  enabled: z.boolean().optional(),
320
- maxReconnectDelayMs: z.number().int().min(0).optional(),
321
- reconnectDelayMs: z.number().int().min(0).optional(),
422
+ maxReconnectDelayMs: TimerMsSchema.min(0).optional(),
423
+ reconnectDelayMs: TimerMsSchema.min(0).optional(),
322
424
  });
323
425
  const MattermostConfigSchema = z.strictObject({
324
426
  baseUrl: HttpUrlSchema.optional(),
@@ -331,6 +433,11 @@ const MattermostConfigSchema = z.strictObject({
331
433
  path: "/mattermost/webhook",
332
434
  port: 8793,
333
435
  }),
436
+ webhookToken: z
437
+ .string()
438
+ .min(1)
439
+ .refine((value) => value.trim().length > 0, "webhookToken must not be blank")
440
+ .optional(),
334
441
  websocket: MattermostWebsocketSchema.optional(),
335
442
  });
336
443
  const ZaloRecorderSchema = z.strictObject({
@@ -338,12 +445,12 @@ const ZaloRecorderSchema = z.strictObject({
338
445
  });
339
446
  const ZaloWebhookSchema = z.strictObject({
340
447
  host: z.string().min(1).default("127.0.0.1"),
341
- path: z.string().min(1).default("/zalo/webhook"),
448
+ path: WebhookPathSchema.default("/zalo/webhook"),
342
449
  port: z.number().int().min(0).max(65_535).default(8794),
343
450
  publicUrl: HttpUrlSchema.optional(),
344
451
  });
345
452
  const ZaloConfigSchema = z.strictObject({
346
- botToken: z.string().min(1).optional(),
453
+ botToken: HeaderValueSchema.optional(),
347
454
  recorder: ZaloRecorderSchema.default({}),
348
455
  userName: z.string().min(1).optional(),
349
456
  webhook: ZaloWebhookSchema.default({
@@ -351,7 +458,7 @@ const ZaloConfigSchema = z.strictObject({
351
458
  path: "/zalo/webhook",
352
459
  port: 8794,
353
460
  }),
354
- webhookSecret: z.string().min(1).optional(),
461
+ webhookSecret: HeaderValueSchema.optional(),
355
462
  });
356
463
  const MatrixAccessTokenAuthSchema = z.strictObject({
357
464
  accessToken: z.string().min(1),
@@ -374,7 +481,7 @@ const MatrixConfigSchema = z.strictObject({
374
481
  webhook: z
375
482
  .strictObject({
376
483
  host: z.string().min(1).default("127.0.0.1"),
377
- path: z.string().min(1).default("/matrix/webhook"),
484
+ path: WebhookPathSchema.default("/matrix/webhook"),
378
485
  port: z.number().int().min(0).max(65_535).default(8797),
379
486
  publicUrl: HttpUrlSchema.optional(),
380
487
  })
@@ -386,14 +493,14 @@ const MatrixConfigSchema = z.strictObject({
386
493
  });
387
494
  const IMessageConfigSchema = z.strictObject({
388
495
  apiKey: z.string().min(1).optional(),
389
- gatewayDurationMs: z.number().int().min(1000).default(180_000),
496
+ gatewayDurationMs: TimerMsSchema.min(1000).default(180_000),
390
497
  local: z.boolean().optional(),
391
498
  recorder: z.strictObject({ path: z.string().min(1).optional() }).default({}),
392
499
  serverUrl: HttpUrlSchema.optional(),
393
500
  webhook: z
394
501
  .strictObject({
395
502
  host: z.string().min(1).default("127.0.0.1"),
396
- path: z.string().min(1).default("/imessage/webhook"),
503
+ path: WebhookPathSchema.default("/imessage/webhook"),
397
504
  port: z.number().int().min(0).max(65_535).default(8796),
398
505
  publicUrl: HttpUrlSchema.optional(),
399
506
  })
@@ -565,13 +672,11 @@ export const ProviderConfigSchema = z
565
672
  ...value,
566
673
  platform: value.platform ?? inferProviderPlatform(value.adapter) ?? "loopback",
567
674
  }));
568
- export const FixtureSchema = z.strictObject({
675
+ export const FixtureSchema = z
676
+ .strictObject({
569
677
  accountId: z.string().min(1).optional(),
570
678
  env: z.array(z.string().min(1)).default([]),
571
- id: z
572
- .string()
573
- .min(1)
574
- .regex(FIXTURE_ID_PATTERN, "fixture id must contain only letters, numbers, and hyphens"),
679
+ id: z.string().min(1).refine(isValidNonceFixtureId, NONCE_FIXTURE_ID_ERROR),
575
680
  inboundMatch: InboundMatchSchema.default({
576
681
  author: "assistant",
577
682
  nonce: "contains",
@@ -580,10 +685,20 @@ export const FixtureSchema = z.strictObject({
580
685
  mode: z.enum(FIXTURE_MODES),
581
686
  notes: z.string().optional(),
582
687
  provider: z.string().min(1),
583
- retries: z.number().int().min(0).default(0),
688
+ retries: z.number().int().min(0).max(MAX_FIXTURE_RETRIES).default(0),
584
689
  tags: z.array(z.string().min(1)).default([]),
585
690
  target: TargetSchema,
586
- timeoutMs: z.number().int().min(100).max(MAX_TIMER_MS).default(30_000),
691
+ timeoutMs: TimerMsSchema.min(100).default(30_000),
692
+ })
693
+ .superRefine((value, ctx) => {
694
+ const validationIssue = fixtureModeValidationIssue(value);
695
+ if (validationIssue) {
696
+ ctx.addIssue({
697
+ code: z.ZodIssueCode.custom,
698
+ message: validationIssue.message,
699
+ path: validationIssue.path,
700
+ });
701
+ }
587
702
  });
588
703
  const MANIFEST_EXTENSION_KEY_PATTERN = /^x-[a-z0-9][a-z0-9._-]*$/u;
589
704
  function omitManifestExtensions(value) {
@@ -602,6 +717,19 @@ const StrictManifestSchema = z
602
717
  userName: z.string().min(1).default("crabline"),
603
718
  })
604
719
  .superRefine((manifest, ctx) => {
720
+ for (const [providerId, provider] of Object.entries(manifest.providers)) {
721
+ if (provider.adapter !== "matrix" && provider.adapter !== "imessage") {
722
+ continue;
723
+ }
724
+ const webhook = provider[provider.adapter]?.webhook;
725
+ if (webhook?.publicUrl || (webhook && !isLoopbackHost(webhook.host))) {
726
+ ctx.addIssue({
727
+ code: z.ZodIssueCode.custom,
728
+ message: `${provider.adapter} provider ${providerId} does not support external webhook ingress`,
729
+ path: ["providers", providerId, provider.adapter, "webhook"],
730
+ });
731
+ }
732
+ }
605
733
  const seenFixtureIds = new Set();
606
734
  for (const [index, fixture] of manifest.fixtures.entries()) {
607
735
  if (seenFixtureIds.has(fixture.id)) {