@sentry/junior 0.40.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-SPNY2HJJ.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-SY4ULGUN.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-EU6E7QU2.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
  );
@@ -4528,7 +4424,7 @@ var McpToolManager = class {
4528
4424
  const errorAttributes = {
4529
4425
  ...baseAttributes,
4530
4426
  "error.type": getMcpAwareErrorType(error, "mcp_tool_error"),
4531
- "error.message": getMcpAwareErrorMessage(error)
4427
+ "exception.message": getMcpAwareErrorMessage(error)
4532
4428
  };
4533
4429
  setSpanAttributes(errorAttributes);
4534
4430
  if (error instanceof McpToolError) {
@@ -5516,7 +5412,7 @@ async function enrichImagePrompt(rawPrompt) {
5516
5412
  logWarn(
5517
5413
  "image_prompt_enrichment_failed",
5518
5414
  {},
5519
- { "error.message": String(error) },
5415
+ { "exception.message": String(error) },
5520
5416
  "Image prompt enrichment failed, using raw prompt"
5521
5417
  );
5522
5418
  return rawPrompt;
@@ -6116,6 +6012,7 @@ async function listThreadReplies(input) {
6116
6012
  (value) => typeof value === "string" && value.length > 0
6117
6013
  )
6118
6014
  );
6015
+ const hasTargetMessages = pendingTargets.size > 0;
6119
6016
  const replies = [];
6120
6017
  let cursor;
6121
6018
  let pages = 0;
@@ -6140,7 +6037,7 @@ async function listThreadReplies(input) {
6140
6037
  }
6141
6038
  }
6142
6039
  cursor = response.response_metadata?.next_cursor || void 0;
6143
- if (!cursor || pendingTargets.size === 0) {
6040
+ if (!cursor || hasTargetMessages && pendingTargets.size === 0) {
6144
6041
  break;
6145
6042
  }
6146
6043
  }
@@ -7156,13 +7053,236 @@ function createSlackListUpdateItemTool(state) {
7156
7053
  });
7157
7054
  }
7158
7055
 
7159
- // src/chat/tools/system-time.ts
7056
+ // src/chat/tools/slack/thread-read.ts
7160
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";
7161
7281
  function createSystemTimeTool() {
7162
7282
  return tool({
7163
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.",
7164
7284
  annotations: { readOnlyHint: true, destructiveHint: false },
7165
- inputSchema: Type18.Object({}),
7285
+ inputSchema: Type19.Object({}),
7166
7286
  execute: async () => {
7167
7287
  const now = /* @__PURE__ */ new Date();
7168
7288
  return {
@@ -7180,7 +7300,7 @@ function createSystemTimeTool() {
7180
7300
  import {
7181
7301
  Agent
7182
7302
  } from "@mariozechner/pi-agent-core";
7183
- import { Type as Type19 } from "@sinclair/typebox";
7303
+ import { Type as Type20 } from "@sinclair/typebox";
7184
7304
 
7185
7305
  // src/chat/respond-helpers.ts
7186
7306
  var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
@@ -7468,12 +7588,12 @@ function createAdvisorTool(context) {
7468
7588
  const spanContext = context.logContext ?? {};
7469
7589
  return tool({
7470
7590
  description: ADVISOR_TOOL_DESCRIPTION,
7471
- inputSchema: Type19.Object({
7472
- question: Type19.String({
7591
+ inputSchema: Type20.Object({
7592
+ question: Type20.String({
7473
7593
  minLength: 1,
7474
7594
  description: "Focused advisor question or decision point."
7475
7595
  }),
7476
- context: Type19.String({
7596
+ context: Type20.String({
7477
7597
  minLength: 1,
7478
7598
  description: "Curated evidence packet: relevant requirements, constraints, current plan, alternatives, code snippets, diffs, command output, and open questions."
7479
7599
  })
@@ -7585,7 +7705,7 @@ function createAdvisorTool(context) {
7585
7705
  }
7586
7706
 
7587
7707
  // src/chat/tools/web/fetch-tool.ts
7588
- import { Type as Type20 } from "@sinclair/typebox";
7708
+ import { Type as Type21 } from "@sinclair/typebox";
7589
7709
 
7590
7710
  // src/chat/tools/web/constants.ts
7591
7711
  var USER_AGENT = "junior-bot/0.1";
@@ -7938,13 +8058,13 @@ function createWebFetchTool(hooks) {
7938
8058
  destructiveHint: false,
7939
8059
  openWorldHint: true
7940
8060
  },
7941
- inputSchema: Type20.Object({
7942
- url: Type20.String({
8061
+ inputSchema: Type21.Object({
8062
+ url: Type21.String({
7943
8063
  minLength: 1,
7944
8064
  description: "HTTP(S) URL to fetch."
7945
8065
  }),
7946
- max_chars: Type20.Optional(
7947
- Type20.Integer({
8066
+ max_chars: Type21.Optional(
8067
+ Type21.Integer({
7948
8068
  minimum: 500,
7949
8069
  maximum: MAX_FETCH_CHARS,
7950
8070
  description: "Optional maximum number of extracted characters to return."
@@ -8004,7 +8124,7 @@ function createWebFetchTool(hooks) {
8004
8124
  // src/chat/tools/web/search.ts
8005
8125
  import { generateText } from "ai";
8006
8126
  import { createGatewayProvider } from "@ai-sdk/gateway";
8007
- import { Type as Type21 } from "@sinclair/typebox";
8127
+ import { Type as Type22 } from "@sinclair/typebox";
8008
8128
  var SEARCH_TIMEOUT_MS = 6e4;
8009
8129
  var MAX_RESULTS2 = 5;
8010
8130
  var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
@@ -8052,14 +8172,14 @@ function createWebSearchTool() {
8052
8172
  destructiveHint: false,
8053
8173
  openWorldHint: true
8054
8174
  },
8055
- inputSchema: Type21.Object({
8056
- query: Type21.String({
8175
+ inputSchema: Type22.Object({
8176
+ query: Type22.String({
8057
8177
  minLength: 1,
8058
8178
  maxLength: 500,
8059
8179
  description: "Search query."
8060
8180
  }),
8061
- max_results: Type21.Optional(
8062
- Type21.Integer({
8181
+ max_results: Type22.Optional(
8182
+ Type22.Integer({
8063
8183
  minimum: 1,
8064
8184
  maximum: MAX_RESULTS2,
8065
8185
  description: "Max results to return."
@@ -8128,20 +8248,20 @@ function createWebSearchTool() {
8128
8248
  }
8129
8249
 
8130
8250
  // src/chat/tools/sandbox/write-file.ts
8131
- import { Type as Type22 } from "@sinclair/typebox";
8251
+ import { Type as Type23 } from "@sinclair/typebox";
8132
8252
  function createWriteFileTool() {
8133
8253
  return tool({
8134
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.",
8135
8255
  promptSnippet: "new file or deliberate full-file replacement",
8136
8256
  promptGuidelines: ["targeted existing-file changes: editFile"],
8137
8257
  executionMode: "sequential",
8138
- inputSchema: Type22.Object(
8258
+ inputSchema: Type23.Object(
8139
8259
  {
8140
- path: Type22.String({
8260
+ path: Type23.String({
8141
8261
  minLength: 1,
8142
8262
  description: "Path to write in the sandbox workspace."
8143
8263
  }),
8144
- content: Type22.String({
8264
+ content: Type23.String({
8145
8265
  description: "UTF-8 file content to write."
8146
8266
  })
8147
8267
  },
@@ -8214,6 +8334,7 @@ function createTools(availableSkills, hooks = {}, context) {
8214
8334
  ),
8215
8335
  slackCanvasRead: createSlackCanvasReadTool(),
8216
8336
  slackCanvasUpdate: createSlackCanvasUpdateTool(state, context),
8337
+ slackThreadRead: createSlackThreadReadTool(context),
8217
8338
  slackListCreate: createSlackListCreateTool(state),
8218
8339
  slackListAddItems: createSlackListAddItemsTool(state),
8219
8340
  slackListGetItems: createSlackListGetItemsTool(state),
@@ -8283,7 +8404,7 @@ function extractHttpErrorDetails(error, options = {}) {
8283
8404
  const err = error ?? {};
8284
8405
  const attributes = {
8285
8406
  "error.type": normalizedError.name || "Error",
8286
- "error.message": toTrimmedString(normalizedError.message, previewLimit) ?? "HTTP error"
8407
+ "exception.message": toTrimmedString(normalizedError.message, previewLimit) ?? "HTTP error"
8287
8408
  };
8288
8409
  const response = err.response;
8289
8410
  const statusCode = typeof response?.status === "number" ? response.status : void 0;
@@ -9150,7 +9271,7 @@ function createSandboxSessionManager(options) {
9150
9271
  "sandbox_network_policy_restore_failed",
9151
9272
  traceContext,
9152
9273
  {
9153
- "error.message": reason instanceof Error ? reason.message : String(reason)
9274
+ "exception.message": reason instanceof Error ? reason.message : String(reason)
9154
9275
  },
9155
9276
  "Sandbox network policy restore failed; discarding sandbox instance"
9156
9277
  );
@@ -10144,7 +10265,7 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
10144
10265
  "gen_ai.tool.name": toolName,
10145
10266
  ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
10146
10267
  "error.type": errorType,
10147
- "error.message": errorMessage
10268
+ "exception.message": errorMessage
10148
10269
  },
10149
10270
  "Agent tool call failed"
10150
10271
  );
@@ -11390,8 +11511,6 @@ async function generateAssistantReply(messageText, context = {}) {
11390
11511
  const shouldTrace = shouldEmitDevAgentTrace();
11391
11512
  const spanContext = {
11392
11513
  conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
11393
- turnId: context.correlation?.turnId,
11394
- agentId: context.correlation?.turnId,
11395
11514
  slackThreadId: context.correlation?.threadId,
11396
11515
  slackUserId: context.correlation?.requesterId,
11397
11516
  slackChannelId: context.correlation?.channelId,
@@ -11414,7 +11533,7 @@ async function generateAssistantReply(messageText, context = {}) {
11414
11533
  {
11415
11534
  "app.skill.count": availableSkills.length,
11416
11535
  "app.skill.names": availableSkills.map((skill) => skill.name).sort(),
11417
- "file.directories": roots,
11536
+ "app.file.directories": roots,
11418
11537
  "app.plugin.count": plugins.length,
11419
11538
  "app.plugin.names": plugins.map((plugin) => plugin.manifest.name).sort()
11420
11539
  },
@@ -11654,8 +11773,6 @@ async function generateAssistantReply(messageText, context = {}) {
11654
11773
  };
11655
11774
  setTags({
11656
11775
  conversationId: spanContext.conversationId,
11657
- turnId: spanContext.turnId,
11658
- agentId: spanContext.agentId,
11659
11776
  slackThreadId: context.correlation?.threadId,
11660
11777
  slackUserId: context.correlation?.requesterId,
11661
11778
  slackChannelId: context.correlation?.channelId,
@@ -11800,7 +11917,7 @@ async function generateAssistantReply(messageText, context = {}) {
11800
11917
  spanContext,
11801
11918
  {
11802
11919
  "gen_ai.tool.name": toolName,
11803
- "error.message": error instanceof Error ? error.message : String(error)
11920
+ "exception.message": error instanceof Error ? error.message : String(error)
11804
11921
  },
11805
11922
  "Tool invocation observer failed"
11806
11923
  );
@@ -11844,7 +11961,7 @@ async function generateAssistantReply(messageText, context = {}) {
11844
11961
  "streaming_message_start_error",
11845
11962
  {},
11846
11963
  {
11847
- "error.message": error instanceof Error ? error.message : String(error)
11964
+ "exception.message": error instanceof Error ? error.message : String(error)
11848
11965
  },
11849
11966
  "Failed to deliver assistant message start to stream coordinator"
11850
11967
  );
@@ -11866,7 +11983,7 @@ async function generateAssistantReply(messageText, context = {}) {
11866
11983
  "streaming_text_delta_error",
11867
11984
  {},
11868
11985
  {
11869
- "error.message": error instanceof Error ? error.message : String(error)
11986
+ "exception.message": error instanceof Error ? error.message : String(error)
11870
11987
  },
11871
11988
  "Failed to deliver text delta to stream"
11872
11989
  );
@@ -11952,8 +12069,7 @@ async function generateAssistantReply(messageText, context = {}) {
11952
12069
  ) ? usageSummary : void 0;
11953
12070
  setSpanAttributes({
11954
12071
  ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
11955
- ...usageSummary.inputTokens !== void 0 ? { "gen_ai.usage.input_tokens": usageSummary.inputTokens } : {},
11956
- ...usageSummary.outputTokens !== void 0 ? { "gen_ai.usage.output_tokens": usageSummary.outputTokens } : {}
12072
+ ...extractGenAiUsageAttributes(usageSummary)
11957
12073
  });
11958
12074
  if (getPendingAuthPause()) {
11959
12075
  timeoutResumeMessages = [...agent.state.messages];
@@ -12118,7 +12234,7 @@ async function generateAssistantReply(messageText, context = {}) {
12118
12234
  "mcp_tool_manager_close_failed",
12119
12235
  {},
12120
12236
  {
12121
- "error.message": closeError instanceof Error ? closeError.message : String(closeError)
12237
+ "exception.message": closeError instanceof Error ? closeError.message : String(closeError)
12122
12238
  },
12123
12239
  "Failed to close MCP tool manager"
12124
12240
  );
@@ -12183,7 +12299,7 @@ function getAgentTurnDiagnosticsAttributes(reply) {
12183
12299
  ...reply.diagnostics.stopReason ? {
12184
12300
  "gen_ai.response.finish_reasons": [reply.diagnostics.stopReason]
12185
12301
  } : {},
12186
- ...reply.diagnostics.errorMessage ? { "error.message": reply.diagnostics.errorMessage } : {}
12302
+ ...reply.diagnostics.errorMessage ? { "exception.message": reply.diagnostics.errorMessage } : {}
12187
12303
  };
12188
12304
  }
12189
12305
  function finalizeFailedTurnReply(args) {
@@ -12539,7 +12655,7 @@ function logAssistantStatusFailure(args) {
12539
12655
  "app.slack.channel_id_raw": args.channelId,
12540
12656
  "app.slack.channel_id": args.normalizedChannelId,
12541
12657
  "app.slack.thread_ts": args.threadTs,
12542
- "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)
12543
12659
  },
12544
12660
  `Failed to update assistant status channel=${args.normalizedChannelId} raw=${args.channelId} thread=${args.threadTs}`
12545
12661
  );
@@ -12577,24 +12693,18 @@ function createSlackWebApiAssistantStatusSession(args) {
12577
12693
 
12578
12694
  // src/chat/slack/footer.ts
12579
12695
  var SENTRY_CONVERSATION_SEARCH_STATS_PERIOD = "14d";
12580
- var ORG_ID_HOST_RE = /^o(\d+)\./;
12581
12696
  function escapeSlackMrkdwn(text) {
12582
12697
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
12583
12698
  }
12584
12699
  function escapeSlackLinkUrl(url) {
12585
12700
  return url.replaceAll("&", "&amp;").replaceAll("<", "%3C").replaceAll(">", "%3E");
12586
12701
  }
12587
- function toOptionalString2(value) {
12588
- if (typeof value === "number" && Number.isFinite(value)) {
12589
- return String(value);
12590
- }
12591
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
12592
- }
12593
12702
  function quoteSentrySearchValue(value) {
12594
12703
  return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
12595
12704
  }
12596
- function getDsnOrgId(host) {
12597
- 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;
12598
12708
  }
12599
12709
  function isSentrySaasDsnHost(host) {
12600
12710
  return host === "sentry.io" || host.endsWith(".sentry.io");
@@ -12613,8 +12723,8 @@ function getSentryConversationSearchUrl(conversationId) {
12613
12723
  if (!dsn?.host || !dsn.projectId) {
12614
12724
  return void 0;
12615
12725
  }
12616
- const orgId = toOptionalString2(client2?.getOptions().orgId) ?? getDsnOrgId(dsn.host);
12617
- if (!orgId) {
12726
+ const orgSlug = getSentryOrgSlug();
12727
+ if (!orgSlug) {
12618
12728
  return void 0;
12619
12729
  }
12620
12730
  const params = new URLSearchParams();
@@ -12624,7 +12734,11 @@ function getSentryConversationSearchUrl(conversationId) {
12624
12734
  );
12625
12735
  params.set("project", dsn.projectId);
12626
12736
  params.set("statsPeriod", SENTRY_CONVERSATION_SEARCH_STATS_PERIOD);
12627
- 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}`;
12628
12742
  }
12629
12743
  function formatSlackTokenCount(value) {
12630
12744
  if (value >= 1e6) {
@@ -12875,7 +12989,7 @@ async function postSlackApiReplyPosts(args) {
12875
12989
  return lastPostedMessageTs;
12876
12990
  }
12877
12991
 
12878
- // src/chat/slack/resume.ts
12992
+ // src/chat/runtime/slack-resume.ts
12879
12993
  function resolveReplyTimeoutMs(explicitTimeoutMs) {
12880
12994
  if (typeof explicitTimeoutMs === "number" && explicitTimeoutMs > 0) {
12881
12995
  return explicitTimeoutMs;
@@ -12975,20 +13089,12 @@ async function handleResumeFailure(args) {
12975
13089
  );
12976
13090
  await args.resumeArgs.onFailure?.(args.error);
12977
13091
  const eventId = requireTurnFailureEventId(capturedEventId, args.eventName);
12978
- let postError;
12979
- try {
12980
- await postResumeFailureReply({
12981
- channelId: args.resumeArgs.channelId,
12982
- threadTs: args.resumeArgs.threadTs,
12983
- eventId,
12984
- logContext
12985
- });
12986
- } catch (error) {
12987
- postError = error;
12988
- }
12989
- if (postError) {
12990
- throw postError;
12991
- }
13092
+ await postResumeFailureReply({
13093
+ channelId: args.resumeArgs.channelId,
13094
+ threadTs: args.resumeArgs.threadTs,
13095
+ eventId,
13096
+ logContext
13097
+ });
12992
13098
  }
12993
13099
  function createResumeReplyContext(args, statusSession) {
12994
13100
  const replyContext = args.replyContext ?? {};
@@ -13022,8 +13128,7 @@ function createResumeReplyContext(args, statusSession) {
13022
13128
  };
13023
13129
  }
13024
13130
  async function resumeSlackTurn(args) {
13025
- const requesterUserId = args.replyContext?.requester?.userId;
13026
- if (!requesterUserId) {
13131
+ if (!args.replyContext?.requester?.userId) {
13027
13132
  throw new Error("Resumed turn requires replyContext.requester.userId");
13028
13133
  }
13029
13134
  const stateAdapter = getStateAdapter();
@@ -13053,9 +13158,7 @@ async function resumeSlackTurn(args) {
13053
13158
  status.start();
13054
13159
  const generateReply = args.generateReply ?? generateAssistantReply;
13055
13160
  const replyContext = createResumeReplyContext(args, status);
13056
- const replyPromise = generateReply(args.messageText, {
13057
- ...replyContext
13058
- });
13161
+ const replyPromise = generateReply(args.messageText, replyContext);
13059
13162
  const replyTimeoutMs = resolveReplyTimeoutMs(args.replyTimeoutMs);
13060
13163
  let reply = typeof replyTimeoutMs === "number" ? await Promise.race([
13061
13164
  replyPromise,
@@ -13092,13 +13195,15 @@ async function resumeSlackTurn(args) {
13092
13195
  await args.onSuccess?.(reply);
13093
13196
  } catch (error) {
13094
13197
  await status.stop();
13095
- 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) {
13096
13201
  deferredPauseHandler = async () => {
13097
- await args.onAuthPause?.(error);
13202
+ await onAuthPause(error);
13098
13203
  };
13099
- } else if (isRetryableTurnError(error, "turn_timeout_resume") && args.onTimeoutPause) {
13204
+ } else if (isRetryableTurnError(error, "turn_timeout_resume") && onTimeoutPause) {
13100
13205
  deferredPauseHandler = async () => {
13101
- await args.onTimeoutPause?.(error);
13206
+ await onTimeoutPause(error);
13102
13207
  };
13103
13208
  } else {
13104
13209
  deferredFailureHandler = async () => {
@@ -13524,7 +13629,7 @@ async function resumeAuthorizedMcpTurn(args) {
13524
13629
  {},
13525
13630
  {
13526
13631
  "app.credential.provider": provider,
13527
- ...isRetryableTurnError(error) ? { "app.turn.retryable_reason": error.reason } : {}
13632
+ ...isRetryableTurnError(error) ? { "app.ai.retryable_reason": error.reason } : {}
13528
13633
  },
13529
13634
  "Resumed MCP turn requested another authorization flow"
13530
13635
  );
@@ -14790,73 +14895,7 @@ async function decideSubscribedThreadReply(args) {
14790
14895
  }
14791
14896
  }
14792
14897
 
14793
- // src/chat/runtime/thread-context.ts
14794
- function escapeRegExp3(value) {
14795
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14796
- }
14797
- function stripLeadingBotMention(text, options = {}) {
14798
- if (!text.trim()) return text;
14799
- let next = text;
14800
- if (options.stripLeadingSlackMentionToken) {
14801
- next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
14802
- }
14803
- const mentionByNameRe = new RegExp(
14804
- `^\\s*@${escapeRegExp3(botConfig.userName)}\\b[\\s,:-]*`,
14805
- "i"
14806
- );
14807
- next = next.replace(mentionByNameRe, "").trim();
14808
- const mentionByLabeledEntityRe = new RegExp(
14809
- `^\\s*<@[^>|]+\\|${escapeRegExp3(botConfig.userName)}>[\\s,:-]*`,
14810
- "i"
14811
- );
14812
- next = next.replace(mentionByLabeledEntityRe, "").trim();
14813
- return next;
14814
- }
14815
- function getThreadId(thread, _message) {
14816
- return toOptionalString(thread.id);
14817
- }
14818
- function getRunId(thread, message) {
14819
- return toOptionalString(thread.runId) ?? toOptionalString(message.runId);
14820
- }
14821
- function getChannelId(thread, message) {
14822
- return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
14823
- }
14824
- function getThreadTs(threadId) {
14825
- return parseSlackThreadId(threadId)?.threadTs;
14826
- }
14827
- function getAssistantThreadContext(message) {
14828
- const raw = message.raw;
14829
- const rawRecord = raw && typeof raw === "object" ? raw : void 0;
14830
- const channelId = toOptionalString(rawRecord?.channel);
14831
- if (channelId) {
14832
- const rawThreadTs = toOptionalString(rawRecord?.thread_ts);
14833
- const threadTs = isDmChannel(channelId) ? rawThreadTs : rawThreadTs ?? toOptionalString(rawRecord?.ts);
14834
- if (threadTs) {
14835
- return { channelId, threadTs };
14836
- }
14837
- }
14838
- const parsedThreadId = parseSlackThreadId(
14839
- toOptionalString(message.threadId)
14840
- );
14841
- if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) {
14842
- return void 0;
14843
- }
14844
- return parsedThreadId;
14845
- }
14846
- function getMessageTs(message) {
14847
- const directTs = toOptionalString(
14848
- message.ts
14849
- );
14850
- if (directTs) {
14851
- return directTs;
14852
- }
14853
- const raw = message.raw;
14854
- if (!raw || typeof raw !== "object") {
14855
- return void 0;
14856
- }
14857
- const rawRecord = raw;
14858
- return toOptionalString(rawRecord.ts) ?? toOptionalString(rawRecord.event_ts) ?? toOptionalString(rawRecord.message?.ts);
14859
- }
14898
+ // src/chat/slack/errors.ts
14860
14899
  function getSlackApiErrorCode(error) {
14861
14900
  if (!error || typeof error !== "object") {
14862
14901
  return void 0;
@@ -15005,7 +15044,7 @@ function createSlackTurnRuntime(deps) {
15005
15044
  error,
15006
15045
  "mention_handler_auth_pause",
15007
15046
  errorContext,
15008
- { "app.turn.retryable_reason": error.reason },
15047
+ { "app.ai.retryable_reason": error.reason },
15009
15048
  "onNewMention parked turn for auth resume"
15010
15049
  );
15011
15050
  return;
@@ -15140,7 +15179,7 @@ function createSlackTurnRuntime(deps) {
15140
15179
  error,
15141
15180
  "subscribed_message_handler_auth_pause",
15142
15181
  errorContext,
15143
- { "app.turn.retryable_reason": error.reason },
15182
+ { "app.ai.retryable_reason": error.reason },
15144
15183
  "onSubscribedMessage parked turn for auth resume"
15145
15184
  );
15146
15185
  return;
@@ -15287,7 +15326,7 @@ async function lookupSlackUser(userId) {
15287
15326
  {},
15288
15327
  {
15289
15328
  "enduser.id": userId,
15290
- "error.message": error instanceof Error ? error.message : String(error)
15329
+ "exception.message": error instanceof Error ? error.message : String(error)
15291
15330
  },
15292
15331
  "Slack user lookup failed with exception"
15293
15332
  );
@@ -15315,7 +15354,7 @@ function createSubscribedReplyPolicy(deps) {
15315
15354
  modelId: botConfig.fastModelId
15316
15355
  },
15317
15356
  {
15318
- "error.message": error instanceof Error ? error.message : String(error)
15357
+ "exception.message": error instanceof Error ? error.message : String(error)
15319
15358
  },
15320
15359
  "Subscribed-message classifier failed; skipping reply"
15321
15360
  );
@@ -15527,7 +15566,7 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
15527
15566
  },
15528
15567
  {
15529
15568
  "file.size": data.byteLength,
15530
- "file.mime_type": mediaType
15569
+ "app.file.mime_type": mediaType
15531
15570
  },
15532
15571
  "Skipping user attachment that exceeds size limit"
15533
15572
  );
@@ -15551,8 +15590,8 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
15551
15590
  modelId: botConfig.visionModelId ?? botConfig.modelId
15552
15591
  },
15553
15592
  {
15554
- "error.message": error instanceof Error ? error.message : String(error),
15555
- "file.mime_type": mediaType,
15593
+ "exception.message": error instanceof Error ? error.message : String(error),
15594
+ "app.file.mime_type": mediaType,
15556
15595
  ...attachment.name ? { "file.name": attachment.name } : {}
15557
15596
  },
15558
15597
  "Image attachment processing failed"
@@ -15570,8 +15609,8 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
15570
15609
  modelId: botConfig.modelId
15571
15610
  },
15572
15611
  {
15573
- "error.message": error instanceof Error ? error.message : String(error),
15574
- "file.mime_type": mediaType
15612
+ "exception.message": error instanceof Error ? error.message : String(error),
15613
+ "app.file.mime_type": mediaType
15575
15614
  },
15576
15615
  "Failed to resolve user attachment"
15577
15616
  );
@@ -15620,9 +15659,9 @@ async function summarizeConversationImage(args, deps) {
15620
15659
  modelId: visionModelId
15621
15660
  },
15622
15661
  {
15623
- "error.message": error instanceof Error ? error.message : String(error),
15624
- "file.id": args.fileId,
15625
- "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
15626
15665
  },
15627
15666
  "Image analysis failed while hydrating conversation context"
15628
15667
  );
@@ -15668,7 +15707,7 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15668
15707
  modelId: botConfig.modelId
15669
15708
  },
15670
15709
  {
15671
- "error.message": error instanceof Error ? error.message : String(error)
15710
+ "exception.message": error instanceof Error ? error.message : String(error)
15672
15711
  },
15673
15712
  "Failed to fetch thread replies for image context hydration"
15674
15713
  );
@@ -15735,9 +15774,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15735
15774
  modelId: botConfig.modelId
15736
15775
  },
15737
15776
  {
15738
- "file.id": fileId,
15777
+ "app.file.id": fileId,
15739
15778
  "file.size": fileSize,
15740
- "file.mime_type": mimeType
15779
+ "app.file.mime_type": mimeType
15741
15780
  },
15742
15781
  "Skipping thread image that exceeds size limit"
15743
15782
  );
@@ -15749,7 +15788,7 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15749
15788
  }
15750
15789
  let imageData;
15751
15790
  try {
15752
- imageData = await deps.downloadPrivateSlackFile(downloadUrl);
15791
+ imageData = await deps.downloadFile(downloadUrl);
15753
15792
  } catch (error) {
15754
15793
  logWarn(
15755
15794
  "conversation_image_download_failed",
@@ -15762,9 +15801,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15762
15801
  modelId: botConfig.modelId
15763
15802
  },
15764
15803
  {
15765
- "error.message": error instanceof Error ? error.message : String(error),
15766
- "file.id": fileId,
15767
- "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
15768
15807
  },
15769
15808
  "Failed to download thread image for context hydration"
15770
15809
  );
@@ -15782,9 +15821,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15782
15821
  modelId: botConfig.modelId
15783
15822
  },
15784
15823
  {
15785
- "file.id": fileId,
15824
+ "app.file.id": fileId,
15786
15825
  "file.size": imageData.byteLength,
15787
- "file.mime_type": mimeType
15826
+ "app.file.mime_type": mimeType
15788
15827
  },
15789
15828
  "Skipping downloaded thread image that exceeds size limit"
15790
15829
  );
@@ -15847,12 +15886,6 @@ function createVisionContextService(deps) {
15847
15886
  )
15848
15887
  };
15849
15888
  }
15850
- var defaultVisionContextService = createVisionContextService({
15851
- completeText,
15852
- downloadPrivateSlackFile,
15853
- listThreadReplies
15854
- });
15855
- var hydrateConversationVisionContext = defaultVisionContextService.hydrateConversationVisionContext;
15856
15889
 
15857
15890
  // src/chat/app/services.ts
15858
15891
  function createJuniorRuntimeServices(overrides = {}) {
@@ -15862,7 +15895,7 @@ function createJuniorRuntimeServices(overrides = {}) {
15862
15895
  const visionContext = createVisionContextService({
15863
15896
  completeText: overrides.visionContext?.completeText ?? completeText,
15864
15897
  listThreadReplies: overrides.visionContext?.listThreadReplies ?? listThreadReplies,
15865
- downloadPrivateSlackFile: overrides.visionContext?.downloadPrivateSlackFile ?? downloadPrivateSlackFile
15898
+ downloadFile: overrides.visionContext?.downloadFile ?? downloadPrivateSlackFile
15866
15899
  });
15867
15900
  return {
15868
15901
  conversationMemory,
@@ -15879,6 +15912,85 @@ function createJuniorRuntimeServices(overrides = {}) {
15879
15912
  };
15880
15913
  }
15881
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
+
15882
15994
  // src/chat/slack/assistant-thread/title.ts
15883
15995
  function maybeUpdateAssistantTitle(args) {
15884
15996
  const assistantThreadContext = args.assistantThreadContext;
@@ -15937,7 +16049,7 @@ function maybeUpdateAssistantTitle(args) {
15937
16049
  modelId: args.modelId
15938
16050
  },
15939
16051
  {
15940
- "error.message": error instanceof Error ? error.message : String(error)
16052
+ "exception.message": error instanceof Error ? error.message : String(error)
15941
16053
  },
15942
16054
  "Thread title generation failed"
15943
16055
  );
@@ -15996,11 +16108,8 @@ function createReplyToThread(deps) {
15996
16108
  nextTurnId: turnId,
15997
16109
  updateConversationStats
15998
16110
  });
15999
- const turnStartedAtMs = Date.now();
16000
16111
  const turnTraceContext = {
16001
16112
  conversationId,
16002
- turnId,
16003
- agentId: turnId,
16004
16113
  slackThreadId: threadId,
16005
16114
  slackUserId: message.author.userId,
16006
16115
  slackChannelId: channelId,
@@ -16009,9 +16118,7 @@ function createReplyToThread(deps) {
16009
16118
  modelId: botConfig.modelId
16010
16119
  };
16011
16120
  setTags({
16012
- conversationId,
16013
- turnId,
16014
- agentId: turnId
16121
+ conversationId
16015
16122
  });
16016
16123
  if (shouldEmitDevAgentTrace()) {
16017
16124
  logInfo(
@@ -16279,7 +16386,6 @@ function createReplyToThread(deps) {
16279
16386
  "agent_turn_completed",
16280
16387
  turnTraceContext,
16281
16388
  {
16282
- "app.turn.duration_ms": Date.now() - turnStartedAtMs,
16283
16389
  "app.ai.outcome": reply.diagnostics.outcome,
16284
16390
  "app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
16285
16391
  "app.ai.tool_error_results": reply.diagnostics.toolErrorCount
@@ -16365,9 +16471,7 @@ function createReplyToThread(deps) {
16365
16471
  logWarn(
16366
16472
  "agent_turn_failed",
16367
16473
  turnTraceContext,
16368
- {
16369
- "app.turn.duration_ms": Date.now() - turnStartedAtMs
16370
- },
16474
+ {},
16371
16475
  "Agent turn failed"
16372
16476
  );
16373
16477
  }
@@ -16380,7 +16484,6 @@ function createReplyToThread(deps) {
16380
16484
  }
16381
16485
 
16382
16486
  // src/chat/slack/assistant-thread/lifecycle.ts
16383
- import { ThreadImpl } from "chat";
16384
16487
  async function syncAssistantThreadContext(event, options) {
16385
16488
  const channelId = normalizeSlackConversationId(event.channelId);
16386
16489
  if (!channelId) {
@@ -16405,20 +16508,7 @@ async function syncAssistantThreadContext(event, options) {
16405
16508
  if (!sourceChannelId) {
16406
16509
  return;
16407
16510
  }
16408
- const thread = ThreadImpl.fromJSON({
16409
- _type: "chat:Thread",
16410
- adapterName: "slack",
16411
- channelId,
16412
- id: event.threadId,
16413
- isDM: channelId.startsWith("D")
16414
- });
16415
- const currentArtifacts = coerceThreadArtifactsState(await thread.state);
16416
- const nextArtifacts = mergeArtifactsState(currentArtifacts, {
16417
- assistantContextChannelId: sourceChannelId
16418
- });
16419
- await persistThreadState(thread, {
16420
- artifacts: nextArtifacts
16421
- });
16511
+ await event.onContextChannelResolved(sourceChannelId);
16422
16512
  }
16423
16513
  async function initializeAssistantThread(event) {
16424
16514
  await syncAssistantThreadContext(event, { setInitialTitle: true });
@@ -16428,11 +16518,96 @@ async function refreshAssistantThreadContext(event) {
16428
16518
  }
16429
16519
 
16430
16520
  // src/chat/runtime/turn-preparation.ts
16521
+ var BACKFILL_MESSAGE_LIMIT = 80;
16431
16522
  function hasPendingImageHydration(conversation) {
16432
16523
  return conversation.messages.some(
16433
16524
  (message) => isHumanConversationMessage(message) && !message.meta?.imagesHydrated
16434
16525
  );
16435
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
+ }
16436
16611
  function createPrepareTurnState(deps) {
16437
16612
  return async function prepareTurnState(args) {
16438
16613
  const existingState = await args.thread.state;
@@ -16518,6 +16693,17 @@ function createPrepareTurnState(deps) {
16518
16693
  }
16519
16694
 
16520
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
+ }
16521
16707
  function createSlackRuntime(options) {
16522
16708
  const services = createJuniorRuntimeServices(options.services);
16523
16709
  const prepareTurnState = createPrepareTurnState({
@@ -16617,11 +16803,14 @@ function createSlackRuntime(options) {
16617
16803
  sourceChannelId
16618
16804
  }) => {
16619
16805
  await initializeAssistantThread({
16620
- threadId,
16621
16806
  channelId,
16622
16807
  threadTs,
16623
16808
  sourceChannelId,
16624
- getSlackAdapter: options.getSlackAdapter
16809
+ getSlackAdapter: options.getSlackAdapter,
16810
+ onContextChannelResolved: (resolvedSourceChannelId) => persistAssistantContextChannelId({
16811
+ sourceChannelId: resolvedSourceChannelId,
16812
+ threadId
16813
+ })
16625
16814
  });
16626
16815
  },
16627
16816
  refreshAssistantThreadContext: async ({
@@ -16631,11 +16820,14 @@ function createSlackRuntime(options) {
16631
16820
  sourceChannelId
16632
16821
  }) => {
16633
16822
  await refreshAssistantThreadContext({
16634
- threadId,
16635
16823
  channelId,
16636
16824
  threadTs,
16637
16825
  sourceChannelId,
16638
- getSlackAdapter: options.getSlackAdapter
16826
+ getSlackAdapter: options.getSlackAdapter,
16827
+ onContextChannelResolved: (resolvedSourceChannelId) => persistAssistantContextChannelId({
16828
+ sourceChannelId: resolvedSourceChannelId,
16829
+ threadId
16830
+ })
16639
16831
  });
16640
16832
  }
16641
16833
  });