@sentry/junior 0.9.4 → 0.10.0
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 +0 -7
- package/dist/{chunk-FLP5I4NM.js → chunk-7RM2Y52T.js} +283 -589
- package/dist/{chunk-FS5Y4CF2.js → chunk-BJ4EBVQK.js} +17 -34
- package/dist/cli/init.js +21 -12
- package/dist/cli/snapshot-warmup.js +1 -1
- package/dist/handlers/router.d.ts +2 -4
- package/dist/handlers/router.js +26 -48
- package/dist/handlers/webhooks.d.ts +8 -1
- package/dist/handlers/webhooks.js +6 -5
- package/dist/next-config.js +0 -1
- package/package.json +16 -18
- package/dist/chunk-SO4ORZJR.js +0 -103
- package/dist/chunk-YGERYE7Y.js +0 -685
- package/dist/handlers/queue-callback.d.ts +0 -10
- package/dist/handlers/queue-callback.js +0 -11
package/dist/chunk-YGERYE7Y.js
DELETED
|
@@ -1,685 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DeferredThreadMessageError,
|
|
3
|
-
buildDeterministicTurnId,
|
|
4
|
-
coerceThreadConversationState,
|
|
5
|
-
createQueueCallbackHandler,
|
|
6
|
-
downloadPrivateSlackFile,
|
|
7
|
-
getProductionSlackRuntime,
|
|
8
|
-
getThreadMessageTopic,
|
|
9
|
-
removeReactionFromMessage
|
|
10
|
-
} from "./chunk-FLP5I4NM.js";
|
|
11
|
-
import {
|
|
12
|
-
getConnectedStateContext,
|
|
13
|
-
getStateAdapter
|
|
14
|
-
} from "./chunk-FS5Y4CF2.js";
|
|
15
|
-
import {
|
|
16
|
-
createRequestContext,
|
|
17
|
-
logError,
|
|
18
|
-
logException,
|
|
19
|
-
logInfo,
|
|
20
|
-
logWarn,
|
|
21
|
-
setSpanStatus,
|
|
22
|
-
withContext,
|
|
23
|
-
withSpan
|
|
24
|
-
} from "./chunk-MY7JNCS2.js";
|
|
25
|
-
|
|
26
|
-
// src/chat/queue/process-thread-message.ts
|
|
27
|
-
import { Message, ThreadImpl } from "chat";
|
|
28
|
-
|
|
29
|
-
// src/chat/state/queue-processing-store.ts
|
|
30
|
-
var QUEUE_MESSAGE_PROCESSING_PREFIX = "junior:queue_message";
|
|
31
|
-
var QUEUE_MESSAGE_PROCESSING_TTL_MS = 30 * 60 * 1e3;
|
|
32
|
-
var QUEUE_MESSAGE_COMPLETED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
33
|
-
var QUEUE_MESSAGE_FAILED_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
34
|
-
var CLAIM_OR_RECLAIM_PROCESSING_SCRIPT = `
|
|
35
|
-
local key = KEYS[1]
|
|
36
|
-
local nowMs = tonumber(ARGV[1])
|
|
37
|
-
local ttlMs = tonumber(ARGV[2])
|
|
38
|
-
local payload = ARGV[3]
|
|
39
|
-
local current = redis.call("get", key)
|
|
40
|
-
|
|
41
|
-
if not current then
|
|
42
|
-
redis.call("set", key, payload, "PX", ttlMs)
|
|
43
|
-
return 1
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
local ok, parsed = pcall(cjson.decode, current)
|
|
47
|
-
if not ok or type(parsed) ~= "table" then
|
|
48
|
-
return 0
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
local status = parsed["status"]
|
|
52
|
-
if status == "failed" then
|
|
53
|
-
redis.call("set", key, payload, "PX", ttlMs)
|
|
54
|
-
return 3
|
|
55
|
-
end
|
|
56
|
-
if status ~= "processing" then
|
|
57
|
-
return 0
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
local updatedAtMs = tonumber(parsed["updatedAtMs"])
|
|
61
|
-
if not updatedAtMs then
|
|
62
|
-
return 0
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
if updatedAtMs + ttlMs < nowMs then
|
|
66
|
-
redis.call("set", key, payload, "PX", ttlMs)
|
|
67
|
-
return 2
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
return 0
|
|
71
|
-
`;
|
|
72
|
-
var UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT = `
|
|
73
|
-
local key = KEYS[1]
|
|
74
|
-
local ownerToken = ARGV[1]
|
|
75
|
-
local ttlMs = tonumber(ARGV[2])
|
|
76
|
-
local payload = ARGV[3]
|
|
77
|
-
local current = redis.call("get", key)
|
|
78
|
-
|
|
79
|
-
if not current then
|
|
80
|
-
return 0
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
local ok, parsed = pcall(cjson.decode, current)
|
|
84
|
-
if not ok or type(parsed) ~= "table" then
|
|
85
|
-
return 0
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
local currentOwner = parsed["ownerToken"]
|
|
89
|
-
local status = parsed["status"]
|
|
90
|
-
if currentOwner ~= ownerToken then
|
|
91
|
-
return 0
|
|
92
|
-
end
|
|
93
|
-
if status ~= "processing" then
|
|
94
|
-
return 0
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
redis.call("set", key, payload, "PX", ttlMs)
|
|
98
|
-
return 1
|
|
99
|
-
`;
|
|
100
|
-
function queueMessageKey(rawKey) {
|
|
101
|
-
return `${QUEUE_MESSAGE_PROCESSING_PREFIX}:${rawKey}`;
|
|
102
|
-
}
|
|
103
|
-
function buildQueueMessageProcessingPayload(args) {
|
|
104
|
-
return JSON.stringify({
|
|
105
|
-
status: args.status,
|
|
106
|
-
updatedAtMs: args.updatedAtMs ?? Date.now(),
|
|
107
|
-
ownerToken: args.ownerToken,
|
|
108
|
-
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
109
|
-
...args.queueMessageId ? { queueMessageId: args.queueMessageId } : {}
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
function parseQueueMessageState(value) {
|
|
113
|
-
if (typeof value !== "string") {
|
|
114
|
-
return void 0;
|
|
115
|
-
}
|
|
116
|
-
try {
|
|
117
|
-
const parsed = JSON.parse(value);
|
|
118
|
-
if (!parsed || parsed.status !== "processing" && parsed.status !== "completed" && parsed.status !== "failed" || typeof parsed.updatedAtMs !== "number") {
|
|
119
|
-
return void 0;
|
|
120
|
-
}
|
|
121
|
-
return {
|
|
122
|
-
status: parsed.status,
|
|
123
|
-
updatedAtMs: parsed.updatedAtMs,
|
|
124
|
-
...typeof parsed.ownerToken === "string" ? { ownerToken: parsed.ownerToken } : {},
|
|
125
|
-
...typeof parsed.queueMessageId === "string" ? { queueMessageId: parsed.queueMessageId } : {},
|
|
126
|
-
...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {}
|
|
127
|
-
};
|
|
128
|
-
} catch {
|
|
129
|
-
return void 0;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
async function updateQueueMessageProcessingStateIfOwner(args) {
|
|
133
|
-
const existingState = parseQueueMessageState(
|
|
134
|
-
await args.stateAdapter.get(queueMessageKey(args.rawKey))
|
|
135
|
-
);
|
|
136
|
-
if (!existingState || existingState.status !== "processing" || existingState.ownerToken !== args.ownerToken) {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
await args.stateAdapter.set(
|
|
140
|
-
queueMessageKey(args.rawKey),
|
|
141
|
-
args.payload,
|
|
142
|
-
args.ttlMs
|
|
143
|
-
);
|
|
144
|
-
return true;
|
|
145
|
-
}
|
|
146
|
-
async function getQueueMessageProcessingState(rawKey) {
|
|
147
|
-
const adapter = getStateAdapter();
|
|
148
|
-
await adapter.connect();
|
|
149
|
-
const state = await adapter.get(queueMessageKey(rawKey));
|
|
150
|
-
return parseQueueMessageState(state);
|
|
151
|
-
}
|
|
152
|
-
async function acquireQueueMessageProcessingOwnership(args) {
|
|
153
|
-
const { stateAdapter, redisStateAdapter } = await getConnectedStateContext();
|
|
154
|
-
const key = queueMessageKey(args.rawKey);
|
|
155
|
-
const nowMs = Date.now();
|
|
156
|
-
const payload = buildQueueMessageProcessingPayload({
|
|
157
|
-
ownerToken: args.ownerToken,
|
|
158
|
-
queueMessageId: args.queueMessageId,
|
|
159
|
-
status: "processing",
|
|
160
|
-
updatedAtMs: nowMs
|
|
161
|
-
});
|
|
162
|
-
if (redisStateAdapter) {
|
|
163
|
-
const result = await redisStateAdapter.getClient().eval(CLAIM_OR_RECLAIM_PROCESSING_SCRIPT, {
|
|
164
|
-
keys: [key],
|
|
165
|
-
arguments: [
|
|
166
|
-
String(nowMs),
|
|
167
|
-
String(QUEUE_MESSAGE_PROCESSING_TTL_MS),
|
|
168
|
-
payload
|
|
169
|
-
]
|
|
170
|
-
});
|
|
171
|
-
if (result === 1) {
|
|
172
|
-
return "acquired";
|
|
173
|
-
}
|
|
174
|
-
if (result === 2) {
|
|
175
|
-
return "reclaimed";
|
|
176
|
-
}
|
|
177
|
-
if (result === 3) {
|
|
178
|
-
return "recovered";
|
|
179
|
-
}
|
|
180
|
-
return "blocked";
|
|
181
|
-
}
|
|
182
|
-
const existingState = parseQueueMessageState(await stateAdapter.get(key));
|
|
183
|
-
if (!existingState) {
|
|
184
|
-
const claimed = await stateAdapter.setIfNotExists(
|
|
185
|
-
key,
|
|
186
|
-
payload,
|
|
187
|
-
QUEUE_MESSAGE_PROCESSING_TTL_MS
|
|
188
|
-
);
|
|
189
|
-
return claimed ? "acquired" : "blocked";
|
|
190
|
-
}
|
|
191
|
-
if (existingState.status === "failed") {
|
|
192
|
-
await stateAdapter.set(key, payload, QUEUE_MESSAGE_PROCESSING_TTL_MS);
|
|
193
|
-
return "recovered";
|
|
194
|
-
}
|
|
195
|
-
if (existingState.status === "processing" && existingState.updatedAtMs + QUEUE_MESSAGE_PROCESSING_TTL_MS < nowMs) {
|
|
196
|
-
await stateAdapter.set(key, payload, QUEUE_MESSAGE_PROCESSING_TTL_MS);
|
|
197
|
-
return "reclaimed";
|
|
198
|
-
}
|
|
199
|
-
return "blocked";
|
|
200
|
-
}
|
|
201
|
-
async function refreshQueueMessageProcessingOwnership(args) {
|
|
202
|
-
const { stateAdapter, redisStateAdapter } = await getConnectedStateContext();
|
|
203
|
-
const payload = buildQueueMessageProcessingPayload({
|
|
204
|
-
ownerToken: args.ownerToken,
|
|
205
|
-
queueMessageId: args.queueMessageId,
|
|
206
|
-
status: "processing"
|
|
207
|
-
});
|
|
208
|
-
if (redisStateAdapter) {
|
|
209
|
-
const result = await redisStateAdapter.getClient().eval(UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT, {
|
|
210
|
-
keys: [queueMessageKey(args.rawKey)],
|
|
211
|
-
arguments: [
|
|
212
|
-
args.ownerToken,
|
|
213
|
-
String(QUEUE_MESSAGE_PROCESSING_TTL_MS),
|
|
214
|
-
payload
|
|
215
|
-
]
|
|
216
|
-
});
|
|
217
|
-
return result === 1;
|
|
218
|
-
}
|
|
219
|
-
return await updateQueueMessageProcessingStateIfOwner({
|
|
220
|
-
rawKey: args.rawKey,
|
|
221
|
-
ownerToken: args.ownerToken,
|
|
222
|
-
payload,
|
|
223
|
-
ttlMs: QUEUE_MESSAGE_PROCESSING_TTL_MS,
|
|
224
|
-
stateAdapter
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
async function completeQueueMessageProcessingOwnership(args) {
|
|
228
|
-
const { stateAdapter, redisStateAdapter } = await getConnectedStateContext();
|
|
229
|
-
const payload = buildQueueMessageProcessingPayload({
|
|
230
|
-
ownerToken: args.ownerToken,
|
|
231
|
-
queueMessageId: args.queueMessageId,
|
|
232
|
-
status: "completed"
|
|
233
|
-
});
|
|
234
|
-
if (redisStateAdapter) {
|
|
235
|
-
const result = await redisStateAdapter.getClient().eval(UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT, {
|
|
236
|
-
keys: [queueMessageKey(args.rawKey)],
|
|
237
|
-
arguments: [
|
|
238
|
-
args.ownerToken,
|
|
239
|
-
String(QUEUE_MESSAGE_COMPLETED_TTL_MS),
|
|
240
|
-
payload
|
|
241
|
-
]
|
|
242
|
-
});
|
|
243
|
-
return result === 1;
|
|
244
|
-
}
|
|
245
|
-
return await updateQueueMessageProcessingStateIfOwner({
|
|
246
|
-
rawKey: args.rawKey,
|
|
247
|
-
ownerToken: args.ownerToken,
|
|
248
|
-
payload,
|
|
249
|
-
ttlMs: QUEUE_MESSAGE_COMPLETED_TTL_MS,
|
|
250
|
-
stateAdapter
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
async function failQueueMessageProcessingOwnership(args) {
|
|
254
|
-
const { stateAdapter, redisStateAdapter } = await getConnectedStateContext();
|
|
255
|
-
const payload = buildQueueMessageProcessingPayload({
|
|
256
|
-
errorMessage: args.errorMessage,
|
|
257
|
-
ownerToken: args.ownerToken,
|
|
258
|
-
queueMessageId: args.queueMessageId,
|
|
259
|
-
status: "failed"
|
|
260
|
-
});
|
|
261
|
-
if (redisStateAdapter) {
|
|
262
|
-
const result = await redisStateAdapter.getClient().eval(UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT, {
|
|
263
|
-
keys: [queueMessageKey(args.rawKey)],
|
|
264
|
-
arguments: [
|
|
265
|
-
args.ownerToken,
|
|
266
|
-
String(QUEUE_MESSAGE_FAILED_TTL_MS),
|
|
267
|
-
payload
|
|
268
|
-
]
|
|
269
|
-
});
|
|
270
|
-
return result === 1;
|
|
271
|
-
}
|
|
272
|
-
return await updateQueueMessageProcessingStateIfOwner({
|
|
273
|
-
rawKey: args.rawKey,
|
|
274
|
-
ownerToken: args.ownerToken,
|
|
275
|
-
payload,
|
|
276
|
-
ttlMs: QUEUE_MESSAGE_FAILED_TTL_MS,
|
|
277
|
-
stateAdapter
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// src/chat/queue/process-thread-message.ts
|
|
282
|
-
var THREAD_PROCESSING_LOCK_TTL_MS = 5 * 60 * 1e3;
|
|
283
|
-
var THREAD_PROCESSING_LOCK_HEARTBEAT_MS = 60 * 1e3;
|
|
284
|
-
function isSerializedThread(thread) {
|
|
285
|
-
return typeof thread === "object" && thread !== null && thread._type === "chat:Thread";
|
|
286
|
-
}
|
|
287
|
-
function isSerializedMessage(message) {
|
|
288
|
-
return typeof message === "object" && message !== null && message._type === "chat:Message";
|
|
289
|
-
}
|
|
290
|
-
function getPayloadChannelId(payload) {
|
|
291
|
-
return payload.thread.channelId;
|
|
292
|
-
}
|
|
293
|
-
function getPayloadUserId(payload) {
|
|
294
|
-
return payload.message.author?.userId;
|
|
295
|
-
}
|
|
296
|
-
function createMessageOwnerToken() {
|
|
297
|
-
return `msg-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
298
|
-
}
|
|
299
|
-
var QueueMessageOwnershipError = class extends Error {
|
|
300
|
-
constructor(stage, dedupKey) {
|
|
301
|
-
super(
|
|
302
|
-
`Queue message ownership lost during ${stage} for dedupKey=${dedupKey}`
|
|
303
|
-
);
|
|
304
|
-
this.name = "QueueMessageOwnershipError";
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
var defaultProcessQueuedThreadMessageDeps = {
|
|
308
|
-
clearProcessingReaction: async ({
|
|
309
|
-
channelId,
|
|
310
|
-
timestamp
|
|
311
|
-
}) => {
|
|
312
|
-
await removeReactionFromMessage({
|
|
313
|
-
channelId,
|
|
314
|
-
timestamp,
|
|
315
|
-
emoji: "eyes"
|
|
316
|
-
});
|
|
317
|
-
},
|
|
318
|
-
logInfo,
|
|
319
|
-
logWarn
|
|
320
|
-
};
|
|
321
|
-
function resolveDeps(deps) {
|
|
322
|
-
return {
|
|
323
|
-
...defaultProcessQueuedThreadMessageDeps,
|
|
324
|
-
...deps
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
function deserializeThread(thread) {
|
|
328
|
-
if (isSerializedThread(thread)) {
|
|
329
|
-
return ThreadImpl.fromJSON(thread);
|
|
330
|
-
}
|
|
331
|
-
return thread;
|
|
332
|
-
}
|
|
333
|
-
function deserializeMessage(message) {
|
|
334
|
-
if (isSerializedMessage(message)) {
|
|
335
|
-
return Message.fromJSON(message);
|
|
336
|
-
}
|
|
337
|
-
return message;
|
|
338
|
-
}
|
|
339
|
-
async function logThreadMessageFailure(payload, errorMessage) {
|
|
340
|
-
logError(
|
|
341
|
-
"queue_message_failed",
|
|
342
|
-
{
|
|
343
|
-
slackThreadId: payload.normalizedThreadId,
|
|
344
|
-
slackChannelId: getPayloadChannelId(payload),
|
|
345
|
-
slackUserId: getPayloadUserId(payload)
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
"messaging.message.id": payload.message.id,
|
|
349
|
-
"app.queue.message_kind": payload.kind,
|
|
350
|
-
"app.queue.message_id": payload.queueMessageId,
|
|
351
|
-
"error.message": errorMessage
|
|
352
|
-
},
|
|
353
|
-
"Queue message processing failed"
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
async function processQueuedThreadMessage(payload, deps) {
|
|
357
|
-
const resolvedDeps = resolveDeps(deps);
|
|
358
|
-
const existingMessageState = await getQueueMessageProcessingState(
|
|
359
|
-
payload.dedupKey
|
|
360
|
-
);
|
|
361
|
-
if (existingMessageState?.status === "completed") {
|
|
362
|
-
resolvedDeps.logInfo(
|
|
363
|
-
"queue_message_skipped_completed",
|
|
364
|
-
{
|
|
365
|
-
slackThreadId: payload.normalizedThreadId,
|
|
366
|
-
slackChannelId: getPayloadChannelId(payload),
|
|
367
|
-
slackUserId: getPayloadUserId(payload)
|
|
368
|
-
},
|
|
369
|
-
{
|
|
370
|
-
"messaging.message.id": payload.message.id,
|
|
371
|
-
"app.queue.message_kind": payload.kind,
|
|
372
|
-
"app.queue.message_id": payload.queueMessageId,
|
|
373
|
-
"app.queue.processing_state": existingMessageState.status
|
|
374
|
-
},
|
|
375
|
-
"Skipping queue message because it is already completed"
|
|
376
|
-
);
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
const state = getStateAdapter();
|
|
380
|
-
await state.connect();
|
|
381
|
-
const threadLock = await state.acquireLock(
|
|
382
|
-
payload.normalizedThreadId,
|
|
383
|
-
THREAD_PROCESSING_LOCK_TTL_MS
|
|
384
|
-
);
|
|
385
|
-
if (!threadLock) {
|
|
386
|
-
resolvedDeps.logInfo(
|
|
387
|
-
"queue_message_deferred_thread_locked",
|
|
388
|
-
{
|
|
389
|
-
slackThreadId: payload.normalizedThreadId,
|
|
390
|
-
slackChannelId: getPayloadChannelId(payload),
|
|
391
|
-
slackUserId: getPayloadUserId(payload)
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
"messaging.message.id": payload.message.id,
|
|
395
|
-
"app.queue.message_kind": payload.kind,
|
|
396
|
-
"app.queue.message_id": payload.queueMessageId
|
|
397
|
-
},
|
|
398
|
-
"Deferring queue message because another turn already owns the thread lock"
|
|
399
|
-
);
|
|
400
|
-
throw new DeferredThreadMessageError(
|
|
401
|
-
"thread_locked",
|
|
402
|
-
payload.normalizedThreadId
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
const threadLockHeartbeat = startThreadLockHeartbeat({
|
|
406
|
-
lock: threadLock,
|
|
407
|
-
state,
|
|
408
|
-
payload,
|
|
409
|
-
logWarn: resolvedDeps.logWarn
|
|
410
|
-
});
|
|
411
|
-
try {
|
|
412
|
-
const runtimePayload = {
|
|
413
|
-
...payload,
|
|
414
|
-
thread: deserializeThread(payload.thread),
|
|
415
|
-
message: deserializeMessage(payload.message)
|
|
416
|
-
};
|
|
417
|
-
const currentTurnId = buildDeterministicTurnId(runtimePayload.message.id);
|
|
418
|
-
const activeTurnId = coerceThreadConversationState(
|
|
419
|
-
await runtimePayload.thread.state
|
|
420
|
-
).processing.activeTurnId;
|
|
421
|
-
if (activeTurnId && activeTurnId !== currentTurnId) {
|
|
422
|
-
resolvedDeps.logInfo(
|
|
423
|
-
"queue_message_deferred_active_turn",
|
|
424
|
-
{
|
|
425
|
-
slackThreadId: payload.normalizedThreadId,
|
|
426
|
-
slackChannelId: getPayloadChannelId(payload),
|
|
427
|
-
slackUserId: getPayloadUserId(payload)
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
"messaging.message.id": payload.message.id,
|
|
431
|
-
"app.queue.message_kind": payload.kind,
|
|
432
|
-
"app.queue.message_id": payload.queueMessageId,
|
|
433
|
-
"app.thread.active_turn_id": activeTurnId,
|
|
434
|
-
"app.thread.current_turn_id": currentTurnId
|
|
435
|
-
},
|
|
436
|
-
"Deferring queue message because another turn is still active for the thread"
|
|
437
|
-
);
|
|
438
|
-
throw new DeferredThreadMessageError(
|
|
439
|
-
"active_turn",
|
|
440
|
-
payload.normalizedThreadId,
|
|
441
|
-
{
|
|
442
|
-
activeTurnId,
|
|
443
|
-
currentTurnId
|
|
444
|
-
}
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
const ownerToken = createMessageOwnerToken();
|
|
448
|
-
const claimResult = await acquireQueueMessageProcessingOwnership({
|
|
449
|
-
rawKey: payload.dedupKey,
|
|
450
|
-
ownerToken,
|
|
451
|
-
queueMessageId: payload.queueMessageId
|
|
452
|
-
});
|
|
453
|
-
if (claimResult === "blocked") {
|
|
454
|
-
resolvedDeps.logInfo(
|
|
455
|
-
"queue_message_skipped_blocked",
|
|
456
|
-
{
|
|
457
|
-
slackThreadId: payload.normalizedThreadId,
|
|
458
|
-
slackChannelId: getPayloadChannelId(payload),
|
|
459
|
-
slackUserId: getPayloadUserId(payload)
|
|
460
|
-
},
|
|
461
|
-
{
|
|
462
|
-
"messaging.message.id": payload.message.id,
|
|
463
|
-
"app.queue.message_kind": payload.kind,
|
|
464
|
-
"app.queue.message_id": payload.queueMessageId,
|
|
465
|
-
"app.queue.claim_result": claimResult,
|
|
466
|
-
"app.queue.processing_state": "processing"
|
|
467
|
-
},
|
|
468
|
-
"Skipping queue message because another worker owns it"
|
|
469
|
-
);
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
let reactionCleared = false;
|
|
473
|
-
const clearProcessingReaction = async () => {
|
|
474
|
-
if (reactionCleared) {
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
reactionCleared = true;
|
|
478
|
-
try {
|
|
479
|
-
await resolvedDeps.clearProcessingReaction({
|
|
480
|
-
channelId: runtimePayload.thread.channelId,
|
|
481
|
-
timestamp: runtimePayload.message.id
|
|
482
|
-
});
|
|
483
|
-
} catch (error) {
|
|
484
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
485
|
-
resolvedDeps.logWarn(
|
|
486
|
-
"queue_processing_reaction_clear_failed",
|
|
487
|
-
{
|
|
488
|
-
slackThreadId: payload.normalizedThreadId,
|
|
489
|
-
slackChannelId: getPayloadChannelId(payload),
|
|
490
|
-
slackUserId: getPayloadUserId(payload)
|
|
491
|
-
},
|
|
492
|
-
{
|
|
493
|
-
"messaging.message.id": payload.message.id,
|
|
494
|
-
"app.queue.message_kind": payload.kind,
|
|
495
|
-
"app.queue.message_id": payload.queueMessageId,
|
|
496
|
-
"error.message": errorMessage
|
|
497
|
-
},
|
|
498
|
-
"Failed to remove processing reaction after queue turn completion"
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
};
|
|
502
|
-
try {
|
|
503
|
-
const refreshed = await refreshQueueMessageProcessingOwnership({
|
|
504
|
-
rawKey: payload.dedupKey,
|
|
505
|
-
ownerToken,
|
|
506
|
-
queueMessageId: payload.queueMessageId
|
|
507
|
-
});
|
|
508
|
-
if (!refreshed) {
|
|
509
|
-
throw new QueueMessageOwnershipError("refresh", payload.dedupKey);
|
|
510
|
-
}
|
|
511
|
-
await resolvedDeps.dispatch({
|
|
512
|
-
kind: runtimePayload.kind,
|
|
513
|
-
thread: runtimePayload.thread,
|
|
514
|
-
message: runtimePayload.message
|
|
515
|
-
});
|
|
516
|
-
await clearProcessingReaction();
|
|
517
|
-
const completed = await completeQueueMessageProcessingOwnership({
|
|
518
|
-
rawKey: payload.dedupKey,
|
|
519
|
-
ownerToken,
|
|
520
|
-
queueMessageId: payload.queueMessageId
|
|
521
|
-
});
|
|
522
|
-
if (!completed) {
|
|
523
|
-
throw new QueueMessageOwnershipError("complete", payload.dedupKey);
|
|
524
|
-
}
|
|
525
|
-
} catch (error) {
|
|
526
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
527
|
-
await clearProcessingReaction();
|
|
528
|
-
await logThreadMessageFailure(payload, errorMessage);
|
|
529
|
-
const failed = await failQueueMessageProcessingOwnership({
|
|
530
|
-
rawKey: payload.dedupKey,
|
|
531
|
-
ownerToken,
|
|
532
|
-
errorMessage,
|
|
533
|
-
queueMessageId: payload.queueMessageId
|
|
534
|
-
});
|
|
535
|
-
if (!failed && !(error instanceof QueueMessageOwnershipError)) {
|
|
536
|
-
throw new Error(
|
|
537
|
-
`Failed to persist queue message failure state for dedupKey=${payload.dedupKey}: ${errorMessage}`
|
|
538
|
-
);
|
|
539
|
-
}
|
|
540
|
-
throw error;
|
|
541
|
-
}
|
|
542
|
-
} finally {
|
|
543
|
-
await threadLockHeartbeat.stop();
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
function startThreadLockHeartbeat(args) {
|
|
547
|
-
const interval = setInterval(() => {
|
|
548
|
-
void args.state.extendLock(args.lock, THREAD_PROCESSING_LOCK_TTL_MS).catch((error) => {
|
|
549
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
550
|
-
args.logWarn(
|
|
551
|
-
"queue_thread_lock_extend_failed",
|
|
552
|
-
{
|
|
553
|
-
slackThreadId: args.payload.normalizedThreadId,
|
|
554
|
-
slackChannelId: getPayloadChannelId(args.payload),
|
|
555
|
-
slackUserId: getPayloadUserId(args.payload)
|
|
556
|
-
},
|
|
557
|
-
{
|
|
558
|
-
"messaging.message.id": args.payload.message.id,
|
|
559
|
-
"app.queue.message_kind": args.payload.kind,
|
|
560
|
-
"app.queue.message_id": args.payload.queueMessageId,
|
|
561
|
-
"error.message": errorMessage
|
|
562
|
-
},
|
|
563
|
-
"Failed to extend thread-processing lock during queue execution"
|
|
564
|
-
);
|
|
565
|
-
});
|
|
566
|
-
}, THREAD_PROCESSING_LOCK_HEARTBEAT_MS);
|
|
567
|
-
interval.unref?.();
|
|
568
|
-
return {
|
|
569
|
-
stop: async () => {
|
|
570
|
-
clearInterval(interval);
|
|
571
|
-
await args.state.releaseLock(args.lock);
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// src/chat/queue/thread-message-dispatcher.ts
|
|
577
|
-
function rehydrateAttachmentFetchers(message, downloadPrivateSlackFile2) {
|
|
578
|
-
for (const attachment of message.attachments) {
|
|
579
|
-
if (!attachment.fetchData && attachment.url) {
|
|
580
|
-
attachment.fetchData = () => downloadPrivateSlackFile2(attachment.url);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
function createThreadMessageDispatcher(options) {
|
|
585
|
-
const downloadPrivateSlackFile2 = options.downloadPrivateSlackFile ?? downloadPrivateSlackFile;
|
|
586
|
-
return async function dispatch2(args) {
|
|
587
|
-
rehydrateAttachmentFetchers(args.message, downloadPrivateSlackFile2);
|
|
588
|
-
if (args.kind === "new_mention") {
|
|
589
|
-
await options.runtime.handleNewMention(args.thread, args.message, {
|
|
590
|
-
beforeFirstResponsePost: args.beforeFirstResponsePost
|
|
591
|
-
});
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
await options.runtime.handleSubscribedMessage(args.thread, args.message, {
|
|
595
|
-
beforeFirstResponsePost: args.beforeFirstResponsePost
|
|
596
|
-
});
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// src/handlers/queue-callback.ts
|
|
601
|
-
var productionThreadDispatch;
|
|
602
|
-
function getProductionDispatch() {
|
|
603
|
-
if (!productionThreadDispatch) {
|
|
604
|
-
productionThreadDispatch = createThreadMessageDispatcher({
|
|
605
|
-
runtime: getProductionSlackRuntime()
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
return productionThreadDispatch;
|
|
609
|
-
}
|
|
610
|
-
var dispatch = (args) => getProductionDispatch()(args);
|
|
611
|
-
var callbackHandler = createQueueCallbackHandler(
|
|
612
|
-
async (message, metadata) => {
|
|
613
|
-
if (metadata.topicName === getThreadMessageTopic()) {
|
|
614
|
-
const payload = {
|
|
615
|
-
...message,
|
|
616
|
-
queueMessageId: metadata.messageId
|
|
617
|
-
};
|
|
618
|
-
logInfo(
|
|
619
|
-
"queue_callback_received",
|
|
620
|
-
{
|
|
621
|
-
slackThreadId: payload.normalizedThreadId,
|
|
622
|
-
slackChannelId: payload.thread.channelId,
|
|
623
|
-
slackUserId: payload.message.author?.userId
|
|
624
|
-
},
|
|
625
|
-
{
|
|
626
|
-
"messaging.message.id": payload.message.id,
|
|
627
|
-
"app.queue.message_kind": payload.kind,
|
|
628
|
-
"app.queue.message_id": payload.queueMessageId,
|
|
629
|
-
"app.queue.delivery_count": metadata.deliveryCount,
|
|
630
|
-
"app.queue.topic": metadata.topicName
|
|
631
|
-
},
|
|
632
|
-
"Received queue callback payload"
|
|
633
|
-
);
|
|
634
|
-
await withSpan(
|
|
635
|
-
"queue.process_message",
|
|
636
|
-
"queue.process_message",
|
|
637
|
-
{
|
|
638
|
-
slackThreadId: payload.normalizedThreadId,
|
|
639
|
-
slackChannelId: payload.thread.channelId,
|
|
640
|
-
slackUserId: payload.message.author?.userId
|
|
641
|
-
},
|
|
642
|
-
async () => {
|
|
643
|
-
await processQueuedThreadMessage(payload, {
|
|
644
|
-
dispatch
|
|
645
|
-
});
|
|
646
|
-
},
|
|
647
|
-
{
|
|
648
|
-
"messaging.message.id": payload.message.id,
|
|
649
|
-
"app.queue.message_kind": payload.kind,
|
|
650
|
-
"app.queue.message_id": payload.queueMessageId,
|
|
651
|
-
"app.queue.delivery_count": metadata.deliveryCount,
|
|
652
|
-
"app.queue.topic": metadata.topicName
|
|
653
|
-
}
|
|
654
|
-
);
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
throw new Error(`Unexpected queue topic: ${metadata.topicName}`);
|
|
658
|
-
}
|
|
659
|
-
);
|
|
660
|
-
async function POST(request) {
|
|
661
|
-
const requestContext = createRequestContext(request, { platform: "queue" });
|
|
662
|
-
return withContext(requestContext, async () => {
|
|
663
|
-
try {
|
|
664
|
-
const response = await callbackHandler(request);
|
|
665
|
-
setSpanStatus(response.status >= 500 ? "error" : "ok");
|
|
666
|
-
return response;
|
|
667
|
-
} catch (error) {
|
|
668
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
669
|
-
logError(
|
|
670
|
-
"queue_callback_failed",
|
|
671
|
-
{},
|
|
672
|
-
{
|
|
673
|
-
"error.message": message
|
|
674
|
-
},
|
|
675
|
-
"Queue callback processing failed"
|
|
676
|
-
);
|
|
677
|
-
logException(error, "queue_callback_failed");
|
|
678
|
-
throw error;
|
|
679
|
-
}
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
export {
|
|
684
|
-
POST
|
|
685
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Handles `POST /api/queue/callback` for asynchronous thread processing.
|
|
3
|
-
*
|
|
4
|
-
* Keep this route as a dedicated handler in app code. The catch-all router can
|
|
5
|
-
* mirror this path for local/dev parity, but production queue delivery should
|
|
6
|
-
* always target the dedicated endpoint.
|
|
7
|
-
*/
|
|
8
|
-
declare function POST(request: Request): Promise<Response>;
|
|
9
|
-
|
|
10
|
-
export { POST };
|