@sentry/junior 0.1.0 → 0.1.1

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.
@@ -1,25 +1,3 @@
1
- import {
2
- getAgentTurnSessionCheckpoint,
3
- getStateAdapter,
4
- upsertAgentTurnSessionCheckpoint
5
- } from "./chunk-ZBFSIN6G.js";
6
- import {
7
- addReactionToMessage,
8
- listChannelMembers,
9
- listChannelMessages,
10
- postMessageToChannel
11
- } from "./chunk-MM3YNA4F.js";
12
- import {
13
- SlackActionError,
14
- botConfig,
15
- getFilePermalink,
16
- getSlackClient,
17
- isConversationChannel,
18
- isConversationScopedChannel,
19
- isDmChannel,
20
- normalizeSlackConversationId,
21
- withSlackRetries
22
- } from "./chunk-GDNDYMGX.js";
23
1
  import {
24
2
  logError,
25
3
  logException,
@@ -31,6 +9,370 @@ import {
31
9
  withSpan
32
10
  } from "./chunk-BBOVH5RF.js";
33
11
 
12
+ // src/chat/config.ts
13
+ var MIN_AGENT_TURN_TIMEOUT_MS = 10 * 1e3;
14
+ var DEFAULT_AGENT_TURN_TIMEOUT_MS = 12 * 60 * 1e3;
15
+ var DEFAULT_QUEUE_CALLBACK_MAX_DURATION_SECONDS = 800;
16
+ var TURN_TIMEOUT_BUFFER_SECONDS = 20;
17
+ function parseAgentTurnTimeoutMs(rawValue, maxTimeoutMs) {
18
+ const value = Number.parseInt(rawValue ?? "", 10);
19
+ if (Number.isNaN(value)) {
20
+ return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, Math.min(DEFAULT_AGENT_TURN_TIMEOUT_MS, maxTimeoutMs));
21
+ }
22
+ return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, Math.min(value, maxTimeoutMs));
23
+ }
24
+ function resolveQueueCallbackMaxDurationSeconds() {
25
+ const value = Number.parseInt(process.env.QUEUE_CALLBACK_MAX_DURATION_SECONDS ?? "", 10);
26
+ if (Number.isNaN(value) || value <= 0) {
27
+ return DEFAULT_QUEUE_CALLBACK_MAX_DURATION_SECONDS;
28
+ }
29
+ return value;
30
+ }
31
+ function resolveMaxTurnTimeoutMs(queueCallbackMaxDurationSeconds) {
32
+ const budgetSeconds = queueCallbackMaxDurationSeconds - TURN_TIMEOUT_BUFFER_SECONDS;
33
+ return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, budgetSeconds * 1e3);
34
+ }
35
+ function buildBotConfig() {
36
+ const queueCallbackMaxDurationSeconds = resolveQueueCallbackMaxDurationSeconds();
37
+ const maxTurnTimeoutMs = resolveMaxTurnTimeoutMs(queueCallbackMaxDurationSeconds);
38
+ return {
39
+ userName: process.env.JUNIOR_BOT_NAME ?? "junior",
40
+ modelId: process.env.AI_MODEL ?? "anthropic/claude-sonnet-4.6",
41
+ fastModelId: process.env.AI_FAST_MODEL ?? process.env.AI_MODEL ?? "anthropic/claude-haiku-4.5",
42
+ turnTimeoutMs: parseAgentTurnTimeoutMs(process.env.AGENT_TURN_TIMEOUT_MS, maxTurnTimeoutMs)
43
+ };
44
+ }
45
+ var botConfig = buildBotConfig();
46
+ function toOptionalTrimmed(value) {
47
+ if (!value) {
48
+ return void 0;
49
+ }
50
+ const trimmed = value.trim();
51
+ return trimmed.length > 0 ? trimmed : void 0;
52
+ }
53
+ function getSlackBotToken() {
54
+ return toOptionalTrimmed(process.env.SLACK_BOT_TOKEN) ?? toOptionalTrimmed(process.env.SLACK_BOT_USER_TOKEN);
55
+ }
56
+ function getSlackSigningSecret() {
57
+ return toOptionalTrimmed(process.env.SLACK_SIGNING_SECRET);
58
+ }
59
+ function getSlackClientId() {
60
+ return toOptionalTrimmed(process.env.SLACK_CLIENT_ID);
61
+ }
62
+ function getSlackClientSecret() {
63
+ return toOptionalTrimmed(process.env.SLACK_CLIENT_SECRET);
64
+ }
65
+ function hasRedisConfig() {
66
+ return Boolean(process.env.REDIS_URL);
67
+ }
68
+
69
+ // src/chat/state.ts
70
+ import { createRedisState } from "@chat-adapter/state-redis";
71
+ import { createMemoryState } from "@chat-adapter/state-memory";
72
+ var MIN_LOCK_TTL_MS = 1e3 * 60 * 5;
73
+ var QUEUE_INGRESS_DEDUP_PREFIX = "junior:queue_ingress";
74
+ var QUEUE_MESSAGE_PROCESSING_PREFIX = "junior:queue_message";
75
+ var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
76
+ var QUEUE_MESSAGE_PROCESSING_TTL_MS = 30 * 60 * 1e3;
77
+ var QUEUE_MESSAGE_COMPLETED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
78
+ var QUEUE_MESSAGE_FAILED_TTL_MS = 6 * 60 * 60 * 1e3;
79
+ var AGENT_TURN_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
80
+ var CLAIM_OR_RECLAIM_PROCESSING_SCRIPT = `
81
+ local key = KEYS[1]
82
+ local nowMs = tonumber(ARGV[1])
83
+ local ttlMs = tonumber(ARGV[2])
84
+ local payload = ARGV[3]
85
+ local current = redis.call("get", key)
86
+
87
+ if not current then
88
+ redis.call("set", key, payload, "PX", ttlMs)
89
+ return 1
90
+ end
91
+
92
+ local ok, parsed = pcall(cjson.decode, current)
93
+ if not ok or type(parsed) ~= "table" then
94
+ return 0
95
+ end
96
+
97
+ local status = parsed["status"]
98
+ if status == "failed" then
99
+ redis.call("set", key, payload, "PX", ttlMs)
100
+ return 3
101
+ end
102
+ if status ~= "processing" then
103
+ return 0
104
+ end
105
+
106
+ local updatedAtMs = tonumber(parsed["updatedAtMs"])
107
+ if not updatedAtMs then
108
+ return 0
109
+ end
110
+
111
+ if updatedAtMs + ttlMs < nowMs then
112
+ redis.call("set", key, payload, "PX", ttlMs)
113
+ return 2
114
+ end
115
+
116
+ return 0
117
+ `;
118
+ var UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT = `
119
+ local key = KEYS[1]
120
+ local ownerToken = ARGV[1]
121
+ local ttlMs = tonumber(ARGV[2])
122
+ local payload = ARGV[3]
123
+ local current = redis.call("get", key)
124
+
125
+ if not current then
126
+ return 0
127
+ end
128
+
129
+ local ok, parsed = pcall(cjson.decode, current)
130
+ if not ok or type(parsed) ~= "table" then
131
+ return 0
132
+ end
133
+
134
+ local currentOwner = parsed["ownerToken"]
135
+ local status = parsed["status"]
136
+ if currentOwner ~= ownerToken then
137
+ return 0
138
+ end
139
+ if status ~= "processing" then
140
+ return 0
141
+ end
142
+
143
+ redis.call("set", key, payload, "PX", ttlMs)
144
+ return 1
145
+ `;
146
+ function createQueuedStateAdapter(base) {
147
+ const acquireLock = async (threadId, ttlMs) => {
148
+ const effectiveTtlMs = Math.max(ttlMs, MIN_LOCK_TTL_MS);
149
+ const lock = await base.acquireLock(threadId, effectiveTtlMs);
150
+ return lock;
151
+ };
152
+ return {
153
+ connect: () => base.connect(),
154
+ disconnect: () => base.disconnect(),
155
+ subscribe: (threadId) => base.subscribe(threadId),
156
+ unsubscribe: (threadId) => base.unsubscribe(threadId),
157
+ isSubscribed: (threadId) => base.isSubscribed(threadId),
158
+ acquireLock,
159
+ releaseLock: (lock) => base.releaseLock(lock),
160
+ extendLock: (lock, ttlMs) => base.extendLock(lock, Math.max(ttlMs, MIN_LOCK_TTL_MS)),
161
+ get: (key) => base.get(key),
162
+ set: (key, value, ttlMs) => base.set(key, value, ttlMs),
163
+ setIfNotExists: (key, value, ttlMs) => base.setIfNotExists(key, value, ttlMs),
164
+ delete: (key) => base.delete(key)
165
+ };
166
+ }
167
+ function createStateAdapter() {
168
+ if (process.env.JUNIOR_STATE_ADAPTER?.trim().toLowerCase() === "memory") {
169
+ _redisStateAdapter = void 0;
170
+ return createQueuedStateAdapter(createMemoryState());
171
+ }
172
+ if (!hasRedisConfig()) {
173
+ throw new Error("REDIS_URL is required for durable Slack thread state");
174
+ }
175
+ const redisState = createRedisState({
176
+ url: process.env.REDIS_URL
177
+ });
178
+ _redisStateAdapter = redisState;
179
+ return createQueuedStateAdapter(redisState);
180
+ }
181
+ var _stateAdapter;
182
+ var _redisStateAdapter;
183
+ function getRedisStateAdapter() {
184
+ if (!_redisStateAdapter) {
185
+ getStateAdapter();
186
+ }
187
+ if (!_redisStateAdapter) {
188
+ throw new Error("Redis state adapter is unavailable for this runtime");
189
+ }
190
+ return _redisStateAdapter;
191
+ }
192
+ function queueMessageKey(rawKey) {
193
+ return `${QUEUE_MESSAGE_PROCESSING_PREFIX}:${rawKey}`;
194
+ }
195
+ function parseQueueMessageState(value) {
196
+ if (typeof value !== "string") {
197
+ return void 0;
198
+ }
199
+ try {
200
+ const parsed = JSON.parse(value);
201
+ if (!parsed || parsed.status !== "processing" && parsed.status !== "completed" && parsed.status !== "failed" || typeof parsed.updatedAtMs !== "number") {
202
+ return void 0;
203
+ }
204
+ return {
205
+ status: parsed.status,
206
+ updatedAtMs: parsed.updatedAtMs,
207
+ ...typeof parsed.ownerToken === "string" ? { ownerToken: parsed.ownerToken } : {},
208
+ ...typeof parsed.queueMessageId === "string" ? { queueMessageId: parsed.queueMessageId } : {},
209
+ ...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {}
210
+ };
211
+ } catch {
212
+ return void 0;
213
+ }
214
+ }
215
+ function agentTurnSessionKey(conversationId, sessionId) {
216
+ return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
217
+ }
218
+ function isRecord(value) {
219
+ return typeof value === "object" && value !== null;
220
+ }
221
+ function parseAgentTurnSessionCheckpoint(value) {
222
+ if (typeof value !== "string") {
223
+ return void 0;
224
+ }
225
+ try {
226
+ const parsed = JSON.parse(value);
227
+ if (!isRecord(parsed)) {
228
+ return void 0;
229
+ }
230
+ const status = parsed.state;
231
+ if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed") {
232
+ return void 0;
233
+ }
234
+ const conversationId = parsed.conversationId;
235
+ const sessionId = parsed.sessionId;
236
+ const sliceId = parsed.sliceId;
237
+ const checkpointVersion = parsed.checkpointVersion;
238
+ const updatedAtMs = parsed.updatedAtMs;
239
+ if (typeof conversationId !== "string" || typeof sessionId !== "string" || typeof sliceId !== "number" || typeof checkpointVersion !== "number" || typeof updatedAtMs !== "number") {
240
+ return void 0;
241
+ }
242
+ return {
243
+ checkpointVersion,
244
+ conversationId,
245
+ sessionId,
246
+ sliceId,
247
+ state: status,
248
+ updatedAtMs,
249
+ piMessages: Array.isArray(parsed.piMessages) ? parsed.piMessages : [],
250
+ ...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
251
+ ...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {}
252
+ };
253
+ } catch {
254
+ return void 0;
255
+ }
256
+ }
257
+ function getStateAdapter() {
258
+ if (!_stateAdapter) {
259
+ _stateAdapter = createStateAdapter();
260
+ }
261
+ return _stateAdapter;
262
+ }
263
+ async function claimQueueIngressDedup(rawKey, ttlMs) {
264
+ await getStateAdapter().connect();
265
+ const key = `${QUEUE_INGRESS_DEDUP_PREFIX}:${rawKey}`;
266
+ const result = await getRedisStateAdapter().getClient().set(key, "1", {
267
+ NX: true,
268
+ PX: ttlMs
269
+ });
270
+ return result === "OK";
271
+ }
272
+ async function hasQueueIngressDedup(rawKey) {
273
+ await getStateAdapter().connect();
274
+ const key = `${QUEUE_INGRESS_DEDUP_PREFIX}:${rawKey}`;
275
+ const value = await getRedisStateAdapter().getClient().get(key);
276
+ return typeof value === "string" && value.length > 0;
277
+ }
278
+ async function getQueueMessageProcessingState(rawKey) {
279
+ await getStateAdapter().connect();
280
+ const state = await getStateAdapter().get(queueMessageKey(rawKey));
281
+ return parseQueueMessageState(state);
282
+ }
283
+ async function acquireQueueMessageProcessingOwnership(args) {
284
+ await getStateAdapter().connect();
285
+ const key = queueMessageKey(args.rawKey);
286
+ const nowMs = Date.now();
287
+ const payload = JSON.stringify({
288
+ status: "processing",
289
+ updatedAtMs: nowMs,
290
+ ownerToken: args.ownerToken,
291
+ ...args.queueMessageId ? { queueMessageId: args.queueMessageId } : {}
292
+ });
293
+ const result = await getRedisStateAdapter().getClient().eval(CLAIM_OR_RECLAIM_PROCESSING_SCRIPT, {
294
+ keys: [key],
295
+ arguments: [String(nowMs), String(QUEUE_MESSAGE_PROCESSING_TTL_MS), payload]
296
+ });
297
+ if (result === 1) {
298
+ return "acquired";
299
+ }
300
+ if (result === 2) {
301
+ return "reclaimed";
302
+ }
303
+ if (result === 3) {
304
+ return "recovered";
305
+ }
306
+ return "blocked";
307
+ }
308
+ async function refreshQueueMessageProcessingOwnership(args) {
309
+ await getStateAdapter().connect();
310
+ const nowMs = Date.now();
311
+ const payload = JSON.stringify({
312
+ status: "processing",
313
+ updatedAtMs: nowMs,
314
+ ownerToken: args.ownerToken,
315
+ ...args.queueMessageId ? { queueMessageId: args.queueMessageId } : {}
316
+ });
317
+ const result = await getRedisStateAdapter().getClient().eval(UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT, {
318
+ keys: [queueMessageKey(args.rawKey)],
319
+ arguments: [args.ownerToken, String(QUEUE_MESSAGE_PROCESSING_TTL_MS), payload]
320
+ });
321
+ return result === 1;
322
+ }
323
+ async function completeQueueMessageProcessingOwnership(args) {
324
+ await getStateAdapter().connect();
325
+ const payload = JSON.stringify({
326
+ status: "completed",
327
+ updatedAtMs: Date.now(),
328
+ ownerToken: args.ownerToken,
329
+ ...args.queueMessageId ? { queueMessageId: args.queueMessageId } : {}
330
+ });
331
+ const result = await getRedisStateAdapter().getClient().eval(UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT, {
332
+ keys: [queueMessageKey(args.rawKey)],
333
+ arguments: [args.ownerToken, String(QUEUE_MESSAGE_COMPLETED_TTL_MS), payload]
334
+ });
335
+ return result === 1;
336
+ }
337
+ async function failQueueMessageProcessingOwnership(args) {
338
+ await getStateAdapter().connect();
339
+ const payload = JSON.stringify({
340
+ status: "failed",
341
+ updatedAtMs: Date.now(),
342
+ ownerToken: args.ownerToken,
343
+ errorMessage: args.errorMessage,
344
+ ...args.queueMessageId ? { queueMessageId: args.queueMessageId } : {}
345
+ });
346
+ const result = await getRedisStateAdapter().getClient().eval(UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT, {
347
+ keys: [queueMessageKey(args.rawKey)],
348
+ arguments: [args.ownerToken, String(QUEUE_MESSAGE_FAILED_TTL_MS), payload]
349
+ });
350
+ return result === 1;
351
+ }
352
+ async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
353
+ await getStateAdapter().connect();
354
+ const value = await getStateAdapter().get(agentTurnSessionKey(conversationId, sessionId));
355
+ return parseAgentTurnSessionCheckpoint(value);
356
+ }
357
+ async function upsertAgentTurnSessionCheckpoint(args) {
358
+ await getStateAdapter().connect();
359
+ const existing = await getAgentTurnSessionCheckpoint(args.conversationId, args.sessionId);
360
+ const checkpoint = {
361
+ checkpointVersion: (existing?.checkpointVersion ?? 0) + 1,
362
+ conversationId: args.conversationId,
363
+ sessionId: args.sessionId,
364
+ sliceId: args.sliceId,
365
+ state: args.state,
366
+ updatedAtMs: Date.now(),
367
+ piMessages: Array.isArray(args.piMessages) ? args.piMessages : [],
368
+ ...args.errorMessage ? { errorMessage: args.errorMessage } : {},
369
+ ...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
370
+ };
371
+ const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
372
+ await getStateAdapter().set(agentTurnSessionKey(args.conversationId, args.sessionId), JSON.stringify(checkpoint), ttlMs);
373
+ return checkpoint;
374
+ }
375
+
34
376
  // src/chat/plugins/registry.ts
35
377
  import { readFileSync as readFileSync2, readdirSync, statSync as statSync2 } from "fs";
36
378
  import path3 from "path";
@@ -1314,13 +1656,268 @@ function createSkillCapabilityRuntime(options = {}) {
1314
1656
  });
1315
1657
  }
1316
1658
 
1659
+ // src/chat/slack-actions/client.ts
1660
+ import { WebClient } from "@slack/web-api";
1661
+ var SlackActionError = class extends Error {
1662
+ code;
1663
+ apiError;
1664
+ needed;
1665
+ provided;
1666
+ statusCode;
1667
+ requestId;
1668
+ errorData;
1669
+ retryAfterSeconds;
1670
+ detail;
1671
+ detailLine;
1672
+ detailRule;
1673
+ constructor(message, code, options = {}) {
1674
+ super(message);
1675
+ this.name = "SlackActionError";
1676
+ this.code = code;
1677
+ this.apiError = options.apiError;
1678
+ this.needed = options.needed;
1679
+ this.provided = options.provided;
1680
+ this.statusCode = options.statusCode;
1681
+ this.requestId = options.requestId;
1682
+ this.errorData = options.errorData;
1683
+ this.retryAfterSeconds = options.retryAfterSeconds;
1684
+ this.detail = options.detail;
1685
+ this.detailLine = options.detailLine;
1686
+ this.detailRule = options.detailRule;
1687
+ }
1688
+ };
1689
+ function serializeSlackErrorData(data) {
1690
+ if (!data || typeof data !== "object") {
1691
+ return void 0;
1692
+ }
1693
+ const filtered = Object.fromEntries(
1694
+ Object.entries(data).filter(([key]) => key !== "error")
1695
+ );
1696
+ if (Object.keys(filtered).length === 0) {
1697
+ return void 0;
1698
+ }
1699
+ try {
1700
+ const serialized = JSON.stringify(filtered);
1701
+ return serialized.length <= 600 ? serialized : `${serialized.slice(0, 597)}...`;
1702
+ } catch {
1703
+ return void 0;
1704
+ }
1705
+ }
1706
+ function getHeaderString(headers, name) {
1707
+ if (!headers || typeof headers !== "object") {
1708
+ return void 0;
1709
+ }
1710
+ const key = name.toLowerCase();
1711
+ const entries = headers;
1712
+ for (const [entryKey, value] of Object.entries(entries)) {
1713
+ if (entryKey.toLowerCase() !== key) continue;
1714
+ if (typeof value === "string") return value;
1715
+ if (Array.isArray(value)) {
1716
+ const first = value.find((entry) => typeof entry === "string");
1717
+ return typeof first === "string" ? first : void 0;
1718
+ }
1719
+ }
1720
+ return void 0;
1721
+ }
1722
+ function parseSlackCanvasDetail(detail) {
1723
+ if (typeof detail !== "string") {
1724
+ return {};
1725
+ }
1726
+ const trimmed = detail.trim();
1727
+ if (!trimmed) {
1728
+ return {};
1729
+ }
1730
+ const parsed = {
1731
+ detail: trimmed
1732
+ };
1733
+ const lineMatch = trimmed.match(/line\s+(\d+):/i);
1734
+ if (lineMatch) {
1735
+ const line = Number.parseInt(lineMatch[1] ?? "", 10);
1736
+ if (Number.isFinite(line)) {
1737
+ parsed.detailLine = line;
1738
+ }
1739
+ }
1740
+ if (/unsupported heading depth/i.test(trimmed)) {
1741
+ parsed.detailRule = "unsupported_heading_depth";
1742
+ }
1743
+ return parsed;
1744
+ }
1745
+ var client = null;
1746
+ function normalizeSlackConversationId(channelId) {
1747
+ if (!channelId) return void 0;
1748
+ const trimmed = channelId.trim();
1749
+ if (!trimmed) return void 0;
1750
+ if (!trimmed.startsWith("slack:")) {
1751
+ return trimmed;
1752
+ }
1753
+ const parts = trimmed.split(":");
1754
+ return parts[1]?.trim() || void 0;
1755
+ }
1756
+ function getClient() {
1757
+ if (client) return client;
1758
+ const token = getSlackBotToken();
1759
+ if (!token) {
1760
+ throw new SlackActionError(
1761
+ "SLACK_BOT_TOKEN (or SLACK_BOT_USER_TOKEN) is required for Slack canvas/list actions in this service",
1762
+ "missing_token"
1763
+ );
1764
+ }
1765
+ client = new WebClient(token);
1766
+ return client;
1767
+ }
1768
+ function mapSlackError(error) {
1769
+ if (error instanceof SlackActionError) {
1770
+ return error;
1771
+ }
1772
+ const candidate = error;
1773
+ const apiError = candidate.data?.error;
1774
+ const message = candidate.message ?? "Slack action failed";
1775
+ const baseOptions = {
1776
+ apiError,
1777
+ statusCode: candidate.statusCode,
1778
+ requestId: getHeaderString(candidate.headers, "x-slack-req-id"),
1779
+ errorData: serializeSlackErrorData(candidate.data),
1780
+ ...parseSlackCanvasDetail(candidate.data?.detail)
1781
+ };
1782
+ if (apiError === "missing_scope") {
1783
+ return new SlackActionError(message, "missing_scope", {
1784
+ ...baseOptions,
1785
+ needed: candidate.data?.needed,
1786
+ provided: candidate.data?.provided
1787
+ });
1788
+ }
1789
+ if (apiError === "not_in_channel") {
1790
+ return new SlackActionError(message, "not_in_channel", baseOptions);
1791
+ }
1792
+ if (apiError === "invalid_arguments") {
1793
+ return new SlackActionError(message, "invalid_arguments", baseOptions);
1794
+ }
1795
+ if (apiError === "invalid_name") {
1796
+ return new SlackActionError(message, "invalid_arguments", baseOptions);
1797
+ }
1798
+ if (apiError === "not_found") {
1799
+ return new SlackActionError(message, "not_found", baseOptions);
1800
+ }
1801
+ if (apiError === "feature_not_enabled" || apiError === "not_allowed_token_type") {
1802
+ return new SlackActionError(message, "feature_unavailable", baseOptions);
1803
+ }
1804
+ if (apiError === "canvas_creation_failed") {
1805
+ return new SlackActionError(message, "canvas_creation_failed", baseOptions);
1806
+ }
1807
+ if (apiError === "canvas_editing_failed") {
1808
+ return new SlackActionError(message, "canvas_editing_failed", baseOptions);
1809
+ }
1810
+ if (candidate.code === "slack_webapi_rate_limited_error" || candidate.statusCode === 429) {
1811
+ return new SlackActionError(message, "rate_limited", {
1812
+ ...baseOptions,
1813
+ retryAfterSeconds: candidate.retryAfter
1814
+ });
1815
+ }
1816
+ return new SlackActionError(message, "internal_error", baseOptions);
1817
+ }
1818
+ function sleep(ms) {
1819
+ return new Promise((resolve) => setTimeout(resolve, ms));
1820
+ }
1821
+ async function withSlackRetries(task, maxAttempts = 3, context = {}) {
1822
+ let attempt = 0;
1823
+ while (attempt < maxAttempts) {
1824
+ attempt += 1;
1825
+ try {
1826
+ return await task();
1827
+ } catch (error) {
1828
+ const mapped = mapSlackError(error);
1829
+ const isRetryable = mapped.code === "rate_limited";
1830
+ const baseLogAttributes = {
1831
+ "app.slack.action": context.action ?? "unknown",
1832
+ "app.slack.error_code": mapped.code,
1833
+ ...mapped.apiError ? { "app.slack.api_error": mapped.apiError } : {},
1834
+ ...mapped.detail ? { "app.slack.detail": mapped.detail } : {},
1835
+ ...mapped.detailLine !== void 0 ? { "app.slack.detail_line": mapped.detailLine } : {},
1836
+ ...mapped.detailRule ? { "app.slack.detail_rule": mapped.detailRule } : {},
1837
+ ...mapped.requestId ? { "app.slack.request_id": mapped.requestId } : {},
1838
+ ...mapped.statusCode !== void 0 ? { "http.response.status_code": mapped.statusCode } : {},
1839
+ ...context.attributes ?? {}
1840
+ };
1841
+ if (!isRetryable || attempt >= maxAttempts) {
1842
+ logWarn(
1843
+ "slack_action_failed",
1844
+ {},
1845
+ {
1846
+ ...baseLogAttributes,
1847
+ ...mapped.errorData ? { "app.slack.error_data": mapped.errorData } : {}
1848
+ },
1849
+ "Slack action failed"
1850
+ );
1851
+ throw mapped;
1852
+ }
1853
+ logWarn(
1854
+ "slack_action_retrying",
1855
+ {},
1856
+ {
1857
+ ...baseLogAttributes,
1858
+ "app.slack.retry_attempt": attempt
1859
+ },
1860
+ "Retrying Slack action after transient failure"
1861
+ );
1862
+ const retryAfterMs = mapped.code === "rate_limited" && mapped.retryAfterSeconds && mapped.retryAfterSeconds > 0 ? mapped.retryAfterSeconds * 1e3 : void 0;
1863
+ const backoffMs = Math.min(2e3, 250 * 2 ** (attempt - 1));
1864
+ await sleep(retryAfterMs ?? backoffMs);
1865
+ }
1866
+ }
1867
+ throw new SlackActionError("Slack action exhausted retries", "internal_error");
1868
+ }
1869
+ function getSlackClient() {
1870
+ return getClient();
1871
+ }
1872
+ function isDmChannel(channelId) {
1873
+ const normalized = normalizeSlackConversationId(channelId);
1874
+ return Boolean(normalized && normalized.startsWith("D"));
1875
+ }
1876
+ function isConversationScopedChannel(channelId) {
1877
+ const normalized = normalizeSlackConversationId(channelId);
1878
+ if (!normalized) return false;
1879
+ return normalized.startsWith("C") || normalized.startsWith("G") || normalized.startsWith("D");
1880
+ }
1881
+ function isConversationChannel(channelId) {
1882
+ const normalized = normalizeSlackConversationId(channelId);
1883
+ if (!normalized) return false;
1884
+ return normalized.startsWith("C") || normalized.startsWith("G");
1885
+ }
1886
+ async function getFilePermalink(fileId) {
1887
+ const client2 = getClient();
1888
+ const response = await withSlackRetries(
1889
+ () => client2.files.info({
1890
+ file: fileId
1891
+ })
1892
+ );
1893
+ return response.file?.permalink;
1894
+ }
1895
+ async function downloadPrivateSlackFile(url) {
1896
+ const token = getSlackBotToken();
1897
+ if (!token) {
1898
+ throw new SlackActionError(
1899
+ "SLACK_BOT_TOKEN (or SLACK_BOT_USER_TOKEN) is required for Slack file downloads in this service",
1900
+ "missing_token"
1901
+ );
1902
+ }
1903
+ const response = await fetch(url, {
1904
+ headers: {
1905
+ Authorization: `Bearer ${token}`
1906
+ }
1907
+ });
1908
+ if (!response.ok) {
1909
+ throw new Error(`Slack file download failed: ${response.status}`);
1910
+ }
1911
+ return Buffer.from(await response.arrayBuffer());
1912
+ }
1913
+
1317
1914
  // src/chat/capabilities/jr-rpc-command.ts
1318
1915
  import { randomBytes } from "crypto";
1319
1916
  import { Bash, defineCommand } from "just-bash";
1320
1917
  async function deliverPrivateMessage(input) {
1321
- let client;
1918
+ let client2;
1322
1919
  try {
1323
- client = getSlackClient();
1920
+ client2 = getSlackClient();
1324
1921
  } catch {
1325
1922
  logWarn("oauth_private_delivery_skip", {}, { "app.reason": "missing_bot_token" }, "Skipped private message delivery \u2014 no SLACK_BOT_TOKEN");
1326
1923
  return false;
@@ -1329,13 +1926,13 @@ async function deliverPrivateMessage(input) {
1329
1926
  const isDm = isDmChannel(input.channelId);
1330
1927
  try {
1331
1928
  if (isDm) {
1332
- await client.chat.postMessage({
1929
+ await client2.chat.postMessage({
1333
1930
  channel: input.channelId,
1334
1931
  text: input.text,
1335
1932
  ...input.threadTs ? { thread_ts: input.threadTs } : {}
1336
1933
  });
1337
1934
  } else {
1338
- await client.chat.postEphemeral({
1935
+ await client2.chat.postEphemeral({
1339
1936
  channel: input.channelId,
1340
1937
  user: input.userId,
1341
1938
  text: input.text,
@@ -1354,13 +1951,13 @@ async function deliverPrivateMessage(input) {
1354
1951
  }
1355
1952
  }
1356
1953
  try {
1357
- const openResult = await client.conversations.open({ users: input.userId });
1954
+ const openResult = await client2.conversations.open({ users: input.userId });
1358
1955
  const dmChannelId = openResult.channel?.id;
1359
1956
  if (!dmChannelId) {
1360
1957
  logWarn("oauth_dm_fallback_failed", {}, { "app.reason": "no_dm_channel_id" }, "conversations.open returned no channel ID");
1361
1958
  return false;
1362
1959
  }
1363
- await client.chat.postMessage({ channel: dmChannelId, text: input.text });
1960
+ await client2.chat.postMessage({ channel: dmChannelId, text: input.text });
1364
1961
  return "fallback_dm";
1365
1962
  } catch (error) {
1366
1963
  const slackError = error instanceof Error ? error.message : String(error);
@@ -3052,7 +3649,7 @@ var GATEWAY_PROVIDER = "vercel-ai-gateway";
3052
3649
  var GEN_AI_PROVIDER_NAME = GATEWAY_PROVIDER;
3053
3650
  var GEN_AI_OPERATION_CHAT = "chat";
3054
3651
  var MISSING_GATEWAY_CREDENTIALS_ERROR = "Missing AI gateway credentials (AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN)";
3055
- function toOptionalTrimmed(value) {
3652
+ function toOptionalTrimmed2(value) {
3056
3653
  if (!value) {
3057
3654
  return void 0;
3058
3655
  }
@@ -3063,14 +3660,14 @@ function isVercelRuntime() {
3063
3660
  return process.env.VERCEL === "1" || Boolean(process.env.VERCEL_ENV) || Boolean(process.env.VERCEL_REGION);
3064
3661
  }
3065
3662
  function getGatewayApiKey() {
3066
- const explicitApiKey = toOptionalTrimmed(getEnvApiKey("vercel-ai-gateway"));
3663
+ const explicitApiKey = toOptionalTrimmed2(getEnvApiKey("vercel-ai-gateway"));
3067
3664
  if (explicitApiKey) {
3068
3665
  return explicitApiKey;
3069
3666
  }
3070
3667
  if (!isVercelRuntime()) {
3071
3668
  return void 0;
3072
3669
  }
3073
- return toOptionalTrimmed(process.env.VERCEL_OIDC_TOKEN);
3670
+ return toOptionalTrimmed2(process.env.VERCEL_OIDC_TOKEN);
3074
3671
  }
3075
3672
  function extractText(message) {
3076
3673
  return (message.content ?? []).filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join("").trim();
@@ -3488,6 +4085,197 @@ function createReadFileTool() {
3488
4085
 
3489
4086
  // src/chat/tools/slack-channel-list-members.ts
3490
4087
  import { Type as Type5 } from "@sinclair/typebox";
4088
+
4089
+ // src/chat/slack-actions/channel.ts
4090
+ async function postMessageToChannel(input) {
4091
+ const client2 = getSlackClient();
4092
+ const channelId = normalizeSlackConversationId(input.channelId);
4093
+ if (!channelId) {
4094
+ throw new Error("Slack channel message posting requires a valid channel ID");
4095
+ }
4096
+ const response = await withSlackRetries(
4097
+ () => client2.chat.postMessage({
4098
+ channel: channelId,
4099
+ text: input.text,
4100
+ mrkdwn: true
4101
+ }),
4102
+ 3,
4103
+ { action: "chat.postMessage" }
4104
+ );
4105
+ if (!response.ts) {
4106
+ throw new Error("Slack channel message posted without ts");
4107
+ }
4108
+ let permalink;
4109
+ try {
4110
+ const permalinkResponse = await withSlackRetries(
4111
+ () => client2.chat.getPermalink({
4112
+ channel: channelId,
4113
+ message_ts: response.ts
4114
+ }),
4115
+ 3,
4116
+ { action: "chat.getPermalink" }
4117
+ );
4118
+ permalink = permalinkResponse.permalink;
4119
+ } catch {
4120
+ }
4121
+ return {
4122
+ ts: response.ts,
4123
+ permalink
4124
+ };
4125
+ }
4126
+ async function addReactionToMessage(input) {
4127
+ const client2 = getSlackClient();
4128
+ const channelId = normalizeSlackConversationId(input.channelId);
4129
+ if (!channelId) {
4130
+ throw new Error("Slack reaction requires a valid channel ID");
4131
+ }
4132
+ const timestamp = input.timestamp.trim();
4133
+ if (!timestamp) {
4134
+ throw new Error("Slack reaction requires a target message timestamp");
4135
+ }
4136
+ const emoji = input.emoji.trim().replaceAll(":", "");
4137
+ if (!emoji) {
4138
+ throw new Error("Slack reaction requires a non-empty emoji name");
4139
+ }
4140
+ await withSlackRetries(
4141
+ () => client2.reactions.add({
4142
+ channel: channelId,
4143
+ timestamp,
4144
+ name: emoji
4145
+ }),
4146
+ 3,
4147
+ { action: "reactions.add" }
4148
+ );
4149
+ return { ok: true };
4150
+ }
4151
+ async function removeReactionFromMessage(input) {
4152
+ const client2 = getSlackClient();
4153
+ const channelId = normalizeSlackConversationId(input.channelId);
4154
+ if (!channelId) {
4155
+ throw new Error("Slack reaction requires a valid channel ID");
4156
+ }
4157
+ const timestamp = input.timestamp.trim();
4158
+ if (!timestamp) {
4159
+ throw new Error("Slack reaction requires a target message timestamp");
4160
+ }
4161
+ const emoji = input.emoji.trim().replaceAll(":", "");
4162
+ if (!emoji) {
4163
+ throw new Error("Slack reaction requires a non-empty emoji name");
4164
+ }
4165
+ await withSlackRetries(
4166
+ () => client2.reactions.remove({
4167
+ channel: channelId,
4168
+ timestamp,
4169
+ name: emoji
4170
+ }),
4171
+ 3,
4172
+ { action: "reactions.remove" }
4173
+ );
4174
+ return { ok: true };
4175
+ }
4176
+ async function listChannelMessages(input) {
4177
+ const client2 = getSlackClient();
4178
+ const channelId = normalizeSlackConversationId(input.channelId);
4179
+ if (!channelId) {
4180
+ throw new Error("Slack channel history lookup requires a valid channel ID");
4181
+ }
4182
+ const targetLimit = Math.max(1, Math.min(input.limit, 1e3));
4183
+ const maxPages = Math.max(1, Math.min(input.maxPages ?? 5, 10));
4184
+ const messages = [];
4185
+ let cursor = input.cursor;
4186
+ let pages = 0;
4187
+ while (messages.length < targetLimit && pages < maxPages) {
4188
+ pages += 1;
4189
+ const pageLimit = Math.max(1, Math.min(200, targetLimit - messages.length));
4190
+ const response = await withSlackRetries(
4191
+ () => client2.conversations.history({
4192
+ channel: channelId,
4193
+ limit: pageLimit,
4194
+ cursor,
4195
+ oldest: input.oldest,
4196
+ latest: input.latest,
4197
+ inclusive: input.inclusive
4198
+ }),
4199
+ 3,
4200
+ { action: "conversations.history" }
4201
+ );
4202
+ const batch = response.messages ?? [];
4203
+ messages.push(...batch);
4204
+ cursor = response.response_metadata?.next_cursor || void 0;
4205
+ if (!cursor) {
4206
+ break;
4207
+ }
4208
+ }
4209
+ return {
4210
+ messages: messages.slice(0, targetLimit),
4211
+ nextCursor: cursor
4212
+ };
4213
+ }
4214
+ async function listChannelMembers(input) {
4215
+ const client2 = getSlackClient();
4216
+ const channelId = normalizeSlackConversationId(input.channelId);
4217
+ if (!channelId) {
4218
+ throw new Error("Slack channel member lookup requires a valid channel ID");
4219
+ }
4220
+ const targetLimit = Math.max(1, Math.min(input.limit, 200));
4221
+ const response = await withSlackRetries(
4222
+ () => client2.conversations.members({
4223
+ channel: channelId,
4224
+ limit: targetLimit,
4225
+ cursor: input.cursor
4226
+ }),
4227
+ 3,
4228
+ { action: "conversations.members" }
4229
+ );
4230
+ const members = (response.members ?? []).slice(0, targetLimit);
4231
+ return {
4232
+ members: members.map((userId) => ({ user_id: userId })),
4233
+ nextCursor: response.response_metadata?.next_cursor || void 0
4234
+ };
4235
+ }
4236
+ async function listThreadReplies(input) {
4237
+ const client2 = getSlackClient();
4238
+ const channelId = normalizeSlackConversationId(input.channelId);
4239
+ if (!channelId) {
4240
+ throw new Error("Slack thread reply lookup requires a valid channel ID");
4241
+ }
4242
+ const targetLimit = Math.max(1, Math.min(input.limit ?? 1e3, 1e3));
4243
+ const maxPages = Math.max(1, Math.min(input.maxPages ?? 10, 10));
4244
+ const pendingTargets = new Set(
4245
+ (input.targetMessageTs ?? []).filter((value) => typeof value === "string" && value.length > 0)
4246
+ );
4247
+ const replies = [];
4248
+ let cursor;
4249
+ let pages = 0;
4250
+ while (replies.length < targetLimit && pages < maxPages) {
4251
+ pages += 1;
4252
+ const pageLimit = Math.max(1, Math.min(200, targetLimit - replies.length));
4253
+ const response = await withSlackRetries(
4254
+ () => client2.conversations.replies({
4255
+ channel: channelId,
4256
+ ts: input.threadTs,
4257
+ limit: pageLimit,
4258
+ cursor
4259
+ }),
4260
+ 3,
4261
+ { action: "conversations.replies" }
4262
+ );
4263
+ const batch = response.messages ?? [];
4264
+ replies.push(...batch);
4265
+ for (const reply of batch) {
4266
+ if (typeof reply.ts === "string" && pendingTargets.size > 0) {
4267
+ pendingTargets.delete(reply.ts);
4268
+ }
4269
+ }
4270
+ cursor = response.response_metadata?.next_cursor || void 0;
4271
+ if (!cursor || pendingTargets.size === 0) {
4272
+ break;
4273
+ }
4274
+ }
4275
+ return replies.slice(0, targetLimit);
4276
+ }
4277
+
4278
+ // src/chat/tools/slack-channel-list-members.ts
3491
4279
  function createSlackChannelListMembersTool(context) {
3492
4280
  return tool({
3493
4281
  description: "List member IDs in the active Slack channel context. Use when the user asks who is in a channel, who to assign, or who should be notified. Do not use when thread-local participant context is sufficient.",
@@ -3737,7 +4525,7 @@ function normalizeCanvasMarkdown(markdown) {
3737
4525
  };
3738
4526
  }
3739
4527
  async function createCanvas(input) {
3740
- const client = getSlackClient();
4528
+ const client2 = getSlackClient();
3741
4529
  const normalizedChannelId = normalizeSlackConversationId(input.channelId);
3742
4530
  const isConversationScoped = isConversationScopedChannel(normalizedChannelId);
3743
4531
  if (!isConversationScoped) {
@@ -3749,7 +4537,7 @@ async function createCanvas(input) {
3749
4537
  const action = "conversations.canvases.create";
3750
4538
  const normalizedContent = normalizeCanvasMarkdown(input.markdown);
3751
4539
  const result = await withSlackRetries(async () => {
3752
- return client.conversations.canvases.create({
4540
+ return client2.conversations.canvases.create({
3753
4541
  channel_id: normalizedChannelId,
3754
4542
  title: input.title,
3755
4543
  document_content: {
@@ -3782,9 +4570,9 @@ async function createCanvas(input) {
3782
4570
  };
3783
4571
  }
3784
4572
  async function lookupCanvasSection(canvasId, containsText) {
3785
- const client = getSlackClient();
4573
+ const client2 = getSlackClient();
3786
4574
  const response = await withSlackRetries(
3787
- () => client.canvases.sections.lookup({
4575
+ () => client2.canvases.sections.lookup({
3788
4576
  canvas_id: canvasId,
3789
4577
  criteria: {
3790
4578
  contains_text: containsText
@@ -3802,10 +4590,10 @@ async function lookupCanvasSection(canvasId, containsText) {
3802
4590
  return response.sections?.[0]?.id;
3803
4591
  }
3804
4592
  async function updateCanvas(input) {
3805
- const client = getSlackClient();
4593
+ const client2 = getSlackClient();
3806
4594
  const normalizedContent = normalizeCanvasMarkdown(input.markdown);
3807
4595
  await withSlackRetries(
3808
- () => client.canvases.edit({
4596
+ () => client2.canvases.edit({
3809
4597
  canvas_id: input.canvasId,
3810
4598
  changes: [
3811
4599
  {
@@ -4048,9 +4836,9 @@ var DEFAULT_TODO_SCHEMA = [
4048
4836
  { key: "due_date", name: "Due Date", type: "date" }
4049
4837
  ];
4050
4838
  async function createTodoList(name) {
4051
- const client = getSlackClient();
4839
+ const client2 = getSlackClient();
4052
4840
  const result = await withSlackRetries(
4053
- () => client.slackLists.create({
4841
+ () => client2.slackLists.create({
4054
4842
  name,
4055
4843
  schema: DEFAULT_TODO_SCHEMA,
4056
4844
  todo_mode: true
@@ -4072,7 +4860,7 @@ async function createTodoList(name) {
4072
4860
  };
4073
4861
  }
4074
4862
  async function addListItems(input) {
4075
- const client = getSlackClient();
4863
+ const client2 = getSlackClient();
4076
4864
  const listColumnMap = input.listColumnMap ?? {};
4077
4865
  if (!listColumnMap.titleColumnId) {
4078
4866
  throw new Error("Cannot add list items because title column could not be inferred");
@@ -4093,7 +4881,7 @@ async function addListItems(input) {
4093
4881
  });
4094
4882
  }
4095
4883
  const response = await withSlackRetries(
4096
- () => client.slackLists.items.create({
4884
+ () => client2.slackLists.items.create({
4097
4885
  list_id: input.listId,
4098
4886
  initial_fields: initialFields
4099
4887
  })
@@ -4108,13 +4896,13 @@ async function addListItems(input) {
4108
4896
  };
4109
4897
  }
4110
4898
  async function listItems(listId, limit = 100) {
4111
- const client = getSlackClient();
4899
+ const client2 = getSlackClient();
4112
4900
  const items = [];
4113
4901
  let cursor;
4114
4902
  const cappedLimit = Math.max(1, Math.min(limit, 200));
4115
4903
  do {
4116
4904
  const response = await withSlackRetries(
4117
- () => client.slackLists.items.list({
4905
+ () => client2.slackLists.items.list({
4118
4906
  list_id: listId,
4119
4907
  limit: cappedLimit,
4120
4908
  cursor
@@ -4133,7 +4921,7 @@ async function listItems(listId, limit = 100) {
4133
4921
  return items;
4134
4922
  }
4135
4923
  async function updateListItem(input) {
4136
- const client = getSlackClient();
4924
+ const client2 = getSlackClient();
4137
4925
  const cells = [];
4138
4926
  if (typeof input.completed === "boolean" && input.listColumnMap.completedColumnId) {
4139
4927
  cells.push({
@@ -4152,7 +4940,7 @@ async function updateListItem(input) {
4152
4940
  throw new Error("No updatable fields were provided or inferred for this list item");
4153
4941
  }
4154
4942
  await withSlackRetries(
4155
- () => client.slackLists.items.update({
4943
+ () => client2.slackLists.items.update({
4156
4944
  list_id: input.listId,
4157
4945
  cells
4158
4946
  })
@@ -5001,7 +5789,7 @@ var SNAPSHOT_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
5001
5789
  var SNAPSHOT_BUILD_LOCK_TTL_MS = 10 * 60 * 1e3;
5002
5790
  var SNAPSHOT_WAIT_FOR_LOCK_MS = SNAPSHOT_BUILD_LOCK_TTL_MS + 30 * 1e3;
5003
5791
  var DEFAULT_FLOATING_DEP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
5004
- function sleep(ms) {
5792
+ function sleep2(ms) {
5005
5793
  return new Promise((resolve) => {
5006
5794
  setTimeout(resolve, ms);
5007
5795
  });
@@ -5286,7 +6074,7 @@ async function withBuildLock(profileHash, callback, canUseCachedSnapshot) {
5286
6074
  await state.releaseLock(lock);
5287
6075
  }
5288
6076
  }
5289
- await sleep(500);
6077
+ await sleep2(500);
5290
6078
  }
5291
6079
  const cached = await getCachedSnapshot(profileHash);
5292
6080
  if (cached?.snapshotId && canUseCachedSnapshot(cached)) {
@@ -6046,14 +6834,14 @@ function createSandboxExecutor(options) {
6046
6834
  }
6047
6835
 
6048
6836
  // src/chat/runtime-metadata.ts
6049
- function toOptionalTrimmed2(value) {
6837
+ function toOptionalTrimmed3(value) {
6050
6838
  if (!value) return void 0;
6051
6839
  const trimmed = value.trim();
6052
6840
  return trimmed.length > 0 ? trimmed : void 0;
6053
6841
  }
6054
6842
  function getRuntimeMetadata() {
6055
6843
  return {
6056
- version: toOptionalTrimmed2(process.env.VERCEL_GIT_COMMIT_SHA)
6844
+ version: toOptionalTrimmed3(process.env.VERCEL_GIT_COMMIT_SHA)
6057
6845
  };
6058
6846
  }
6059
6847
 
@@ -7284,7 +8072,23 @@ async function publishAppHomeView(slackClient, userId, userTokenStore) {
7284
8072
 
7285
8073
  export {
7286
8074
  isPluginProvider,
8075
+ botConfig,
8076
+ getSlackBotToken,
8077
+ getSlackSigningSecret,
8078
+ getSlackClientId,
8079
+ getSlackClientSecret,
8080
+ getStateAdapter,
8081
+ claimQueueIngressDedup,
8082
+ hasQueueIngressDedup,
8083
+ getQueueMessageProcessingState,
8084
+ acquireQueueMessageProcessingOwnership,
8085
+ refreshQueueMessageProcessingOwnership,
8086
+ completeQueueMessageProcessingOwnership,
8087
+ failQueueMessageProcessingOwnership,
7287
8088
  getUserTokenStore,
8089
+ getSlackClient,
8090
+ isDmChannel,
8091
+ downloadPrivateSlackFile,
7288
8092
  getOAuthProviderConfig,
7289
8093
  resolveBaseUrl,
7290
8094
  startOAuthFlow,
@@ -7295,6 +8099,9 @@ export {
7295
8099
  GEN_AI_PROVIDER_NAME,
7296
8100
  completeText,
7297
8101
  completeObject,
8102
+ addReactionToMessage,
8103
+ removeReactionFromMessage,
8104
+ listThreadReplies,
7298
8105
  shouldEmitDevAgentTrace,
7299
8106
  truncateStatusText,
7300
8107
  isRetryableTurnError,