@sentry/junior 0.39.0 → 0.41.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/dist/app.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  findSkillByName,
4
4
  loadSkillsByName,
5
5
  parseSkillInvocation
6
- } from "./chunk-7QMPV6YJ.js";
6
+ } from "./chunk-7QYONRLH.js";
7
7
  import {
8
8
  GEN_AI_PROVIDER_NAME,
9
9
  MISSING_GATEWAY_CREDENTIALS_ERROR,
@@ -30,7 +30,7 @@ import {
30
30
  runNonInteractiveCommand,
31
31
  sandboxSkillDir,
32
32
  sandboxSkillFile
33
- } from "./chunk-DVMGFG4W.js";
33
+ } from "./chunk-Y3UO7NR6.js";
34
34
  import {
35
35
  CredentialUnavailableError,
36
36
  buildOAuthTokenRequest,
@@ -66,7 +66,7 @@ import {
66
66
  toOptionalString,
67
67
  withContext,
68
68
  withSpan
69
- } from "./chunk-EQPY4742.js";
69
+ } from "./chunk-SCE5C645.js";
70
70
  import {
71
71
  sentry_exports
72
72
  } from "./chunk-Z3YD6NHK.js";
@@ -2111,17 +2111,6 @@ function getTurnUserReplyAttachmentContext(message) {
2111
2111
  };
2112
2112
  }
2113
2113
 
2114
- // src/chat/slack/message.ts
2115
- function getSlackMessageTs(message) {
2116
- if (message.id.endsWith(":message_changed_mention") && message.raw && typeof message.raw === "object") {
2117
- const ts = message.raw.ts;
2118
- if (typeof ts === "string" && ts.length > 0) {
2119
- return ts;
2120
- }
2121
- }
2122
- return message.id;
2123
- }
2124
-
2125
2114
  // src/chat/services/conversation-memory.ts
2126
2115
  var CONTEXT_COMPACTION_TRIGGER_TOKENS = 9e3;
2127
2116
  var CONTEXT_COMPACTION_TARGET_TOKENS = 7e3;
@@ -2129,7 +2118,6 @@ var CONTEXT_MIN_LIVE_MESSAGES = 12;
2129
2118
  var CONTEXT_COMPACTION_BATCH_SIZE = 24;
2130
2119
  var CONTEXT_MAX_COMPACTIONS = 16;
2131
2120
  var CONTEXT_MAX_MESSAGE_CHARS = 3200;
2132
- var BACKFILL_MESSAGE_LIMIT = 80;
2133
2121
  function generateConversationId(prefix) {
2134
2122
  return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
2135
2123
  }
@@ -2308,7 +2296,7 @@ async function summarizeConversationChunk(messages, conversation, context, deps)
2308
2296
  modelId: botConfig.fastModelId
2309
2297
  },
2310
2298
  {
2311
- "error.message": error instanceof Error ? error.message : String(error),
2299
+ "exception.message": error instanceof Error ? error.message : String(error),
2312
2300
  "app.compaction_messages_covered": messages.length
2313
2301
  },
2314
2302
  "Compaction summarization failed; using fallback summary"
@@ -2406,95 +2394,6 @@ function createConversationMemoryService(deps) {
2406
2394
  generateThreadTitle: async (sourceText) => await generateThreadTitleWithDeps(sourceText, deps)
2407
2395
  };
2408
2396
  }
2409
- var defaultConversationMemoryService = createConversationMemoryService({
2410
- completeText
2411
- });
2412
- var compactConversationIfNeeded = defaultConversationMemoryService.compactConversationIfNeeded;
2413
- var generateThreadTitle = defaultConversationMemoryService.generateThreadTitle;
2414
- function createConversationMessageFromSdkMessage(entry) {
2415
- const rawText = normalizeConversationText(entry.text);
2416
- if (!rawText) {
2417
- return null;
2418
- }
2419
- return {
2420
- id: entry.id,
2421
- role: entry.author.isMe ? "assistant" : "user",
2422
- text: rawText,
2423
- createdAtMs: entry.metadata.dateSent.getTime(),
2424
- author: {
2425
- userId: entry.author.userId,
2426
- userName: entry.author.userName,
2427
- fullName: entry.author.fullName,
2428
- isBot: typeof entry.author.isBot === "boolean" ? entry.author.isBot : void 0
2429
- },
2430
- meta: {
2431
- slackTs: getSlackMessageTs(entry)
2432
- }
2433
- };
2434
- }
2435
- async function seedConversationBackfill(thread, conversation, currentTurn) {
2436
- if (conversation.backfill.completedAtMs) {
2437
- return;
2438
- }
2439
- if (conversation.messages.length > 0 || conversation.compactions.length > 0) {
2440
- conversation.backfill = {
2441
- completedAtMs: Date.now(),
2442
- source: "recent_messages"
2443
- };
2444
- updateConversationStats(conversation);
2445
- return;
2446
- }
2447
- const seeded = [];
2448
- let source = "recent_messages";
2449
- try {
2450
- const fetchedNewestFirst = [];
2451
- for await (const entry of thread.messages) {
2452
- fetchedNewestFirst.push(entry);
2453
- if (fetchedNewestFirst.length >= BACKFILL_MESSAGE_LIMIT) {
2454
- break;
2455
- }
2456
- }
2457
- fetchedNewestFirst.reverse();
2458
- for (const entry of fetchedNewestFirst) {
2459
- const message = createConversationMessageFromSdkMessage(entry);
2460
- if (message) {
2461
- seeded.push(message);
2462
- }
2463
- }
2464
- if (seeded.length > 0) {
2465
- source = "thread_fetch";
2466
- }
2467
- } catch {
2468
- }
2469
- if (seeded.length === 0) {
2470
- try {
2471
- await thread.refresh();
2472
- } catch {
2473
- }
2474
- const fromRecent = thread.recentMessages.slice(-BACKFILL_MESSAGE_LIMIT);
2475
- for (const entry of fromRecent) {
2476
- const message = createConversationMessageFromSdkMessage(entry);
2477
- if (message) {
2478
- seeded.push(message);
2479
- }
2480
- }
2481
- source = "recent_messages";
2482
- }
2483
- for (const message of seeded) {
2484
- if (message.id !== currentTurn.messageId && message.createdAtMs > currentTurn.messageCreatedAtMs) {
2485
- continue;
2486
- }
2487
- if (message.id !== currentTurn.messageId && message.createdAtMs === currentTurn.messageCreatedAtMs && message.id > currentTurn.messageId) {
2488
- continue;
2489
- }
2490
- upsertConversationMessage(conversation, message);
2491
- }
2492
- conversation.backfill = {
2493
- completedAtMs: Date.now(),
2494
- source
2495
- };
2496
- updateConversationStats(conversation);
2497
- }
2498
2397
  function isHumanConversationMessage(message) {
2499
2398
  return message.role === "user" && message.author?.isBot !== true;
2500
2399
  }
@@ -2509,20 +2408,17 @@ import { Agent as Agent2 } from "@mariozechner/pi-agent-core";
2509
2408
  import fs from "fs";
2510
2409
  import path2 from "path";
2511
2410
 
2512
- // src/chat/runtime/status-format.ts
2411
+ // src/chat/slack/status-format.ts
2513
2412
  var SLACK_STATUS_MAX_LENGTH = 50;
2514
- function truncateWithEllipsis(text, maxLength) {
2515
- if (text.length <= maxLength) {
2516
- return text;
2517
- }
2518
- return `${text.slice(0, Math.max(1, maxLength - 3)).trimEnd()}...`;
2519
- }
2520
2413
  function truncateStatusText(text) {
2521
2414
  const trimmed = text.trim();
2522
2415
  if (!trimmed) {
2523
2416
  return "";
2524
2417
  }
2525
- return truncateWithEllipsis(trimmed, SLACK_STATUS_MAX_LENGTH);
2418
+ if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) {
2419
+ return trimmed;
2420
+ }
2421
+ return `${trimmed.slice(0, SLACK_STATUS_MAX_LENGTH - 3).trimEnd()}...`;
2526
2422
  }
2527
2423
 
2528
2424
  // src/chat/slack/mrkdwn.ts
@@ -2830,7 +2726,7 @@ function loadSoul() {
2830
2726
  "soul_load_fallback",
2831
2727
  {},
2832
2728
  {
2833
- "file.candidates": soulPathCandidates()
2729
+ "app.file.candidates": soulPathCandidates()
2834
2730
  },
2835
2731
  "SOUL.md not found; using built-in default personality"
2836
2732
  );
@@ -2847,7 +2743,7 @@ var JUNIOR_PERSONALITY = (() => {
2847
2743
  "soul_load_failed",
2848
2744
  {},
2849
2745
  {
2850
- "error.message": error instanceof Error ? error.message : String(error)
2746
+ "exception.message": error instanceof Error ? error.message : String(error)
2851
2747
  },
2852
2748
  "Failed to load SOUL.md; using built-in default personality"
2853
2749
  );
@@ -2862,7 +2758,7 @@ var JUNIOR_WORLD = (() => {
2862
2758
  "world_load_failed",
2863
2759
  {},
2864
2760
  {
2865
- "error.message": error instanceof Error ? error.message : String(error)
2761
+ "exception.message": error instanceof Error ? error.message : String(error)
2866
2762
  },
2867
2763
  "Failed to load WORLD.md; omitting world prompt context"
2868
2764
  );
@@ -3424,7 +3320,7 @@ var SkillCapabilityRuntime = class {
3424
3320
  {
3425
3321
  "app.skill.name": input.activeSkill?.name,
3426
3322
  "app.credential.provider": provider,
3427
- "error.message": error instanceof Error ? error.message : String(error)
3323
+ "exception.message": error instanceof Error ? error.message : String(error)
3428
3324
  },
3429
3325
  "Provider credential resolution failed"
3430
3326
  );
@@ -3494,7 +3390,10 @@ var TestCredentialBroker = class {
3494
3390
  async issue(input) {
3495
3391
  const token = process.env.EVAL_TEST_CREDENTIAL_TOKEN?.trim() || "eval-test-token";
3496
3392
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
3497
- const env = this.config.envKey && this.config.placeholder ? { [this.config.envKey]: this.config.placeholder } : {};
3393
+ const env = {
3394
+ ...this.config.env ?? {},
3395
+ ...this.config.envKey && this.config.placeholder ? { [this.config.envKey]: this.config.placeholder } : {}
3396
+ };
3498
3397
  const tokenTransforms = this.config.domains?.map((domain) => ({
3499
3398
  domain,
3500
3399
  headers: {
@@ -3551,7 +3450,8 @@ function createSkillCapabilityRuntime(options = {}) {
3551
3450
  if (!credentials) {
3552
3451
  brokersByProvider[name] = useTestBroker ? new TestCredentialBroker({
3553
3452
  provider: name,
3554
- headerTransforms: () => resolveTestApiHeaderTransforms(plugin.manifest)
3453
+ headerTransforms: () => resolveTestApiHeaderTransforms(plugin.manifest),
3454
+ ...plugin.manifest.commandEnv ? { env: plugin.manifest.commandEnv } : {}
3555
3455
  }) : createPluginBroker(name, { userTokenStore });
3556
3456
  continue;
3557
3457
  }
@@ -3563,6 +3463,7 @@ function createSkillCapabilityRuntime(options = {}) {
3563
3463
  ...apiHeaders ? {
3564
3464
  headerTransforms: () => resolveTestApiHeaderTransforms(plugin.manifest)
3565
3465
  } : {},
3466
+ ...plugin.manifest.commandEnv ? { env: plugin.manifest.commandEnv } : {},
3566
3467
  envKey: credentials.authTokenEnv,
3567
3468
  placeholder
3568
3469
  }) : createPluginBroker(name, { userTokenStore });
@@ -4523,7 +4424,7 @@ var McpToolManager = class {
4523
4424
  const errorAttributes = {
4524
4425
  ...baseAttributes,
4525
4426
  "error.type": getMcpAwareErrorType(error, "mcp_tool_error"),
4526
- "error.message": getMcpAwareErrorMessage(error)
4427
+ "exception.message": getMcpAwareErrorMessage(error)
4527
4428
  };
4528
4429
  setSpanAttributes(errorAttributes);
4529
4430
  if (error instanceof McpToolError) {
@@ -5511,7 +5412,7 @@ async function enrichImagePrompt(rawPrompt) {
5511
5412
  logWarn(
5512
5413
  "image_prompt_enrichment_failed",
5513
5414
  {},
5514
- { "error.message": String(error) },
5415
+ { "exception.message": String(error) },
5515
5416
  "Image prompt enrichment failed, using raw prompt"
5516
5417
  );
5517
5418
  return rawPrompt;
@@ -6111,6 +6012,7 @@ async function listThreadReplies(input) {
6111
6012
  (value) => typeof value === "string" && value.length > 0
6112
6013
  )
6113
6014
  );
6015
+ const hasTargetMessages = pendingTargets.size > 0;
6114
6016
  const replies = [];
6115
6017
  let cursor;
6116
6018
  let pages = 0;
@@ -6135,7 +6037,7 @@ async function listThreadReplies(input) {
6135
6037
  }
6136
6038
  }
6137
6039
  cursor = response.response_metadata?.next_cursor || void 0;
6138
- if (!cursor || pendingTargets.size === 0) {
6040
+ if (!cursor || hasTargetMessages && pendingTargets.size === 0) {
6139
6041
  break;
6140
6042
  }
6141
6043
  }
@@ -7151,13 +7053,236 @@ function createSlackListUpdateItemTool(state) {
7151
7053
  });
7152
7054
  }
7153
7055
 
7154
- // src/chat/tools/system-time.ts
7056
+ // src/chat/tools/slack/thread-read.ts
7155
7057
  import { Type as Type18 } from "@sinclair/typebox";
7058
+
7059
+ // src/chat/tools/slack/slack-message-url.ts
7060
+ var SLACK_HOST_PATTERN = /^[a-z0-9-]+\.slack(?:-gov)?\.com$/;
7061
+ var ARCHIVE_PATH_PATTERN = /^\/archives\/([CDG][A-Z0-9]+)\/p(\d{10})(\d{6})$/;
7062
+ var SLACK_TS_PATTERN = /^\d{10}\.\d{6}$/;
7063
+ function pTimestampToTs(seconds, micros) {
7064
+ return `${seconds}.${micros}`;
7065
+ }
7066
+ function unwrapMrkdwn(input) {
7067
+ const trimmed = input.trim();
7068
+ if (trimmed.startsWith("<") && trimmed.endsWith(">")) {
7069
+ const inner = trimmed.slice(1, -1);
7070
+ const pipeIndex = inner.indexOf("|");
7071
+ return pipeIndex >= 0 ? inner.slice(0, pipeIndex) : inner;
7072
+ }
7073
+ return trimmed;
7074
+ }
7075
+ function parseSlackMessageReference(input) {
7076
+ const raw = unwrapMrkdwn(input);
7077
+ let parsed;
7078
+ try {
7079
+ parsed = new URL(raw);
7080
+ } catch {
7081
+ return { ok: false, error: "Input is not a valid URL" };
7082
+ }
7083
+ if (parsed.protocol !== "https:") {
7084
+ return { ok: false, error: "Slack archive URL must use HTTPS" };
7085
+ }
7086
+ if (!SLACK_HOST_PATTERN.test(parsed.hostname)) {
7087
+ return { ok: false, error: "Not a Slack archive URL" };
7088
+ }
7089
+ const pathMatch = ARCHIVE_PATH_PATTERN.exec(parsed.pathname);
7090
+ if (!pathMatch) {
7091
+ return { ok: false, error: "URL path does not match Slack archive format" };
7092
+ }
7093
+ const channelId = pathMatch[1];
7094
+ const messageTs = pTimestampToTs(pathMatch[2], pathMatch[3]);
7095
+ const params = new URLSearchParams(parsed.search.replace(/&amp;/g, "&"));
7096
+ const threadTs = params.get("thread_ts") || void 0;
7097
+ if (threadTs && !SLACK_TS_PATTERN.test(threadTs)) {
7098
+ return { ok: false, error: "Invalid thread timestamp in URL" };
7099
+ }
7100
+ return {
7101
+ ok: true,
7102
+ reference: { channelId, messageTs, threadTs }
7103
+ };
7104
+ }
7105
+
7106
+ // src/chat/tools/slack/thread-read.ts
7107
+ var MAX_THREAD_READ_CHARS = 4e4;
7108
+ function sanitizeMessage(msg) {
7109
+ return {
7110
+ ts: msg.ts,
7111
+ user: msg.user,
7112
+ text: msg.text,
7113
+ thread_ts: msg.thread_ts,
7114
+ subtype: msg.subtype,
7115
+ bot_id: msg.bot_id,
7116
+ type: msg.type,
7117
+ ...msg.files?.length ? {
7118
+ files: msg.files.map((f) => ({
7119
+ id: f.id,
7120
+ name: f.name,
7121
+ mimetype: f.mimetype,
7122
+ size: f.size
7123
+ }))
7124
+ } : {}
7125
+ };
7126
+ }
7127
+ function truncateMessages(messages, maxChars) {
7128
+ let chars = 0;
7129
+ const kept = [];
7130
+ for (const msg of messages) {
7131
+ const textLen = msg.text?.length ?? 0;
7132
+ if (kept.length > 0 && chars + textLen > maxChars) {
7133
+ break;
7134
+ }
7135
+ kept.push(msg);
7136
+ chars += textLen;
7137
+ }
7138
+ return { messages: kept, omitted: messages.length - kept.length };
7139
+ }
7140
+ function checkChannelAccess(targetChannelId, currentChannelId) {
7141
+ const target = normalizeSlackConversationId(targetChannelId);
7142
+ const current = normalizeSlackConversationId(currentChannelId);
7143
+ if (!target) {
7144
+ return { allowed: false, error: "Invalid Slack channel ID." };
7145
+ }
7146
+ if (target.startsWith("C")) {
7147
+ return { allowed: true };
7148
+ }
7149
+ if (target === current) {
7150
+ return { allowed: true };
7151
+ }
7152
+ return {
7153
+ allowed: false,
7154
+ error: "Cannot read private channels or DMs unless the link is from the current conversation."
7155
+ };
7156
+ }
7157
+ function createSlackThreadReadTool(context) {
7158
+ return tool({
7159
+ description: "Read a Slack thread from a shared Slack message archive URL or explicit channel + timestamp. Use when the user shares a Slack message link (https://*.slack.com/archives/...) and you need the referenced message and its thread context. Public channel links can be read if the bot has access; private channels and DMs are only readable when they are the current conversation.",
7160
+ annotations: { readOnlyHint: true, destructiveHint: false },
7161
+ inputSchema: Type18.Object({
7162
+ url: Type18.Optional(
7163
+ Type18.String({
7164
+ minLength: 1,
7165
+ description: "Slack message archive URL, e.g. https://workspace.slack.com/archives/C123/p1700000000123456"
7166
+ })
7167
+ ),
7168
+ channel_id: Type18.Optional(
7169
+ Type18.String({
7170
+ minLength: 1,
7171
+ description: "Slack channel/conversation ID (e.g. C123). Use with `ts` as an alternative to `url`."
7172
+ })
7173
+ ),
7174
+ ts: Type18.Optional(
7175
+ Type18.String({
7176
+ minLength: 1,
7177
+ description: "Slack message timestamp (e.g. 1700000000.123456). May be the thread root or any message in the thread."
7178
+ })
7179
+ ),
7180
+ limit: Type18.Optional(
7181
+ Type18.Integer({
7182
+ minimum: 1,
7183
+ maximum: 1e3,
7184
+ description: "Maximum number of thread messages to fetch."
7185
+ })
7186
+ ),
7187
+ max_pages: Type18.Optional(
7188
+ Type18.Integer({
7189
+ minimum: 1,
7190
+ maximum: 10,
7191
+ description: "Maximum number of Slack API pages to traverse."
7192
+ })
7193
+ )
7194
+ }),
7195
+ execute: async ({ url, channel_id, ts, limit, max_pages }) => {
7196
+ let channelId;
7197
+ let messageTs;
7198
+ let threadTs;
7199
+ if (url) {
7200
+ const parsed = parseSlackMessageReference(url);
7201
+ if (!parsed.ok) {
7202
+ return { ok: false, error: parsed.error };
7203
+ }
7204
+ channelId = parsed.reference.channelId;
7205
+ messageTs = parsed.reference.messageTs;
7206
+ threadTs = parsed.reference.threadTs;
7207
+ } else if (channel_id && ts) {
7208
+ if (!SLACK_TS_PATTERN.test(ts)) {
7209
+ return { ok: false, error: "Invalid Slack message timestamp." };
7210
+ }
7211
+ channelId = channel_id;
7212
+ messageTs = ts;
7213
+ } else {
7214
+ return {
7215
+ ok: false,
7216
+ error: "Provide either a Slack message `url` or both `channel_id` and `ts`."
7217
+ };
7218
+ }
7219
+ const access = checkChannelAccess(channelId, context.channelId);
7220
+ if (!access.allowed) {
7221
+ return {
7222
+ ok: false,
7223
+ channel_id: channelId,
7224
+ target_message_ts: messageTs,
7225
+ error: access.error
7226
+ };
7227
+ }
7228
+ const lookupTs = threadTs ?? messageTs;
7229
+ let replies;
7230
+ try {
7231
+ replies = await listThreadReplies({
7232
+ channelId,
7233
+ threadTs: lookupTs,
7234
+ limit: limit ?? 1e3,
7235
+ maxPages: max_pages
7236
+ });
7237
+ } catch (error) {
7238
+ if (error instanceof SlackActionError) {
7239
+ return {
7240
+ ok: false,
7241
+ channel_id: channelId,
7242
+ target_message_ts: messageTs,
7243
+ error: "Could not read this Slack thread. The bot may not be in the channel or may lack history scopes.",
7244
+ slack_error: error.apiError
7245
+ };
7246
+ }
7247
+ throw error;
7248
+ }
7249
+ if (replies.length === 0) {
7250
+ return {
7251
+ ok: false,
7252
+ channel_id: channelId,
7253
+ target_message_ts: messageTs,
7254
+ error: "No messages found for this thread."
7255
+ };
7256
+ }
7257
+ const root = replies[0];
7258
+ const resolvedThreadTs = threadTs ?? root?.thread_ts ?? root?.ts ?? lookupTs;
7259
+ const sanitized = replies.map(sanitizeMessage);
7260
+ const { messages, omitted } = truncateMessages(
7261
+ sanitized,
7262
+ MAX_THREAD_READ_CHARS
7263
+ );
7264
+ return {
7265
+ ok: true,
7266
+ channel_id: channelId,
7267
+ target_message_ts: messageTs,
7268
+ thread_ts: resolvedThreadTs,
7269
+ count: messages.length,
7270
+ fetched_count: replies.length,
7271
+ truncated: omitted > 0,
7272
+ ...omitted > 0 ? { omitted_message_count: omitted } : {},
7273
+ messages
7274
+ };
7275
+ }
7276
+ });
7277
+ }
7278
+
7279
+ // src/chat/tools/system-time.ts
7280
+ import { Type as Type19 } from "@sinclair/typebox";
7156
7281
  function createSystemTimeTool() {
7157
7282
  return tool({
7158
7283
  description: "Return current system time in UTC and local ISO formats. Use when the user asks for current time/date context. Do not use as a substitute for historical or timezone-conversion research.",
7159
7284
  annotations: { readOnlyHint: true, destructiveHint: false },
7160
- inputSchema: Type18.Object({}),
7285
+ inputSchema: Type19.Object({}),
7161
7286
  execute: async () => {
7162
7287
  const now = /* @__PURE__ */ new Date();
7163
7288
  return {
@@ -7175,7 +7300,7 @@ function createSystemTimeTool() {
7175
7300
  import {
7176
7301
  Agent
7177
7302
  } from "@mariozechner/pi-agent-core";
7178
- import { Type as Type19 } from "@sinclair/typebox";
7303
+ import { Type as Type20 } from "@sinclair/typebox";
7179
7304
 
7180
7305
  // src/chat/respond-helpers.ts
7181
7306
  var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
@@ -7463,12 +7588,12 @@ function createAdvisorTool(context) {
7463
7588
  const spanContext = context.logContext ?? {};
7464
7589
  return tool({
7465
7590
  description: ADVISOR_TOOL_DESCRIPTION,
7466
- inputSchema: Type19.Object({
7467
- question: Type19.String({
7591
+ inputSchema: Type20.Object({
7592
+ question: Type20.String({
7468
7593
  minLength: 1,
7469
7594
  description: "Focused advisor question or decision point."
7470
7595
  }),
7471
- context: Type19.String({
7596
+ context: Type20.String({
7472
7597
  minLength: 1,
7473
7598
  description: "Curated evidence packet: relevant requirements, constraints, current plan, alternatives, code snippets, diffs, command output, and open questions."
7474
7599
  })
@@ -7580,7 +7705,7 @@ function createAdvisorTool(context) {
7580
7705
  }
7581
7706
 
7582
7707
  // src/chat/tools/web/fetch-tool.ts
7583
- import { Type as Type20 } from "@sinclair/typebox";
7708
+ import { Type as Type21 } from "@sinclair/typebox";
7584
7709
 
7585
7710
  // src/chat/tools/web/constants.ts
7586
7711
  var USER_AGENT = "junior-bot/0.1";
@@ -7933,13 +8058,13 @@ function createWebFetchTool(hooks) {
7933
8058
  destructiveHint: false,
7934
8059
  openWorldHint: true
7935
8060
  },
7936
- inputSchema: Type20.Object({
7937
- url: Type20.String({
8061
+ inputSchema: Type21.Object({
8062
+ url: Type21.String({
7938
8063
  minLength: 1,
7939
8064
  description: "HTTP(S) URL to fetch."
7940
8065
  }),
7941
- max_chars: Type20.Optional(
7942
- Type20.Integer({
8066
+ max_chars: Type21.Optional(
8067
+ Type21.Integer({
7943
8068
  minimum: 500,
7944
8069
  maximum: MAX_FETCH_CHARS,
7945
8070
  description: "Optional maximum number of extracted characters to return."
@@ -7999,7 +8124,7 @@ function createWebFetchTool(hooks) {
7999
8124
  // src/chat/tools/web/search.ts
8000
8125
  import { generateText } from "ai";
8001
8126
  import { createGatewayProvider } from "@ai-sdk/gateway";
8002
- import { Type as Type21 } from "@sinclair/typebox";
8127
+ import { Type as Type22 } from "@sinclair/typebox";
8003
8128
  var SEARCH_TIMEOUT_MS = 6e4;
8004
8129
  var MAX_RESULTS2 = 5;
8005
8130
  var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
@@ -8047,14 +8172,14 @@ function createWebSearchTool() {
8047
8172
  destructiveHint: false,
8048
8173
  openWorldHint: true
8049
8174
  },
8050
- inputSchema: Type21.Object({
8051
- query: Type21.String({
8175
+ inputSchema: Type22.Object({
8176
+ query: Type22.String({
8052
8177
  minLength: 1,
8053
8178
  maxLength: 500,
8054
8179
  description: "Search query."
8055
8180
  }),
8056
- max_results: Type21.Optional(
8057
- Type21.Integer({
8181
+ max_results: Type22.Optional(
8182
+ Type22.Integer({
8058
8183
  minimum: 1,
8059
8184
  maximum: MAX_RESULTS2,
8060
8185
  description: "Max results to return."
@@ -8123,20 +8248,20 @@ function createWebSearchTool() {
8123
8248
  }
8124
8249
 
8125
8250
  // src/chat/tools/sandbox/write-file.ts
8126
- import { Type as Type22 } from "@sinclair/typebox";
8251
+ import { Type as Type23 } from "@sinclair/typebox";
8127
8252
  function createWriteFileTool() {
8128
8253
  return tool({
8129
8254
  description: "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or replacement after validation. Do not use for exploratory analysis-only turns.",
8130
8255
  promptSnippet: "new file or deliberate full-file replacement",
8131
8256
  promptGuidelines: ["targeted existing-file changes: editFile"],
8132
8257
  executionMode: "sequential",
8133
- inputSchema: Type22.Object(
8258
+ inputSchema: Type23.Object(
8134
8259
  {
8135
- path: Type22.String({
8260
+ path: Type23.String({
8136
8261
  minLength: 1,
8137
8262
  description: "Path to write in the sandbox workspace."
8138
8263
  }),
8139
- content: Type22.String({
8264
+ content: Type23.String({
8140
8265
  description: "UTF-8 file content to write."
8141
8266
  })
8142
8267
  },
@@ -8209,6 +8334,7 @@ function createTools(availableSkills, hooks = {}, context) {
8209
8334
  ),
8210
8335
  slackCanvasRead: createSlackCanvasReadTool(),
8211
8336
  slackCanvasUpdate: createSlackCanvasUpdateTool(state, context),
8337
+ slackThreadRead: createSlackThreadReadTool(context),
8212
8338
  slackListCreate: createSlackListCreateTool(state),
8213
8339
  slackListAddItems: createSlackListAddItemsTool(state),
8214
8340
  slackListGetItems: createSlackListGetItemsTool(state),
@@ -8278,7 +8404,7 @@ function extractHttpErrorDetails(error, options = {}) {
8278
8404
  const err = error ?? {};
8279
8405
  const attributes = {
8280
8406
  "error.type": normalizedError.name || "Error",
8281
- "error.message": toTrimmedString(normalizedError.message, previewLimit) ?? "HTTP error"
8407
+ "exception.message": toTrimmedString(normalizedError.message, previewLimit) ?? "HTTP error"
8282
8408
  };
8283
8409
  const response = err.response;
8284
8410
  const statusCode = typeof response?.status === "number" ? response.status : void 0;
@@ -9145,7 +9271,7 @@ function createSandboxSessionManager(options) {
9145
9271
  "sandbox_network_policy_restore_failed",
9146
9272
  traceContext,
9147
9273
  {
9148
- "error.message": reason instanceof Error ? reason.message : String(reason)
9274
+ "exception.message": reason instanceof Error ? reason.message : String(reason)
9149
9275
  },
9150
9276
  "Sandbox network policy restore failed; discarding sandbox instance"
9151
9277
  );
@@ -10139,7 +10265,7 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
10139
10265
  "gen_ai.tool.name": toolName,
10140
10266
  ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
10141
10267
  "error.type": errorType,
10142
- "error.message": errorMessage
10268
+ "exception.message": errorMessage
10143
10269
  },
10144
10270
  "Agent tool call failed"
10145
10271
  );
@@ -11385,8 +11511,6 @@ async function generateAssistantReply(messageText, context = {}) {
11385
11511
  const shouldTrace = shouldEmitDevAgentTrace();
11386
11512
  const spanContext = {
11387
11513
  conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
11388
- turnId: context.correlation?.turnId,
11389
- agentId: context.correlation?.turnId,
11390
11514
  slackThreadId: context.correlation?.threadId,
11391
11515
  slackUserId: context.correlation?.requesterId,
11392
11516
  slackChannelId: context.correlation?.channelId,
@@ -11409,7 +11533,7 @@ async function generateAssistantReply(messageText, context = {}) {
11409
11533
  {
11410
11534
  "app.skill.count": availableSkills.length,
11411
11535
  "app.skill.names": availableSkills.map((skill) => skill.name).sort(),
11412
- "file.directories": roots,
11536
+ "app.file.directories": roots,
11413
11537
  "app.plugin.count": plugins.length,
11414
11538
  "app.plugin.names": plugins.map((plugin) => plugin.manifest.name).sort()
11415
11539
  },
@@ -11649,8 +11773,6 @@ async function generateAssistantReply(messageText, context = {}) {
11649
11773
  };
11650
11774
  setTags({
11651
11775
  conversationId: spanContext.conversationId,
11652
- turnId: spanContext.turnId,
11653
- agentId: spanContext.agentId,
11654
11776
  slackThreadId: context.correlation?.threadId,
11655
11777
  slackUserId: context.correlation?.requesterId,
11656
11778
  slackChannelId: context.correlation?.channelId,
@@ -11795,7 +11917,7 @@ async function generateAssistantReply(messageText, context = {}) {
11795
11917
  spanContext,
11796
11918
  {
11797
11919
  "gen_ai.tool.name": toolName,
11798
- "error.message": error instanceof Error ? error.message : String(error)
11920
+ "exception.message": error instanceof Error ? error.message : String(error)
11799
11921
  },
11800
11922
  "Tool invocation observer failed"
11801
11923
  );
@@ -11839,7 +11961,7 @@ async function generateAssistantReply(messageText, context = {}) {
11839
11961
  "streaming_message_start_error",
11840
11962
  {},
11841
11963
  {
11842
- "error.message": error instanceof Error ? error.message : String(error)
11964
+ "exception.message": error instanceof Error ? error.message : String(error)
11843
11965
  },
11844
11966
  "Failed to deliver assistant message start to stream coordinator"
11845
11967
  );
@@ -11861,7 +11983,7 @@ async function generateAssistantReply(messageText, context = {}) {
11861
11983
  "streaming_text_delta_error",
11862
11984
  {},
11863
11985
  {
11864
- "error.message": error instanceof Error ? error.message : String(error)
11986
+ "exception.message": error instanceof Error ? error.message : String(error)
11865
11987
  },
11866
11988
  "Failed to deliver text delta to stream"
11867
11989
  );
@@ -11947,8 +12069,7 @@ async function generateAssistantReply(messageText, context = {}) {
11947
12069
  ) ? usageSummary : void 0;
11948
12070
  setSpanAttributes({
11949
12071
  ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
11950
- ...usageSummary.inputTokens !== void 0 ? { "gen_ai.usage.input_tokens": usageSummary.inputTokens } : {},
11951
- ...usageSummary.outputTokens !== void 0 ? { "gen_ai.usage.output_tokens": usageSummary.outputTokens } : {}
12072
+ ...extractGenAiUsageAttributes(usageSummary)
11952
12073
  });
11953
12074
  if (getPendingAuthPause()) {
11954
12075
  timeoutResumeMessages = [...agent.state.messages];
@@ -12113,7 +12234,7 @@ async function generateAssistantReply(messageText, context = {}) {
12113
12234
  "mcp_tool_manager_close_failed",
12114
12235
  {},
12115
12236
  {
12116
- "error.message": closeError instanceof Error ? closeError.message : String(closeError)
12237
+ "exception.message": closeError instanceof Error ? closeError.message : String(closeError)
12117
12238
  },
12118
12239
  "Failed to close MCP tool manager"
12119
12240
  );
@@ -12178,7 +12299,7 @@ function getAgentTurnDiagnosticsAttributes(reply) {
12178
12299
  ...reply.diagnostics.stopReason ? {
12179
12300
  "gen_ai.response.finish_reasons": [reply.diagnostics.stopReason]
12180
12301
  } : {},
12181
- ...reply.diagnostics.errorMessage ? { "error.message": reply.diagnostics.errorMessage } : {}
12302
+ ...reply.diagnostics.errorMessage ? { "exception.message": reply.diagnostics.errorMessage } : {}
12182
12303
  };
12183
12304
  }
12184
12305
  function finalizeFailedTurnReply(args) {
@@ -12534,7 +12655,7 @@ function logAssistantStatusFailure(args) {
12534
12655
  "app.slack.channel_id_raw": args.channelId,
12535
12656
  "app.slack.channel_id": args.normalizedChannelId,
12536
12657
  "app.slack.thread_ts": args.threadTs,
12537
- "error.message": args.error instanceof Error ? args.error.message : String(args.error)
12658
+ "exception.message": args.error instanceof Error ? args.error.message : String(args.error)
12538
12659
  },
12539
12660
  `Failed to update assistant status channel=${args.normalizedChannelId} raw=${args.channelId} thread=${args.threadTs}`
12540
12661
  );
@@ -12572,24 +12693,18 @@ function createSlackWebApiAssistantStatusSession(args) {
12572
12693
 
12573
12694
  // src/chat/slack/footer.ts
12574
12695
  var SENTRY_CONVERSATION_SEARCH_STATS_PERIOD = "14d";
12575
- var ORG_ID_HOST_RE = /^o(\d+)\./;
12576
12696
  function escapeSlackMrkdwn(text) {
12577
12697
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
12578
12698
  }
12579
12699
  function escapeSlackLinkUrl(url) {
12580
12700
  return url.replaceAll("&", "&amp;").replaceAll("<", "%3C").replaceAll(">", "%3E");
12581
12701
  }
12582
- function toOptionalString2(value) {
12583
- if (typeof value === "number" && Number.isFinite(value)) {
12584
- return String(value);
12585
- }
12586
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
12587
- }
12588
12702
  function quoteSentrySearchValue(value) {
12589
12703
  return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
12590
12704
  }
12591
- function getDsnOrgId(host) {
12592
- return host?.match(ORG_ID_HOST_RE)?.[1];
12705
+ function getSentryOrgSlug() {
12706
+ const slug = process.env.SENTRY_ORG_SLUG?.trim();
12707
+ return slug || void 0;
12593
12708
  }
12594
12709
  function isSentrySaasDsnHost(host) {
12595
12710
  return host === "sentry.io" || host.endsWith(".sentry.io");
@@ -12608,8 +12723,8 @@ function getSentryConversationSearchUrl(conversationId) {
12608
12723
  if (!dsn?.host || !dsn.projectId) {
12609
12724
  return void 0;
12610
12725
  }
12611
- const orgId = toOptionalString2(client2?.getOptions().orgId) ?? getDsnOrgId(dsn.host);
12612
- if (!orgId) {
12726
+ const orgSlug = getSentryOrgSlug();
12727
+ if (!orgSlug) {
12613
12728
  return void 0;
12614
12729
  }
12615
12730
  const params = new URLSearchParams();
@@ -12619,7 +12734,11 @@ function getSentryConversationSearchUrl(conversationId) {
12619
12734
  );
12620
12735
  params.set("project", dsn.projectId);
12621
12736
  params.set("statsPeriod", SENTRY_CONVERSATION_SEARCH_STATS_PERIOD);
12622
- return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgId}/explore/traces/?${params.toString()}`;
12737
+ const search = `explore/traces/?${params.toString()}`;
12738
+ if (isSentrySaasDsnHost(dsn.host)) {
12739
+ return `https://${orgSlug}.sentry.io/${search}`;
12740
+ }
12741
+ return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${search}`;
12623
12742
  }
12624
12743
  function formatSlackTokenCount(value) {
12625
12744
  if (value >= 1e6) {
@@ -12870,7 +12989,7 @@ async function postSlackApiReplyPosts(args) {
12870
12989
  return lastPostedMessageTs;
12871
12990
  }
12872
12991
 
12873
- // src/chat/slack/resume.ts
12992
+ // src/chat/runtime/slack-resume.ts
12874
12993
  function resolveReplyTimeoutMs(explicitTimeoutMs) {
12875
12994
  if (typeof explicitTimeoutMs === "number" && explicitTimeoutMs > 0) {
12876
12995
  return explicitTimeoutMs;
@@ -12970,20 +13089,12 @@ async function handleResumeFailure(args) {
12970
13089
  );
12971
13090
  await args.resumeArgs.onFailure?.(args.error);
12972
13091
  const eventId = requireTurnFailureEventId(capturedEventId, args.eventName);
12973
- let postError;
12974
- try {
12975
- await postResumeFailureReply({
12976
- channelId: args.resumeArgs.channelId,
12977
- threadTs: args.resumeArgs.threadTs,
12978
- eventId,
12979
- logContext
12980
- });
12981
- } catch (error) {
12982
- postError = error;
12983
- }
12984
- if (postError) {
12985
- throw postError;
12986
- }
13092
+ await postResumeFailureReply({
13093
+ channelId: args.resumeArgs.channelId,
13094
+ threadTs: args.resumeArgs.threadTs,
13095
+ eventId,
13096
+ logContext
13097
+ });
12987
13098
  }
12988
13099
  function createResumeReplyContext(args, statusSession) {
12989
13100
  const replyContext = args.replyContext ?? {};
@@ -13017,8 +13128,7 @@ function createResumeReplyContext(args, statusSession) {
13017
13128
  };
13018
13129
  }
13019
13130
  async function resumeSlackTurn(args) {
13020
- const requesterUserId = args.replyContext?.requester?.userId;
13021
- if (!requesterUserId) {
13131
+ if (!args.replyContext?.requester?.userId) {
13022
13132
  throw new Error("Resumed turn requires replyContext.requester.userId");
13023
13133
  }
13024
13134
  const stateAdapter = getStateAdapter();
@@ -13048,9 +13158,7 @@ async function resumeSlackTurn(args) {
13048
13158
  status.start();
13049
13159
  const generateReply = args.generateReply ?? generateAssistantReply;
13050
13160
  const replyContext = createResumeReplyContext(args, status);
13051
- const replyPromise = generateReply(args.messageText, {
13052
- ...replyContext
13053
- });
13161
+ const replyPromise = generateReply(args.messageText, replyContext);
13054
13162
  const replyTimeoutMs = resolveReplyTimeoutMs(args.replyTimeoutMs);
13055
13163
  let reply = typeof replyTimeoutMs === "number" ? await Promise.race([
13056
13164
  replyPromise,
@@ -13087,13 +13195,15 @@ async function resumeSlackTurn(args) {
13087
13195
  await args.onSuccess?.(reply);
13088
13196
  } catch (error) {
13089
13197
  await status.stop();
13090
- if ((isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) && args.onAuthPause) {
13198
+ const onAuthPause = args.onAuthPause;
13199
+ const onTimeoutPause = args.onTimeoutPause;
13200
+ if ((isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) && onAuthPause) {
13091
13201
  deferredPauseHandler = async () => {
13092
- await args.onAuthPause?.(error);
13202
+ await onAuthPause(error);
13093
13203
  };
13094
- } else if (isRetryableTurnError(error, "turn_timeout_resume") && args.onTimeoutPause) {
13204
+ } else if (isRetryableTurnError(error, "turn_timeout_resume") && onTimeoutPause) {
13095
13205
  deferredPauseHandler = async () => {
13096
- await args.onTimeoutPause?.(error);
13206
+ await onTimeoutPause(error);
13097
13207
  };
13098
13208
  } else {
13099
13209
  deferredFailureHandler = async () => {
@@ -13519,7 +13629,7 @@ async function resumeAuthorizedMcpTurn(args) {
13519
13629
  {},
13520
13630
  {
13521
13631
  "app.credential.provider": provider,
13522
- ...isRetryableTurnError(error) ? { "app.turn.retryable_reason": error.reason } : {}
13632
+ ...isRetryableTurnError(error) ? { "app.ai.retryable_reason": error.reason } : {}
13523
13633
  },
13524
13634
  "Resumed MCP turn requested another authorization flow"
13525
13635
  );
@@ -14785,73 +14895,7 @@ async function decideSubscribedThreadReply(args) {
14785
14895
  }
14786
14896
  }
14787
14897
 
14788
- // src/chat/runtime/thread-context.ts
14789
- function escapeRegExp3(value) {
14790
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14791
- }
14792
- function stripLeadingBotMention(text, options = {}) {
14793
- if (!text.trim()) return text;
14794
- let next = text;
14795
- if (options.stripLeadingSlackMentionToken) {
14796
- next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
14797
- }
14798
- const mentionByNameRe = new RegExp(
14799
- `^\\s*@${escapeRegExp3(botConfig.userName)}\\b[\\s,:-]*`,
14800
- "i"
14801
- );
14802
- next = next.replace(mentionByNameRe, "").trim();
14803
- const mentionByLabeledEntityRe = new RegExp(
14804
- `^\\s*<@[^>|]+\\|${escapeRegExp3(botConfig.userName)}>[\\s,:-]*`,
14805
- "i"
14806
- );
14807
- next = next.replace(mentionByLabeledEntityRe, "").trim();
14808
- return next;
14809
- }
14810
- function getThreadId(thread, _message) {
14811
- return toOptionalString(thread.id);
14812
- }
14813
- function getRunId(thread, message) {
14814
- return toOptionalString(thread.runId) ?? toOptionalString(message.runId);
14815
- }
14816
- function getChannelId(thread, message) {
14817
- return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
14818
- }
14819
- function getThreadTs(threadId) {
14820
- return parseSlackThreadId(threadId)?.threadTs;
14821
- }
14822
- function getAssistantThreadContext(message) {
14823
- const raw = message.raw;
14824
- const rawRecord = raw && typeof raw === "object" ? raw : void 0;
14825
- const channelId = toOptionalString(rawRecord?.channel);
14826
- if (channelId) {
14827
- const rawThreadTs = toOptionalString(rawRecord?.thread_ts);
14828
- const threadTs = isDmChannel(channelId) ? rawThreadTs : rawThreadTs ?? toOptionalString(rawRecord?.ts);
14829
- if (threadTs) {
14830
- return { channelId, threadTs };
14831
- }
14832
- }
14833
- const parsedThreadId = parseSlackThreadId(
14834
- toOptionalString(message.threadId)
14835
- );
14836
- if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) {
14837
- return void 0;
14838
- }
14839
- return parsedThreadId;
14840
- }
14841
- function getMessageTs(message) {
14842
- const directTs = toOptionalString(
14843
- message.ts
14844
- );
14845
- if (directTs) {
14846
- return directTs;
14847
- }
14848
- const raw = message.raw;
14849
- if (!raw || typeof raw !== "object") {
14850
- return void 0;
14851
- }
14852
- const rawRecord = raw;
14853
- return toOptionalString(rawRecord.ts) ?? toOptionalString(rawRecord.event_ts) ?? toOptionalString(rawRecord.message?.ts);
14854
- }
14898
+ // src/chat/slack/errors.ts
14855
14899
  function getSlackApiErrorCode(error) {
14856
14900
  if (!error || typeof error !== "object") {
14857
14901
  return void 0;
@@ -15000,7 +15044,7 @@ function createSlackTurnRuntime(deps) {
15000
15044
  error,
15001
15045
  "mention_handler_auth_pause",
15002
15046
  errorContext,
15003
- { "app.turn.retryable_reason": error.reason },
15047
+ { "app.ai.retryable_reason": error.reason },
15004
15048
  "onNewMention parked turn for auth resume"
15005
15049
  );
15006
15050
  return;
@@ -15135,7 +15179,7 @@ function createSlackTurnRuntime(deps) {
15135
15179
  error,
15136
15180
  "subscribed_message_handler_auth_pause",
15137
15181
  errorContext,
15138
- { "app.turn.retryable_reason": error.reason },
15182
+ { "app.ai.retryable_reason": error.reason },
15139
15183
  "onSubscribedMessage parked turn for auth resume"
15140
15184
  );
15141
15185
  return;
@@ -15282,7 +15326,7 @@ async function lookupSlackUser(userId) {
15282
15326
  {},
15283
15327
  {
15284
15328
  "enduser.id": userId,
15285
- "error.message": error instanceof Error ? error.message : String(error)
15329
+ "exception.message": error instanceof Error ? error.message : String(error)
15286
15330
  },
15287
15331
  "Slack user lookup failed with exception"
15288
15332
  );
@@ -15310,7 +15354,7 @@ function createSubscribedReplyPolicy(deps) {
15310
15354
  modelId: botConfig.fastModelId
15311
15355
  },
15312
15356
  {
15313
- "error.message": error instanceof Error ? error.message : String(error)
15357
+ "exception.message": error instanceof Error ? error.message : String(error)
15314
15358
  },
15315
15359
  "Subscribed-message classifier failed; skipping reply"
15316
15360
  );
@@ -15522,7 +15566,7 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
15522
15566
  },
15523
15567
  {
15524
15568
  "file.size": data.byteLength,
15525
- "file.mime_type": mediaType
15569
+ "app.file.mime_type": mediaType
15526
15570
  },
15527
15571
  "Skipping user attachment that exceeds size limit"
15528
15572
  );
@@ -15546,8 +15590,8 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
15546
15590
  modelId: botConfig.visionModelId ?? botConfig.modelId
15547
15591
  },
15548
15592
  {
15549
- "error.message": error instanceof Error ? error.message : String(error),
15550
- "file.mime_type": mediaType,
15593
+ "exception.message": error instanceof Error ? error.message : String(error),
15594
+ "app.file.mime_type": mediaType,
15551
15595
  ...attachment.name ? { "file.name": attachment.name } : {}
15552
15596
  },
15553
15597
  "Image attachment processing failed"
@@ -15565,8 +15609,8 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
15565
15609
  modelId: botConfig.modelId
15566
15610
  },
15567
15611
  {
15568
- "error.message": error instanceof Error ? error.message : String(error),
15569
- "file.mime_type": mediaType
15612
+ "exception.message": error instanceof Error ? error.message : String(error),
15613
+ "app.file.mime_type": mediaType
15570
15614
  },
15571
15615
  "Failed to resolve user attachment"
15572
15616
  );
@@ -15615,9 +15659,9 @@ async function summarizeConversationImage(args, deps) {
15615
15659
  modelId: visionModelId
15616
15660
  },
15617
15661
  {
15618
- "error.message": error instanceof Error ? error.message : String(error),
15619
- "file.id": args.fileId,
15620
- "file.mime_type": args.mimeType
15662
+ "exception.message": error instanceof Error ? error.message : String(error),
15663
+ "app.file.id": args.fileId,
15664
+ "app.file.mime_type": args.mimeType
15621
15665
  },
15622
15666
  "Image analysis failed while hydrating conversation context"
15623
15667
  );
@@ -15663,7 +15707,7 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15663
15707
  modelId: botConfig.modelId
15664
15708
  },
15665
15709
  {
15666
- "error.message": error instanceof Error ? error.message : String(error)
15710
+ "exception.message": error instanceof Error ? error.message : String(error)
15667
15711
  },
15668
15712
  "Failed to fetch thread replies for image context hydration"
15669
15713
  );
@@ -15730,9 +15774,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15730
15774
  modelId: botConfig.modelId
15731
15775
  },
15732
15776
  {
15733
- "file.id": fileId,
15777
+ "app.file.id": fileId,
15734
15778
  "file.size": fileSize,
15735
- "file.mime_type": mimeType
15779
+ "app.file.mime_type": mimeType
15736
15780
  },
15737
15781
  "Skipping thread image that exceeds size limit"
15738
15782
  );
@@ -15744,7 +15788,7 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15744
15788
  }
15745
15789
  let imageData;
15746
15790
  try {
15747
- imageData = await deps.downloadPrivateSlackFile(downloadUrl);
15791
+ imageData = await deps.downloadFile(downloadUrl);
15748
15792
  } catch (error) {
15749
15793
  logWarn(
15750
15794
  "conversation_image_download_failed",
@@ -15757,9 +15801,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15757
15801
  modelId: botConfig.modelId
15758
15802
  },
15759
15803
  {
15760
- "error.message": error instanceof Error ? error.message : String(error),
15761
- "file.id": fileId,
15762
- "file.mime_type": mimeType
15804
+ "exception.message": error instanceof Error ? error.message : String(error),
15805
+ "app.file.id": fileId,
15806
+ "app.file.mime_type": mimeType
15763
15807
  },
15764
15808
  "Failed to download thread image for context hydration"
15765
15809
  );
@@ -15777,9 +15821,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15777
15821
  modelId: botConfig.modelId
15778
15822
  },
15779
15823
  {
15780
- "file.id": fileId,
15824
+ "app.file.id": fileId,
15781
15825
  "file.size": imageData.byteLength,
15782
- "file.mime_type": mimeType
15826
+ "app.file.mime_type": mimeType
15783
15827
  },
15784
15828
  "Skipping downloaded thread image that exceeds size limit"
15785
15829
  );
@@ -15842,12 +15886,6 @@ function createVisionContextService(deps) {
15842
15886
  )
15843
15887
  };
15844
15888
  }
15845
- var defaultVisionContextService = createVisionContextService({
15846
- completeText,
15847
- downloadPrivateSlackFile,
15848
- listThreadReplies
15849
- });
15850
- var hydrateConversationVisionContext = defaultVisionContextService.hydrateConversationVisionContext;
15851
15889
 
15852
15890
  // src/chat/app/services.ts
15853
15891
  function createJuniorRuntimeServices(overrides = {}) {
@@ -15857,7 +15895,7 @@ function createJuniorRuntimeServices(overrides = {}) {
15857
15895
  const visionContext = createVisionContextService({
15858
15896
  completeText: overrides.visionContext?.completeText ?? completeText,
15859
15897
  listThreadReplies: overrides.visionContext?.listThreadReplies ?? listThreadReplies,
15860
- downloadPrivateSlackFile: overrides.visionContext?.downloadPrivateSlackFile ?? downloadPrivateSlackFile
15898
+ downloadFile: overrides.visionContext?.downloadFile ?? downloadPrivateSlackFile
15861
15899
  });
15862
15900
  return {
15863
15901
  conversationMemory,
@@ -15874,6 +15912,85 @@ function createJuniorRuntimeServices(overrides = {}) {
15874
15912
  };
15875
15913
  }
15876
15914
 
15915
+ // src/chat/slack/message.ts
15916
+ function getSlackMessageTs(message) {
15917
+ if (message.id.endsWith(":message_changed_mention") && message.raw && typeof message.raw === "object") {
15918
+ const ts = message.raw.ts;
15919
+ if (typeof ts === "string" && ts.length > 0) {
15920
+ return ts;
15921
+ }
15922
+ }
15923
+ return message.id;
15924
+ }
15925
+
15926
+ // src/chat/runtime/thread-context.ts
15927
+ function escapeRegExp3(value) {
15928
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15929
+ }
15930
+ function stripLeadingBotMention(text, options = {}) {
15931
+ if (!text.trim()) return text;
15932
+ let next = text;
15933
+ if (options.stripLeadingSlackMentionToken) {
15934
+ next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
15935
+ }
15936
+ const mentionByNameRe = new RegExp(
15937
+ `^\\s*@${escapeRegExp3(botConfig.userName)}\\b[\\s,:-]*`,
15938
+ "i"
15939
+ );
15940
+ next = next.replace(mentionByNameRe, "").trim();
15941
+ const mentionByLabeledEntityRe = new RegExp(
15942
+ `^\\s*<@[^>|]+\\|${escapeRegExp3(botConfig.userName)}>[\\s,:-]*`,
15943
+ "i"
15944
+ );
15945
+ next = next.replace(mentionByLabeledEntityRe, "").trim();
15946
+ return next;
15947
+ }
15948
+ function getThreadId(thread, _message) {
15949
+ return toOptionalString(thread.id);
15950
+ }
15951
+ function getRunId(thread, message) {
15952
+ return toOptionalString(thread.runId) ?? toOptionalString(message.runId);
15953
+ }
15954
+ function getChannelId(thread, message) {
15955
+ return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
15956
+ }
15957
+ function getThreadTs(threadId) {
15958
+ return parseSlackThreadId(threadId)?.threadTs;
15959
+ }
15960
+ function getAssistantThreadContext(message) {
15961
+ const raw = message.raw;
15962
+ const rawRecord = raw && typeof raw === "object" ? raw : void 0;
15963
+ const channelId = toOptionalString(rawRecord?.channel);
15964
+ if (channelId) {
15965
+ const rawThreadTs = toOptionalString(rawRecord?.thread_ts);
15966
+ const threadTs = isDmChannel(channelId) ? rawThreadTs : rawThreadTs ?? toOptionalString(rawRecord?.ts);
15967
+ if (threadTs) {
15968
+ return { channelId, threadTs };
15969
+ }
15970
+ }
15971
+ const parsedThreadId = parseSlackThreadId(
15972
+ toOptionalString(message.threadId)
15973
+ );
15974
+ if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) {
15975
+ return void 0;
15976
+ }
15977
+ return parsedThreadId;
15978
+ }
15979
+ function getMessageTs(message) {
15980
+ const directTs = toOptionalString(
15981
+ message.ts
15982
+ );
15983
+ if (directTs) {
15984
+ return directTs;
15985
+ }
15986
+ const raw = message.raw;
15987
+ if (!raw || typeof raw !== "object") {
15988
+ return void 0;
15989
+ }
15990
+ const rawRecord = raw;
15991
+ return toOptionalString(rawRecord.ts) ?? toOptionalString(rawRecord.event_ts) ?? toOptionalString(rawRecord.message?.ts);
15992
+ }
15993
+
15877
15994
  // src/chat/slack/assistant-thread/title.ts
15878
15995
  function maybeUpdateAssistantTitle(args) {
15879
15996
  const assistantThreadContext = args.assistantThreadContext;
@@ -15932,7 +16049,7 @@ function maybeUpdateAssistantTitle(args) {
15932
16049
  modelId: args.modelId
15933
16050
  },
15934
16051
  {
15935
- "error.message": error instanceof Error ? error.message : String(error)
16052
+ "exception.message": error instanceof Error ? error.message : String(error)
15936
16053
  },
15937
16054
  "Thread title generation failed"
15938
16055
  );
@@ -15991,11 +16108,8 @@ function createReplyToThread(deps) {
15991
16108
  nextTurnId: turnId,
15992
16109
  updateConversationStats
15993
16110
  });
15994
- const turnStartedAtMs = Date.now();
15995
16111
  const turnTraceContext = {
15996
16112
  conversationId,
15997
- turnId,
15998
- agentId: turnId,
15999
16113
  slackThreadId: threadId,
16000
16114
  slackUserId: message.author.userId,
16001
16115
  slackChannelId: channelId,
@@ -16004,9 +16118,7 @@ function createReplyToThread(deps) {
16004
16118
  modelId: botConfig.modelId
16005
16119
  };
16006
16120
  setTags({
16007
- conversationId,
16008
- turnId,
16009
- agentId: turnId
16121
+ conversationId
16010
16122
  });
16011
16123
  if (shouldEmitDevAgentTrace()) {
16012
16124
  logInfo(
@@ -16274,7 +16386,6 @@ function createReplyToThread(deps) {
16274
16386
  "agent_turn_completed",
16275
16387
  turnTraceContext,
16276
16388
  {
16277
- "app.turn.duration_ms": Date.now() - turnStartedAtMs,
16278
16389
  "app.ai.outcome": reply.diagnostics.outcome,
16279
16390
  "app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
16280
16391
  "app.ai.tool_error_results": reply.diagnostics.toolErrorCount
@@ -16360,9 +16471,7 @@ function createReplyToThread(deps) {
16360
16471
  logWarn(
16361
16472
  "agent_turn_failed",
16362
16473
  turnTraceContext,
16363
- {
16364
- "app.turn.duration_ms": Date.now() - turnStartedAtMs
16365
- },
16474
+ {},
16366
16475
  "Agent turn failed"
16367
16476
  );
16368
16477
  }
@@ -16375,7 +16484,6 @@ function createReplyToThread(deps) {
16375
16484
  }
16376
16485
 
16377
16486
  // src/chat/slack/assistant-thread/lifecycle.ts
16378
- import { ThreadImpl } from "chat";
16379
16487
  async function syncAssistantThreadContext(event, options) {
16380
16488
  const channelId = normalizeSlackConversationId(event.channelId);
16381
16489
  if (!channelId) {
@@ -16400,20 +16508,7 @@ async function syncAssistantThreadContext(event, options) {
16400
16508
  if (!sourceChannelId) {
16401
16509
  return;
16402
16510
  }
16403
- const thread = ThreadImpl.fromJSON({
16404
- _type: "chat:Thread",
16405
- adapterName: "slack",
16406
- channelId,
16407
- id: event.threadId,
16408
- isDM: channelId.startsWith("D")
16409
- });
16410
- const currentArtifacts = coerceThreadArtifactsState(await thread.state);
16411
- const nextArtifacts = mergeArtifactsState(currentArtifacts, {
16412
- assistantContextChannelId: sourceChannelId
16413
- });
16414
- await persistThreadState(thread, {
16415
- artifacts: nextArtifacts
16416
- });
16511
+ await event.onContextChannelResolved(sourceChannelId);
16417
16512
  }
16418
16513
  async function initializeAssistantThread(event) {
16419
16514
  await syncAssistantThreadContext(event, { setInitialTitle: true });
@@ -16423,11 +16518,96 @@ async function refreshAssistantThreadContext(event) {
16423
16518
  }
16424
16519
 
16425
16520
  // src/chat/runtime/turn-preparation.ts
16521
+ var BACKFILL_MESSAGE_LIMIT = 80;
16426
16522
  function hasPendingImageHydration(conversation) {
16427
16523
  return conversation.messages.some(
16428
16524
  (message) => isHumanConversationMessage(message) && !message.meta?.imagesHydrated
16429
16525
  );
16430
16526
  }
16527
+ function createConversationMessageFromSdkMessage(entry) {
16528
+ const rawText = normalizeConversationText(entry.text);
16529
+ if (!rawText) {
16530
+ return null;
16531
+ }
16532
+ return {
16533
+ id: entry.id,
16534
+ role: entry.author.isMe ? "assistant" : "user",
16535
+ text: rawText,
16536
+ createdAtMs: entry.metadata.dateSent.getTime(),
16537
+ author: {
16538
+ userId: entry.author.userId,
16539
+ userName: entry.author.userName,
16540
+ fullName: entry.author.fullName,
16541
+ isBot: typeof entry.author.isBot === "boolean" ? entry.author.isBot : void 0
16542
+ },
16543
+ meta: {
16544
+ slackTs: getSlackMessageTs(entry)
16545
+ }
16546
+ };
16547
+ }
16548
+ async function seedConversationBackfill(thread, conversation, currentTurn) {
16549
+ if (conversation.backfill.completedAtMs) {
16550
+ return;
16551
+ }
16552
+ if (conversation.messages.length > 0 || conversation.compactions.length > 0) {
16553
+ conversation.backfill = {
16554
+ completedAtMs: Date.now(),
16555
+ source: "recent_messages"
16556
+ };
16557
+ updateConversationStats(conversation);
16558
+ return;
16559
+ }
16560
+ const seeded = [];
16561
+ let source = "recent_messages";
16562
+ try {
16563
+ const fetchedNewestFirst = [];
16564
+ for await (const entry of thread.messages) {
16565
+ fetchedNewestFirst.push(entry);
16566
+ if (fetchedNewestFirst.length >= BACKFILL_MESSAGE_LIMIT) {
16567
+ break;
16568
+ }
16569
+ }
16570
+ fetchedNewestFirst.reverse();
16571
+ for (const entry of fetchedNewestFirst) {
16572
+ const message = createConversationMessageFromSdkMessage(entry);
16573
+ if (message) {
16574
+ seeded.push(message);
16575
+ }
16576
+ }
16577
+ if (seeded.length > 0) {
16578
+ source = "thread_fetch";
16579
+ }
16580
+ } catch {
16581
+ }
16582
+ if (seeded.length === 0) {
16583
+ try {
16584
+ await thread.refresh();
16585
+ } catch {
16586
+ }
16587
+ const fromRecent = thread.recentMessages.slice(-BACKFILL_MESSAGE_LIMIT);
16588
+ for (const entry of fromRecent) {
16589
+ const message = createConversationMessageFromSdkMessage(entry);
16590
+ if (message) {
16591
+ seeded.push(message);
16592
+ }
16593
+ }
16594
+ source = "recent_messages";
16595
+ }
16596
+ for (const message of seeded) {
16597
+ if (message.id !== currentTurn.messageId && message.createdAtMs > currentTurn.messageCreatedAtMs) {
16598
+ continue;
16599
+ }
16600
+ if (message.id !== currentTurn.messageId && message.createdAtMs === currentTurn.messageCreatedAtMs && message.id > currentTurn.messageId) {
16601
+ continue;
16602
+ }
16603
+ upsertConversationMessage(conversation, message);
16604
+ }
16605
+ conversation.backfill = {
16606
+ completedAtMs: Date.now(),
16607
+ source
16608
+ };
16609
+ updateConversationStats(conversation);
16610
+ }
16431
16611
  function createPrepareTurnState(deps) {
16432
16612
  return async function prepareTurnState(args) {
16433
16613
  const existingState = await args.thread.state;
@@ -16513,6 +16693,17 @@ function createPrepareTurnState(deps) {
16513
16693
  }
16514
16694
 
16515
16695
  // src/chat/app/factory.ts
16696
+ async function persistAssistantContextChannelId(args) {
16697
+ const currentArtifacts = coerceThreadArtifactsState(
16698
+ await getPersistedThreadState(args.threadId)
16699
+ );
16700
+ const nextArtifacts = mergeArtifactsState(currentArtifacts, {
16701
+ assistantContextChannelId: args.sourceChannelId
16702
+ });
16703
+ await persistThreadStateById(args.threadId, {
16704
+ artifacts: nextArtifacts
16705
+ });
16706
+ }
16516
16707
  function createSlackRuntime(options) {
16517
16708
  const services = createJuniorRuntimeServices(options.services);
16518
16709
  const prepareTurnState = createPrepareTurnState({
@@ -16612,11 +16803,14 @@ function createSlackRuntime(options) {
16612
16803
  sourceChannelId
16613
16804
  }) => {
16614
16805
  await initializeAssistantThread({
16615
- threadId,
16616
16806
  channelId,
16617
16807
  threadTs,
16618
16808
  sourceChannelId,
16619
- getSlackAdapter: options.getSlackAdapter
16809
+ getSlackAdapter: options.getSlackAdapter,
16810
+ onContextChannelResolved: (resolvedSourceChannelId) => persistAssistantContextChannelId({
16811
+ sourceChannelId: resolvedSourceChannelId,
16812
+ threadId
16813
+ })
16620
16814
  });
16621
16815
  },
16622
16816
  refreshAssistantThreadContext: async ({
@@ -16626,11 +16820,14 @@ function createSlackRuntime(options) {
16626
16820
  sourceChannelId
16627
16821
  }) => {
16628
16822
  await refreshAssistantThreadContext({
16629
- threadId,
16630
16823
  channelId,
16631
16824
  threadTs,
16632
16825
  sourceChannelId,
16633
- getSlackAdapter: options.getSlackAdapter
16826
+ getSlackAdapter: options.getSlackAdapter,
16827
+ onContextChannelResolved: (resolvedSourceChannelId) => persistAssistantContextChannelId({
16828
+ sourceChannelId: resolvedSourceChannelId,
16829
+ threadId
16830
+ })
16634
16831
  });
16635
16832
  }
16636
16833
  });