@sentry/junior 0.71.0 → 0.71.2
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/dist/app.js +85 -17
- package/dist/chat/task-execution/queue-signing.d.ts +13 -0
- package/dist/chat/task-execution/queue.d.ts +10 -0
- package/dist/chat/task-execution/vercel-callback.d.ts +1 -1
- package/dist/chat/task-execution/vercel-queue.d.ts +1 -0
- package/dist/chat/task-execution/worker.d.ts +1 -1
- package/dist/{chunk-IGLNC5H6.js → chunk-XE2VFQQN.js} +21 -8
- package/dist/nitro.js +1 -1
- package/package.json +3 -3
package/dist/app.js
CHANGED
|
@@ -93,8 +93,8 @@ import {
|
|
|
93
93
|
pluginCatalogConfigFromPluginSet,
|
|
94
94
|
pluginHookRegistrationsFromPluginSet,
|
|
95
95
|
resolveConversationWorkQueueTopic,
|
|
96
|
-
|
|
97
|
-
} from "./chunk-
|
|
96
|
+
verifyConversationQueueMessage
|
|
97
|
+
} from "./chunk-XE2VFQQN.js";
|
|
98
98
|
import {
|
|
99
99
|
SlackActionError,
|
|
100
100
|
createSlackDestination,
|
|
@@ -23259,6 +23259,21 @@ import {
|
|
|
23259
23259
|
registerDevConsumer
|
|
23260
23260
|
} from "@vercel/queue";
|
|
23261
23261
|
|
|
23262
|
+
// src/chat/task-execution/queue.ts
|
|
23263
|
+
var ConversationQueueMessageRejectedError = class extends Error {
|
|
23264
|
+
conversationId;
|
|
23265
|
+
reason;
|
|
23266
|
+
constructor(reason, message, options = {}) {
|
|
23267
|
+
super(message);
|
|
23268
|
+
this.name = "ConversationQueueMessageRejectedError";
|
|
23269
|
+
this.reason = reason;
|
|
23270
|
+
this.conversationId = options.conversationId;
|
|
23271
|
+
}
|
|
23272
|
+
};
|
|
23273
|
+
function isConversationQueueMessageRejectedError(error) {
|
|
23274
|
+
return error instanceof ConversationQueueMessageRejectedError;
|
|
23275
|
+
}
|
|
23276
|
+
|
|
23262
23277
|
// src/chat/task-execution/worker.ts
|
|
23263
23278
|
var CONVERSATION_WORK_DEFER_DELAY_MS = 15e3;
|
|
23264
23279
|
var CONVERSATION_WORK_SOFT_YIELD_AFTER_MS = 24e4;
|
|
@@ -23279,11 +23294,21 @@ async function sendWakeNudge(args) {
|
|
|
23279
23294
|
idempotencyKey: args.idempotencyKey
|
|
23280
23295
|
}
|
|
23281
23296
|
);
|
|
23282
|
-
|
|
23283
|
-
|
|
23284
|
-
|
|
23285
|
-
|
|
23286
|
-
|
|
23297
|
+
try {
|
|
23298
|
+
await markConversationWorkEnqueued({
|
|
23299
|
+
conversationId: args.conversationId,
|
|
23300
|
+
nowMs: args.nowMs,
|
|
23301
|
+
state: args.options.state
|
|
23302
|
+
});
|
|
23303
|
+
} catch (error) {
|
|
23304
|
+
logException(
|
|
23305
|
+
error,
|
|
23306
|
+
"conversation_work_enqueue_marker_failed",
|
|
23307
|
+
{ conversationId: args.conversationId },
|
|
23308
|
+
{},
|
|
23309
|
+
"Conversation work enqueue marker failed after queue acceptance"
|
|
23310
|
+
);
|
|
23311
|
+
}
|
|
23287
23312
|
}
|
|
23288
23313
|
async function requestLostLeaseRecovery(args) {
|
|
23289
23314
|
const continuationMarked = await requestConversationContinuation({
|
|
@@ -23361,8 +23386,10 @@ async function processConversationWork(message, options) {
|
|
|
23361
23386
|
return { status: "no_work" };
|
|
23362
23387
|
}
|
|
23363
23388
|
if (!sameDestination(initial.destination, message.destination)) {
|
|
23364
|
-
throw new
|
|
23365
|
-
|
|
23389
|
+
throw new ConversationQueueMessageRejectedError(
|
|
23390
|
+
"destination_mismatch",
|
|
23391
|
+
`Conversation work queue destination changed for ${conversationId}`,
|
|
23392
|
+
{ conversationId }
|
|
23366
23393
|
);
|
|
23367
23394
|
}
|
|
23368
23395
|
const destination = initial.destination;
|
|
@@ -23711,19 +23738,59 @@ async function processConversationQueueMessage(message, options) {
|
|
|
23711
23738
|
state: options.state
|
|
23712
23739
|
});
|
|
23713
23740
|
}
|
|
23714
|
-
async function handleConversationQueueMessage(message, options) {
|
|
23715
|
-
const
|
|
23716
|
-
if (
|
|
23717
|
-
|
|
23741
|
+
async function handleConversationQueueMessage(message, metadata, options) {
|
|
23742
|
+
const verification = verifyConversationQueueMessage(message);
|
|
23743
|
+
if (verification.status === "rejected") {
|
|
23744
|
+
logConversationQueueMessageRejected(verification.reason, metadata);
|
|
23745
|
+
return;
|
|
23746
|
+
}
|
|
23747
|
+
if (verification.status === "unavailable") {
|
|
23748
|
+
throw new Error(
|
|
23749
|
+
`Conversation queue message verification unavailable: ${verification.reason}`
|
|
23750
|
+
);
|
|
23751
|
+
}
|
|
23752
|
+
try {
|
|
23753
|
+
await runWithTurnRequestDeadline(
|
|
23754
|
+
() => processConversationQueueMessage(verification.message, options)
|
|
23755
|
+
);
|
|
23756
|
+
} catch (error) {
|
|
23757
|
+
if (isConversationQueueMessageRejectedError(error)) {
|
|
23758
|
+
logConversationQueueMessageRejected(error.reason, metadata, {
|
|
23759
|
+
conversationId: error.conversationId
|
|
23760
|
+
});
|
|
23761
|
+
return;
|
|
23762
|
+
}
|
|
23763
|
+
throw error;
|
|
23718
23764
|
}
|
|
23719
|
-
|
|
23720
|
-
|
|
23765
|
+
}
|
|
23766
|
+
function logConversationQueueMessageRejected(reason, metadata, context = {}) {
|
|
23767
|
+
logWarn(
|
|
23768
|
+
"conversation_queue_message_rejected",
|
|
23769
|
+
context.conversationId ? { conversationId: context.conversationId } : {},
|
|
23770
|
+
{
|
|
23771
|
+
"app.queue.consumer_group": metadata.consumerGroup,
|
|
23772
|
+
"app.queue.delivery_count": metadata.deliveryCount,
|
|
23773
|
+
"app.queue.message_id": metadata.messageId,
|
|
23774
|
+
"app.queue.reject_reason": reason,
|
|
23775
|
+
"app.queue.topic_name": metadata.topicName
|
|
23776
|
+
},
|
|
23777
|
+
"Conversation queue message rejected without retry"
|
|
23721
23778
|
);
|
|
23722
23779
|
}
|
|
23780
|
+
function handleConversationQueueRetry(error, metadata) {
|
|
23781
|
+
if (!isConversationQueueMessageRejectedError(error)) {
|
|
23782
|
+
return void 0;
|
|
23783
|
+
}
|
|
23784
|
+
logConversationQueueMessageRejected(error.reason, metadata, {
|
|
23785
|
+
conversationId: error.conversationId
|
|
23786
|
+
});
|
|
23787
|
+
return { acknowledge: true };
|
|
23788
|
+
}
|
|
23723
23789
|
function createVercelConversationWorkCallback(options) {
|
|
23724
23790
|
return handleCallback(
|
|
23725
|
-
(message) => handleConversationQueueMessage(message, options),
|
|
23791
|
+
(message, metadata) => handleConversationQueueMessage(message, metadata, options),
|
|
23726
23792
|
{
|
|
23793
|
+
retry: handleConversationQueueRetry,
|
|
23727
23794
|
visibilityTimeoutSeconds: options.visibilityTimeoutSeconds ?? resolveConversationWorkVisibilityTimeoutSeconds()
|
|
23728
23795
|
}
|
|
23729
23796
|
);
|
|
@@ -23735,7 +23802,8 @@ function registerVercelConversationWorkDevConsumer(options) {
|
|
|
23735
23802
|
return registerDevConsumer({
|
|
23736
23803
|
client: new QueueClient(),
|
|
23737
23804
|
consumerGroup: CONVERSATION_WORK_DEV_CONSUMER_GROUP,
|
|
23738
|
-
handler: (message) => handleConversationQueueMessage(message, options),
|
|
23805
|
+
handler: (message, metadata) => handleConversationQueueMessage(message, metadata, options),
|
|
23806
|
+
retry: handleConversationQueueRetry,
|
|
23739
23807
|
topic: resolveConversationWorkQueueTopic(options),
|
|
23740
23808
|
visibilityTimeoutSeconds: options.visibilityTimeoutSeconds ?? resolveConversationWorkVisibilityTimeoutSeconds()
|
|
23741
23809
|
});
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
import type { ConversationQueueMessage } from "./queue";
|
|
2
2
|
declare const CONVERSATION_WORK_QUEUE_SIGNATURE_VERSION = "v1";
|
|
3
|
+
export declare const CONVERSATION_WORK_QUEUE_SIGNATURE_MAX_SKEW_MS: number;
|
|
3
4
|
interface SignedConversationQueueMessage extends ConversationQueueMessage {
|
|
4
5
|
signature: string;
|
|
5
6
|
signatureVersion: typeof CONVERSATION_WORK_QUEUE_SIGNATURE_VERSION;
|
|
6
7
|
signedAtMs: number;
|
|
7
8
|
}
|
|
9
|
+
export type ConversationQueueMessageVerificationResult = {
|
|
10
|
+
message: ConversationQueueMessage;
|
|
11
|
+
status: "verified";
|
|
12
|
+
} | {
|
|
13
|
+
reason: "expired" | "malformed" | "signature_mismatch";
|
|
14
|
+
status: "rejected";
|
|
15
|
+
} | {
|
|
16
|
+
reason: "invalid_clock" | "missing_secret";
|
|
17
|
+
status: "unavailable";
|
|
18
|
+
};
|
|
8
19
|
/** Sign a conversation queue payload before it crosses the public callback route. */
|
|
9
20
|
export declare function signConversationQueueMessage(message: ConversationQueueMessage, nowMs?: number): SignedConversationQueueMessage;
|
|
21
|
+
/** Explain whether a queue payload is verified, rejected, or temporarily unverifiable. */
|
|
22
|
+
export declare function verifyConversationQueueMessage(value: unknown, nowMs?: number): ConversationQueueMessageVerificationResult;
|
|
10
23
|
/** Verify a signed conversation queue payload from the Vercel Queue callback. */
|
|
11
24
|
export declare function verifySignedConversationQueueMessage(value: unknown, nowMs?: number): ConversationQueueMessage | undefined;
|
|
12
25
|
export {};
|
|
@@ -3,6 +3,16 @@ export interface ConversationQueueMessage {
|
|
|
3
3
|
conversationId: string;
|
|
4
4
|
destination: Destination;
|
|
5
5
|
}
|
|
6
|
+
export type ConversationQueueMessageRejectReason = "destination_mismatch" | "expired" | "malformed" | "signature_mismatch" | "unauthorized";
|
|
7
|
+
export declare class ConversationQueueMessageRejectedError extends Error {
|
|
8
|
+
conversationId?: string;
|
|
9
|
+
reason: ConversationQueueMessageRejectReason;
|
|
10
|
+
constructor(reason: ConversationQueueMessageRejectReason, message: string, options?: {
|
|
11
|
+
conversationId?: string;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/** Return whether a queue payload was permanently rejected at the message boundary. */
|
|
15
|
+
export declare function isConversationQueueMessageRejectedError(error: unknown): error is ConversationQueueMessageRejectedError;
|
|
6
16
|
export interface ConversationQueueSendOptions {
|
|
7
17
|
delayMs?: number;
|
|
8
18
|
idempotencyKey?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StateAdapter } from "chat";
|
|
2
|
-
import type
|
|
2
|
+
import { type ConversationWorkQueue } from "./queue";
|
|
3
3
|
import { type ConversationWorkProcessResult, type ConversationWorkerResult, type ConversationWorkerContext } from "./worker";
|
|
4
4
|
export declare const CONVERSATION_WORK_VISIBILITY_TIMEOUT_BUFFER_SECONDS = 30;
|
|
5
5
|
export declare const CONVERSATION_WORK_DEV_CONSUMER_GROUP = "junior_conversation_work_dev";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SendOptions, SendResult } from "@vercel/queue";
|
|
2
2
|
import type { ConversationWorkQueue } from "./queue";
|
|
3
3
|
export declare const DEFAULT_CONVERSATION_WORK_QUEUE_TOPIC = "junior_conversation_work";
|
|
4
|
+
export declare const CONVERSATION_WORK_QUEUE_RETENTION_SECONDS: number;
|
|
4
5
|
interface QueueSender {
|
|
5
6
|
send<T = unknown>(topicName: string, payload: T, options?: SendOptions): Promise<SendResult>;
|
|
6
7
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { StateAdapter } from "chat";
|
|
2
2
|
import type { Destination } from "@sentry/junior-plugin-api";
|
|
3
|
-
import type
|
|
3
|
+
import { type ConversationQueueMessage, type ConversationWorkQueue } from "./queue";
|
|
4
4
|
import { type InboundMessageRecord } from "./store";
|
|
5
5
|
export declare const CONVERSATION_WORK_DEFER_DELAY_MS = 15000;
|
|
6
6
|
export declare const CONVERSATION_WORK_SOFT_YIELD_AFTER_MS = 240000;
|
|
@@ -163,24 +163,37 @@ function signConversationQueueMessage(message, nowMs = Date.now()) {
|
|
|
163
163
|
signature: signPayload(message, nowMs, secret)
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
|
-
function
|
|
166
|
+
function verifyConversationQueueMessage(value, nowMs = Date.now()) {
|
|
167
167
|
const message = parseSignedConversationQueueMessage(value);
|
|
168
|
+
if (!message) {
|
|
169
|
+
return { status: "rejected", reason: "malformed" };
|
|
170
|
+
}
|
|
168
171
|
const secret = getConversationWorkQueueSecret();
|
|
169
|
-
if (!
|
|
170
|
-
return
|
|
172
|
+
if (!secret) {
|
|
173
|
+
return { status: "unavailable", reason: "missing_secret" };
|
|
174
|
+
}
|
|
175
|
+
if (!Number.isFinite(nowMs)) {
|
|
176
|
+
return { status: "unavailable", reason: "invalid_clock" };
|
|
177
|
+
}
|
|
178
|
+
if (Math.abs(nowMs - message.signedAtMs) > CONVERSATION_WORK_QUEUE_SIGNATURE_MAX_SKEW_MS) {
|
|
179
|
+
return { status: "rejected", reason: "expired" };
|
|
171
180
|
}
|
|
172
181
|
const expected = signPayload(message, message.signedAtMs, secret);
|
|
173
182
|
if (!timingSafeMatch(expected, message.signature)) {
|
|
174
|
-
return
|
|
183
|
+
return { status: "rejected", reason: "signature_mismatch" };
|
|
175
184
|
}
|
|
176
185
|
return {
|
|
177
|
-
|
|
178
|
-
|
|
186
|
+
status: "verified",
|
|
187
|
+
message: {
|
|
188
|
+
conversationId: message.conversationId,
|
|
189
|
+
destination: message.destination
|
|
190
|
+
}
|
|
179
191
|
};
|
|
180
192
|
}
|
|
181
193
|
|
|
182
194
|
// src/chat/task-execution/vercel-queue.ts
|
|
183
195
|
var DEFAULT_CONVERSATION_WORK_QUEUE_TOPIC = "junior_conversation_work";
|
|
196
|
+
var CONVERSATION_WORK_QUEUE_RETENTION_SECONDS = CONVERSATION_WORK_QUEUE_SIGNATURE_MAX_SKEW_MS / 1e3;
|
|
184
197
|
var defaultQueue;
|
|
185
198
|
function resolveConversationWorkQueueTopic(options = {}) {
|
|
186
199
|
const topic = options.topic?.trim();
|
|
@@ -203,7 +216,7 @@ function createVercelConversationWorkQueue(options = {}) {
|
|
|
203
216
|
{
|
|
204
217
|
idempotencyKey: sendOptions?.idempotencyKey,
|
|
205
218
|
delaySeconds: toDelaySeconds(sendOptions),
|
|
206
|
-
retentionSeconds: options.retentionSeconds
|
|
219
|
+
retentionSeconds: options.retentionSeconds ?? CONVERSATION_WORK_QUEUE_RETENTION_SECONDS
|
|
207
220
|
}
|
|
208
221
|
);
|
|
209
222
|
return result.messageId ? { messageId: result.messageId } : {};
|
|
@@ -219,7 +232,7 @@ export {
|
|
|
219
232
|
defineJuniorPlugins,
|
|
220
233
|
pluginCatalogConfigFromPluginSet,
|
|
221
234
|
pluginHookRegistrationsFromPluginSet,
|
|
222
|
-
|
|
235
|
+
verifyConversationQueueMessage,
|
|
223
236
|
resolveConversationWorkQueueTopic,
|
|
224
237
|
getVercelConversationWorkQueue
|
|
225
238
|
};
|
package/dist/nitro.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
pluginCatalogConfigFromPluginSet,
|
|
3
3
|
pluginHookRegistrationsFromPluginSet,
|
|
4
4
|
resolveConversationWorkQueueTopic
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XE2VFQQN.js";
|
|
6
6
|
import "./chunk-76YMBKW7.js";
|
|
7
7
|
import {
|
|
8
8
|
JUNIOR_CONVERSATION_WORK_CALLBACK_ROUTE,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior",
|
|
3
|
-
"version": "0.71.
|
|
3
|
+
"version": "0.71.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"node-html-markdown": "^2.0.0",
|
|
66
66
|
"yaml": "^2.9.0",
|
|
67
67
|
"zod": "^4.4.3",
|
|
68
|
-
"@sentry/junior-plugin-api": "0.71.
|
|
68
|
+
"@sentry/junior-plugin-api": "0.71.2"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.9.1",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"typescript": "^6.0.3",
|
|
79
79
|
"vercel": "^54.4.0",
|
|
80
80
|
"vitest": "^4.1.7",
|
|
81
|
-
"@sentry/junior-scheduler": "0.71.
|
|
81
|
+
"@sentry/junior-scheduler": "0.71.2"
|
|
82
82
|
},
|
|
83
83
|
"scripts": {
|
|
84
84
|
"build": "tsup && tsc -p tsconfig.build.json --emitDeclarationOnly",
|