@openclaw/crabline 0.1.11 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/README.md +99 -12
  2. package/dist/src/bin/crabline.js +3 -1
  3. package/dist/src/bin/crabline.js.map +1 -1
  4. package/dist/src/cli/program.d.ts +5 -1
  5. package/dist/src/cli/program.js +264 -50
  6. package/dist/src/cli/program.js.map +1 -1
  7. package/dist/src/config/load.js +39 -3
  8. package/dist/src/config/load.js.map +1 -1
  9. package/dist/src/config/schema.d.ts +20 -0
  10. package/dist/src/config/schema.js +163 -35
  11. package/dist/src/config/schema.js.map +1 -1
  12. package/dist/src/core/errors.d.ts +1 -0
  13. package/dist/src/core/errors.js +6 -3
  14. package/dist/src/core/errors.js.map +1 -1
  15. package/dist/src/core/http-path.d.ts +1 -0
  16. package/dist/src/core/http-path.js +14 -0
  17. package/dist/src/core/http-path.js.map +1 -0
  18. package/dist/src/core/matcher.d.ts +3 -1
  19. package/dist/src/core/matcher.js +58 -2
  20. package/dist/src/core/matcher.js.map +1 -1
  21. package/dist/src/core/nonces.d.ts +2 -0
  22. package/dist/src/core/nonces.js +11 -1
  23. package/dist/src/core/nonces.js.map +1 -1
  24. package/dist/src/core/reporters.d.ts +5 -0
  25. package/dist/src/core/reporters.js +26 -2
  26. package/dist/src/core/reporters.js.map +1 -1
  27. package/dist/src/core/run.d.ts +3 -1
  28. package/dist/src/core/run.js +178 -66
  29. package/dist/src/core/run.js.map +1 -1
  30. package/dist/src/matrix-ids.d.ts +5 -0
  31. package/dist/src/matrix-ids.js +83 -0
  32. package/dist/src/matrix-ids.js.map +1 -0
  33. package/dist/src/openclaw/artifact-generation.d.ts +7 -2
  34. package/dist/src/openclaw/artifact-generation.js +399 -27
  35. package/dist/src/openclaw/artifact-generation.js.map +1 -1
  36. package/dist/src/openclaw/bridges/matrix.js +21 -4
  37. package/dist/src/openclaw/bridges/matrix.js.map +1 -1
  38. package/dist/src/openclaw/bridges/mattermost.js +13 -8
  39. package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
  40. package/dist/src/openclaw/bridges/probe-response.d.ts +1 -0
  41. package/dist/src/openclaw/bridges/probe-response.js +5 -0
  42. package/dist/src/openclaw/bridges/probe-response.js.map +1 -0
  43. package/dist/src/openclaw/bridges/signal.js +15 -6
  44. package/dist/src/openclaw/bridges/signal.js.map +1 -1
  45. package/dist/src/openclaw/bridges/slack.js +116 -23
  46. package/dist/src/openclaw/bridges/slack.js.map +1 -1
  47. package/dist/src/openclaw/bridges/telegram.js +45 -19
  48. package/dist/src/openclaw/bridges/telegram.js.map +1 -1
  49. package/dist/src/openclaw/bridges/whatsapp.js +32 -16
  50. package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
  51. package/dist/src/openclaw/bridges/zalo.js +8 -3
  52. package/dist/src/openclaw/bridges/zalo.js.map +1 -1
  53. package/dist/src/openclaw/private-file.d.ts +38 -3
  54. package/dist/src/openclaw/private-file.js +2008 -87
  55. package/dist/src/openclaw/private-file.js.map +1 -1
  56. package/dist/src/openclaw/shared.d.ts +1 -0
  57. package/dist/src/openclaw/shared.js +48 -13
  58. package/dist/src/openclaw/shared.js.map +1 -1
  59. package/dist/src/openclaw/smoke-lock.d.ts +9 -0
  60. package/dist/src/openclaw/smoke-lock.js +626 -172
  61. package/dist/src/openclaw/smoke-lock.js.map +1 -1
  62. package/dist/src/openclaw.js +225 -42
  63. package/dist/src/openclaw.js.map +1 -1
  64. package/dist/src/platform/process-owned-lock.d.ts +11 -0
  65. package/dist/src/platform/process-owned-lock.js +1698 -0
  66. package/dist/src/platform/process-owned-lock.js.map +1 -0
  67. package/dist/src/platform/recorder-directory.d.ts +1 -0
  68. package/dist/src/platform/recorder-directory.js +14 -0
  69. package/dist/src/platform/recorder-directory.js.map +1 -0
  70. package/dist/src/platform/windows-acl.d.ts +18 -0
  71. package/dist/src/platform/windows-acl.js +1367 -0
  72. package/dist/src/platform/windows-acl.js.map +1 -0
  73. package/dist/src/platform/windows-lock-root.d.ts +13 -0
  74. package/dist/src/platform/windows-lock-root.js +98 -0
  75. package/dist/src/platform/windows-lock-root.js.map +1 -0
  76. package/dist/src/providers/builtin/discord.d.ts +4 -0
  77. package/dist/src/providers/builtin/discord.js +52 -9
  78. package/dist/src/providers/builtin/discord.js.map +1 -1
  79. package/dist/src/providers/builtin/external-webhook-auth.d.ts +1 -0
  80. package/dist/src/providers/builtin/external-webhook-auth.js +38 -1
  81. package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -1
  82. package/dist/src/providers/builtin/feishu.d.ts +1 -0
  83. package/dist/src/providers/builtin/feishu.js +96 -26
  84. package/dist/src/providers/builtin/feishu.js.map +1 -1
  85. package/dist/src/providers/builtin/googlechat.d.ts +1 -0
  86. package/dist/src/providers/builtin/googlechat.js +95 -26
  87. package/dist/src/providers/builtin/googlechat.js.map +1 -1
  88. package/dist/src/providers/builtin/imessage.js +60 -17
  89. package/dist/src/providers/builtin/imessage.js.map +1 -1
  90. package/dist/src/providers/builtin/loopback.js +88 -35
  91. package/dist/src/providers/builtin/loopback.js.map +1 -1
  92. package/dist/src/providers/builtin/matrix.d.ts +2 -2
  93. package/dist/src/providers/builtin/matrix.js +78 -21
  94. package/dist/src/providers/builtin/matrix.js.map +1 -1
  95. package/dist/src/providers/builtin/mattermost.d.ts +11 -7
  96. package/dist/src/providers/builtin/mattermost.js +138 -24
  97. package/dist/src/providers/builtin/mattermost.js.map +1 -1
  98. package/dist/src/providers/builtin/msteams.d.ts +1 -0
  99. package/dist/src/providers/builtin/msteams.js +118 -38
  100. package/dist/src/providers/builtin/msteams.js.map +1 -1
  101. package/dist/src/providers/builtin/native-local-mock.js +4 -4
  102. package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
  103. package/dist/src/providers/builtin/script.js +1057 -146
  104. package/dist/src/providers/builtin/script.js.map +1 -1
  105. package/dist/src/providers/builtin/slack.d.ts +8 -1
  106. package/dist/src/providers/builtin/slack.js +327 -24
  107. package/dist/src/providers/builtin/slack.js.map +1 -1
  108. package/dist/src/providers/builtin/telegram.d.ts +1 -1
  109. package/dist/src/providers/builtin/telegram.js +55 -14
  110. package/dist/src/providers/builtin/telegram.js.map +1 -1
  111. package/dist/src/providers/builtin/whatsapp.d.ts +4 -3
  112. package/dist/src/providers/builtin/whatsapp.js +157 -29
  113. package/dist/src/providers/builtin/whatsapp.js.map +1 -1
  114. package/dist/src/providers/builtin/zalo.d.ts +5 -2
  115. package/dist/src/providers/builtin/zalo.js +31 -7
  116. package/dist/src/providers/builtin/zalo.js.map +1 -1
  117. package/dist/src/providers/catalog.d.ts +1 -1
  118. package/dist/src/providers/catalog.js +1 -1
  119. package/dist/src/providers/catalog.js.map +1 -1
  120. package/dist/src/providers/local-mock.d.ts +4 -0
  121. package/dist/src/providers/local-mock.js +162 -35
  122. package/dist/src/providers/local-mock.js.map +1 -1
  123. package/dist/src/providers/native-ids.d.ts +2 -0
  124. package/dist/src/providers/native-ids.js +3 -0
  125. package/dist/src/providers/native-ids.js.map +1 -1
  126. package/dist/src/providers/recorder.d.ts +14 -3
  127. package/dist/src/providers/recorder.js +521 -41
  128. package/dist/src/providers/recorder.js.map +1 -1
  129. package/dist/src/providers/registry.d.ts +1 -0
  130. package/dist/src/providers/registry.js +65 -20
  131. package/dist/src/providers/registry.js.map +1 -1
  132. package/dist/src/providers/signed-jwt.d.ts +6 -1
  133. package/dist/src/providers/signed-jwt.js +241 -50
  134. package/dist/src/providers/signed-jwt.js.map +1 -1
  135. package/dist/src/providers/slack-ids.d.ts +1 -0
  136. package/dist/src/providers/slack-ids.js +5 -0
  137. package/dist/src/providers/slack-ids.js.map +1 -1
  138. package/dist/src/providers/target-normalizers.d.ts +9 -1
  139. package/dist/src/providers/target-normalizers.js +177 -58
  140. package/dist/src/providers/target-normalizers.js.map +1 -1
  141. package/dist/src/providers/webhook-server.d.ts +3 -0
  142. package/dist/src/providers/webhook-server.js +268 -47
  143. package/dist/src/providers/webhook-server.js.map +1 -1
  144. package/dist/src/servers/http.d.ts +14 -2
  145. package/dist/src/servers/http.js +229 -58
  146. package/dist/src/servers/http.js.map +1 -1
  147. package/dist/src/servers/matrix.d.ts +3 -0
  148. package/dist/src/servers/matrix.js +268 -110
  149. package/dist/src/servers/matrix.js.map +1 -1
  150. package/dist/src/servers/mattermost.d.ts +5 -0
  151. package/dist/src/servers/mattermost.js +389 -91
  152. package/dist/src/servers/mattermost.js.map +1 -1
  153. package/dist/src/servers/recorder.d.ts +11 -0
  154. package/dist/src/servers/recorder.js +1072 -69
  155. package/dist/src/servers/recorder.js.map +1 -1
  156. package/dist/src/servers/signal.d.ts +1 -0
  157. package/dist/src/servers/signal.js +279 -44
  158. package/dist/src/servers/signal.js.map +1 -1
  159. package/dist/src/servers/slack.d.ts +15 -0
  160. package/dist/src/servers/slack.js +259 -51
  161. package/dist/src/servers/slack.js.map +1 -1
  162. package/dist/src/servers/telegram-identity.d.ts +6 -0
  163. package/dist/src/servers/telegram-identity.js +27 -0
  164. package/dist/src/servers/telegram-identity.js.map +1 -0
  165. package/dist/src/servers/telegram.d.ts +17 -6
  166. package/dist/src/servers/telegram.js +1169 -107
  167. package/dist/src/servers/telegram.js.map +1 -1
  168. package/dist/src/servers/webhook-target.d.ts +12 -4
  169. package/dist/src/servers/webhook-target.js +226 -28
  170. package/dist/src/servers/webhook-target.js.map +1 -1
  171. package/dist/src/servers/whatsapp-baileys-websocket.d.ts +60 -1
  172. package/dist/src/servers/whatsapp-baileys-websocket.js +782 -112
  173. package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
  174. package/dist/src/servers/whatsapp-jid.d.ts +1 -0
  175. package/dist/src/servers/whatsapp-jid.js +9 -5
  176. package/dist/src/servers/whatsapp-jid.js.map +1 -1
  177. package/dist/src/servers/whatsapp-wire/binary-node.d.ts +1 -0
  178. package/dist/src/servers/whatsapp-wire/binary-node.js +37 -13
  179. package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
  180. package/dist/src/servers/whatsapp-wire/crypto.d.ts +3 -0
  181. package/dist/src/servers/whatsapp-wire/crypto.js +23 -8
  182. package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
  183. package/dist/src/servers/whatsapp-wire/handshake.d.ts +9 -3
  184. package/dist/src/servers/whatsapp-wire/handshake.js +173 -12
  185. package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
  186. package/dist/src/servers/whatsapp-wire/signal.js +3 -3
  187. package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
  188. package/dist/src/servers/whatsapp.d.ts +7 -0
  189. package/dist/src/servers/whatsapp.js +149 -24
  190. package/dist/src/servers/whatsapp.js.map +1 -1
  191. package/dist/src/servers/zalo.d.ts +1 -0
  192. package/dist/src/servers/zalo.js +193 -82
  193. package/dist/src/servers/zalo.js.map +1 -1
  194. package/docs/channel-setup.md +185 -43
  195. package/fixtures/examples/crabline.example.yaml +3 -2
  196. package/package.json +18 -10
  197. package/fixtures/examples/openclaw-bridge.yaml +0 -125
@@ -2,24 +2,35 @@ import { createHmac, timingSafeEqual } from "node:crypto";
2
2
  import path from "node:path";
3
3
  import { CrablineError } from "../../core/errors.js";
4
4
  import { LocalMockProviderAdapter } from "../local-mock.js";
5
- import { slackTargetKey, SLACK_CHANNEL_ID_RULE, SLACK_TS_RULE } from "../slack-ids.js";
5
+ import { slackTargetKey, SLACK_CHANNEL_ID_RULE, SLACK_EVENT_ID_RULE, SLACK_TS_RULE, SLACK_USER_ID_RULE, } from "../slack-ids.js";
6
6
  import { getBuiltinTargetCodec } from "../target-normalizers.js";
7
- import { genericMockPayloadWithNativeThread, isRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
7
+ import { genericMockPayloadWithNativeThread, isRecord, optionalRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
8
8
  import { requireExternalWebhookAuthentication } from "./external-webhook-auth.js";
9
9
  const SLACK_SIGNATURE_TOLERANCE_SECONDS = 5 * 60;
10
+ const SLACK_EVENT_RETRY_RETENTION_HOURS = 25;
11
+ const SLACK_EVENT_RATE_LIMIT_PER_HOUR = 30_000;
12
+ const SLACK_EVENT_RETRY_RETENTION_MS = SLACK_EVENT_RETRY_RETENTION_HOURS * 60 * 60_000;
13
+ const SLACK_EVENT_REPLAY_CACHE_LIMIT = SLACK_EVENT_RATE_LIMIT_PER_HOUR * SLACK_EVENT_RETRY_RETENTION_HOURS;
10
14
  export function resolveSlackAdapterConfig(config, env = process.env) {
15
+ const configuredSigningSecret = config.slack?.signingSecret;
16
+ const signingSecret = configuredSigningSecret ?? env.SLACK_SIGNING_SECRET;
17
+ if (signingSecret !== undefined && !signingSecret.trim()) {
18
+ throw new CrablineError(configuredSigningSecret === undefined
19
+ ? "SLACK_SIGNING_SECRET must not be empty or whitespace-only."
20
+ : "Slack signingSecret must not be empty or whitespace-only.", { kind: "config" });
21
+ }
11
22
  return {
12
- signingSecret: config.slack?.signingSecret ?? env.SLACK_SIGNING_SECRET,
23
+ signingSecret,
13
24
  };
14
25
  }
15
- function authenticateSlackWebhook(request, rawBody, signingSecret) {
26
+ function authenticateSlackWebhook(request, rawBody, signingSecret, now = Date.now) {
16
27
  const timestamp = request.headers.get("x-slack-request-timestamp");
17
28
  const signature = request.headers.get("x-slack-signature");
18
29
  const timestampSeconds = timestamp ? Number(timestamp) : Number.NaN;
19
30
  if (!timestamp ||
20
31
  !signature ||
21
32
  !Number.isSafeInteger(timestampSeconds) ||
22
- Math.abs(Date.now() / 1000 - timestampSeconds) > SLACK_SIGNATURE_TOLERANCE_SECONDS) {
33
+ Math.abs(now() / 1000 - timestampSeconds) > SLACK_SIGNATURE_TOLERANCE_SECONDS) {
23
34
  return new Response("unauthorized", { status: 401 });
24
35
  }
25
36
  const expected = `v0=${createHmac("sha256", signingSecret)
@@ -39,32 +50,179 @@ function slackAuthorFromEvent(event) {
39
50
  }
40
51
  return "user";
41
52
  }
42
- function normalizeSlackEventsPayload(payload) {
53
+ function pushSlackText(value, output) {
54
+ if (typeof value === "string" && value.trim()) {
55
+ output.push(value);
56
+ }
57
+ }
58
+ function slackInlineText(value) {
59
+ if (Array.isArray(value)) {
60
+ return value.map(slackInlineText).join("");
61
+ }
62
+ if (!isRecord(value)) {
63
+ return "";
64
+ }
65
+ if (value.type === "link" && typeof value.url === "string") {
66
+ return typeof value.text === "string" && value.text.length > 0 ? value.text : value.url;
67
+ }
68
+ if (value.type === "user" && typeof value.user_id === "string") {
69
+ return `<@${value.user_id}>`;
70
+ }
71
+ if (value.type === "channel" && typeof value.channel_id === "string") {
72
+ return `<#${value.channel_id}>`;
73
+ }
74
+ if (value.type === "usergroup" && typeof value.usergroup_id === "string") {
75
+ return `<!subteam^${value.usergroup_id}>`;
76
+ }
77
+ if (value.type === "emoji" && typeof value.name === "string") {
78
+ return `:${value.name}:`;
79
+ }
80
+ if (value.type === "broadcast" && typeof value.range === "string") {
81
+ return `<!${value.range}>`;
82
+ }
83
+ if (value.type === "date") {
84
+ if (typeof value.fallback === "string" && value.fallback.length > 0) {
85
+ return value.fallback;
86
+ }
87
+ if ((typeof value.timestamp === "number" || typeof value.timestamp === "string") &&
88
+ typeof value.format === "string") {
89
+ return `<!date^${value.timestamp}^${value.format}>`;
90
+ }
91
+ }
92
+ if (typeof value.text === "string") {
93
+ return value.text;
94
+ }
95
+ return slackInlineText(value.elements);
96
+ }
97
+ function collectSlackTextValue(value, output) {
98
+ if (typeof value === "string") {
99
+ pushSlackText(value, output);
100
+ return;
101
+ }
102
+ if (isRecord(value) && typeof value.text === "string") {
103
+ pushSlackText(value.text, output);
104
+ return;
105
+ }
106
+ collectSlackBlockText(value, output);
107
+ }
108
+ function collectSlackBlockText(value, output) {
109
+ if (Array.isArray(value)) {
110
+ for (const entry of value) {
111
+ collectSlackBlockText(entry, output);
112
+ }
113
+ return;
114
+ }
115
+ if (!isRecord(value)) {
116
+ return;
117
+ }
118
+ if (value.type === "rich_text_section" ||
119
+ value.type === "rich_text_preformatted" ||
120
+ value.type === "rich_text_quote") {
121
+ pushSlackText(slackInlineText(value.elements), output);
122
+ return;
123
+ }
124
+ for (const key of [
125
+ "alt_text",
126
+ "title",
127
+ "body",
128
+ "subtitle",
129
+ "subtext",
130
+ "details",
131
+ "output",
132
+ ]) {
133
+ collectSlackTextValue(value[key], output);
134
+ }
135
+ collectSlackTextValue(value.text, output);
136
+ for (const key of [
137
+ "blocks",
138
+ "elements",
139
+ "fields",
140
+ "rows",
141
+ "tasks",
142
+ "actions",
143
+ "hero_image",
144
+ "icon",
145
+ ]) {
146
+ collectSlackBlockText(value[key], output);
147
+ }
148
+ }
149
+ function collectSlackAttachmentText(value, output) {
150
+ if (Array.isArray(value)) {
151
+ for (const entry of value) {
152
+ collectSlackAttachmentText(entry, output);
153
+ }
154
+ return;
155
+ }
156
+ if (!isRecord(value)) {
157
+ return;
158
+ }
159
+ for (const key of ["fallback", "pretext", "author_name", "title", "text", "footer"]) {
160
+ pushSlackText(value[key], output);
161
+ }
162
+ if (Array.isArray(value.fields)) {
163
+ for (const field of value.fields) {
164
+ if (!isRecord(field)) {
165
+ continue;
166
+ }
167
+ pushSlackText(field.title, output);
168
+ pushSlackText(field.value, output);
169
+ }
170
+ }
171
+ collectSlackBlockText(value.blocks, output);
172
+ }
173
+ function slackMessageText(message) {
174
+ if (typeof message.text === "string" && message.text.trim()) {
175
+ return message.text;
176
+ }
177
+ const fallback = [];
178
+ collectSlackBlockText(message.blocks, fallback);
179
+ collectSlackAttachmentText(message.attachments, fallback);
180
+ return fallback.length > 0 ? fallback.join("\n") : undefined;
181
+ }
182
+ function hasMalformedSlackMessageContent(message) {
183
+ return ((message.text !== undefined && typeof message.text !== "string") ||
184
+ (message.blocks !== undefined && !Array.isArray(message.blocks)) ||
185
+ (message.attachments !== undefined && !Array.isArray(message.attachments)));
186
+ }
187
+ export function normalizeSlackEventsPayload(payload) {
43
188
  if (!isRecord(payload)) {
44
189
  throw new CrablineError("Slack webhook payload must be an object", { kind: "inbound" });
45
190
  }
191
+ const { token: _token, ...safePayload } = payload;
46
192
  if (isRecord(payload.event)) {
47
193
  const event = payload.event;
194
+ const isMessageChanged = event.subtype === "message_changed";
48
195
  const changedMessage = isRecord(event.message) ? event.message : undefined;
49
- const isMessageChanged = event.subtype === "message_changed" && changedMessage !== undefined;
196
+ if (isMessageChanged && !changedMessage) {
197
+ throw new CrablineError("Slack message_changed event requires event.message", {
198
+ kind: "inbound",
199
+ });
200
+ }
50
201
  const message = isMessageChanged ? changedMessage : event;
202
+ if (!message || hasMalformedSlackMessageContent(message)) {
203
+ throw new CrablineError("Slack event payload contains malformed message content", {
204
+ kind: "inbound",
205
+ });
206
+ }
51
207
  const channel = event.channel;
52
- const text = message.text;
53
- if (typeof channel !== "string" || typeof text !== "string") {
208
+ const text = slackMessageText(message);
209
+ if (typeof channel !== "string" || text === undefined) {
54
210
  throw new CrablineError("Slack event payload requires event.channel and event.text", {
55
211
  kind: "inbound",
56
212
  });
57
213
  }
58
214
  const threadTs = message.thread_ts;
59
- const eventId = isMessageChanged
60
- ? optionalString(event, "event_ts")
61
- : optionalString(message, "ts");
215
+ const callbackEventId = optionalString(payload, "event_id");
216
+ const eventId = callbackEventId ??
217
+ (isMessageChanged ? optionalString(event, "event_ts") : optionalString(message, "ts"));
62
218
  return {
63
219
  author: slackAuthorFromEvent(message),
64
220
  ...(eventId
65
- ? { id: requireNativeInboundId(eventId, SLACK_TS_RULE, "Slack event timestamp") }
221
+ ? {
222
+ id: requireNativeInboundId(eventId, callbackEventId ? SLACK_EVENT_ID_RULE : SLACK_TS_RULE, callbackEventId ? "Slack event_id" : "Slack event timestamp"),
223
+ }
66
224
  : {}),
67
- raw: payload,
225
+ raw: safePayload,
68
226
  text,
69
227
  threadId: typeof threadTs === "string"
70
228
  ? slackTargetKey(requireNativeInboundId(channel, SLACK_CHANNEL_ID_RULE, "Slack event.channel"), requireNativeInboundId(threadTs, SLACK_TS_RULE, "Slack event.thread_ts"))
@@ -73,7 +231,7 @@ function normalizeSlackEventsPayload(payload) {
73
231
  }
74
232
  const normalized = genericMockPayloadWithNativeThread({
75
233
  channelRule: SLACK_CHANNEL_ID_RULE,
76
- payload,
234
+ payload: safePayload,
77
235
  threadRule: SLACK_TS_RULE,
78
236
  });
79
237
  const message = isRecord(payload.message) ? payload.message : undefined;
@@ -99,26 +257,157 @@ function normalizeSlackEventsPayload(payload) {
99
257
  threadId: scopedThreadId,
100
258
  };
101
259
  }
102
- function matchesSlackThread(candidateThreadId, expectedThreadId, target) {
260
+ function matchesSlackThread(candidateThreadId, expectedThreadId, target, raw) {
103
261
  if (!expectedThreadId) {
104
262
  return true;
105
263
  }
106
264
  const scopedExpectedThreadId = target.channelId && SLACK_TS_RULE.pattern.test(expectedThreadId)
107
265
  ? slackTargetKey(target.channelId, expectedThreadId)
108
266
  : expectedThreadId;
109
- return (candidateThreadId === scopedExpectedThreadId ||
110
- candidateThreadId.startsWith(`${scopedExpectedThreadId}:`));
267
+ if (candidateThreadId === scopedExpectedThreadId ||
268
+ candidateThreadId.startsWith(`${scopedExpectedThreadId}:`)) {
269
+ return true;
270
+ }
271
+ if (!target.channelId || !SLACK_USER_ID_RULE.pattern.test(target.channelId) || !isRecord(raw)) {
272
+ return false;
273
+ }
274
+ const event = optionalRecord(raw, "event");
275
+ const message = event?.subtype === "message_changed" ? optionalRecord(event, "message") : event;
276
+ const channel = event ? optionalString(event, "channel") : undefined;
277
+ const user = (event ? optionalString(event, "user") : undefined) ??
278
+ (message ? optionalString(message, "user") : undefined);
279
+ if (!channel?.startsWith("D") || user !== target.channelId) {
280
+ return false;
281
+ }
282
+ if (expectedThreadId === target.channelId) {
283
+ return candidateThreadId === channel || candidateThreadId.startsWith(`${channel}:thread:`);
284
+ }
285
+ const messageThreadTs = message ? optionalString(message, "thread_ts") : undefined;
286
+ return Boolean(SLACK_TS_RULE.pattern.test(expectedThreadId) &&
287
+ messageThreadTs === expectedThreadId &&
288
+ candidateThreadId === slackTargetKey(channel, expectedThreadId));
111
289
  }
112
290
  export function handleSlackWebhookPayload(payload) {
113
- if (isRecord(payload) &&
114
- payload.type === "url_verification" &&
115
- typeof payload.challenge === "string") {
291
+ if (!isRecord(payload)) {
292
+ return undefined;
293
+ }
294
+ if (payload.type === "url_verification" && typeof payload.challenge === "string") {
116
295
  return Response.json({ challenge: payload.challenge });
117
296
  }
297
+ if (payload.type === "event_callback") {
298
+ if (!isRecord(payload.event)) {
299
+ return undefined;
300
+ }
301
+ const eventType = optionalString(payload.event, "type");
302
+ if (!eventType) {
303
+ return new Response(null, { status: 200 });
304
+ }
305
+ if (eventType !== "message" && eventType !== "app_mention") {
306
+ return new Response(null, { status: 200 });
307
+ }
308
+ const isMessageChanged = payload.event.subtype === "message_changed";
309
+ if (isMessageChanged && !isRecord(payload.event.message)) {
310
+ return undefined;
311
+ }
312
+ const message = isMessageChanged ? payload.event.message : payload.event;
313
+ if (!isRecord(message)) {
314
+ return undefined;
315
+ }
316
+ if (typeof payload.event.channel !== "string" || hasMalformedSlackMessageContent(message)) {
317
+ return undefined;
318
+ }
319
+ if (slackMessageText(message) === undefined) {
320
+ return new Response(null, { status: 200 });
321
+ }
322
+ return undefined;
323
+ }
324
+ if (typeof payload.type === "string" && payload.type.trim()) {
325
+ return new Response(null, { status: 200 });
326
+ }
118
327
  return undefined;
119
328
  }
329
+ async function handleSlackEventReplay(payload, state) {
330
+ if (!isRecord(payload) || payload.type !== "event_callback") {
331
+ return undefined;
332
+ }
333
+ const eventId = optionalString(payload, "event_id");
334
+ if (!eventId || !SLACK_EVENT_ID_RULE.pattern.test(eventId)) {
335
+ return undefined;
336
+ }
337
+ const now = state.now();
338
+ pruneSlackEventReplayState(state, now);
339
+ for (;;) {
340
+ if (state.accepted.has(eventId)) {
341
+ return new Response(null, { status: 200 });
342
+ }
343
+ const pending = state.inFlight.get(eventId);
344
+ if (!pending) {
345
+ if (state.accepted.size + state.inFlight.size >= state.cacheLimit) {
346
+ return slackEventReplayCapacityResponse();
347
+ }
348
+ reserveSlackEvent(state, eventId);
349
+ return undefined;
350
+ }
351
+ if (await pending.promise) {
352
+ return new Response(null, { status: 200 });
353
+ }
354
+ }
355
+ }
356
+ function reserveSlackEvent(state, eventId) {
357
+ let resolveReservation;
358
+ const promise = new Promise((resolve) => {
359
+ resolveReservation = resolve;
360
+ });
361
+ state.inFlight.set(eventId, {
362
+ promise,
363
+ resolve: resolveReservation,
364
+ });
365
+ }
366
+ function settleSlackEventReplay(state, payload, accepted) {
367
+ if (!isRecord(payload)) {
368
+ return;
369
+ }
370
+ const eventId = optionalString(payload, "event_id");
371
+ if (!eventId) {
372
+ return;
373
+ }
374
+ const reservation = state.inFlight.get(eventId);
375
+ if (!reservation) {
376
+ return;
377
+ }
378
+ state.inFlight.delete(eventId);
379
+ if (accepted) {
380
+ const expiresAt = state.now() + SLACK_EVENT_RETRY_RETENTION_MS;
381
+ state.accepted.set(eventId, expiresAt);
382
+ state.expiryQueue.push({ eventId, expiresAt });
383
+ }
384
+ reservation.resolve(accepted);
385
+ }
386
+ function pruneSlackEventReplayState(state, now) {
387
+ while (state.expiryHead < state.expiryQueue.length) {
388
+ const { eventId, expiresAt } = state.expiryQueue[state.expiryHead];
389
+ if (now <= expiresAt) {
390
+ break;
391
+ }
392
+ state.expiryHead += 1;
393
+ if (state.accepted.get(eventId) === expiresAt) {
394
+ state.accepted.delete(eventId);
395
+ }
396
+ }
397
+ if (state.expiryHead >= 1_024 && state.expiryHead * 2 >= state.expiryQueue.length) {
398
+ state.expiryQueue = state.expiryQueue.slice(state.expiryHead);
399
+ state.expiryHead = 0;
400
+ }
401
+ }
402
+ function slackEventReplayCapacityResponse() {
403
+ return new Response("service unavailable", {
404
+ headers: { "cache-control": "no-store" },
405
+ status: 503,
406
+ });
407
+ }
120
408
  export class SlackProviderAdapter extends LocalMockProviderAdapter {
121
- constructor(id, config, _userName, _runtime) {
409
+ constructor(id, config, _userName, runtime) {
410
+ const slackRuntime = runtime ?? {};
122
411
  const resolvedConfig = resolveSlackAdapterConfig(config);
123
412
  requireExternalWebhookAuthentication({
124
413
  authenticated: Boolean(resolvedConfig.signingSecret),
@@ -126,6 +415,14 @@ export class SlackProviderAdapter extends LocalMockProviderAdapter {
126
415
  requirement: "slack.signingSecret or SLACK_SIGNING_SECRET",
127
416
  webhook: config.slack?.webhook,
128
417
  });
418
+ const replayState = {
419
+ accepted: new Map(),
420
+ cacheLimit: slackRuntime.replayCacheLimit ?? SLACK_EVENT_REPLAY_CACHE_LIMIT,
421
+ expiryHead: 0,
422
+ expiryQueue: [],
423
+ inFlight: new Map(),
424
+ now: slackRuntime.now ?? (() => Date.now()),
425
+ };
129
426
  super({
130
427
  codec: getBuiltinTargetCodec("slack"),
131
428
  config,
@@ -134,13 +431,16 @@ export class SlackProviderAdapter extends LocalMockProviderAdapter {
134
431
  ...(resolvedConfig.signingSecret
135
432
  ? {
136
433
  authenticateWebhookRequest(request, rawBody) {
137
- return authenticateSlackWebhook(request, rawBody, resolvedConfig.signingSecret);
434
+ return authenticateSlackWebhook(request, rawBody, resolvedConfig.signingSecret, replayState.now);
138
435
  },
139
436
  }
140
437
  : {}),
141
438
  defaultWebhook: { host: "127.0.0.1", path: "/slack/events", port: 8787 },
142
439
  endpointLabel: "events endpoint",
143
- handleWebhookPayload: handleSlackWebhookPayload,
440
+ async handleWebhookPayload(payload) {
441
+ return (handleSlackWebhookPayload(payload) ??
442
+ (await handleSlackEventReplay(payload, replayState)));
443
+ },
144
444
  matchesThread: matchesSlackThread,
145
445
  normalizeWebhookPayload: normalizeSlackEventsPayload,
146
446
  platform: "slack",
@@ -148,6 +448,9 @@ export class SlackProviderAdapter extends LocalMockProviderAdapter {
148
448
  recorderPath: config.slack?.recorder.path
149
449
  ? path.resolve(config.slack.recorder.path)
150
450
  : undefined,
451
+ settleWebhookRequest({ accepted, payload }) {
452
+ settleSlackEventReplay(replayState, payload, accepted);
453
+ },
151
454
  webhook: config.slack?.webhook,
152
455
  },
153
456
  });
@@ -1 +1 @@
1
- {"version":3,"file":"slack.js","sourceRoot":"","sources":["../../../../src/providers/builtin/slack.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport path from \"node:path\";\nimport { CrablineError } from \"../../core/errors.js\";\nimport type { ProviderConfig } from \"../../config/schema.js\";\nimport { LocalMockProviderAdapter } from \"../local-mock.js\";\nimport { slackTargetKey, SLACK_CHANNEL_ID_RULE, SLACK_TS_RULE } from \"../slack-ids.js\";\nimport { getBuiltinTargetCodec } from \"../target-normalizers.js\";\nimport type { InboundEnvelope, ProviderAdapter } from \"../types.js\";\nimport {\n genericMockPayloadWithNativeThread,\n isRecord,\n optionalString,\n requireNativeInboundId,\n} from \"./native-local-mock.js\";\nimport { requireExternalWebhookAuthentication } from \"./external-webhook-auth.js\";\n\nconst SLACK_SIGNATURE_TOLERANCE_SECONDS = 5 * 60;\n\ntype SlackEnvironment = Partial<Pick<NodeJS.ProcessEnv, \"SLACK_SIGNING_SECRET\">>;\n\nexport function resolveSlackAdapterConfig(\n config: ProviderConfig,\n env: SlackEnvironment = process.env,\n) {\n return {\n signingSecret: config.slack?.signingSecret ?? env.SLACK_SIGNING_SECRET,\n };\n}\n\nfunction authenticateSlackWebhook(\n request: Request,\n rawBody: string,\n signingSecret: string,\n): Response | undefined {\n const timestamp = request.headers.get(\"x-slack-request-timestamp\");\n const signature = request.headers.get(\"x-slack-signature\");\n const timestampSeconds = timestamp ? Number(timestamp) : Number.NaN;\n if (\n !timestamp ||\n !signature ||\n !Number.isSafeInteger(timestampSeconds) ||\n Math.abs(Date.now() / 1000 - timestampSeconds) > SLACK_SIGNATURE_TOLERANCE_SECONDS\n ) {\n return new Response(\"unauthorized\", { status: 401 });\n }\n\n const expected = `v0=${createHmac(\"sha256\", signingSecret)\n .update(`v0:${timestamp}:${rawBody}`)\n .digest(\"hex\")}`;\n const actualBuffer = Buffer.from(signature);\n const expectedBuffer = Buffer.from(expected);\n if (\n actualBuffer.length !== expectedBuffer.length ||\n !timingSafeEqual(actualBuffer, expectedBuffer)\n ) {\n return new Response(\"unauthorized\", { status: 401 });\n }\n return undefined;\n}\n\nfunction slackAuthorFromEvent(event: Record<string, unknown>): InboundEnvelope[\"author\"] {\n if (typeof event.bot_id === \"string\" || event.subtype === \"bot_message\") {\n return \"assistant\";\n }\n return \"user\";\n}\n\nfunction normalizeSlackEventsPayload(payload: unknown) {\n if (!isRecord(payload)) {\n throw new CrablineError(\"Slack webhook payload must be an object\", { kind: \"inbound\" });\n }\n\n if (isRecord(payload.event)) {\n const event = payload.event;\n const changedMessage = isRecord(event.message) ? event.message : undefined;\n const isMessageChanged = event.subtype === \"message_changed\" && changedMessage !== undefined;\n const message = isMessageChanged ? changedMessage : event;\n const channel = event.channel;\n const text = message.text;\n if (typeof channel !== \"string\" || typeof text !== \"string\") {\n throw new CrablineError(\"Slack event payload requires event.channel and event.text\", {\n kind: \"inbound\",\n });\n }\n const threadTs = message.thread_ts;\n const eventId = isMessageChanged\n ? optionalString(event, \"event_ts\")\n : optionalString(message, \"ts\");\n return {\n author: slackAuthorFromEvent(message),\n ...(eventId\n ? { id: requireNativeInboundId(eventId, SLACK_TS_RULE, \"Slack event timestamp\") }\n : {}),\n raw: payload,\n text,\n threadId:\n typeof threadTs === \"string\"\n ? slackTargetKey(\n requireNativeInboundId(channel, SLACK_CHANNEL_ID_RULE, \"Slack event.channel\"),\n requireNativeInboundId(threadTs, SLACK_TS_RULE, \"Slack event.thread_ts\"),\n )\n : requireNativeInboundId(channel, SLACK_CHANNEL_ID_RULE, \"Slack event.channel\"),\n };\n }\n\n const normalized = genericMockPayloadWithNativeThread({\n channelRule: SLACK_CHANNEL_ID_RULE,\n payload,\n threadRule: SLACK_TS_RULE,\n });\n const message = isRecord(payload.message) ? payload.message : undefined;\n const channelId =\n (message ? optionalString(message, \"channelId\") : undefined) ??\n optionalString(payload, \"channelId\") ??\n optionalString(payload, \"channel\");\n const threadId =\n (message ? optionalString(message, \"threadId\") : undefined) ??\n optionalString(payload, \"threadId\");\n if (threadId && SLACK_TS_RULE.pattern.test(threadId) && !channelId) {\n throw new CrablineError(\"Slack timestamp threadId requires a native channelId\", {\n kind: \"inbound\",\n });\n }\n if (!channelId || !threadId || !SLACK_TS_RULE.pattern.test(threadId)) {\n return normalized;\n }\n const scopedThreadId = slackTargetKey(\n requireNativeInboundId(channelId, SLACK_CHANNEL_ID_RULE, \"Slack channelId\"),\n threadId,\n );\n return {\n ...normalized,\n ...(\"message\" in normalized && isRecord(normalized.message)\n ? { message: { ...normalized.message, threadId: scopedThreadId } }\n : {}),\n threadId: scopedThreadId,\n };\n}\n\nfunction matchesSlackThread(\n candidateThreadId: string,\n expectedThreadId: string | undefined,\n target: { channelId?: string | undefined },\n): boolean {\n if (!expectedThreadId) {\n return true;\n }\n const scopedExpectedThreadId =\n target.channelId && SLACK_TS_RULE.pattern.test(expectedThreadId)\n ? slackTargetKey(target.channelId, expectedThreadId)\n : expectedThreadId;\n return (\n candidateThreadId === scopedExpectedThreadId ||\n candidateThreadId.startsWith(`${scopedExpectedThreadId}:`)\n );\n}\n\nexport function handleSlackWebhookPayload(payload: unknown): Response | undefined {\n if (\n isRecord(payload) &&\n payload.type === \"url_verification\" &&\n typeof payload.challenge === \"string\"\n ) {\n return Response.json({ challenge: payload.challenge });\n }\n return undefined;\n}\n\nexport class SlackProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {\n constructor(id: string, config: ProviderConfig, _userName: string, _runtime?: unknown) {\n const resolvedConfig = resolveSlackAdapterConfig(config);\n requireExternalWebhookAuthentication({\n authenticated: Boolean(resolvedConfig.signingSecret),\n provider: \"Slack\",\n requirement: \"slack.signingSecret or SLACK_SIGNING_SECRET\",\n webhook: config.slack?.webhook,\n });\n super({\n codec: getBuiltinTargetCodec(\"slack\"),\n config,\n id,\n options: {\n ...(resolvedConfig.signingSecret\n ? {\n authenticateWebhookRequest(request: Request, rawBody: string) {\n return authenticateSlackWebhook(request, rawBody, resolvedConfig.signingSecret!);\n },\n }\n : {}),\n defaultWebhook: { host: \"127.0.0.1\", path: \"/slack/events\", port: 8787 },\n endpointLabel: \"events endpoint\",\n handleWebhookPayload: handleSlackWebhookPayload,\n matchesThread: matchesSlackThread,\n normalizeWebhookPayload: normalizeSlackEventsPayload,\n platform: \"slack\",\n publicUrl: config.slack?.webhook.publicUrl,\n recorderPath: config.slack?.recorder.path\n ? path.resolve(config.slack.recorder.path)\n : undefined,\n webhook: config.slack?.webhook,\n },\n });\n }\n}\n"]}
1
+ {"version":3,"file":"slack.js","sourceRoot":"","sources":["../../../../src/providers/builtin/slack.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport path from \"node:path\";\nimport { CrablineError } from \"../../core/errors.js\";\nimport type { ProviderConfig } from \"../../config/schema.js\";\nimport { LocalMockProviderAdapter } from \"../local-mock.js\";\nimport {\n slackTargetKey,\n SLACK_CHANNEL_ID_RULE,\n SLACK_EVENT_ID_RULE,\n SLACK_TS_RULE,\n SLACK_USER_ID_RULE,\n} from \"../slack-ids.js\";\nimport { getBuiltinTargetCodec } from \"../target-normalizers.js\";\nimport type { InboundEnvelope, ProviderAdapter } from \"../types.js\";\nimport {\n genericMockPayloadWithNativeThread,\n isRecord,\n optionalRecord,\n optionalString,\n requireNativeInboundId,\n} from \"./native-local-mock.js\";\nimport { requireExternalWebhookAuthentication } from \"./external-webhook-auth.js\";\n\nconst SLACK_SIGNATURE_TOLERANCE_SECONDS = 5 * 60;\nconst SLACK_EVENT_RETRY_RETENTION_HOURS = 25;\nconst SLACK_EVENT_RATE_LIMIT_PER_HOUR = 30_000;\nconst SLACK_EVENT_RETRY_RETENTION_MS = SLACK_EVENT_RETRY_RETENTION_HOURS * 60 * 60_000;\nconst SLACK_EVENT_REPLAY_CACHE_LIMIT =\n SLACK_EVENT_RATE_LIMIT_PER_HOUR * SLACK_EVENT_RETRY_RETENTION_HOURS;\n\ntype SlackEnvironment = Partial<Pick<NodeJS.ProcessEnv, \"SLACK_SIGNING_SECRET\">>;\n\ntype SlackRuntime = {\n now?: (() => number) | undefined;\n replayCacheLimit?: number | undefined;\n};\n\ntype SlackEventReservation = {\n promise: Promise<boolean>;\n resolve: (accepted: boolean) => void;\n};\n\ntype SlackEventReplayState = {\n accepted: Map<string, number>;\n cacheLimit: number;\n expiryHead: number;\n expiryQueue: Array<{ eventId: string; expiresAt: number }>;\n inFlight: Map<string, SlackEventReservation>;\n now: () => number;\n};\n\nexport function resolveSlackAdapterConfig(\n config: ProviderConfig,\n env: SlackEnvironment = process.env,\n) {\n const configuredSigningSecret = config.slack?.signingSecret;\n const signingSecret = configuredSigningSecret ?? env.SLACK_SIGNING_SECRET;\n if (signingSecret !== undefined && !signingSecret.trim()) {\n throw new CrablineError(\n configuredSigningSecret === undefined\n ? \"SLACK_SIGNING_SECRET must not be empty or whitespace-only.\"\n : \"Slack signingSecret must not be empty or whitespace-only.\",\n { kind: \"config\" },\n );\n }\n return {\n signingSecret,\n };\n}\n\nfunction authenticateSlackWebhook(\n request: Request,\n rawBody: string,\n signingSecret: string,\n now: () => number = Date.now,\n): Response | undefined {\n const timestamp = request.headers.get(\"x-slack-request-timestamp\");\n const signature = request.headers.get(\"x-slack-signature\");\n const timestampSeconds = timestamp ? Number(timestamp) : Number.NaN;\n if (\n !timestamp ||\n !signature ||\n !Number.isSafeInteger(timestampSeconds) ||\n Math.abs(now() / 1000 - timestampSeconds) > SLACK_SIGNATURE_TOLERANCE_SECONDS\n ) {\n return new Response(\"unauthorized\", { status: 401 });\n }\n\n const expected = `v0=${createHmac(\"sha256\", signingSecret)\n .update(`v0:${timestamp}:${rawBody}`)\n .digest(\"hex\")}`;\n const actualBuffer = Buffer.from(signature);\n const expectedBuffer = Buffer.from(expected);\n if (\n actualBuffer.length !== expectedBuffer.length ||\n !timingSafeEqual(actualBuffer, expectedBuffer)\n ) {\n return new Response(\"unauthorized\", { status: 401 });\n }\n return undefined;\n}\n\nfunction slackAuthorFromEvent(event: Record<string, unknown>): InboundEnvelope[\"author\"] {\n if (typeof event.bot_id === \"string\" || event.subtype === \"bot_message\") {\n return \"assistant\";\n }\n return \"user\";\n}\n\nfunction pushSlackText(value: unknown, output: string[]): void {\n if (typeof value === \"string\" && value.trim()) {\n output.push(value);\n }\n}\n\nfunction slackInlineText(value: unknown): string {\n if (Array.isArray(value)) {\n return value.map(slackInlineText).join(\"\");\n }\n if (!isRecord(value)) {\n return \"\";\n }\n if (value.type === \"link\" && typeof value.url === \"string\") {\n return typeof value.text === \"string\" && value.text.length > 0 ? value.text : value.url;\n }\n if (value.type === \"user\" && typeof value.user_id === \"string\") {\n return `<@${value.user_id}>`;\n }\n if (value.type === \"channel\" && typeof value.channel_id === \"string\") {\n return `<#${value.channel_id}>`;\n }\n if (value.type === \"usergroup\" && typeof value.usergroup_id === \"string\") {\n return `<!subteam^${value.usergroup_id}>`;\n }\n if (value.type === \"emoji\" && typeof value.name === \"string\") {\n return `:${value.name}:`;\n }\n if (value.type === \"broadcast\" && typeof value.range === \"string\") {\n return `<!${value.range}>`;\n }\n if (value.type === \"date\") {\n if (typeof value.fallback === \"string\" && value.fallback.length > 0) {\n return value.fallback;\n }\n if (\n (typeof value.timestamp === \"number\" || typeof value.timestamp === \"string\") &&\n typeof value.format === \"string\"\n ) {\n return `<!date^${value.timestamp}^${value.format}>`;\n }\n }\n if (typeof value.text === \"string\") {\n return value.text;\n }\n return slackInlineText(value.elements);\n}\n\nfunction collectSlackTextValue(value: unknown, output: string[]): void {\n if (typeof value === \"string\") {\n pushSlackText(value, output);\n return;\n }\n if (isRecord(value) && typeof value.text === \"string\") {\n pushSlackText(value.text, output);\n return;\n }\n collectSlackBlockText(value, output);\n}\n\nfunction collectSlackBlockText(value: unknown, output: string[]): void {\n if (Array.isArray(value)) {\n for (const entry of value) {\n collectSlackBlockText(entry, output);\n }\n return;\n }\n if (!isRecord(value)) {\n return;\n }\n if (\n value.type === \"rich_text_section\" ||\n value.type === \"rich_text_preformatted\" ||\n value.type === \"rich_text_quote\"\n ) {\n pushSlackText(slackInlineText(value.elements), output);\n return;\n }\n for (const key of [\n \"alt_text\",\n \"title\",\n \"body\",\n \"subtitle\",\n \"subtext\",\n \"details\",\n \"output\",\n ] as const) {\n collectSlackTextValue(value[key], output);\n }\n collectSlackTextValue(value.text, output);\n for (const key of [\n \"blocks\",\n \"elements\",\n \"fields\",\n \"rows\",\n \"tasks\",\n \"actions\",\n \"hero_image\",\n \"icon\",\n ] as const) {\n collectSlackBlockText(value[key], output);\n }\n}\n\nfunction collectSlackAttachmentText(value: unknown, output: string[]): void {\n if (Array.isArray(value)) {\n for (const entry of value) {\n collectSlackAttachmentText(entry, output);\n }\n return;\n }\n if (!isRecord(value)) {\n return;\n }\n for (const key of [\"fallback\", \"pretext\", \"author_name\", \"title\", \"text\", \"footer\"] as const) {\n pushSlackText(value[key], output);\n }\n if (Array.isArray(value.fields)) {\n for (const field of value.fields) {\n if (!isRecord(field)) {\n continue;\n }\n pushSlackText(field.title, output);\n pushSlackText(field.value, output);\n }\n }\n collectSlackBlockText(value.blocks, output);\n}\n\nfunction slackMessageText(message: Record<string, unknown>): string | undefined {\n if (typeof message.text === \"string\" && message.text.trim()) {\n return message.text;\n }\n const fallback: string[] = [];\n collectSlackBlockText(message.blocks, fallback);\n collectSlackAttachmentText(message.attachments, fallback);\n return fallback.length > 0 ? fallback.join(\"\\n\") : undefined;\n}\n\nfunction hasMalformedSlackMessageContent(message: Record<string, unknown>): boolean {\n return (\n (message.text !== undefined && typeof message.text !== \"string\") ||\n (message.blocks !== undefined && !Array.isArray(message.blocks)) ||\n (message.attachments !== undefined && !Array.isArray(message.attachments))\n );\n}\n\nexport function normalizeSlackEventsPayload(payload: unknown) {\n if (!isRecord(payload)) {\n throw new CrablineError(\"Slack webhook payload must be an object\", { kind: \"inbound\" });\n }\n const { token: _token, ...safePayload } = payload;\n\n if (isRecord(payload.event)) {\n const event = payload.event;\n const isMessageChanged = event.subtype === \"message_changed\";\n const changedMessage = isRecord(event.message) ? event.message : undefined;\n if (isMessageChanged && !changedMessage) {\n throw new CrablineError(\"Slack message_changed event requires event.message\", {\n kind: \"inbound\",\n });\n }\n const message = isMessageChanged ? changedMessage : event;\n if (!message || hasMalformedSlackMessageContent(message)) {\n throw new CrablineError(\"Slack event payload contains malformed message content\", {\n kind: \"inbound\",\n });\n }\n const channel = event.channel;\n const text = slackMessageText(message);\n if (typeof channel !== \"string\" || text === undefined) {\n throw new CrablineError(\"Slack event payload requires event.channel and event.text\", {\n kind: \"inbound\",\n });\n }\n const threadTs = message.thread_ts;\n const callbackEventId = optionalString(payload, \"event_id\");\n const eventId =\n callbackEventId ??\n (isMessageChanged ? optionalString(event, \"event_ts\") : optionalString(message, \"ts\"));\n return {\n author: slackAuthorFromEvent(message),\n ...(eventId\n ? {\n id: requireNativeInboundId(\n eventId,\n callbackEventId ? SLACK_EVENT_ID_RULE : SLACK_TS_RULE,\n callbackEventId ? \"Slack event_id\" : \"Slack event timestamp\",\n ),\n }\n : {}),\n raw: safePayload,\n text,\n threadId:\n typeof threadTs === \"string\"\n ? slackTargetKey(\n requireNativeInboundId(channel, SLACK_CHANNEL_ID_RULE, \"Slack event.channel\"),\n requireNativeInboundId(threadTs, SLACK_TS_RULE, \"Slack event.thread_ts\"),\n )\n : requireNativeInboundId(channel, SLACK_CHANNEL_ID_RULE, \"Slack event.channel\"),\n };\n }\n\n const normalized = genericMockPayloadWithNativeThread({\n channelRule: SLACK_CHANNEL_ID_RULE,\n payload: safePayload,\n threadRule: SLACK_TS_RULE,\n });\n const message = isRecord(payload.message) ? payload.message : undefined;\n const channelId =\n (message ? optionalString(message, \"channelId\") : undefined) ??\n optionalString(payload, \"channelId\") ??\n optionalString(payload, \"channel\");\n const threadId =\n (message ? optionalString(message, \"threadId\") : undefined) ??\n optionalString(payload, \"threadId\");\n if (threadId && SLACK_TS_RULE.pattern.test(threadId) && !channelId) {\n throw new CrablineError(\"Slack timestamp threadId requires a native channelId\", {\n kind: \"inbound\",\n });\n }\n if (!channelId || !threadId || !SLACK_TS_RULE.pattern.test(threadId)) {\n return normalized;\n }\n const scopedThreadId = slackTargetKey(\n requireNativeInboundId(channelId, SLACK_CHANNEL_ID_RULE, \"Slack channelId\"),\n threadId,\n );\n return {\n ...normalized,\n ...(\"message\" in normalized && isRecord(normalized.message)\n ? { message: { ...normalized.message, threadId: scopedThreadId } }\n : {}),\n threadId: scopedThreadId,\n };\n}\n\nfunction matchesSlackThread(\n candidateThreadId: string,\n expectedThreadId: string | undefined,\n target: { channelId?: string | undefined },\n raw?: unknown,\n): boolean {\n if (!expectedThreadId) {\n return true;\n }\n const scopedExpectedThreadId =\n target.channelId && SLACK_TS_RULE.pattern.test(expectedThreadId)\n ? slackTargetKey(target.channelId, expectedThreadId)\n : expectedThreadId;\n if (\n candidateThreadId === scopedExpectedThreadId ||\n candidateThreadId.startsWith(`${scopedExpectedThreadId}:`)\n ) {\n return true;\n }\n if (!target.channelId || !SLACK_USER_ID_RULE.pattern.test(target.channelId) || !isRecord(raw)) {\n return false;\n }\n const event = optionalRecord(raw, \"event\");\n const message = event?.subtype === \"message_changed\" ? optionalRecord(event, \"message\") : event;\n const channel = event ? optionalString(event, \"channel\") : undefined;\n const user =\n (event ? optionalString(event, \"user\") : undefined) ??\n (message ? optionalString(message, \"user\") : undefined);\n if (!channel?.startsWith(\"D\") || user !== target.channelId) {\n return false;\n }\n if (expectedThreadId === target.channelId) {\n return candidateThreadId === channel || candidateThreadId.startsWith(`${channel}:thread:`);\n }\n const messageThreadTs = message ? optionalString(message, \"thread_ts\") : undefined;\n return Boolean(\n SLACK_TS_RULE.pattern.test(expectedThreadId) &&\n messageThreadTs === expectedThreadId &&\n candidateThreadId === slackTargetKey(channel, expectedThreadId),\n );\n}\n\nexport function handleSlackWebhookPayload(payload: unknown): Response | undefined {\n if (!isRecord(payload)) {\n return undefined;\n }\n if (payload.type === \"url_verification\" && typeof payload.challenge === \"string\") {\n return Response.json({ challenge: payload.challenge });\n }\n if (payload.type === \"event_callback\") {\n if (!isRecord(payload.event)) {\n return undefined;\n }\n const eventType = optionalString(payload.event, \"type\");\n if (!eventType) {\n return new Response(null, { status: 200 });\n }\n if (eventType !== \"message\" && eventType !== \"app_mention\") {\n return new Response(null, { status: 200 });\n }\n const isMessageChanged = payload.event.subtype === \"message_changed\";\n if (isMessageChanged && !isRecord(payload.event.message)) {\n return undefined;\n }\n const message = isMessageChanged ? payload.event.message : payload.event;\n if (!isRecord(message)) {\n return undefined;\n }\n if (typeof payload.event.channel !== \"string\" || hasMalformedSlackMessageContent(message)) {\n return undefined;\n }\n if (slackMessageText(message) === undefined) {\n return new Response(null, { status: 200 });\n }\n return undefined;\n }\n if (typeof payload.type === \"string\" && payload.type.trim()) {\n return new Response(null, { status: 200 });\n }\n return undefined;\n}\n\nasync function handleSlackEventReplay(\n payload: unknown,\n state: SlackEventReplayState,\n): Promise<Response | undefined> {\n if (!isRecord(payload) || payload.type !== \"event_callback\") {\n return undefined;\n }\n const eventId = optionalString(payload, \"event_id\");\n if (!eventId || !SLACK_EVENT_ID_RULE.pattern.test(eventId)) {\n return undefined;\n }\n const now = state.now();\n pruneSlackEventReplayState(state, now);\n for (;;) {\n if (state.accepted.has(eventId)) {\n return new Response(null, { status: 200 });\n }\n const pending = state.inFlight.get(eventId);\n if (!pending) {\n if (state.accepted.size + state.inFlight.size >= state.cacheLimit) {\n return slackEventReplayCapacityResponse();\n }\n reserveSlackEvent(state, eventId);\n return undefined;\n }\n if (await pending.promise) {\n return new Response(null, { status: 200 });\n }\n }\n}\n\nfunction reserveSlackEvent(state: SlackEventReplayState, eventId: string): void {\n let resolveReservation!: (accepted: boolean) => void;\n const promise = new Promise<boolean>((resolve) => {\n resolveReservation = resolve;\n });\n state.inFlight.set(eventId, {\n promise,\n resolve: resolveReservation,\n });\n}\n\nfunction settleSlackEventReplay(\n state: SlackEventReplayState,\n payload: unknown,\n accepted: boolean,\n): void {\n if (!isRecord(payload)) {\n return;\n }\n const eventId = optionalString(payload, \"event_id\");\n if (!eventId) {\n return;\n }\n const reservation = state.inFlight.get(eventId);\n if (!reservation) {\n return;\n }\n state.inFlight.delete(eventId);\n if (accepted) {\n const expiresAt = state.now() + SLACK_EVENT_RETRY_RETENTION_MS;\n state.accepted.set(eventId, expiresAt);\n state.expiryQueue.push({ eventId, expiresAt });\n }\n reservation.resolve(accepted);\n}\n\nfunction pruneSlackEventReplayState(state: SlackEventReplayState, now: number): void {\n while (state.expiryHead < state.expiryQueue.length) {\n const { eventId, expiresAt } = state.expiryQueue[state.expiryHead]!;\n if (now <= expiresAt) {\n break;\n }\n state.expiryHead += 1;\n if (state.accepted.get(eventId) === expiresAt) {\n state.accepted.delete(eventId);\n }\n }\n if (state.expiryHead >= 1_024 && state.expiryHead * 2 >= state.expiryQueue.length) {\n state.expiryQueue = state.expiryQueue.slice(state.expiryHead);\n state.expiryHead = 0;\n }\n}\n\nfunction slackEventReplayCapacityResponse(): Response {\n return new Response(\"service unavailable\", {\n headers: { \"cache-control\": \"no-store\" },\n status: 503,\n });\n}\n\nexport class SlackProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {\n constructor(id: string, config: ProviderConfig, _userName: string, runtime?: unknown) {\n const slackRuntime = (runtime as SlackRuntime | undefined) ?? {};\n const resolvedConfig = resolveSlackAdapterConfig(config);\n requireExternalWebhookAuthentication({\n authenticated: Boolean(resolvedConfig.signingSecret),\n provider: \"Slack\",\n requirement: \"slack.signingSecret or SLACK_SIGNING_SECRET\",\n webhook: config.slack?.webhook,\n });\n const replayState: SlackEventReplayState = {\n accepted: new Map(),\n cacheLimit: slackRuntime.replayCacheLimit ?? SLACK_EVENT_REPLAY_CACHE_LIMIT,\n expiryHead: 0,\n expiryQueue: [],\n inFlight: new Map(),\n now: slackRuntime.now ?? (() => Date.now()),\n };\n super({\n codec: getBuiltinTargetCodec(\"slack\"),\n config,\n id,\n options: {\n ...(resolvedConfig.signingSecret\n ? {\n authenticateWebhookRequest(request: Request, rawBody: string) {\n return authenticateSlackWebhook(\n request,\n rawBody,\n resolvedConfig.signingSecret!,\n replayState.now,\n );\n },\n }\n : {}),\n defaultWebhook: { host: \"127.0.0.1\", path: \"/slack/events\", port: 8787 },\n endpointLabel: \"events endpoint\",\n async handleWebhookPayload(payload) {\n return (\n handleSlackWebhookPayload(payload) ??\n (await handleSlackEventReplay(payload, replayState))\n );\n },\n matchesThread: matchesSlackThread,\n normalizeWebhookPayload: normalizeSlackEventsPayload,\n platform: \"slack\",\n publicUrl: config.slack?.webhook.publicUrl,\n recorderPath: config.slack?.recorder.path\n ? path.resolve(config.slack.recorder.path)\n : undefined,\n settleWebhookRequest({ accepted, payload }) {\n settleSlackEventReplay(replayState, payload, accepted);\n },\n webhook: config.slack?.webhook,\n },\n });\n }\n}\n"]}
@@ -22,6 +22,6 @@ export declare function normalizeTelegramWebhookPayload(payload: unknown): {
22
22
  threadId: string;
23
23
  };
24
24
  export declare class TelegramProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {
25
- constructor(id: string, config: ProviderConfig, _userName: string);
25
+ constructor(id: string, config: ProviderConfig, _userName: string, runtime?: unknown);
26
26
  }
27
27
  export {};