@sentry/junior 0.6.0 → 0.7.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.
@@ -4,9 +4,9 @@ import {
4
4
  createNormalizingStream,
5
5
  resetBotDepsForTests,
6
6
  setBotDepsForTests
7
- } from "./chunk-QNOV65P4.js";
7
+ } from "./chunk-JRKU55W5.js";
8
8
  import "./chunk-KT5HARSN.js";
9
- import "./chunk-5UJSQX4R.js";
9
+ import "./chunk-RKOO42TW.js";
10
10
  import "./chunk-Z5E25LRN.js";
11
11
  import "./chunk-PY4AI2GZ.js";
12
12
  import "./chunk-VW26MOSO.js";
@@ -4,7 +4,7 @@ import {
4
4
  downloadPrivateSlackFile,
5
5
  getThreadMessageTopic,
6
6
  removeReactionFromMessage
7
- } from "./chunk-QNOV65P4.js";
7
+ } from "./chunk-JRKU55W5.js";
8
8
  import {
9
9
  acquireQueueMessageProcessingOwnership,
10
10
  completeQueueMessageProcessingOwnership,
@@ -12,11 +12,12 @@ import {
12
12
  getQueueMessageProcessingState,
13
13
  getStateAdapter,
14
14
  refreshQueueMessageProcessingOwnership
15
- } from "./chunk-5UJSQX4R.js";
15
+ } from "./chunk-RKOO42TW.js";
16
16
  import {
17
17
  createRequestContext,
18
18
  logError,
19
19
  logException,
20
+ logInfo,
20
21
  logWarn,
21
22
  setSpanStatus,
22
23
  withContext,
@@ -77,7 +78,9 @@ function createMessageOwnerToken() {
77
78
  }
78
79
  var QueueMessageOwnershipError = class extends Error {
79
80
  constructor(stage, dedupKey) {
80
- super(`Queue message ownership lost during ${stage} for dedupKey=${dedupKey}`);
81
+ super(
82
+ `Queue message ownership lost during ${stage} for dedupKey=${dedupKey}`
83
+ );
81
84
  this.name = "QueueMessageOwnershipError";
82
85
  }
83
86
  };
@@ -89,6 +92,7 @@ var defaultProcessQueuedThreadMessageDeps = {
89
92
  emoji: "eyes"
90
93
  });
91
94
  },
95
+ logInfo,
92
96
  logWarn,
93
97
  processRuntime: processThreadMessageRuntime
94
98
  };
@@ -122,8 +126,25 @@ async function logThreadMessageFailure(payload, errorMessage) {
122
126
  );
123
127
  }
124
128
  async function processQueuedThreadMessage(payload, deps = defaultProcessQueuedThreadMessageDeps) {
125
- const existingMessageState = await getQueueMessageProcessingState(payload.dedupKey);
129
+ const existingMessageState = await getQueueMessageProcessingState(
130
+ payload.dedupKey
131
+ );
126
132
  if (existingMessageState?.status === "completed") {
133
+ deps.logInfo(
134
+ "queue_message_skipped_completed",
135
+ {
136
+ slackThreadId: payload.normalizedThreadId,
137
+ slackChannelId: getPayloadChannelId(payload),
138
+ slackUserId: getPayloadUserId(payload)
139
+ },
140
+ {
141
+ "messaging.message.id": payload.message.id,
142
+ "app.queue.message_kind": payload.kind,
143
+ "app.queue.message_id": payload.queueMessageId,
144
+ "app.queue.processing_state": existingMessageState.status
145
+ },
146
+ "Skipping queue message because it is already completed"
147
+ );
127
148
  return;
128
149
  }
129
150
  const ownerToken = createMessageOwnerToken();
@@ -133,6 +154,22 @@ async function processQueuedThreadMessage(payload, deps = defaultProcessQueuedTh
133
154
  queueMessageId: payload.queueMessageId
134
155
  });
135
156
  if (claimResult === "blocked") {
157
+ deps.logInfo(
158
+ "queue_message_skipped_blocked",
159
+ {
160
+ slackThreadId: payload.normalizedThreadId,
161
+ slackChannelId: getPayloadChannelId(payload),
162
+ slackUserId: getPayloadUserId(payload)
163
+ },
164
+ {
165
+ "messaging.message.id": payload.message.id,
166
+ "app.queue.message_kind": payload.kind,
167
+ "app.queue.message_id": payload.queueMessageId,
168
+ "app.queue.claim_result": claimResult,
169
+ "app.queue.processing_state": "processing"
170
+ },
171
+ "Skipping queue message because another worker owns it"
172
+ );
136
173
  return;
137
174
  }
138
175
  const threadWasSerialized = isSerializedThread(payload.thread);
@@ -209,42 +246,62 @@ async function processQueuedThreadMessage(payload, deps = defaultProcessQueuedTh
209
246
  queueMessageId: payload.queueMessageId
210
247
  });
211
248
  if (!failed && !(error instanceof QueueMessageOwnershipError)) {
212
- throw new Error(`Failed to persist queue message failure state for dedupKey=${payload.dedupKey}: ${errorMessage}`);
249
+ throw new Error(
250
+ `Failed to persist queue message failure state for dedupKey=${payload.dedupKey}: ${errorMessage}`
251
+ );
213
252
  }
214
253
  throw error;
215
254
  }
216
255
  }
217
256
 
218
257
  // src/handlers/queue-callback.ts
219
- var callbackHandler = createQueueCallbackHandler(async (message, metadata) => {
220
- if (metadata.topicName === getThreadMessageTopic()) {
221
- const payload = {
222
- ...message,
223
- queueMessageId: metadata.messageId
224
- };
225
- await withSpan(
226
- "queue.process_message",
227
- "queue.process_message",
228
- {
229
- slackThreadId: payload.normalizedThreadId,
230
- slackChannelId: payload.thread.channelId,
231
- slackUserId: payload.message.author?.userId
232
- },
233
- async () => {
234
- await processQueuedThreadMessage(payload);
235
- },
236
- {
237
- "messaging.message.id": payload.message.id,
238
- "app.queue.message_kind": payload.kind,
239
- "app.queue.message_id": payload.queueMessageId,
240
- "app.queue.delivery_count": metadata.deliveryCount,
241
- "app.queue.topic": metadata.topicName
242
- }
243
- );
244
- return;
258
+ var callbackHandler = createQueueCallbackHandler(
259
+ async (message, metadata) => {
260
+ if (metadata.topicName === getThreadMessageTopic()) {
261
+ const payload = {
262
+ ...message,
263
+ queueMessageId: metadata.messageId
264
+ };
265
+ logInfo(
266
+ "queue_callback_received",
267
+ {
268
+ slackThreadId: payload.normalizedThreadId,
269
+ slackChannelId: payload.thread.channelId,
270
+ slackUserId: payload.message.author?.userId
271
+ },
272
+ {
273
+ "messaging.message.id": payload.message.id,
274
+ "app.queue.message_kind": payload.kind,
275
+ "app.queue.message_id": payload.queueMessageId,
276
+ "app.queue.delivery_count": metadata.deliveryCount,
277
+ "app.queue.topic": metadata.topicName
278
+ },
279
+ "Received queue callback payload"
280
+ );
281
+ await withSpan(
282
+ "queue.process_message",
283
+ "queue.process_message",
284
+ {
285
+ slackThreadId: payload.normalizedThreadId,
286
+ slackChannelId: payload.thread.channelId,
287
+ slackUserId: payload.message.author?.userId
288
+ },
289
+ async () => {
290
+ await processQueuedThreadMessage(payload);
291
+ },
292
+ {
293
+ "messaging.message.id": payload.message.id,
294
+ "app.queue.message_kind": payload.kind,
295
+ "app.queue.message_id": payload.queueMessageId,
296
+ "app.queue.delivery_count": metadata.deliveryCount,
297
+ "app.queue.topic": metadata.topicName
298
+ }
299
+ );
300
+ return;
301
+ }
302
+ throw new Error(`Unexpected queue topic: ${metadata.topicName}`);
245
303
  }
246
- throw new Error(`Unexpected queue topic: ${metadata.topicName}`);
247
- });
304
+ );
248
305
  async function POST(request) {
249
306
  const requestContext = createRequestContext(request, { platform: "queue" });
250
307
  return withContext(requestContext, async () => {
@@ -31,7 +31,7 @@ import {
31
31
  skillRoots,
32
32
  soulPathCandidates,
33
33
  upsertAgentTurnSessionCheckpoint
34
- } from "./chunk-5UJSQX4R.js";
34
+ } from "./chunk-RKOO42TW.js";
35
35
  import {
36
36
  logError,
37
37
  logException,
@@ -275,7 +275,7 @@ function escapeXml(value) {
275
275
  var replyDecisionSchema = z.object({
276
276
  should_reply: z.boolean().describe("Whether Junior should respond to this thread message."),
277
277
  confidence: z.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
278
- reason: z.string().max(160).optional().describe("Short reason for the decision.")
278
+ reason: z.string().optional().describe("Short reason for the decision.")
279
279
  });
280
280
  var ROUTER_CONFIDENCE_THRESHOLD = 0.72;
281
281
  var ACK_REGEXES = [
@@ -1070,11 +1070,12 @@ function buildSystemPrompt(params) {
1070
1070
  "- For factual or external questions, run tools/skills first, then answer from evidence.",
1071
1071
  "- Use tool descriptions as the source of truth for when each tool should or should not be called.",
1072
1072
  "- Use `bash` to inspect skill files from `skill_dir` and run shell commands inside the sandbox workspace.",
1073
- "- Use `attachFile` to attach files from the sandbox (for example screenshots, PDFs, logs) to the Slack reply.",
1073
+ "- Use `attachFile` for files that actually exist in the sandbox (for example screenshots, PDFs, logs), or for `attachment_path` values returned by `imageGenerate`.",
1074
1074
  "- If the user asks to see/share/show a screenshot or file, attach the file with `attachFile` instead of only reporting its path.",
1075
1075
  "- Never claim a screenshot/file is attached unless `attachFile` succeeded in this turn.",
1076
1076
  "- If `attachFile` fails, explain the failure and do not say the file was shared.",
1077
1077
  "- Use `imageGenerate` when the user asks for image creation.",
1078
+ "- `imageGenerate` returns generated image metadata, including `attachment_path` values you can pass to `attachFile` when the user should receive the image.",
1078
1079
  "- Use `slackCanvasCreate` for long-form docs/specs and `slackCanvasUpdate` for doc follow-ups.",
1079
1080
  "- `slackCanvasUpdate` targets the active artifact-context canvas automatically; do not ask the user for `canvas_id`.",
1080
1081
  "- When you create or update a Slack artifact in this turn (for example a canvas, list, posted message, or attached file), mention it explicitly in the final reply and include its link when the tool returned one.",
@@ -2915,7 +2916,7 @@ async function detectMimeType(sandbox, targetPath) {
2915
2916
  }
2916
2917
  function createAttachFileTool(sandbox, hooks = {}) {
2917
2918
  return tool({
2918
- description: "Attach a file from the sandbox to the Slack reply. Use this immediately after creating screenshots/reports when the user asks to see/share the actual file, not just its path.",
2919
+ description: "Attach a file to the Slack reply. Use this for files that exist in the sandbox, such as screenshots, PDFs, or logs, or for generated image `attachment_path` values returned earlier in the turn.",
2919
2920
  inputSchema: Type2.Object(
2920
2921
  {
2921
2922
  path: Type2.String({
@@ -2941,6 +2942,20 @@ function createAttachFileTool(sandbox, hooks = {}) {
2941
2942
  const targetPath = normalizeSandboxPath(requestedPath);
2942
2943
  const fileBuffer = await sandbox.readFileToBuffer({ path: targetPath });
2943
2944
  if (!fileBuffer) {
2945
+ const generatedFile = hooks.getGeneratedFile?.(
2946
+ path3.posix.basename(targetPath)
2947
+ );
2948
+ if (generatedFile) {
2949
+ hooks.onGeneratedFiles?.([generatedFile]);
2950
+ return {
2951
+ ok: true,
2952
+ attached: true,
2953
+ path: targetPath,
2954
+ filename: generatedFile.filename,
2955
+ mime_type: generatedFile.mimeType ?? inferMimeType(generatedFile.filename),
2956
+ bytes: Buffer.isBuffer(generatedFile.data) ? generatedFile.data.byteLength : generatedFile.data instanceof ArrayBuffer ? generatedFile.data.byteLength : generatedFile.data.size
2957
+ };
2958
+ }
2944
2959
  throw new Error(`failed to read file: ${targetPath}`);
2945
2960
  }
2946
2961
  if (fileBuffer.byteLength === 0) {
@@ -3224,12 +3239,22 @@ async function enrichImagePrompt(rawPrompt) {
3224
3239
  maxTokens: 1024
3225
3240
  });
3226
3241
  if (text && text.trim().length > 0) {
3227
- logInfo("image_prompt_enriched", {}, { "app.image.enriched_prompt_length": text.trim().length }, "Image prompt enriched with persona");
3242
+ logInfo(
3243
+ "image_prompt_enriched",
3244
+ {},
3245
+ { "app.image.enriched_prompt_length": text.trim().length },
3246
+ "Image prompt enriched with persona"
3247
+ );
3228
3248
  return text.trim();
3229
3249
  }
3230
3250
  return rawPrompt;
3231
3251
  } catch (error) {
3232
- logWarn("image_prompt_enrichment_failed", {}, { "error.message": String(error) }, "Image prompt enrichment failed, using raw prompt");
3252
+ logWarn(
3253
+ "image_prompt_enrichment_failed",
3254
+ {},
3255
+ { "error.message": String(error) },
3256
+ "Image prompt enrichment failed, using raw prompt"
3257
+ );
3233
3258
  return rawPrompt;
3234
3259
  }
3235
3260
  }
@@ -3254,7 +3279,7 @@ function parseImageGenerationError(status, body, model) {
3254
3279
  return `image generation failed: ${status} ${body}`;
3255
3280
  }
3256
3281
  }
3257
- function createImageGenerateTool(hooks) {
3282
+ function createImageGenerateTool(hooks, deps = {}) {
3258
3283
  return tool({
3259
3284
  description: "Generate images from a prompt. Use when the user wants to visually show or represent something \u2014 feelings, concepts, art, humor, or any visual idea. Also use for explicit image creation requests.",
3260
3285
  inputSchema: Type3.Object({
@@ -3265,27 +3290,35 @@ function createImageGenerateTool(hooks) {
3265
3290
  })
3266
3291
  }),
3267
3292
  execute: async ({ prompt }) => {
3293
+ const fetchImpl = deps.fetch ?? fetch;
3268
3294
  const apiKey = process.env.AI_GATEWAY_API_KEY ?? process.env.VERCEL_OIDC_TOKEN;
3269
3295
  if (!apiKey) {
3270
- throw new Error("Missing AI gateway credentials (AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN)");
3296
+ throw new Error(
3297
+ "Missing AI gateway credentials (AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN)"
3298
+ );
3271
3299
  }
3272
3300
  const model = process.env.AI_IMAGE_MODEL ?? DEFAULT_IMAGE_MODEL;
3273
3301
  const enrichedPrompt = await enrichImagePrompt(prompt);
3274
- const response = await fetch("https://ai-gateway.vercel.sh/v1/chat/completions", {
3275
- method: "POST",
3276
- headers: {
3277
- "content-type": "application/json",
3278
- authorization: `Bearer ${apiKey}`
3279
- },
3280
- body: JSON.stringify({
3281
- model,
3282
- messages: [{ role: "user", content: enrichedPrompt }],
3283
- modalities: ["image"]
3284
- })
3285
- });
3302
+ const response = await fetchImpl(
3303
+ "https://ai-gateway.vercel.sh/v1/chat/completions",
3304
+ {
3305
+ method: "POST",
3306
+ headers: {
3307
+ "content-type": "application/json",
3308
+ authorization: `Bearer ${apiKey}`
3309
+ },
3310
+ body: JSON.stringify({
3311
+ model,
3312
+ messages: [{ role: "user", content: enrichedPrompt }],
3313
+ modalities: ["image"]
3314
+ })
3315
+ }
3316
+ );
3286
3317
  if (!response.ok) {
3287
3318
  const text = await response.text();
3288
- throw new Error(parseImageGenerationError(response.status, text, model));
3319
+ throw new Error(
3320
+ parseImageGenerationError(response.status, text, model)
3321
+ );
3289
3322
  }
3290
3323
  const payload = await response.json();
3291
3324
  const uploads = [];
@@ -3300,7 +3333,7 @@ function createImageGenerateTool(hooks) {
3300
3333
  mimeType = match[1] ?? mimeType;
3301
3334
  bytes = Buffer.from(match[2] ?? "", "base64");
3302
3335
  } else if (typeof url === "string" && url.length > 0) {
3303
- const fetched = await fetch(url);
3336
+ const fetched = await fetchImpl(url);
3304
3337
  if (!fetched.ok) continue;
3305
3338
  mimeType = fetched.headers.get("content-type") ?? mimeType;
3306
3339
  bytes = Buffer.from(await fetched.arrayBuffer());
@@ -3314,7 +3347,7 @@ function createImageGenerateTool(hooks) {
3314
3347
  });
3315
3348
  }
3316
3349
  if (uploads.length > 0) {
3317
- hooks.onGeneratedFiles?.(uploads);
3350
+ hooks.onGeneratedArtifactFiles?.(uploads);
3318
3351
  }
3319
3352
  return {
3320
3353
  ok: true,
@@ -3324,10 +3357,11 @@ function createImageGenerateTool(hooks) {
3324
3357
  image_count: uploads.length,
3325
3358
  images: uploads.map((upload) => ({
3326
3359
  filename: upload.filename,
3360
+ attachment_path: upload.filename,
3327
3361
  media_type: upload.mimeType,
3328
3362
  bytes: upload.data.byteLength
3329
3363
  })),
3330
- delivery: "Images will be attached to the Slack response as files."
3364
+ delivery: "Generated images are available to attach with attachFile using the returned attachment_path."
3331
3365
  };
3332
3366
  }
3333
3367
  });
@@ -5039,7 +5073,7 @@ function createTools(availableSkills, hooks = {}, context) {
5039
5073
  webFetch: wrapToolExecution("webFetch", createWebFetchTool(hooks), hooks),
5040
5074
  imageGenerate: wrapToolExecution(
5041
5075
  "imageGenerate",
5042
- createImageGenerateTool(hooks),
5076
+ createImageGenerateTool(hooks, hooks.toolOverrides?.imageGenerate),
5043
5077
  hooks
5044
5078
  ),
5045
5079
  slackCanvasUpdate: wrapToolExecution(
@@ -6785,6 +6819,7 @@ async function generateAssistantReply(messageText, context = {}) {
6785
6819
  };
6786
6820
  }
6787
6821
  const generatedFiles = [];
6822
+ const replyFiles = [];
6788
6823
  const artifactStatePatch = {};
6789
6824
  const toolCalls = [];
6790
6825
  setTags({
@@ -6801,9 +6836,13 @@ async function generateAssistantReply(messageText, context = {}) {
6801
6836
  const tools = createTools(
6802
6837
  availableSkills,
6803
6838
  {
6804
- onGeneratedFiles: (files) => {
6839
+ getGeneratedFile: (filename) => generatedFiles.find((file) => file.filename === filename),
6840
+ onGeneratedArtifactFiles: (files) => {
6805
6841
  generatedFiles.push(...files);
6806
6842
  },
6843
+ onGeneratedFiles: (files) => {
6844
+ replyFiles.push(...files);
6845
+ },
6807
6846
  onArtifactStatePatch: (patch) => {
6808
6847
  Object.assign(artifactStatePatch, patch);
6809
6848
  },
@@ -6817,6 +6856,7 @@ async function generateAssistantReply(messageText, context = {}) {
6817
6856
  `${formatToolResultStatusWithInput(toolName, input)}...`
6818
6857
  );
6819
6858
  },
6859
+ toolOverrides: context.toolOverrides,
6820
6860
  onSkillLoaded: async (loadedSkill) => {
6821
6861
  const resolvedSkill = await skillSandbox.loadSkill(loadedSkill.name);
6822
6862
  const effective = resolvedSkill ?? loadedSkill;
@@ -6909,9 +6949,7 @@ async function generateAssistantReply(messageText, context = {}) {
6909
6949
  {
6910
6950
  onToolCall: (toolName) => {
6911
6951
  toolCalls.push(toolName);
6912
- },
6913
- onGeneratedFiles: (files) => generatedFiles.push(...files),
6914
- onArtifactStatePatch: (patch) => Object.assign(artifactStatePatch, patch)
6952
+ }
6915
6953
  }
6916
6954
  )
6917
6955
  }
@@ -7063,7 +7101,7 @@ async function generateAssistantReply(messageText, context = {}) {
7063
7101
  explicitChannelPostIntent,
7064
7102
  channelPostPerformed,
7065
7103
  reactionPerformed,
7066
- hasFiles: generatedFiles.length > 0,
7104
+ hasFiles: replyFiles.length > 0,
7067
7105
  streamingThreadReply: Boolean(context.onTextDelta)
7068
7106
  });
7069
7107
  const deliveryMode = deliveryPlan.mode;
@@ -7094,7 +7132,7 @@ async function generateAssistantReply(messageText, context = {}) {
7094
7132
  const outcome = primaryText ? stopReason === "error" ? "provider_error" : "success" : "execution_failure";
7095
7133
  const candidateText = primaryText || buildExecutionFailureMessage(toolErrorCount);
7096
7134
  const escapedOrRawPayload = isExecutionEscapeResponse(candidateText) || isRawToolPayloadResponse(candidateText);
7097
- const resolvedText = escapedOrRawPayload ? buildExecutionFailureMessage(toolErrorCount) : enforceAttachmentClaimTruth(candidateText, generatedFiles.length > 0);
7135
+ const resolvedText = escapedOrRawPayload ? buildExecutionFailureMessage(toolErrorCount) : enforceAttachmentClaimTruth(candidateText, replyFiles.length > 0);
7098
7136
  const resolvedOutcome = escapedOrRawPayload ? "execution_failure" : outcome;
7099
7137
  if (shouldTrace) {
7100
7138
  logInfo(
@@ -7114,7 +7152,7 @@ async function generateAssistantReply(messageText, context = {}) {
7114
7152
  if (escapedOrRawPayload) {
7115
7153
  return {
7116
7154
  text: resolvedText,
7117
- files: generatedFiles.length > 0 ? generatedFiles : void 0,
7155
+ files: replyFiles.length > 0 ? replyFiles : void 0,
7118
7156
  artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
7119
7157
  deliveryPlan,
7120
7158
  deliveryMode,
@@ -7137,7 +7175,7 @@ async function generateAssistantReply(messageText, context = {}) {
7137
7175
  }
7138
7176
  return {
7139
7177
  text: resolvedText,
7140
- files: generatedFiles.length > 0 ? generatedFiles : void 0,
7178
+ files: replyFiles.length > 0 ? replyFiles : void 0,
7141
7179
  artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
7142
7180
  deliveryPlan,
7143
7181
  deliveryMode,
@@ -7614,6 +7652,27 @@ function determineThreadMessageKind(args) {
7614
7652
  }
7615
7653
  return void 0;
7616
7654
  }
7655
+ function getMessageLogContext(args) {
7656
+ return {
7657
+ slackThreadId: args.normalizedThreadId,
7658
+ slackChannelId: nonEmptyString(args.message.raw?.channel),
7659
+ slackUserId: args.message.author?.userId
7660
+ };
7661
+ }
7662
+ function logIgnoredIngressResult(args) {
7663
+ args.deps.logInfo(
7664
+ args.eventName,
7665
+ args.logContext,
7666
+ {
7667
+ ...args.messageId ? { "messaging.message.id": args.messageId } : {},
7668
+ ...args.kind ? { "app.queue.message_kind": args.kind } : {},
7669
+ ...args.dedupKey ? { "app.queue.dedup_key": args.dedupKey } : {},
7670
+ ...args.decisionReason ? { "app.decision.reason": args.decisionReason } : {},
7671
+ "app.queue.route_result": args.routeResult
7672
+ },
7673
+ args.body
7674
+ );
7675
+ }
7617
7676
  var defaultQueueRoutingDeps = {
7618
7677
  hasDedup: (key) => hasQueueIngressDedup(key),
7619
7678
  markDedup: (key, ttlMs) => claimQueueIngressDedup(key, ttlMs),
@@ -7623,7 +7682,11 @@ var defaultQueueRoutingDeps = {
7623
7682
  enqueueThreadMessage: async (payload, dedupKey) => await enqueueThreadMessage(payload, {
7624
7683
  idempotencyKey: dedupKey
7625
7684
  }),
7626
- shouldReplyInSubscribedThread: async ({ message, normalizedThreadId, thread }) => {
7685
+ shouldReplyInSubscribedThread: async ({
7686
+ message,
7687
+ normalizedThreadId,
7688
+ thread
7689
+ }) => {
7627
7690
  const rawText = message.text;
7628
7691
  const text = stripLeadingBotMention(rawText, {
7629
7692
  stripLeadingSlackMentionToken: Boolean(message.isMention)
@@ -7668,25 +7731,56 @@ async function routeIncomingMessageToQueue(args) {
7668
7731
  if (!message || typeof message !== "object") {
7669
7732
  return "ignored_non_object";
7670
7733
  }
7671
- const normalizedThreadId = normalizeIncomingSlackThreadId(args.threadId, message);
7734
+ const normalizedThreadId = normalizeIncomingSlackThreadId(
7735
+ args.threadId,
7736
+ message
7737
+ );
7738
+ const baseLogContext = getMessageLogContext({
7739
+ message,
7740
+ normalizedThreadId
7741
+ });
7672
7742
  if ("threadId" in message) {
7673
7743
  message.threadId = normalizedThreadId;
7674
7744
  }
7675
7745
  const typedMessage = message;
7676
7746
  if (typedMessage.author?.isMe) {
7747
+ logIgnoredIngressResult({
7748
+ deps,
7749
+ eventName: "queue_ingress_ignored_self_message",
7750
+ logContext: baseLogContext,
7751
+ messageId: nonEmptyString(typedMessage.id),
7752
+ routeResult: "ignored_self_message",
7753
+ body: "Ignoring self-authored message before queue routing"
7754
+ });
7677
7755
  return "ignored_self_message";
7678
7756
  }
7679
7757
  const messageId = nonEmptyString(typedMessage.id);
7680
7758
  if (!messageId) {
7759
+ logIgnoredIngressResult({
7760
+ deps,
7761
+ eventName: "queue_ingress_ignored_missing_message_id",
7762
+ logContext: baseLogContext,
7763
+ routeResult: "ignored_missing_message_id",
7764
+ body: "Ignoring message without an id before queue routing"
7765
+ });
7681
7766
  return "ignored_missing_message_id";
7682
7767
  }
7683
7768
  const isSubscribed = await deps.getIsSubscribed(normalizedThreadId);
7684
- const isMention = Boolean(typedMessage.isMention || runtime.detectMention?.(adapter, message));
7769
+ const mentionSource = typedMessage.isMention ? "sdk_flag" : runtime.detectMention?.(adapter, message) ? "fallback_detector" : void 0;
7770
+ const isMention = mentionSource !== void 0;
7685
7771
  const kind = determineThreadMessageKind({
7686
7772
  isSubscribed,
7687
7773
  isMention
7688
7774
  });
7689
7775
  if (!kind) {
7776
+ logIgnoredIngressResult({
7777
+ deps,
7778
+ eventName: "queue_ingress_ignored_unsubscribed_non_mention",
7779
+ logContext: baseLogContext,
7780
+ messageId,
7781
+ routeResult: "ignored_unsubscribed_non_mention",
7782
+ body: "Ignoring unsubscribed non-mention message before queue routing"
7783
+ });
7690
7784
  return "ignored_unsubscribed_non_mention";
7691
7785
  }
7692
7786
  const dedupKey = buildQueueIngressDedupKey(normalizedThreadId, messageId);
@@ -7694,21 +7788,25 @@ async function routeIncomingMessageToQueue(args) {
7694
7788
  if (alreadyDeduped) {
7695
7789
  deps.logInfo(
7696
7790
  "queue_ingress_dedup_hit",
7697
- {
7698
- slackThreadId: normalizedThreadId,
7699
- slackUserId: message.author.userId
7700
- },
7791
+ baseLogContext,
7701
7792
  {
7702
7793
  "messaging.message.id": messageId,
7703
7794
  "app.queue.message_kind": kind,
7704
7795
  "app.queue.dedup_key": dedupKey,
7705
- "app.queue.dedup_outcome": "duplicate"
7796
+ "app.queue.dedup_outcome": "duplicate",
7797
+ ...mentionSource ? { "app.slack.mention_source": mentionSource } : {},
7798
+ "app.queue.route_result": "ignored_duplicate"
7706
7799
  },
7707
7800
  "Skipping duplicate incoming message before queue enqueue"
7708
7801
  );
7709
7802
  return "ignored_duplicate";
7710
7803
  }
7711
- const thread = await runtime.createThread(adapter, normalizedThreadId, message, isSubscribed);
7804
+ const thread = await runtime.createThread(
7805
+ adapter,
7806
+ normalizedThreadId,
7807
+ message,
7808
+ isSubscribed
7809
+ );
7712
7810
  const serializedMessage = serializeMessageForQueue(message);
7713
7811
  const serializedThread = serializeThreadForQueue(thread);
7714
7812
  let payloadKind = kind;
@@ -7719,6 +7817,17 @@ async function routeIncomingMessageToQueue(args) {
7719
7817
  thread
7720
7818
  });
7721
7819
  if (!decision.shouldReply) {
7820
+ logIgnoredIngressResult({
7821
+ deps,
7822
+ eventName: "queue_ingress_ignored_passive_no_reply",
7823
+ logContext: baseLogContext,
7824
+ messageId,
7825
+ dedupKey,
7826
+ kind,
7827
+ routeResult: "ignored_passive_no_reply",
7828
+ decisionReason: decision.reason,
7829
+ body: "Skipping passive subscribed-thread reply before queue enqueue"
7830
+ });
7722
7831
  return "ignored_passive_no_reply";
7723
7832
  }
7724
7833
  payloadKind = "subscribed_reply";
@@ -7753,6 +7862,7 @@ async function routeIncomingMessageToQueue(args) {
7753
7862
  {
7754
7863
  "messaging.message.id": messageId,
7755
7864
  "app.queue.message_kind": payloadKind,
7865
+ ...mentionSource ? { "app.slack.mention_source": mentionSource } : {},
7756
7866
  "error.message": errorMessage
7757
7867
  },
7758
7868
  "Failed to add ingress processing reaction"
@@ -7809,8 +7919,10 @@ async function routeIncomingMessageToQueue(args) {
7809
7919
  {
7810
7920
  "messaging.message.id": messageId,
7811
7921
  "app.queue.message_kind": payloadKind,
7922
+ ...mentionSource ? { "app.slack.mention_source": mentionSource } : {},
7812
7923
  "app.queue.dedup_key": dedupKey,
7813
7924
  "app.queue.dedup_outcome": "primary",
7925
+ "app.queue.route_result": "routed",
7814
7926
  ...queueMessageId ? { "app.queue.message_id": queueMessageId } : {}
7815
7927
  },
7816
7928
  "Routing incoming message to queue"
@@ -7860,14 +7972,20 @@ function installChatBackgroundPatch() {
7860
7972
  }
7861
7973
  });
7862
7974
  if (result === "ignored_missing_message_id") {
7863
- const normalizedThreadId = normalizeIncomingSlackThreadId(threadId, message);
7975
+ const normalizedThreadId = normalizeIncomingSlackThreadId(
7976
+ threadId,
7977
+ message
7978
+ );
7864
7979
  this.logger?.error?.("Message processing error", {
7865
7980
  threadId: normalizedThreadId,
7866
7981
  reason: "missing_message_id"
7867
7982
  });
7868
7983
  }
7869
7984
  } catch (err) {
7870
- this.logger?.error?.("Message processing error", { error: err, threadId });
7985
+ this.logger?.error?.("Message processing error", {
7986
+ error: err,
7987
+ threadId
7988
+ });
7871
7989
  }
7872
7990
  };
7873
7991
  scheduleBackgroundWork(options, run);
@@ -7904,7 +8022,12 @@ function installChatBackgroundPatch() {
7904
8022
  const run = async () => {
7905
8023
  try {
7906
8024
  const { relatedThread, relatedMessage, relatedChannel } = await this.retrieveModalContext(event.adapter.name, contextId);
7907
- const fullEvent = { ...event, relatedThread, relatedMessage, relatedChannel };
8025
+ const fullEvent = {
8026
+ ...event,
8027
+ relatedThread,
8028
+ relatedMessage,
8029
+ relatedChannel
8030
+ };
7908
8031
  for (const { callbackIds, handler } of this.modalCloseHandlers) {
7909
8032
  if (callbackIds.length === 0 || callbackIds.includes(event.callbackId)) {
7910
8033
  await handler(fullEvent);
@@ -9700,7 +9823,7 @@ function resolveReplyDelivery(args) {
9700
9823
  postThreadText: (args.reply.deliveryMode ?? "thread") !== "channel_only",
9701
9824
  attachFiles: replyHasFiles ? args.hasStreamedThreadReply ? "followup" : "inline" : "none"
9702
9825
  };
9703
- let attachFiles = deliveryPlan.attachFiles;
9826
+ let attachFiles = replyHasFiles ? deliveryPlan.attachFiles : "none";
9704
9827
  if (attachFiles === "followup" && !args.hasStreamedThreadReply) {
9705
9828
  attachFiles = "inline";
9706
9829
  }
@@ -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-HXAROQ33.js");
15
+ const { bot } = await import("./bot-ZKMCCT3D.js");
16
16
  return bot;
17
17
  }
18
18
  async function POST(request, context) {
@@ -935,6 +935,7 @@ function createQueuedStateAdapter(base) {
935
935
  return lock;
936
936
  };
937
937
  return {
938
+ appendToList: (key, value, options) => base.appendToList(key, value, options),
938
939
  connect: () => base.connect(),
939
940
  disconnect: () => base.disconnect(),
940
941
  subscribe: (threadId) => base.subscribe(threadId),
@@ -943,7 +944,9 @@ function createQueuedStateAdapter(base) {
943
944
  acquireLock,
944
945
  releaseLock: (lock) => base.releaseLock(lock),
945
946
  extendLock: (lock, ttlMs) => base.extendLock(lock, Math.max(ttlMs, MIN_LOCK_TTL_MS)),
947
+ forceReleaseLock: (threadId) => base.forceReleaseLock(threadId),
946
948
  get: (key) => base.get(key),
949
+ getList: (key) => base.getList(key),
947
950
  set: (key, value, ttlMs) => base.set(key, value, ttlMs),
948
951
  setIfNotExists: (key, value, ttlMs) => base.setIfNotExists(key, value, ttlMs),
949
952
  delete: (key) => base.delete(key)
@@ -1088,7 +1091,11 @@ async function acquireQueueMessageProcessingOwnership(args) {
1088
1091
  });
1089
1092
  const result = await getRedisStateAdapter().getClient().eval(CLAIM_OR_RECLAIM_PROCESSING_SCRIPT, {
1090
1093
  keys: [key],
1091
- arguments: [String(nowMs), String(QUEUE_MESSAGE_PROCESSING_TTL_MS), payload]
1094
+ arguments: [
1095
+ String(nowMs),
1096
+ String(QUEUE_MESSAGE_PROCESSING_TTL_MS),
1097
+ payload
1098
+ ]
1092
1099
  });
1093
1100
  if (result === 1) {
1094
1101
  return "acquired";
@@ -1112,7 +1119,11 @@ async function refreshQueueMessageProcessingOwnership(args) {
1112
1119
  });
1113
1120
  const result = await getRedisStateAdapter().getClient().eval(UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT, {
1114
1121
  keys: [queueMessageKey(args.rawKey)],
1115
- arguments: [args.ownerToken, String(QUEUE_MESSAGE_PROCESSING_TTL_MS), payload]
1122
+ arguments: [
1123
+ args.ownerToken,
1124
+ String(QUEUE_MESSAGE_PROCESSING_TTL_MS),
1125
+ payload
1126
+ ]
1116
1127
  });
1117
1128
  return result === 1;
1118
1129
  }
@@ -1126,7 +1137,11 @@ async function completeQueueMessageProcessingOwnership(args) {
1126
1137
  });
1127
1138
  const result = await getRedisStateAdapter().getClient().eval(UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT, {
1128
1139
  keys: [queueMessageKey(args.rawKey)],
1129
- arguments: [args.ownerToken, String(QUEUE_MESSAGE_COMPLETED_TTL_MS), payload]
1140
+ arguments: [
1141
+ args.ownerToken,
1142
+ String(QUEUE_MESSAGE_COMPLETED_TTL_MS),
1143
+ payload
1144
+ ]
1130
1145
  });
1131
1146
  return result === 1;
1132
1147
  }
@@ -1141,18 +1156,27 @@ async function failQueueMessageProcessingOwnership(args) {
1141
1156
  });
1142
1157
  const result = await getRedisStateAdapter().getClient().eval(UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT, {
1143
1158
  keys: [queueMessageKey(args.rawKey)],
1144
- arguments: [args.ownerToken, String(QUEUE_MESSAGE_FAILED_TTL_MS), payload]
1159
+ arguments: [
1160
+ args.ownerToken,
1161
+ String(QUEUE_MESSAGE_FAILED_TTL_MS),
1162
+ payload
1163
+ ]
1145
1164
  });
1146
1165
  return result === 1;
1147
1166
  }
1148
1167
  async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
1149
1168
  await getStateAdapter().connect();
1150
- const value = await getStateAdapter().get(agentTurnSessionKey(conversationId, sessionId));
1169
+ const value = await getStateAdapter().get(
1170
+ agentTurnSessionKey(conversationId, sessionId)
1171
+ );
1151
1172
  return parseAgentTurnSessionCheckpoint(value);
1152
1173
  }
1153
1174
  async function upsertAgentTurnSessionCheckpoint(args) {
1154
1175
  await getStateAdapter().connect();
1155
- const existing = await getAgentTurnSessionCheckpoint(args.conversationId, args.sessionId);
1176
+ const existing = await getAgentTurnSessionCheckpoint(
1177
+ args.conversationId,
1178
+ args.sessionId
1179
+ );
1156
1180
  const checkpoint = {
1157
1181
  checkpointVersion: (existing?.checkpointVersion ?? 0) + 1,
1158
1182
  conversationId: args.conversationId,
@@ -1165,7 +1189,11 @@ async function upsertAgentTurnSessionCheckpoint(args) {
1165
1189
  ...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
1166
1190
  };
1167
1191
  const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
1168
- await getStateAdapter().set(agentTurnSessionKey(args.conversationId, args.sessionId), JSON.stringify(checkpoint), ttlMs);
1192
+ await getStateAdapter().set(
1193
+ agentTurnSessionKey(args.conversationId, args.sessionId),
1194
+ JSON.stringify(checkpoint),
1195
+ ttlMs
1196
+ );
1169
1197
  return checkpoint;
1170
1198
  }
1171
1199
 
@@ -4,7 +4,7 @@ import {
4
4
  getPluginRuntimeDependencies,
5
5
  getPluginRuntimePostinstall,
6
6
  resolveRuntimeDependencySnapshot
7
- } from "../chunk-5UJSQX4R.js";
7
+ } from "../chunk-RKOO42TW.js";
8
8
  import "../chunk-Z5E25LRN.js";
9
9
  import "../chunk-PY4AI2GZ.js";
10
10
  import "../chunk-VW26MOSO.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  POST
3
- } from "../chunk-VPCCZ3PK.js";
4
- import "../chunk-QNOV65P4.js";
3
+ } from "../chunk-56WI5Q7P.js";
4
+ import "../chunk-JRKU55W5.js";
5
5
  import "../chunk-KT5HARSN.js";
6
- import "../chunk-5UJSQX4R.js";
6
+ import "../chunk-RKOO42TW.js";
7
7
  import "../chunk-Z5E25LRN.js";
8
8
  import "../chunk-PY4AI2GZ.js";
9
9
  import "../chunk-VW26MOSO.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  POST as POST2
3
- } from "../chunk-EZ6WIJL2.js";
3
+ } from "../chunk-QHKQ2AWX.js";
4
4
  import {
5
5
  POST
6
- } from "../chunk-VPCCZ3PK.js";
6
+ } from "../chunk-56WI5Q7P.js";
7
7
  import {
8
8
  escapeXml,
9
9
  formatProviderLabel,
@@ -13,7 +13,7 @@ import {
13
13
  publishAppHomeView,
14
14
  resolveBaseUrl,
15
15
  truncateStatusText
16
- } from "../chunk-QNOV65P4.js";
16
+ } from "../chunk-JRKU55W5.js";
17
17
  import {
18
18
  GET
19
19
  } from "../chunk-4RBEYCOG.js";
@@ -24,7 +24,7 @@ import {
24
24
  getPluginOAuthConfig,
25
25
  getStateAdapter,
26
26
  parseOAuthTokenResponse
27
- } from "../chunk-5UJSQX4R.js";
27
+ } from "../chunk-RKOO42TW.js";
28
28
  import "../chunk-Z5E25LRN.js";
29
29
  import {
30
30
  logException,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  POST
3
- } from "../chunk-EZ6WIJL2.js";
3
+ } from "../chunk-QHKQ2AWX.js";
4
4
  import "../chunk-PY4AI2GZ.js";
5
5
  export {
6
6
  POST
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -24,9 +24,9 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "@ai-sdk/gateway": "^3.0.66",
27
- "@chat-adapter/slack": "4.17.0",
28
- "@chat-adapter/state-memory": "4.17.0",
29
- "@chat-adapter/state-redis": "4.17.0",
27
+ "@chat-adapter/slack": "4.20.2",
28
+ "@chat-adapter/state-memory": "4.20.2",
29
+ "@chat-adapter/state-redis": "4.20.2",
30
30
  "@mariozechner/pi-agent-core": "^0.56.3",
31
31
  "@mariozechner/pi-ai": "^0.56.3",
32
32
  "@sinclair/typebox": "^0.34.48",
@@ -35,7 +35,7 @@
35
35
  "@vercel/sandbox": "^1.8.0",
36
36
  "ai": "^6.0.116",
37
37
  "bash-tool": "^1.3.15",
38
- "chat": "4.17.0",
38
+ "chat": "4.20.2",
39
39
  "just-bash": "^2.12.0",
40
40
  "node-html-markdown": "^2.0.0",
41
41
  "yaml": "^2.8.2",