@openclaw/crabline 0.1.9 → 0.1.10

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 (189) hide show
  1. package/README.md +47 -22
  2. package/assets/crabline-banner.svg +20 -0
  3. package/dist/src/bin/crabline.js.map +1 -1
  4. package/dist/src/cli/program.d.ts +25 -1
  5. package/dist/src/cli/program.js +579 -82
  6. package/dist/src/cli/program.js.map +1 -1
  7. package/dist/src/config/load.js +59 -6
  8. package/dist/src/config/load.js.map +1 -1
  9. package/dist/src/config/schema.d.ts +116 -92
  10. package/dist/src/config/schema.js +179 -73
  11. package/dist/src/config/schema.js.map +1 -1
  12. package/dist/src/core/errors.js +9 -2
  13. package/dist/src/core/errors.js.map +1 -1
  14. package/dist/src/core/exit-codes.js.map +1 -1
  15. package/dist/src/core/matcher.js +7 -6
  16. package/dist/src/core/matcher.js.map +1 -1
  17. package/dist/src/core/message-template.js.map +1 -1
  18. package/dist/src/core/nonces.d.ts +1 -0
  19. package/dist/src/core/nonces.js +4 -2
  20. package/dist/src/core/nonces.js.map +1 -1
  21. package/dist/src/core/reporters.d.ts +1 -0
  22. package/dist/src/core/reporters.js +42 -4
  23. package/dist/src/core/reporters.js.map +1 -1
  24. package/dist/src/core/run.d.ts +3 -0
  25. package/dist/src/core/run.js +415 -107
  26. package/dist/src/core/run.js.map +1 -1
  27. package/dist/src/core/safe-regex.d.ts +3 -0
  28. package/dist/src/core/safe-regex.js +21 -0
  29. package/dist/src/core/safe-regex.js.map +1 -0
  30. package/dist/src/index.d.ts +2 -2
  31. package/dist/src/index.js +1 -1
  32. package/dist/src/index.js.map +1 -1
  33. package/dist/src/openclaw/artifact-generation.d.ts +38 -0
  34. package/dist/src/openclaw/artifact-generation.js +314 -0
  35. package/dist/src/openclaw/artifact-generation.js.map +1 -0
  36. package/dist/src/openclaw/bridges/matrix.js +97 -12
  37. package/dist/src/openclaw/bridges/matrix.js.map +1 -1
  38. package/dist/src/openclaw/bridges/mattermost.js +32 -10
  39. package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
  40. package/dist/src/openclaw/bridges/signal.js +55 -18
  41. package/dist/src/openclaw/bridges/signal.js.map +1 -1
  42. package/dist/src/openclaw/bridges/slack.js +85 -7
  43. package/dist/src/openclaw/bridges/slack.js.map +1 -1
  44. package/dist/src/openclaw/bridges/telegram.js +124 -30
  45. package/dist/src/openclaw/bridges/telegram.js.map +1 -1
  46. package/dist/src/openclaw/bridges/whatsapp.js +78 -17
  47. package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
  48. package/dist/src/openclaw/bridges/zalo.js +14 -5
  49. package/dist/src/openclaw/bridges/zalo.js.map +1 -1
  50. package/dist/src/openclaw/outbound-contract.d.ts +5 -0
  51. package/dist/src/openclaw/outbound-contract.js +40 -0
  52. package/dist/src/openclaw/outbound-contract.js.map +1 -0
  53. package/dist/src/openclaw/private-file.d.ts +26 -0
  54. package/dist/src/openclaw/private-file.js +411 -0
  55. package/dist/src/openclaw/private-file.js.map +1 -0
  56. package/dist/src/openclaw/shared.d.ts +21 -2
  57. package/dist/src/openclaw/shared.js +115 -18
  58. package/dist/src/openclaw/shared.js.map +1 -1
  59. package/dist/src/openclaw/smoke-lock.d.ts +63 -0
  60. package/dist/src/openclaw/smoke-lock.js +949 -0
  61. package/dist/src/openclaw/smoke-lock.js.map +1 -0
  62. package/dist/src/openclaw.d.ts +16 -8
  63. package/dist/src/openclaw.js +194 -52
  64. package/dist/src/openclaw.js.map +1 -1
  65. package/dist/src/providers/builtin/discord.d.ts +10 -1
  66. package/dist/src/providers/builtin/discord.js +144 -18
  67. package/dist/src/providers/builtin/discord.js.map +1 -1
  68. package/dist/src/providers/builtin/external-webhook-auth.d.ts +11 -0
  69. package/dist/src/providers/builtin/external-webhook-auth.js +10 -0
  70. package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -0
  71. package/dist/src/providers/builtin/feishu.d.ts +19 -2
  72. package/dist/src/providers/builtin/feishu.js +307 -25
  73. package/dist/src/providers/builtin/feishu.js.map +1 -1
  74. package/dist/src/providers/builtin/googlechat.d.ts +20 -1
  75. package/dist/src/providers/builtin/googlechat.js +178 -19
  76. package/dist/src/providers/builtin/googlechat.js.map +1 -1
  77. package/dist/src/providers/builtin/imessage.d.ts +4 -0
  78. package/dist/src/providers/builtin/imessage.js +26 -11
  79. package/dist/src/providers/builtin/imessage.js.map +1 -1
  80. package/dist/src/providers/builtin/loopback.js +92 -30
  81. package/dist/src/providers/builtin/loopback.js.map +1 -1
  82. package/dist/src/providers/builtin/matrix.d.ts +11 -1
  83. package/dist/src/providers/builtin/matrix.js +53 -29
  84. package/dist/src/providers/builtin/matrix.js.map +1 -1
  85. package/dist/src/providers/builtin/mattermost.d.ts +10 -0
  86. package/dist/src/providers/builtin/mattermost.js +38 -15
  87. package/dist/src/providers/builtin/mattermost.js.map +1 -1
  88. package/dist/src/providers/builtin/msteams.d.ts +17 -1
  89. package/dist/src/providers/builtin/msteams.js +113 -14
  90. package/dist/src/providers/builtin/msteams.js.map +1 -1
  91. package/dist/src/providers/builtin/native-local-mock.d.ts +2 -9
  92. package/dist/src/providers/builtin/native-local-mock.js +12 -35
  93. package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
  94. package/dist/src/providers/builtin/script.d.ts +11 -15
  95. package/dist/src/providers/builtin/script.js +566 -104
  96. package/dist/src/providers/builtin/script.js.map +1 -1
  97. package/dist/src/providers/builtin/slack.d.ts +6 -0
  98. package/dist/src/providers/builtin/slack.js +99 -40
  99. package/dist/src/providers/builtin/slack.js.map +1 -1
  100. package/dist/src/providers/builtin/telegram.d.ts +6 -0
  101. package/dist/src/providers/builtin/telegram.js +64 -43
  102. package/dist/src/providers/builtin/telegram.js.map +1 -1
  103. package/dist/src/providers/builtin/whatsapp.d.ts +28 -8
  104. package/dist/src/providers/builtin/whatsapp.js +567 -30
  105. package/dist/src/providers/builtin/whatsapp.js.map +1 -1
  106. package/dist/src/providers/builtin/zalo.js +30 -19
  107. package/dist/src/providers/builtin/zalo.js.map +1 -1
  108. package/dist/src/providers/catalog.d.ts +3 -3
  109. package/dist/src/providers/catalog.js +3 -3
  110. package/dist/src/providers/catalog.js.map +1 -1
  111. package/dist/src/providers/local-mock.d.ts +11 -2
  112. package/dist/src/providers/local-mock.js +322 -96
  113. package/dist/src/providers/local-mock.js.map +1 -1
  114. package/dist/src/providers/native-ids.d.ts +1 -0
  115. package/dist/src/providers/native-ids.js +6 -1
  116. package/dist/src/providers/native-ids.js.map +1 -1
  117. package/dist/src/providers/recorder.d.ts +30 -2
  118. package/dist/src/providers/recorder.js +539 -70
  119. package/dist/src/providers/recorder.js.map +1 -1
  120. package/dist/src/providers/registry.d.ts +25 -1
  121. package/dist/src/providers/registry.js +280 -40
  122. package/dist/src/providers/registry.js.map +1 -1
  123. package/dist/src/providers/signed-jwt.d.ts +29 -0
  124. package/dist/src/providers/signed-jwt.js +202 -0
  125. package/dist/src/providers/signed-jwt.js.map +1 -0
  126. package/dist/src/providers/slack-ids.js.map +1 -1
  127. package/dist/src/providers/target-normalizers.d.ts +26 -0
  128. package/dist/src/providers/target-normalizers.js +276 -0
  129. package/dist/src/providers/target-normalizers.js.map +1 -0
  130. package/dist/src/providers/types.d.ts +5 -0
  131. package/dist/src/providers/types.js.map +1 -1
  132. package/dist/src/providers/webhook-server.d.ts +2 -0
  133. package/dist/src/providers/webhook-server.js +72 -25
  134. package/dist/src/providers/webhook-server.js.map +1 -1
  135. package/dist/src/servers/http.d.ts +23 -3
  136. package/dist/src/servers/http.js +213 -20
  137. package/dist/src/servers/http.js.map +1 -1
  138. package/dist/src/servers/index.js +20 -21
  139. package/dist/src/servers/index.js.map +1 -1
  140. package/dist/src/servers/matrix.js +465 -51
  141. package/dist/src/servers/matrix.js.map +1 -1
  142. package/dist/src/servers/mattermost.d.ts +5 -0
  143. package/dist/src/servers/mattermost.js +306 -58
  144. package/dist/src/servers/mattermost.js.map +1 -1
  145. package/dist/src/servers/pending-events.d.ts +2 -0
  146. package/dist/src/servers/pending-events.js +11 -0
  147. package/dist/src/servers/pending-events.js.map +1 -0
  148. package/dist/src/servers/recorder.d.ts +5 -0
  149. package/dist/src/servers/recorder.js +46 -3
  150. package/dist/src/servers/recorder.js.map +1 -1
  151. package/dist/src/servers/signal.d.ts +2 -0
  152. package/dist/src/servers/signal.js +444 -56
  153. package/dist/src/servers/signal.js.map +1 -1
  154. package/dist/src/servers/slack.d.ts +5 -0
  155. package/dist/src/servers/slack.js +515 -49
  156. package/dist/src/servers/slack.js.map +1 -1
  157. package/dist/src/servers/telegram.d.ts +111 -0
  158. package/dist/src/servers/telegram.js +811 -152
  159. package/dist/src/servers/telegram.js.map +1 -1
  160. package/dist/src/servers/webhook-target.d.ts +40 -0
  161. package/dist/src/servers/webhook-target.js +349 -0
  162. package/dist/src/servers/webhook-target.js.map +1 -0
  163. package/dist/src/servers/websocket.d.ts +2 -0
  164. package/dist/src/servers/websocket.js +29 -0
  165. package/dist/src/servers/websocket.js.map +1 -0
  166. package/dist/src/servers/whatsapp-baileys-websocket.d.ts +65 -1
  167. package/dist/src/servers/whatsapp-baileys-websocket.js +723 -99
  168. package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
  169. package/dist/src/servers/whatsapp-jid.d.ts +5 -0
  170. package/dist/src/servers/whatsapp-jid.js +32 -0
  171. package/dist/src/servers/whatsapp-jid.js.map +1 -0
  172. package/dist/src/servers/whatsapp-wire/binary-node.d.ts +5 -0
  173. package/dist/src/servers/whatsapp-wire/binary-node.js +87 -20
  174. package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
  175. package/dist/src/servers/whatsapp-wire/crypto.d.ts +6 -1
  176. package/dist/src/servers/whatsapp-wire/crypto.js +99 -45
  177. package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
  178. package/dist/src/servers/whatsapp-wire/handshake.js +39 -4
  179. package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
  180. package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
  181. package/dist/src/servers/whatsapp.d.ts +12 -7
  182. package/dist/src/servers/whatsapp.js +269 -107
  183. package/dist/src/servers/whatsapp.js.map +1 -1
  184. package/dist/src/servers/zalo.d.ts +14 -0
  185. package/dist/src/servers/zalo.js +521 -88
  186. package/dist/src/servers/zalo.js.map +1 -1
  187. package/docs/channel-setup.md +197 -19
  188. package/fixtures/examples/crabline.example.yaml +10 -0
  189. package/package.json +20 -15
@@ -1,6 +1,8 @@
1
1
  import { z } from "zod";
2
+ import { inboundRegexSafetyError } from "../core/safe-regex.js";
2
3
  export const FIXTURE_MODES = ["probe", "send", "roundtrip", "agent"];
3
4
  const FIXTURE_ID_PATTERN = /^[a-z0-9-]+$/i;
5
+ const MAX_TIMER_MS = 2_147_483_647;
4
6
  export const INBOUND_AUTHORS = ["assistant", "user", "system", "any"];
5
7
  export const INBOUND_STRATEGIES = ["contains", "exact", "regex"];
6
8
  export const INBOUND_NONCE_MODES = ["contains", "exact", "ignore"];
@@ -44,66 +46,115 @@ export const PROVIDER_PLATFORMS = [
44
46
  "zalo",
45
47
  "zalouser",
46
48
  ];
49
+ const HttpUrlSchema = z
50
+ .string()
51
+ .url()
52
+ .refine((value) => {
53
+ try {
54
+ const protocol = new URL(value).protocol;
55
+ return protocol === "http:" || protocol === "https:";
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ }, "URL must use http or https");
47
61
  function inferProviderPlatform(adapter) {
48
62
  if (adapter === "script") {
49
63
  return undefined;
50
64
  }
51
65
  return adapter;
52
66
  }
53
- const TargetSchema = z.object({
67
+ const TargetSchema = z.strictObject({
54
68
  id: z.string().min(1),
55
69
  channelId: z.string().min(1).optional(),
56
70
  threadId: z.string().min(1).optional(),
57
71
  behavior: z.enum(["agent", "echo", "sink"]).optional(),
58
72
  metadata: z.record(z.string(), z.string()).default({}),
59
73
  });
60
- const InboundMatchSchema = z.object({
74
+ const InboundMatchSchema = z
75
+ .strictObject({
61
76
  author: z.enum(INBOUND_AUTHORS).default("assistant"),
62
77
  nonce: z.enum(INBOUND_NONCE_MODES).default("contains"),
63
78
  pattern: z.string().min(1).optional(),
64
79
  strategy: z.enum(INBOUND_STRATEGIES).default("contains"),
80
+ })
81
+ .superRefine((value, ctx) => {
82
+ if (value.strategy === "exact" && value.pattern && value.nonce !== "ignore") {
83
+ ctx.addIssue({
84
+ code: z.ZodIssueCode.custom,
85
+ message: "inboundMatch.strategy=exact requires inboundMatch.nonce=ignore",
86
+ path: ["nonce"],
87
+ });
88
+ }
89
+ if (value.strategy !== "regex" || !value.pattern) {
90
+ return;
91
+ }
92
+ try {
93
+ RegExp(value.pattern, "u");
94
+ }
95
+ catch {
96
+ ctx.addIssue({
97
+ code: z.ZodIssueCode.custom,
98
+ message: "inboundMatch.pattern must be a valid Unicode regular expression",
99
+ path: ["pattern"],
100
+ });
101
+ return;
102
+ }
103
+ const safetyError = inboundRegexSafetyError(value.pattern);
104
+ if (safetyError) {
105
+ ctx.addIssue({
106
+ code: z.ZodIssueCode.custom,
107
+ message: `inboundMatch.pattern ${safetyError}`,
108
+ path: ["pattern"],
109
+ });
110
+ }
65
111
  });
66
- const ScriptCommandsSchema = z.object({
67
- probe: z.string().min(1).optional(),
68
- send: z.string().min(1).optional(),
69
- waitForInbound: z.string().min(1).optional(),
70
- watch: z.string().min(1).optional(),
112
+ const ScriptCommandSchema = z
113
+ .string()
114
+ .min(1)
115
+ .refine((value) => value.trim().length > 0, "script command must not be blank");
116
+ const ScriptCommandsSchema = z.strictObject({
117
+ probe: ScriptCommandSchema.optional(),
118
+ send: ScriptCommandSchema.optional(),
119
+ waitForInbound: ScriptCommandSchema.optional(),
120
+ watch: ScriptCommandSchema.optional(),
71
121
  });
72
- const LoopbackConfigSchema = z.object({
73
- delayMs: z.number().int().min(0).default(25),
122
+ const LoopbackConfigSchema = z.strictObject({
123
+ delayMs: z.number().int().min(0).max(MAX_TIMER_MS).default(25),
74
124
  });
75
- const ScriptConfigSchema = z.object({
125
+ const ScriptConfigSchema = z.strictObject({
76
126
  commands: ScriptCommandsSchema,
77
127
  cwd: z.string().min(1).optional(),
78
128
  shell: z.string().min(1).optional(),
79
129
  });
80
- const SlackRecorderSchema = z.object({
130
+ const SlackRecorderSchema = z.strictObject({
81
131
  path: z.string().min(1).optional(),
82
132
  });
83
- const SlackWebhookSchema = z.object({
133
+ const SlackWebhookSchema = z.strictObject({
84
134
  host: z.string().min(1).default("127.0.0.1"),
85
135
  path: z.string().min(1).default("/slack/events"),
86
136
  port: z.number().int().min(0).max(65_535).default(8787),
87
- publicUrl: z.string().url().optional(),
137
+ publicUrl: HttpUrlSchema.optional(),
88
138
  });
89
- const SlackConfigSchema = z.object({
139
+ const SlackConfigSchema = z.strictObject({
90
140
  recorder: SlackRecorderSchema.default({}),
141
+ signingSecret: z.string().min(1).optional(),
91
142
  webhook: SlackWebhookSchema.default({
92
143
  host: "127.0.0.1",
93
144
  path: "/slack/events",
94
145
  port: 8787,
95
146
  }),
96
147
  });
97
- const DiscordRecorderSchema = z.object({
148
+ const DiscordRecorderSchema = z.strictObject({
98
149
  path: z.string().min(1).optional(),
99
150
  });
100
- const DiscordWebhookSchema = z.object({
151
+ const DiscordWebhookSchema = z.strictObject({
101
152
  host: z.string().min(1).default("127.0.0.1"),
102
153
  path: z.string().min(1).default("/discord/interactions"),
103
154
  port: z.number().int().min(0).max(65_535).default(8788),
104
- publicUrl: z.string().url().optional(),
155
+ publicUrl: HttpUrlSchema.optional(),
105
156
  });
106
- const DiscordConfigSchema = z.object({
157
+ const DiscordConfigSchema = z.strictObject({
107
158
  applicationId: z.string().min(1).optional(),
108
159
  botToken: z.string().min(1).optional(),
109
160
  gatewayDurationMs: z.number().int().min(1000).default(180_000),
@@ -116,18 +167,18 @@ const DiscordConfigSchema = z.object({
116
167
  port: 8788,
117
168
  }),
118
169
  });
119
- const WhatsAppRecorderSchema = z.object({
170
+ const WhatsAppRecorderSchema = z.strictObject({
120
171
  path: z.string().min(1).optional(),
121
172
  });
122
- const WhatsAppWebhookSchema = z.object({
173
+ const WhatsAppWebhookSchema = z.strictObject({
123
174
  host: z.string().min(1).default("127.0.0.1"),
124
175
  path: z.string().min(1).default("/whatsapp/webhook"),
125
176
  port: z.number().int().min(0).max(65_535).default(8789),
126
- publicUrl: z.string().url().optional(),
177
+ publicUrl: HttpUrlSchema.optional(),
127
178
  });
128
- const WhatsAppConfigSchema = z.object({
179
+ const WhatsAppConfigSchema = z.strictObject({
129
180
  accessToken: z.string().min(1).optional(),
130
- apiUrl: z.string().url().optional(),
181
+ apiUrl: HttpUrlSchema.optional(),
131
182
  apiVersion: z.string().min(1).optional(),
132
183
  appSecret: z.string().min(1).optional(),
133
184
  phoneNumberId: z.string().min(1).optional(),
@@ -140,21 +191,21 @@ const WhatsAppConfigSchema = z.object({
140
191
  port: 8789,
141
192
  }),
142
193
  });
143
- const MsTeamsFederatedSchema = z.object({
194
+ const MsTeamsFederatedSchema = z.strictObject({
144
195
  clientAudience: z.string().min(1).optional(),
145
196
  clientId: z.string().min(1),
146
197
  });
147
- const MsTeamsRecorderSchema = z.object({
198
+ const MsTeamsRecorderSchema = z.strictObject({
148
199
  path: z.string().min(1).optional(),
149
200
  });
150
- const MsTeamsWebhookSchema = z.object({
201
+ const MsTeamsWebhookSchema = z.strictObject({
151
202
  host: z.string().min(1).default("127.0.0.1"),
152
203
  path: z.string().min(1).default("/msteams/webhook"),
153
204
  port: z.number().int().min(0).max(65_535).default(8791),
154
- publicUrl: z.string().url().optional(),
205
+ publicUrl: HttpUrlSchema.optional(),
155
206
  });
156
- const MsTeamsConfigSchema = z.object({
157
- apiUrl: z.string().url().optional(),
207
+ const MsTeamsConfigSchema = z.strictObject({
208
+ apiUrl: HttpUrlSchema.optional(),
158
209
  appId: z.string().min(1).optional(),
159
210
  appPassword: z.string().min(1).optional(),
160
211
  appTenantId: z.string().min(1).optional(),
@@ -176,23 +227,24 @@ const GoogleChatCredentialsSchema = z
176
227
  project_id: z.string().min(1).optional(),
177
228
  })
178
229
  .passthrough();
179
- const GoogleChatRecorderSchema = z.object({
230
+ const GoogleChatRecorderSchema = z.strictObject({
180
231
  path: z.string().min(1).optional(),
181
232
  });
182
- const GoogleChatWebhookSchema = z.object({
233
+ const GoogleChatWebhookSchema = z.strictObject({
183
234
  host: z.string().min(1).default("127.0.0.1"),
184
235
  path: z.string().min(1).default("/googlechat/webhook"),
185
236
  port: z.number().int().min(0).max(65_535).default(8792),
186
- publicUrl: z.string().url().optional(),
237
+ publicUrl: HttpUrlSchema.optional(),
187
238
  });
188
- const GoogleChatConfigSchema = z.object({
189
- apiUrl: z.string().url().optional(),
239
+ const GoogleChatConfigSchema = z.strictObject({
240
+ apiUrl: HttpUrlSchema.optional(),
190
241
  credentials: GoogleChatCredentialsSchema.optional(),
191
242
  disableSignatureVerification: z.boolean().optional(),
192
- endpointUrl: z.string().url().optional(),
243
+ endpointUrl: HttpUrlSchema.optional(),
193
244
  googleChatProjectNumber: z.string().min(1).optional(),
194
245
  impersonateUser: z.string().min(1).optional(),
195
246
  pubsubAudience: z.string().min(1).optional(),
247
+ pubsubServiceAccountEmail: z.string().min(1).optional(),
196
248
  pubsubTopic: z.string().min(1).optional(),
197
249
  recorder: GoogleChatRecorderSchema.default({}),
198
250
  useApplicationDefaultCredentials: z.boolean().optional(),
@@ -203,7 +255,7 @@ const GoogleChatConfigSchema = z.object({
203
255
  port: 8792,
204
256
  }),
205
257
  });
206
- const TelegramLongPollingSchema = z.object({
258
+ const TelegramLongPollingSchema = z.strictObject({
207
259
  allowedUpdates: z.array(z.string().min(1)).optional(),
208
260
  deleteWebhook: z.boolean().optional(),
209
261
  dropPendingUpdates: z.boolean().optional(),
@@ -211,17 +263,17 @@ const TelegramLongPollingSchema = z.object({
211
263
  retryDelayMs: z.number().int().min(0).optional(),
212
264
  timeout: z.number().int().min(0).optional(),
213
265
  });
214
- const TelegramRecorderSchema = z.object({
266
+ const TelegramRecorderSchema = z.strictObject({
215
267
  path: z.string().min(1).optional(),
216
268
  });
217
- const TelegramWebhookSchema = z.object({
269
+ const TelegramWebhookSchema = z.strictObject({
218
270
  host: z.string().min(1).default("127.0.0.1"),
219
271
  path: z.string().min(1).default("/telegram/webhook"),
220
272
  port: z.number().int().min(0).max(65_535).default(8790),
221
- publicUrl: z.string().url().optional(),
273
+ publicUrl: HttpUrlSchema.optional(),
222
274
  });
223
- const TelegramConfigSchema = z.object({
224
- apiUrl: z.string().url().optional(),
275
+ const TelegramConfigSchema = z.strictObject({
276
+ apiUrl: HttpUrlSchema.optional(),
225
277
  botToken: z.string().min(1).optional(),
226
278
  longPolling: TelegramLongPollingSchema.optional(),
227
279
  mode: z.enum(["auto", "polling", "webhook"]).default("auto"),
@@ -234,17 +286,19 @@ const TelegramConfigSchema = z.object({
234
286
  port: 8790,
235
287
  }),
236
288
  });
237
- const FeishuConfigSchema = z.object({
289
+ const FeishuConfigSchema = z.strictObject({
238
290
  appId: z.string().min(1).optional(),
239
291
  appSecret: z.string().min(1).optional(),
240
- recorder: z.object({ path: z.string().min(1).optional() }).default({}),
292
+ encryptKey: z.string().min(1).optional(),
293
+ recorder: z.strictObject({ path: z.string().min(1).optional() }).default({}),
241
294
  userName: z.string().min(1).optional(),
295
+ verificationToken: z.string().min(1).optional(),
242
296
  webhook: z
243
- .object({
297
+ .strictObject({
244
298
  host: z.string().min(1).default("127.0.0.1"),
245
299
  path: z.string().min(1).default("/feishu/webhook"),
246
300
  port: z.number().int().min(0).max(65_535).default(8795),
247
- publicUrl: z.string().url().optional(),
301
+ publicUrl: HttpUrlSchema.optional(),
248
302
  })
249
303
  .default({
250
304
  host: "127.0.0.1",
@@ -252,24 +306,24 @@ const FeishuConfigSchema = z.object({
252
306
  port: 8795,
253
307
  }),
254
308
  });
255
- const MattermostRecorderSchema = z.object({
309
+ const MattermostRecorderSchema = z.strictObject({
256
310
  path: z.string().min(1).optional(),
257
311
  });
258
- const MattermostWebhookSchema = z.object({
312
+ const MattermostWebhookSchema = z.strictObject({
259
313
  host: z.string().min(1).default("127.0.0.1"),
260
314
  path: z.string().min(1).default("/mattermost/webhook"),
261
315
  port: z.number().int().min(0).max(65_535).default(8793),
262
- publicUrl: z.string().url().optional(),
316
+ publicUrl: HttpUrlSchema.optional(),
263
317
  });
264
- const MattermostWebsocketSchema = z.object({
318
+ const MattermostWebsocketSchema = z.strictObject({
265
319
  enabled: z.boolean().optional(),
266
320
  maxReconnectDelayMs: z.number().int().min(0).optional(),
267
321
  reconnectDelayMs: z.number().int().min(0).optional(),
268
322
  });
269
- const MattermostConfigSchema = z.object({
270
- baseUrl: z.string().url().optional(),
323
+ const MattermostConfigSchema = z.strictObject({
324
+ baseUrl: HttpUrlSchema.optional(),
271
325
  botToken: z.string().min(1).optional(),
272
- callbackUrl: z.string().url().optional(),
326
+ callbackUrl: HttpUrlSchema.optional(),
273
327
  recorder: MattermostRecorderSchema.default({}),
274
328
  userName: z.string().min(1).optional(),
275
329
  webhook: MattermostWebhookSchema.default({
@@ -279,16 +333,16 @@ const MattermostConfigSchema = z.object({
279
333
  }),
280
334
  websocket: MattermostWebsocketSchema.optional(),
281
335
  });
282
- const ZaloRecorderSchema = z.object({
336
+ const ZaloRecorderSchema = z.strictObject({
283
337
  path: z.string().min(1).optional(),
284
338
  });
285
- const ZaloWebhookSchema = z.object({
339
+ const ZaloWebhookSchema = z.strictObject({
286
340
  host: z.string().min(1).default("127.0.0.1"),
287
341
  path: z.string().min(1).default("/zalo/webhook"),
288
342
  port: z.number().int().min(0).max(65_535).default(8794),
289
- publicUrl: z.string().url().optional(),
343
+ publicUrl: HttpUrlSchema.optional(),
290
344
  });
291
- const ZaloConfigSchema = z.object({
345
+ const ZaloConfigSchema = z.strictObject({
292
346
  botToken: z.string().min(1).optional(),
293
347
  recorder: ZaloRecorderSchema.default({}),
294
348
  userName: z.string().min(1).optional(),
@@ -299,30 +353,30 @@ const ZaloConfigSchema = z.object({
299
353
  }),
300
354
  webhookSecret: z.string().min(1).optional(),
301
355
  });
302
- const MatrixAccessTokenAuthSchema = z.object({
356
+ const MatrixAccessTokenAuthSchema = z.strictObject({
303
357
  accessToken: z.string().min(1),
304
358
  type: z.literal("accessToken"),
305
359
  userID: z.string().min(1).optional(),
306
360
  });
307
- const MatrixPasswordAuthSchema = z.object({
361
+ const MatrixPasswordAuthSchema = z.strictObject({
308
362
  password: z.string().min(1),
309
363
  type: z.literal("password"),
310
364
  userID: z.string().min(1).optional(),
311
365
  username: z.string().min(1),
312
366
  });
313
- const MatrixConfigSchema = z.object({
367
+ const MatrixConfigSchema = z.strictObject({
314
368
  auth: z.union([MatrixAccessTokenAuthSchema, MatrixPasswordAuthSchema]).optional(),
315
- baseURL: z.string().url().optional(),
369
+ baseURL: HttpUrlSchema.optional(),
316
370
  commandPrefix: z.string().min(1).optional(),
317
- recorder: z.object({ path: z.string().min(1).optional() }).default({}),
371
+ recorder: z.strictObject({ path: z.string().min(1).optional() }).default({}),
318
372
  recoveryKey: z.string().min(1).optional(),
319
373
  roomAllowlist: z.array(z.string().min(1)).optional(),
320
374
  webhook: z
321
- .object({
375
+ .strictObject({
322
376
  host: z.string().min(1).default("127.0.0.1"),
323
377
  path: z.string().min(1).default("/matrix/webhook"),
324
378
  port: z.number().int().min(0).max(65_535).default(8797),
325
- publicUrl: z.string().url().optional(),
379
+ publicUrl: HttpUrlSchema.optional(),
326
380
  })
327
381
  .default({
328
382
  host: "127.0.0.1",
@@ -330,18 +384,18 @@ const MatrixConfigSchema = z.object({
330
384
  port: 8797,
331
385
  }),
332
386
  });
333
- const IMessageConfigSchema = z.object({
387
+ const IMessageConfigSchema = z.strictObject({
334
388
  apiKey: z.string().min(1).optional(),
335
389
  gatewayDurationMs: z.number().int().min(1000).default(180_000),
336
390
  local: z.boolean().optional(),
337
- recorder: z.object({ path: z.string().min(1).optional() }).default({}),
338
- serverUrl: z.string().url().optional(),
391
+ recorder: z.strictObject({ path: z.string().min(1).optional() }).default({}),
392
+ serverUrl: HttpUrlSchema.optional(),
339
393
  webhook: z
340
- .object({
394
+ .strictObject({
341
395
  host: z.string().min(1).default("127.0.0.1"),
342
396
  path: z.string().min(1).default("/imessage/webhook"),
343
397
  port: z.number().int().min(0).max(65_535).default(8796),
344
- publicUrl: z.string().url().optional(),
398
+ publicUrl: HttpUrlSchema.optional(),
345
399
  })
346
400
  .default({
347
401
  host: "127.0.0.1",
@@ -350,7 +404,7 @@ const IMessageConfigSchema = z.object({
350
404
  }),
351
405
  });
352
406
  export const ProviderConfigSchema = z
353
- .object({
407
+ .strictObject({
354
408
  adapter: z.enum(BUILTIN_ADAPTERS),
355
409
  capabilities: z.array(z.enum(FIXTURE_MODES)).default(["probe", "send", "roundtrip", "agent"]),
356
410
  discord: DiscordConfigSchema.optional(),
@@ -373,6 +427,18 @@ export const ProviderConfigSchema = z
373
427
  })
374
428
  .superRefine((value, ctx) => {
375
429
  const platform = value.platform ?? inferProviderPlatform(value.adapter);
430
+ for (const adapterConfigKey of BUILTIN_ADAPTERS) {
431
+ if (adapterConfigKey === value.adapter || value[adapterConfigKey] === undefined) {
432
+ continue;
433
+ }
434
+ ctx.addIssue({
435
+ code: z.ZodIssueCode.custom,
436
+ message: value.adapter === "script"
437
+ ? `script adapter cannot use ${adapterConfigKey} configuration; configure provider behavior through script.commands`
438
+ : `${adapterConfigKey} configuration requires adapter=${adapterConfigKey}, got adapter=${value.adapter}`,
439
+ path: [adapterConfigKey],
440
+ });
441
+ }
376
442
  if (value.adapter === "script" && !value.script) {
377
443
  ctx.addIssue({
378
444
  code: z.ZodIssueCode.custom,
@@ -499,7 +565,7 @@ export const ProviderConfigSchema = z
499
565
  ...value,
500
566
  platform: value.platform ?? inferProviderPlatform(value.adapter) ?? "loopback",
501
567
  }));
502
- export const FixtureSchema = z.object({
568
+ export const FixtureSchema = z.strictObject({
503
569
  accountId: z.string().min(1).optional(),
504
570
  env: z.array(z.string().min(1)).default([]),
505
571
  id: z
@@ -517,12 +583,52 @@ export const FixtureSchema = z.object({
517
583
  retries: z.number().int().min(0).default(0),
518
584
  tags: z.array(z.string().min(1)).default([]),
519
585
  target: TargetSchema,
520
- timeoutMs: z.number().int().min(100).default(30_000),
586
+ timeoutMs: z.number().int().min(100).max(MAX_TIMER_MS).default(30_000),
521
587
  });
522
- export const ManifestSchema = z.object({
588
+ const MANIFEST_EXTENSION_KEY_PATTERN = /^x-[a-z0-9][a-z0-9._-]*$/u;
589
+ function omitManifestExtensions(value) {
590
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
591
+ return value;
592
+ }
593
+ const entries = Object.entries(value);
594
+ const manifestEntries = entries.filter(([key]) => !MANIFEST_EXTENSION_KEY_PATTERN.test(key));
595
+ return manifestEntries.length === entries.length ? value : Object.fromEntries(manifestEntries);
596
+ }
597
+ const StrictManifestSchema = z
598
+ .strictObject({
523
599
  configVersion: z.literal(1).default(1),
524
600
  fixtures: z.array(FixtureSchema).default([]),
525
- providers: z.record(z.string(), ProviderConfigSchema).default({}),
601
+ providers: z.record(z.string().min(1), ProviderConfigSchema).default({}),
526
602
  userName: z.string().min(1).default("crabline"),
603
+ })
604
+ .superRefine((manifest, ctx) => {
605
+ const seenFixtureIds = new Set();
606
+ for (const [index, fixture] of manifest.fixtures.entries()) {
607
+ if (seenFixtureIds.has(fixture.id)) {
608
+ ctx.addIssue({
609
+ code: z.ZodIssueCode.custom,
610
+ message: `duplicate fixture id: ${fixture.id}`,
611
+ path: ["fixtures", index, "id"],
612
+ });
613
+ }
614
+ seenFixtureIds.add(fixture.id);
615
+ if (!Object.hasOwn(manifest.providers, fixture.provider)) {
616
+ ctx.addIssue({
617
+ code: z.ZodIssueCode.custom,
618
+ message: `fixture ${fixture.id} references unknown provider ${fixture.provider}`,
619
+ path: ["fixtures", index, "provider"],
620
+ });
621
+ continue;
622
+ }
623
+ const provider = manifest.providers[fixture.provider];
624
+ if (!provider.capabilities.includes(fixture.mode)) {
625
+ ctx.addIssue({
626
+ code: z.ZodIssueCode.custom,
627
+ message: `fixture ${fixture.id} uses mode ${fixture.mode}, but provider ${fixture.provider} declares capabilities ${provider.capabilities.join(", ") || "(none)"}`,
628
+ path: ["fixtures", index, "mode"],
629
+ });
630
+ }
631
+ }
527
632
  });
633
+ export const ManifestSchema = z.preprocess(omitManifestExtensions, StrictManifestSchema);
528
634
  //# sourceMappingURL=schema.js.map