@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.
- package/README.md +111 -2
- package/dist/bot-6KXJ366H.js +16 -0
- package/dist/{chunk-7E56WM6K.js → chunk-5LLCJPTH.js} +856 -49
- package/dist/{bot-DLML4Z7F.js → chunk-CJFEZLEN.js} +59 -31
- package/dist/{chunk-OD6TOSY4.js → chunk-OVG2HBNM.js} +1 -1
- package/dist/handlers/queue-callback.js +6 -8
- package/dist/handlers/router.js +2 -2
- package/dist/handlers/webhooks.js +1 -1
- package/dist/{route-XLYK6CKP.js → route-DMVINKJW.js} +4 -9
- package/package.json +3 -3
- package/dist/channel-HJO33DGJ.js +0 -18
- package/dist/chunk-GDNDYMGX.js +0 -333
- package/dist/chunk-MM3YNA4F.js +0 -203
- package/dist/chunk-ZA2IDPVG.js +0 -39
- package/dist/chunk-ZBFSIN6G.js +0 -323
- package/dist/client-3GAEMIQ3.js +0 -10
|
@@ -1,39 +1,35 @@
|
|
|
1
1
|
import {
|
|
2
2
|
GEN_AI_PROVIDER_NAME,
|
|
3
|
+
addReactionToMessage,
|
|
4
|
+
botConfig,
|
|
3
5
|
buildSlackOutputMessage,
|
|
6
|
+
claimQueueIngressDedup,
|
|
4
7
|
completeObject,
|
|
5
8
|
completeText,
|
|
9
|
+
downloadPrivateSlackFile,
|
|
6
10
|
ensureBlockSpacing,
|
|
7
11
|
escapeXml,
|
|
8
12
|
generateAssistantReply,
|
|
9
13
|
getOAuthProviderConfig,
|
|
14
|
+
getSlackBotToken,
|
|
15
|
+
getSlackClient,
|
|
16
|
+
getSlackClientId,
|
|
17
|
+
getSlackClientSecret,
|
|
18
|
+
getSlackSigningSecret,
|
|
19
|
+
getStateAdapter,
|
|
10
20
|
getUserTokenStore,
|
|
21
|
+
hasQueueIngressDedup,
|
|
22
|
+
isDmChannel,
|
|
11
23
|
isExplicitChannelPostIntent,
|
|
12
24
|
isPluginProvider,
|
|
13
25
|
isRetryableTurnError,
|
|
26
|
+
listThreadReplies,
|
|
14
27
|
publishAppHomeView,
|
|
28
|
+
removeReactionFromMessage,
|
|
15
29
|
shouldEmitDevAgentTrace,
|
|
16
30
|
startOAuthFlow,
|
|
17
31
|
truncateStatusText
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import {
|
|
20
|
-
claimQueueIngressDedup,
|
|
21
|
-
getStateAdapter,
|
|
22
|
-
hasQueueIngressDedup
|
|
23
|
-
} from "./chunk-ZBFSIN6G.js";
|
|
24
|
-
import {
|
|
25
|
-
listThreadReplies
|
|
26
|
-
} from "./chunk-MM3YNA4F.js";
|
|
27
|
-
import {
|
|
28
|
-
botConfig,
|
|
29
|
-
downloadPrivateSlackFile,
|
|
30
|
-
getSlackBotToken,
|
|
31
|
-
getSlackClient,
|
|
32
|
-
getSlackClientId,
|
|
33
|
-
getSlackClientSecret,
|
|
34
|
-
getSlackSigningSecret,
|
|
35
|
-
isDmChannel
|
|
36
|
-
} from "./chunk-GDNDYMGX.js";
|
|
32
|
+
} from "./chunk-5LLCJPTH.js";
|
|
37
33
|
import {
|
|
38
34
|
logError,
|
|
39
35
|
logException,
|
|
@@ -229,6 +225,40 @@ function buildConversationStatePatch(conversation) {
|
|
|
229
225
|
};
|
|
230
226
|
}
|
|
231
227
|
|
|
228
|
+
// src/chat/queue/client.ts
|
|
229
|
+
import { handleCallback, send } from "@vercel/queue";
|
|
230
|
+
var DEFAULT_TOPIC_NAME = "junior-thread-message";
|
|
231
|
+
var MAX_DELIVERY_ATTEMPTS = 10;
|
|
232
|
+
function getThreadMessageTopic() {
|
|
233
|
+
return DEFAULT_TOPIC_NAME;
|
|
234
|
+
}
|
|
235
|
+
async function enqueueThreadMessage(payload, options) {
|
|
236
|
+
const result = await send(getThreadMessageTopic(), payload, {
|
|
237
|
+
...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}
|
|
238
|
+
});
|
|
239
|
+
return result.messageId ?? void 0;
|
|
240
|
+
}
|
|
241
|
+
function createQueueCallbackHandler(handler) {
|
|
242
|
+
return handleCallback(
|
|
243
|
+
async (message, metadata) => {
|
|
244
|
+
await handler(message, {
|
|
245
|
+
messageId: metadata.messageId,
|
|
246
|
+
deliveryCount: metadata.deliveryCount,
|
|
247
|
+
topicName: metadata.topicName
|
|
248
|
+
});
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
retry: (_error, metadata) => {
|
|
252
|
+
if (metadata.deliveryCount >= MAX_DELIVERY_ATTEMPTS) {
|
|
253
|
+
return { acknowledge: true };
|
|
254
|
+
}
|
|
255
|
+
const backoffSeconds = Math.min(300, Math.max(5, metadata.deliveryCount * 5));
|
|
256
|
+
return { afterSeconds: backoffSeconds };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
232
262
|
// src/chat/routing/subscribed-decision.ts
|
|
233
263
|
import { z } from "zod";
|
|
234
264
|
var replyDecisionSchema = z.object({
|
|
@@ -698,12 +728,9 @@ var defaultQueueRoutingDeps = {
|
|
|
698
728
|
getIsSubscribed: (threadId) => getStateAdapter().isSubscribed(threadId),
|
|
699
729
|
logInfo,
|
|
700
730
|
logWarn,
|
|
701
|
-
enqueueThreadMessage: async (payload, dedupKey) => {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
idempotencyKey: dedupKey
|
|
705
|
-
});
|
|
706
|
-
},
|
|
731
|
+
enqueueThreadMessage: async (payload, dedupKey) => await enqueueThreadMessage(payload, {
|
|
732
|
+
idempotencyKey: dedupKey
|
|
733
|
+
}),
|
|
707
734
|
shouldReplyInSubscribedThread: async ({ message, normalizedThreadId, thread }) => {
|
|
708
735
|
const rawText = message.text;
|
|
709
736
|
const text = stripLeadingBotMention(rawText, {
|
|
@@ -728,7 +755,6 @@ var defaultQueueRoutingDeps = {
|
|
|
728
755
|
});
|
|
729
756
|
},
|
|
730
757
|
addProcessingReaction: async ({ channelId, timestamp }) => {
|
|
731
|
-
const { addReactionToMessage } = await import("./channel-HJO33DGJ.js");
|
|
732
758
|
await addReactionToMessage({
|
|
733
759
|
channelId,
|
|
734
760
|
timestamp,
|
|
@@ -736,7 +762,6 @@ var defaultQueueRoutingDeps = {
|
|
|
736
762
|
});
|
|
737
763
|
},
|
|
738
764
|
removeProcessingReaction: async ({ channelId, timestamp }) => {
|
|
739
|
-
const { removeReactionFromMessage } = await import("./channel-HJO33DGJ.js");
|
|
740
765
|
await removeReactionFromMessage({
|
|
741
766
|
channelId,
|
|
742
767
|
timestamp,
|
|
@@ -3099,10 +3124,13 @@ registerBotHandlers({
|
|
|
3099
3124
|
bot,
|
|
3100
3125
|
appSlackRuntime
|
|
3101
3126
|
});
|
|
3127
|
+
|
|
3102
3128
|
export {
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3129
|
+
getThreadMessageTopic,
|
|
3130
|
+
createQueueCallbackHandler,
|
|
3131
|
+
setBotDepsForTests,
|
|
3106
3132
|
resetBotDepsForTests,
|
|
3107
|
-
|
|
3133
|
+
createNormalizingStream,
|
|
3134
|
+
bot,
|
|
3135
|
+
appSlackRuntime
|
|
3108
3136
|
};
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { after } from "next/server";
|
|
13
13
|
import * as Sentry from "@sentry/nextjs";
|
|
14
14
|
async function loadBot() {
|
|
15
|
-
const { bot } = await import("./bot-
|
|
15
|
+
const { bot } = await import("./bot-6KXJ366H.js");
|
|
16
16
|
return bot;
|
|
17
17
|
}
|
|
18
18
|
async function POST(request, context) {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
+
appSlackRuntime,
|
|
2
3
|
createQueueCallbackHandler,
|
|
3
4
|
getThreadMessageTopic
|
|
4
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-CJFEZLEN.js";
|
|
5
6
|
import {
|
|
6
7
|
acquireQueueMessageProcessingOwnership,
|
|
7
8
|
completeQueueMessageProcessingOwnership,
|
|
9
|
+
downloadPrivateSlackFile,
|
|
8
10
|
failQueueMessageProcessingOwnership,
|
|
9
11
|
getQueueMessageProcessingState,
|
|
10
12
|
getStateAdapter,
|
|
11
|
-
refreshQueueMessageProcessingOwnership
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
downloadPrivateSlackFile
|
|
15
|
-
} from "../chunk-GDNDYMGX.js";
|
|
13
|
+
refreshQueueMessageProcessingOwnership,
|
|
14
|
+
removeReactionFromMessage
|
|
15
|
+
} from "../chunk-5LLCJPTH.js";
|
|
16
16
|
import {
|
|
17
17
|
createRequestContext,
|
|
18
18
|
logError,
|
|
@@ -35,7 +35,6 @@ function rehydrateAttachmentFetchers(payload) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
async function processThreadMessageRuntime(args) {
|
|
38
|
-
const { appSlackRuntime } = await import("../bot-DLML4Z7F.js");
|
|
39
38
|
const runtimePayload = {
|
|
40
39
|
message: args.message,
|
|
41
40
|
thread: args.thread
|
|
@@ -84,7 +83,6 @@ var QueueMessageOwnershipError = class extends Error {
|
|
|
84
83
|
};
|
|
85
84
|
var defaultProcessQueuedThreadMessageDeps = {
|
|
86
85
|
clearProcessingReaction: async ({ channelId, timestamp }) => {
|
|
87
|
-
const { removeReactionFromMessage } = await import("../channel-HJO33DGJ.js");
|
|
88
86
|
await removeReactionFromMessage({
|
|
89
87
|
channelId,
|
|
90
88
|
timestamp,
|
package/dist/handlers/router.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
POST
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-OVG2HBNM.js";
|
|
4
4
|
import {
|
|
5
5
|
GET
|
|
6
6
|
} from "../chunk-4RBEYCOG.js";
|
|
@@ -8,7 +8,7 @@ import "../chunk-BBOVH5RF.js";
|
|
|
8
8
|
|
|
9
9
|
// src/handlers/oauth-callback.ts
|
|
10
10
|
async function loadOAuthRoute() {
|
|
11
|
-
return import("../route-
|
|
11
|
+
return import("../route-DMVINKJW.js");
|
|
12
12
|
}
|
|
13
13
|
async function GET2(request, context) {
|
|
14
14
|
const route = await loadOAuthRoute();
|
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
+
botConfig,
|
|
2
3
|
escapeXml,
|
|
3
4
|
generateAssistantReply,
|
|
4
5
|
getOAuthProviderConfig,
|
|
6
|
+
getSlackClient,
|
|
7
|
+
getStateAdapter,
|
|
5
8
|
getUserTokenStore,
|
|
6
9
|
publishAppHomeView,
|
|
7
10
|
resolveBaseUrl,
|
|
8
11
|
truncateStatusText
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import {
|
|
11
|
-
getStateAdapter
|
|
12
|
-
} from "./chunk-ZBFSIN6G.js";
|
|
13
|
-
import "./chunk-MM3YNA4F.js";
|
|
14
|
-
import {
|
|
15
|
-
botConfig,
|
|
16
|
-
getSlackClient
|
|
17
|
-
} from "./chunk-GDNDYMGX.js";
|
|
12
|
+
} from "./chunk-5LLCJPTH.js";
|
|
18
13
|
import {
|
|
19
14
|
logException,
|
|
20
15
|
logInfo
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"node-html-markdown": "^2.0.0",
|
|
41
41
|
"yaml": "^2.8.2",
|
|
42
42
|
"zod": "^4.3.6",
|
|
43
|
-
"@sentry/junior-github": "0.1.
|
|
44
|
-
"@sentry/junior-sentry": "0.1.
|
|
43
|
+
"@sentry/junior-github": "0.1.1",
|
|
44
|
+
"@sentry/junior-sentry": "0.1.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@sentry/nextjs": ">=10.0.0",
|
package/dist/channel-HJO33DGJ.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
addReactionToMessage,
|
|
3
|
-
listChannelMembers,
|
|
4
|
-
listChannelMessages,
|
|
5
|
-
listThreadReplies,
|
|
6
|
-
postMessageToChannel,
|
|
7
|
-
removeReactionFromMessage
|
|
8
|
-
} from "./chunk-MM3YNA4F.js";
|
|
9
|
-
import "./chunk-GDNDYMGX.js";
|
|
10
|
-
import "./chunk-BBOVH5RF.js";
|
|
11
|
-
export {
|
|
12
|
-
addReactionToMessage,
|
|
13
|
-
listChannelMembers,
|
|
14
|
-
listChannelMessages,
|
|
15
|
-
listThreadReplies,
|
|
16
|
-
postMessageToChannel,
|
|
17
|
-
removeReactionFromMessage
|
|
18
|
-
};
|
package/dist/chunk-GDNDYMGX.js
DELETED
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
logWarn
|
|
3
|
-
} from "./chunk-BBOVH5RF.js";
|
|
4
|
-
|
|
5
|
-
// src/chat/config.ts
|
|
6
|
-
var MIN_AGENT_TURN_TIMEOUT_MS = 10 * 1e3;
|
|
7
|
-
var DEFAULT_AGENT_TURN_TIMEOUT_MS = 12 * 60 * 1e3;
|
|
8
|
-
var DEFAULT_QUEUE_CALLBACK_MAX_DURATION_SECONDS = 800;
|
|
9
|
-
var TURN_TIMEOUT_BUFFER_SECONDS = 20;
|
|
10
|
-
function parseAgentTurnTimeoutMs(rawValue, maxTimeoutMs) {
|
|
11
|
-
const value = Number.parseInt(rawValue ?? "", 10);
|
|
12
|
-
if (Number.isNaN(value)) {
|
|
13
|
-
return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, Math.min(DEFAULT_AGENT_TURN_TIMEOUT_MS, maxTimeoutMs));
|
|
14
|
-
}
|
|
15
|
-
return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, Math.min(value, maxTimeoutMs));
|
|
16
|
-
}
|
|
17
|
-
function resolveQueueCallbackMaxDurationSeconds() {
|
|
18
|
-
const value = Number.parseInt(process.env.QUEUE_CALLBACK_MAX_DURATION_SECONDS ?? "", 10);
|
|
19
|
-
if (Number.isNaN(value) || value <= 0) {
|
|
20
|
-
return DEFAULT_QUEUE_CALLBACK_MAX_DURATION_SECONDS;
|
|
21
|
-
}
|
|
22
|
-
return value;
|
|
23
|
-
}
|
|
24
|
-
function resolveMaxTurnTimeoutMs(queueCallbackMaxDurationSeconds) {
|
|
25
|
-
const budgetSeconds = queueCallbackMaxDurationSeconds - TURN_TIMEOUT_BUFFER_SECONDS;
|
|
26
|
-
return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, budgetSeconds * 1e3);
|
|
27
|
-
}
|
|
28
|
-
function buildBotConfig() {
|
|
29
|
-
const queueCallbackMaxDurationSeconds = resolveQueueCallbackMaxDurationSeconds();
|
|
30
|
-
const maxTurnTimeoutMs = resolveMaxTurnTimeoutMs(queueCallbackMaxDurationSeconds);
|
|
31
|
-
return {
|
|
32
|
-
userName: process.env.JUNIOR_BOT_NAME ?? "junior",
|
|
33
|
-
modelId: process.env.AI_MODEL ?? "anthropic/claude-sonnet-4.6",
|
|
34
|
-
fastModelId: process.env.AI_FAST_MODEL ?? process.env.AI_MODEL ?? "anthropic/claude-haiku-4.5",
|
|
35
|
-
turnTimeoutMs: parseAgentTurnTimeoutMs(process.env.AGENT_TURN_TIMEOUT_MS, maxTurnTimeoutMs)
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
var botConfig = buildBotConfig();
|
|
39
|
-
function toOptionalTrimmed(value) {
|
|
40
|
-
if (!value) {
|
|
41
|
-
return void 0;
|
|
42
|
-
}
|
|
43
|
-
const trimmed = value.trim();
|
|
44
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
45
|
-
}
|
|
46
|
-
function getSlackBotToken() {
|
|
47
|
-
return toOptionalTrimmed(process.env.SLACK_BOT_TOKEN) ?? toOptionalTrimmed(process.env.SLACK_BOT_USER_TOKEN);
|
|
48
|
-
}
|
|
49
|
-
function getSlackSigningSecret() {
|
|
50
|
-
return toOptionalTrimmed(process.env.SLACK_SIGNING_SECRET);
|
|
51
|
-
}
|
|
52
|
-
function getSlackClientId() {
|
|
53
|
-
return toOptionalTrimmed(process.env.SLACK_CLIENT_ID);
|
|
54
|
-
}
|
|
55
|
-
function getSlackClientSecret() {
|
|
56
|
-
return toOptionalTrimmed(process.env.SLACK_CLIENT_SECRET);
|
|
57
|
-
}
|
|
58
|
-
function hasRedisConfig() {
|
|
59
|
-
return Boolean(process.env.REDIS_URL);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// src/chat/slack-actions/client.ts
|
|
63
|
-
import { WebClient } from "@slack/web-api";
|
|
64
|
-
var SlackActionError = class extends Error {
|
|
65
|
-
code;
|
|
66
|
-
apiError;
|
|
67
|
-
needed;
|
|
68
|
-
provided;
|
|
69
|
-
statusCode;
|
|
70
|
-
requestId;
|
|
71
|
-
errorData;
|
|
72
|
-
retryAfterSeconds;
|
|
73
|
-
detail;
|
|
74
|
-
detailLine;
|
|
75
|
-
detailRule;
|
|
76
|
-
constructor(message, code, options = {}) {
|
|
77
|
-
super(message);
|
|
78
|
-
this.name = "SlackActionError";
|
|
79
|
-
this.code = code;
|
|
80
|
-
this.apiError = options.apiError;
|
|
81
|
-
this.needed = options.needed;
|
|
82
|
-
this.provided = options.provided;
|
|
83
|
-
this.statusCode = options.statusCode;
|
|
84
|
-
this.requestId = options.requestId;
|
|
85
|
-
this.errorData = options.errorData;
|
|
86
|
-
this.retryAfterSeconds = options.retryAfterSeconds;
|
|
87
|
-
this.detail = options.detail;
|
|
88
|
-
this.detailLine = options.detailLine;
|
|
89
|
-
this.detailRule = options.detailRule;
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
function serializeSlackErrorData(data) {
|
|
93
|
-
if (!data || typeof data !== "object") {
|
|
94
|
-
return void 0;
|
|
95
|
-
}
|
|
96
|
-
const filtered = Object.fromEntries(
|
|
97
|
-
Object.entries(data).filter(([key]) => key !== "error")
|
|
98
|
-
);
|
|
99
|
-
if (Object.keys(filtered).length === 0) {
|
|
100
|
-
return void 0;
|
|
101
|
-
}
|
|
102
|
-
try {
|
|
103
|
-
const serialized = JSON.stringify(filtered);
|
|
104
|
-
return serialized.length <= 600 ? serialized : `${serialized.slice(0, 597)}...`;
|
|
105
|
-
} catch {
|
|
106
|
-
return void 0;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
function getHeaderString(headers, name) {
|
|
110
|
-
if (!headers || typeof headers !== "object") {
|
|
111
|
-
return void 0;
|
|
112
|
-
}
|
|
113
|
-
const key = name.toLowerCase();
|
|
114
|
-
const entries = headers;
|
|
115
|
-
for (const [entryKey, value] of Object.entries(entries)) {
|
|
116
|
-
if (entryKey.toLowerCase() !== key) continue;
|
|
117
|
-
if (typeof value === "string") return value;
|
|
118
|
-
if (Array.isArray(value)) {
|
|
119
|
-
const first = value.find((entry) => typeof entry === "string");
|
|
120
|
-
return typeof first === "string" ? first : void 0;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return void 0;
|
|
124
|
-
}
|
|
125
|
-
function parseSlackCanvasDetail(detail) {
|
|
126
|
-
if (typeof detail !== "string") {
|
|
127
|
-
return {};
|
|
128
|
-
}
|
|
129
|
-
const trimmed = detail.trim();
|
|
130
|
-
if (!trimmed) {
|
|
131
|
-
return {};
|
|
132
|
-
}
|
|
133
|
-
const parsed = {
|
|
134
|
-
detail: trimmed
|
|
135
|
-
};
|
|
136
|
-
const lineMatch = trimmed.match(/line\s+(\d+):/i);
|
|
137
|
-
if (lineMatch) {
|
|
138
|
-
const line = Number.parseInt(lineMatch[1] ?? "", 10);
|
|
139
|
-
if (Number.isFinite(line)) {
|
|
140
|
-
parsed.detailLine = line;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (/unsupported heading depth/i.test(trimmed)) {
|
|
144
|
-
parsed.detailRule = "unsupported_heading_depth";
|
|
145
|
-
}
|
|
146
|
-
return parsed;
|
|
147
|
-
}
|
|
148
|
-
var client = null;
|
|
149
|
-
function normalizeSlackConversationId(channelId) {
|
|
150
|
-
if (!channelId) return void 0;
|
|
151
|
-
const trimmed = channelId.trim();
|
|
152
|
-
if (!trimmed) return void 0;
|
|
153
|
-
if (!trimmed.startsWith("slack:")) {
|
|
154
|
-
return trimmed;
|
|
155
|
-
}
|
|
156
|
-
const parts = trimmed.split(":");
|
|
157
|
-
return parts[1]?.trim() || void 0;
|
|
158
|
-
}
|
|
159
|
-
function getClient() {
|
|
160
|
-
if (client) return client;
|
|
161
|
-
const token = getSlackBotToken();
|
|
162
|
-
if (!token) {
|
|
163
|
-
throw new SlackActionError(
|
|
164
|
-
"SLACK_BOT_TOKEN (or SLACK_BOT_USER_TOKEN) is required for Slack canvas/list actions in this service",
|
|
165
|
-
"missing_token"
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
client = new WebClient(token);
|
|
169
|
-
return client;
|
|
170
|
-
}
|
|
171
|
-
function mapSlackError(error) {
|
|
172
|
-
if (error instanceof SlackActionError) {
|
|
173
|
-
return error;
|
|
174
|
-
}
|
|
175
|
-
const candidate = error;
|
|
176
|
-
const apiError = candidate.data?.error;
|
|
177
|
-
const message = candidate.message ?? "Slack action failed";
|
|
178
|
-
const baseOptions = {
|
|
179
|
-
apiError,
|
|
180
|
-
statusCode: candidate.statusCode,
|
|
181
|
-
requestId: getHeaderString(candidate.headers, "x-slack-req-id"),
|
|
182
|
-
errorData: serializeSlackErrorData(candidate.data),
|
|
183
|
-
...parseSlackCanvasDetail(candidate.data?.detail)
|
|
184
|
-
};
|
|
185
|
-
if (apiError === "missing_scope") {
|
|
186
|
-
return new SlackActionError(message, "missing_scope", {
|
|
187
|
-
...baseOptions,
|
|
188
|
-
needed: candidate.data?.needed,
|
|
189
|
-
provided: candidate.data?.provided
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
if (apiError === "not_in_channel") {
|
|
193
|
-
return new SlackActionError(message, "not_in_channel", baseOptions);
|
|
194
|
-
}
|
|
195
|
-
if (apiError === "invalid_arguments") {
|
|
196
|
-
return new SlackActionError(message, "invalid_arguments", baseOptions);
|
|
197
|
-
}
|
|
198
|
-
if (apiError === "invalid_name") {
|
|
199
|
-
return new SlackActionError(message, "invalid_arguments", baseOptions);
|
|
200
|
-
}
|
|
201
|
-
if (apiError === "not_found") {
|
|
202
|
-
return new SlackActionError(message, "not_found", baseOptions);
|
|
203
|
-
}
|
|
204
|
-
if (apiError === "feature_not_enabled" || apiError === "not_allowed_token_type") {
|
|
205
|
-
return new SlackActionError(message, "feature_unavailable", baseOptions);
|
|
206
|
-
}
|
|
207
|
-
if (apiError === "canvas_creation_failed") {
|
|
208
|
-
return new SlackActionError(message, "canvas_creation_failed", baseOptions);
|
|
209
|
-
}
|
|
210
|
-
if (apiError === "canvas_editing_failed") {
|
|
211
|
-
return new SlackActionError(message, "canvas_editing_failed", baseOptions);
|
|
212
|
-
}
|
|
213
|
-
if (candidate.code === "slack_webapi_rate_limited_error" || candidate.statusCode === 429) {
|
|
214
|
-
return new SlackActionError(message, "rate_limited", {
|
|
215
|
-
...baseOptions,
|
|
216
|
-
retryAfterSeconds: candidate.retryAfter
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
return new SlackActionError(message, "internal_error", baseOptions);
|
|
220
|
-
}
|
|
221
|
-
function sleep(ms) {
|
|
222
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
223
|
-
}
|
|
224
|
-
async function withSlackRetries(task, maxAttempts = 3, context = {}) {
|
|
225
|
-
let attempt = 0;
|
|
226
|
-
while (attempt < maxAttempts) {
|
|
227
|
-
attempt += 1;
|
|
228
|
-
try {
|
|
229
|
-
return await task();
|
|
230
|
-
} catch (error) {
|
|
231
|
-
const mapped = mapSlackError(error);
|
|
232
|
-
const isRetryable = mapped.code === "rate_limited";
|
|
233
|
-
const baseLogAttributes = {
|
|
234
|
-
"app.slack.action": context.action ?? "unknown",
|
|
235
|
-
"app.slack.error_code": mapped.code,
|
|
236
|
-
...mapped.apiError ? { "app.slack.api_error": mapped.apiError } : {},
|
|
237
|
-
...mapped.detail ? { "app.slack.detail": mapped.detail } : {},
|
|
238
|
-
...mapped.detailLine !== void 0 ? { "app.slack.detail_line": mapped.detailLine } : {},
|
|
239
|
-
...mapped.detailRule ? { "app.slack.detail_rule": mapped.detailRule } : {},
|
|
240
|
-
...mapped.requestId ? { "app.slack.request_id": mapped.requestId } : {},
|
|
241
|
-
...mapped.statusCode !== void 0 ? { "http.response.status_code": mapped.statusCode } : {},
|
|
242
|
-
...context.attributes ?? {}
|
|
243
|
-
};
|
|
244
|
-
if (!isRetryable || attempt >= maxAttempts) {
|
|
245
|
-
logWarn(
|
|
246
|
-
"slack_action_failed",
|
|
247
|
-
{},
|
|
248
|
-
{
|
|
249
|
-
...baseLogAttributes,
|
|
250
|
-
...mapped.errorData ? { "app.slack.error_data": mapped.errorData } : {}
|
|
251
|
-
},
|
|
252
|
-
"Slack action failed"
|
|
253
|
-
);
|
|
254
|
-
throw mapped;
|
|
255
|
-
}
|
|
256
|
-
logWarn(
|
|
257
|
-
"slack_action_retrying",
|
|
258
|
-
{},
|
|
259
|
-
{
|
|
260
|
-
...baseLogAttributes,
|
|
261
|
-
"app.slack.retry_attempt": attempt
|
|
262
|
-
},
|
|
263
|
-
"Retrying Slack action after transient failure"
|
|
264
|
-
);
|
|
265
|
-
const retryAfterMs = mapped.code === "rate_limited" && mapped.retryAfterSeconds && mapped.retryAfterSeconds > 0 ? mapped.retryAfterSeconds * 1e3 : void 0;
|
|
266
|
-
const backoffMs = Math.min(2e3, 250 * 2 ** (attempt - 1));
|
|
267
|
-
await sleep(retryAfterMs ?? backoffMs);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
throw new SlackActionError("Slack action exhausted retries", "internal_error");
|
|
271
|
-
}
|
|
272
|
-
function getSlackClient() {
|
|
273
|
-
return getClient();
|
|
274
|
-
}
|
|
275
|
-
function isDmChannel(channelId) {
|
|
276
|
-
const normalized = normalizeSlackConversationId(channelId);
|
|
277
|
-
return Boolean(normalized && normalized.startsWith("D"));
|
|
278
|
-
}
|
|
279
|
-
function isConversationScopedChannel(channelId) {
|
|
280
|
-
const normalized = normalizeSlackConversationId(channelId);
|
|
281
|
-
if (!normalized) return false;
|
|
282
|
-
return normalized.startsWith("C") || normalized.startsWith("G") || normalized.startsWith("D");
|
|
283
|
-
}
|
|
284
|
-
function isConversationChannel(channelId) {
|
|
285
|
-
const normalized = normalizeSlackConversationId(channelId);
|
|
286
|
-
if (!normalized) return false;
|
|
287
|
-
return normalized.startsWith("C") || normalized.startsWith("G");
|
|
288
|
-
}
|
|
289
|
-
async function getFilePermalink(fileId) {
|
|
290
|
-
const client2 = getClient();
|
|
291
|
-
const response = await withSlackRetries(
|
|
292
|
-
() => client2.files.info({
|
|
293
|
-
file: fileId
|
|
294
|
-
})
|
|
295
|
-
);
|
|
296
|
-
return response.file?.permalink;
|
|
297
|
-
}
|
|
298
|
-
async function downloadPrivateSlackFile(url) {
|
|
299
|
-
const token = getSlackBotToken();
|
|
300
|
-
if (!token) {
|
|
301
|
-
throw new SlackActionError(
|
|
302
|
-
"SLACK_BOT_TOKEN (or SLACK_BOT_USER_TOKEN) is required for Slack file downloads in this service",
|
|
303
|
-
"missing_token"
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
const response = await fetch(url, {
|
|
307
|
-
headers: {
|
|
308
|
-
Authorization: `Bearer ${token}`
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
if (!response.ok) {
|
|
312
|
-
throw new Error(`Slack file download failed: ${response.status}`);
|
|
313
|
-
}
|
|
314
|
-
return Buffer.from(await response.arrayBuffer());
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export {
|
|
318
|
-
botConfig,
|
|
319
|
-
getSlackBotToken,
|
|
320
|
-
getSlackSigningSecret,
|
|
321
|
-
getSlackClientId,
|
|
322
|
-
getSlackClientSecret,
|
|
323
|
-
hasRedisConfig,
|
|
324
|
-
SlackActionError,
|
|
325
|
-
normalizeSlackConversationId,
|
|
326
|
-
withSlackRetries,
|
|
327
|
-
getSlackClient,
|
|
328
|
-
isDmChannel,
|
|
329
|
-
isConversationScopedChannel,
|
|
330
|
-
isConversationChannel,
|
|
331
|
-
getFilePermalink,
|
|
332
|
-
downloadPrivateSlackFile
|
|
333
|
-
};
|