@sentry/junior 0.40.0 → 0.42.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,314 @@ 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/slack/legacy-attachments.ts
7107
+ var MAX_ATTACHMENTS = 10;
7108
+ var MAX_FIELDS_PER_ATTACHMENT = 20;
7109
+ var MAX_FIELD_CHARS = 1e3;
7110
+ var MAX_ATTACHMENT_TEXT_CHARS = 4e3;
7111
+ function toStr(value) {
7112
+ return typeof value === "string" && value.length > 0 ? value : void 0;
7113
+ }
7114
+ function getAttachmentPayload(input) {
7115
+ if (Array.isArray(input)) return input;
7116
+ if (!input || typeof input !== "object") return [];
7117
+ const attachments = input.attachments;
7118
+ return Array.isArray(attachments) ? attachments : [];
7119
+ }
7120
+ function renderField(raw) {
7121
+ if (!raw || typeof raw !== "object") return void 0;
7122
+ const obj = raw;
7123
+ const title = toStr(obj.title);
7124
+ const value = toStr(obj.value)?.slice(0, MAX_FIELD_CHARS);
7125
+ if (title && value) return `${title}: ${value}`;
7126
+ return title || value;
7127
+ }
7128
+ function renderAttachment(raw) {
7129
+ if (!raw || typeof raw !== "object") return "";
7130
+ const obj = raw;
7131
+ const parts = [];
7132
+ const seen = /* @__PURE__ */ new Set();
7133
+ const fallback = toStr(obj.fallback);
7134
+ const pretext = toStr(obj.pretext);
7135
+ const authorName = toStr(obj.author_name);
7136
+ const title = toStr(obj.title);
7137
+ const titleLink = toStr(obj.title_link);
7138
+ const text = toStr(obj.text);
7139
+ const footer = toStr(obj.footer);
7140
+ const fields = Array.isArray(obj.fields) ? obj.fields : [];
7141
+ const add = (value) => {
7142
+ if (!value) return;
7143
+ const normalized = value.trim();
7144
+ if (!normalized || seen.has(normalized)) return;
7145
+ seen.add(normalized);
7146
+ parts.push(normalized);
7147
+ };
7148
+ const hasRichContent = pretext || title || text;
7149
+ if (!hasRichContent) {
7150
+ add(fallback);
7151
+ }
7152
+ add(pretext);
7153
+ add(authorName);
7154
+ if (title && titleLink) {
7155
+ add(`${title} (${titleLink})`);
7156
+ seen.add(title.trim());
7157
+ } else {
7158
+ add(title);
7159
+ }
7160
+ add(text);
7161
+ for (const field of fields.slice(0, MAX_FIELDS_PER_ATTACHMENT)) {
7162
+ add(renderField(field));
7163
+ }
7164
+ add(footer);
7165
+ return parts.join(" | ");
7166
+ }
7167
+ function renderSlackLegacyAttachmentText(input) {
7168
+ const rendered = getAttachmentPayload(input).slice(0, MAX_ATTACHMENTS).map(renderAttachment).filter((line) => line.length > 0).map((line) => `[attachment] ${line}`).join("\n");
7169
+ return rendered.slice(0, MAX_ATTACHMENT_TEXT_CHARS);
7170
+ }
7171
+ function appendSlackLegacyAttachmentText(baseText, rawMessageOrAttachments) {
7172
+ const base = baseText?.trim() ?? "";
7173
+ const attachmentText = renderSlackLegacyAttachmentText(
7174
+ rawMessageOrAttachments
7175
+ );
7176
+ if (!attachmentText) return base;
7177
+ if (!base) return attachmentText;
7178
+ return `${base}
7179
+ ${attachmentText}`;
7180
+ }
7181
+
7182
+ // src/chat/tools/slack/thread-read.ts
7183
+ var MAX_THREAD_READ_CHARS = 4e4;
7184
+ function sanitizeMessage(msg) {
7185
+ const attachmentText = renderSlackLegacyAttachmentText(msg.attachments);
7186
+ return {
7187
+ ts: msg.ts,
7188
+ user: msg.user,
7189
+ text: msg.text,
7190
+ thread_ts: msg.thread_ts,
7191
+ subtype: msg.subtype,
7192
+ bot_id: msg.bot_id,
7193
+ type: msg.type,
7194
+ ...attachmentText ? { attachment_text: attachmentText } : {},
7195
+ ...msg.files?.length ? {
7196
+ files: msg.files.map((f) => ({
7197
+ id: f.id,
7198
+ name: f.name,
7199
+ mimetype: f.mimetype,
7200
+ size: f.size
7201
+ }))
7202
+ } : {}
7203
+ };
7204
+ }
7205
+ function truncateMessages(messages, maxChars) {
7206
+ let chars = 0;
7207
+ const kept = [];
7208
+ for (const msg of messages) {
7209
+ const textLen = (msg.text?.length ?? 0) + (msg.attachment_text?.length ?? 0);
7210
+ if (kept.length > 0 && chars + textLen > maxChars) {
7211
+ break;
7212
+ }
7213
+ kept.push(msg);
7214
+ chars += textLen;
7215
+ }
7216
+ return { messages: kept, omitted: messages.length - kept.length };
7217
+ }
7218
+ function checkChannelAccess(targetChannelId, currentChannelId) {
7219
+ const target = normalizeSlackConversationId(targetChannelId);
7220
+ const current = normalizeSlackConversationId(currentChannelId);
7221
+ if (!target) {
7222
+ return { allowed: false, error: "Invalid Slack channel ID." };
7223
+ }
7224
+ if (target.startsWith("C")) {
7225
+ return { allowed: true };
7226
+ }
7227
+ if (target === current) {
7228
+ return { allowed: true };
7229
+ }
7230
+ return {
7231
+ allowed: false,
7232
+ error: "Cannot read private channels or DMs unless the link is from the current conversation."
7233
+ };
7234
+ }
7235
+ function createSlackThreadReadTool(context) {
7236
+ return tool({
7237
+ 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.",
7238
+ annotations: { readOnlyHint: true, destructiveHint: false },
7239
+ inputSchema: Type18.Object({
7240
+ url: Type18.Optional(
7241
+ Type18.String({
7242
+ minLength: 1,
7243
+ description: "Slack message archive URL, e.g. https://workspace.slack.com/archives/C123/p1700000000123456"
7244
+ })
7245
+ ),
7246
+ channel_id: Type18.Optional(
7247
+ Type18.String({
7248
+ minLength: 1,
7249
+ description: "Slack channel/conversation ID (e.g. C123). Use with `ts` as an alternative to `url`."
7250
+ })
7251
+ ),
7252
+ ts: Type18.Optional(
7253
+ Type18.String({
7254
+ minLength: 1,
7255
+ description: "Slack message timestamp (e.g. 1700000000.123456). May be the thread root or any message in the thread."
7256
+ })
7257
+ ),
7258
+ limit: Type18.Optional(
7259
+ Type18.Integer({
7260
+ minimum: 1,
7261
+ maximum: 1e3,
7262
+ description: "Maximum number of thread messages to fetch."
7263
+ })
7264
+ ),
7265
+ max_pages: Type18.Optional(
7266
+ Type18.Integer({
7267
+ minimum: 1,
7268
+ maximum: 10,
7269
+ description: "Maximum number of Slack API pages to traverse."
7270
+ })
7271
+ )
7272
+ }),
7273
+ execute: async ({ url, channel_id, ts, limit, max_pages }) => {
7274
+ let channelId;
7275
+ let messageTs;
7276
+ let threadTs;
7277
+ if (url) {
7278
+ const parsed = parseSlackMessageReference(url);
7279
+ if (!parsed.ok) {
7280
+ return { ok: false, error: parsed.error };
7281
+ }
7282
+ channelId = parsed.reference.channelId;
7283
+ messageTs = parsed.reference.messageTs;
7284
+ threadTs = parsed.reference.threadTs;
7285
+ } else if (channel_id && ts) {
7286
+ if (!SLACK_TS_PATTERN.test(ts)) {
7287
+ return { ok: false, error: "Invalid Slack message timestamp." };
7288
+ }
7289
+ channelId = channel_id;
7290
+ messageTs = ts;
7291
+ } else {
7292
+ return {
7293
+ ok: false,
7294
+ error: "Provide either a Slack message `url` or both `channel_id` and `ts`."
7295
+ };
7296
+ }
7297
+ const access = checkChannelAccess(channelId, context.channelId);
7298
+ if (!access.allowed) {
7299
+ return {
7300
+ ok: false,
7301
+ channel_id: channelId,
7302
+ target_message_ts: messageTs,
7303
+ error: access.error
7304
+ };
7305
+ }
7306
+ const lookupTs = threadTs ?? messageTs;
7307
+ let replies;
7308
+ try {
7309
+ replies = await listThreadReplies({
7310
+ channelId,
7311
+ threadTs: lookupTs,
7312
+ limit: limit ?? 1e3,
7313
+ maxPages: max_pages
7314
+ });
7315
+ } catch (error) {
7316
+ if (error instanceof SlackActionError) {
7317
+ return {
7318
+ ok: false,
7319
+ channel_id: channelId,
7320
+ target_message_ts: messageTs,
7321
+ error: "Could not read this Slack thread. The bot may not be in the channel or may lack history scopes.",
7322
+ slack_error: error.apiError
7323
+ };
7324
+ }
7325
+ throw error;
7326
+ }
7327
+ if (replies.length === 0) {
7328
+ return {
7329
+ ok: false,
7330
+ channel_id: channelId,
7331
+ target_message_ts: messageTs,
7332
+ error: "No messages found for this thread."
7333
+ };
7334
+ }
7335
+ const root = replies[0];
7336
+ const resolvedThreadTs = threadTs ?? root?.thread_ts ?? root?.ts ?? lookupTs;
7337
+ const sanitized = replies.map(sanitizeMessage);
7338
+ const { messages, omitted } = truncateMessages(
7339
+ sanitized,
7340
+ MAX_THREAD_READ_CHARS
7341
+ );
7342
+ return {
7343
+ ok: true,
7344
+ channel_id: channelId,
7345
+ target_message_ts: messageTs,
7346
+ thread_ts: resolvedThreadTs,
7347
+ count: messages.length,
7348
+ fetched_count: replies.length,
7349
+ truncated: omitted > 0,
7350
+ ...omitted > 0 ? { omitted_message_count: omitted } : {},
7351
+ messages
7352
+ };
7353
+ }
7354
+ });
7355
+ }
7356
+
7357
+ // src/chat/tools/system-time.ts
7358
+ import { Type as Type19 } from "@sinclair/typebox";
7161
7359
  function createSystemTimeTool() {
7162
7360
  return tool({
7163
7361
  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
7362
  annotations: { readOnlyHint: true, destructiveHint: false },
7165
- inputSchema: Type18.Object({}),
7363
+ inputSchema: Type19.Object({}),
7166
7364
  execute: async () => {
7167
7365
  const now = /* @__PURE__ */ new Date();
7168
7366
  return {
@@ -7180,7 +7378,7 @@ function createSystemTimeTool() {
7180
7378
  import {
7181
7379
  Agent
7182
7380
  } from "@mariozechner/pi-agent-core";
7183
- import { Type as Type19 } from "@sinclair/typebox";
7381
+ import { Type as Type20 } from "@sinclair/typebox";
7184
7382
 
7185
7383
  // src/chat/respond-helpers.ts
7186
7384
  var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
@@ -7468,12 +7666,12 @@ function createAdvisorTool(context) {
7468
7666
  const spanContext = context.logContext ?? {};
7469
7667
  return tool({
7470
7668
  description: ADVISOR_TOOL_DESCRIPTION,
7471
- inputSchema: Type19.Object({
7472
- question: Type19.String({
7669
+ inputSchema: Type20.Object({
7670
+ question: Type20.String({
7473
7671
  minLength: 1,
7474
7672
  description: "Focused advisor question or decision point."
7475
7673
  }),
7476
- context: Type19.String({
7674
+ context: Type20.String({
7477
7675
  minLength: 1,
7478
7676
  description: "Curated evidence packet: relevant requirements, constraints, current plan, alternatives, code snippets, diffs, command output, and open questions."
7479
7677
  })
@@ -7585,7 +7783,7 @@ function createAdvisorTool(context) {
7585
7783
  }
7586
7784
 
7587
7785
  // src/chat/tools/web/fetch-tool.ts
7588
- import { Type as Type20 } from "@sinclair/typebox";
7786
+ import { Type as Type21 } from "@sinclair/typebox";
7589
7787
 
7590
7788
  // src/chat/tools/web/constants.ts
7591
7789
  var USER_AGENT = "junior-bot/0.1";
@@ -7938,13 +8136,13 @@ function createWebFetchTool(hooks) {
7938
8136
  destructiveHint: false,
7939
8137
  openWorldHint: true
7940
8138
  },
7941
- inputSchema: Type20.Object({
7942
- url: Type20.String({
8139
+ inputSchema: Type21.Object({
8140
+ url: Type21.String({
7943
8141
  minLength: 1,
7944
8142
  description: "HTTP(S) URL to fetch."
7945
8143
  }),
7946
- max_chars: Type20.Optional(
7947
- Type20.Integer({
8144
+ max_chars: Type21.Optional(
8145
+ Type21.Integer({
7948
8146
  minimum: 500,
7949
8147
  maximum: MAX_FETCH_CHARS,
7950
8148
  description: "Optional maximum number of extracted characters to return."
@@ -8004,7 +8202,7 @@ function createWebFetchTool(hooks) {
8004
8202
  // src/chat/tools/web/search.ts
8005
8203
  import { generateText } from "ai";
8006
8204
  import { createGatewayProvider } from "@ai-sdk/gateway";
8007
- import { Type as Type21 } from "@sinclair/typebox";
8205
+ import { Type as Type22 } from "@sinclair/typebox";
8008
8206
  var SEARCH_TIMEOUT_MS = 6e4;
8009
8207
  var MAX_RESULTS2 = 5;
8010
8208
  var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
@@ -8052,14 +8250,14 @@ function createWebSearchTool() {
8052
8250
  destructiveHint: false,
8053
8251
  openWorldHint: true
8054
8252
  },
8055
- inputSchema: Type21.Object({
8056
- query: Type21.String({
8253
+ inputSchema: Type22.Object({
8254
+ query: Type22.String({
8057
8255
  minLength: 1,
8058
8256
  maxLength: 500,
8059
8257
  description: "Search query."
8060
8258
  }),
8061
- max_results: Type21.Optional(
8062
- Type21.Integer({
8259
+ max_results: Type22.Optional(
8260
+ Type22.Integer({
8063
8261
  minimum: 1,
8064
8262
  maximum: MAX_RESULTS2,
8065
8263
  description: "Max results to return."
@@ -8128,20 +8326,20 @@ function createWebSearchTool() {
8128
8326
  }
8129
8327
 
8130
8328
  // src/chat/tools/sandbox/write-file.ts
8131
- import { Type as Type22 } from "@sinclair/typebox";
8329
+ import { Type as Type23 } from "@sinclair/typebox";
8132
8330
  function createWriteFileTool() {
8133
8331
  return tool({
8134
8332
  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
8333
  promptSnippet: "new file or deliberate full-file replacement",
8136
8334
  promptGuidelines: ["targeted existing-file changes: editFile"],
8137
8335
  executionMode: "sequential",
8138
- inputSchema: Type22.Object(
8336
+ inputSchema: Type23.Object(
8139
8337
  {
8140
- path: Type22.String({
8338
+ path: Type23.String({
8141
8339
  minLength: 1,
8142
8340
  description: "Path to write in the sandbox workspace."
8143
8341
  }),
8144
- content: Type22.String({
8342
+ content: Type23.String({
8145
8343
  description: "UTF-8 file content to write."
8146
8344
  })
8147
8345
  },
@@ -8214,6 +8412,7 @@ function createTools(availableSkills, hooks = {}, context) {
8214
8412
  ),
8215
8413
  slackCanvasRead: createSlackCanvasReadTool(),
8216
8414
  slackCanvasUpdate: createSlackCanvasUpdateTool(state, context),
8415
+ slackThreadRead: createSlackThreadReadTool(context),
8217
8416
  slackListCreate: createSlackListCreateTool(state),
8218
8417
  slackListAddItems: createSlackListAddItemsTool(state),
8219
8418
  slackListGetItems: createSlackListGetItemsTool(state),
@@ -8283,7 +8482,7 @@ function extractHttpErrorDetails(error, options = {}) {
8283
8482
  const err = error ?? {};
8284
8483
  const attributes = {
8285
8484
  "error.type": normalizedError.name || "Error",
8286
- "error.message": toTrimmedString(normalizedError.message, previewLimit) ?? "HTTP error"
8485
+ "exception.message": toTrimmedString(normalizedError.message, previewLimit) ?? "HTTP error"
8287
8486
  };
8288
8487
  const response = err.response;
8289
8488
  const statusCode = typeof response?.status === "number" ? response.status : void 0;
@@ -9150,7 +9349,7 @@ function createSandboxSessionManager(options) {
9150
9349
  "sandbox_network_policy_restore_failed",
9151
9350
  traceContext,
9152
9351
  {
9153
- "error.message": reason instanceof Error ? reason.message : String(reason)
9352
+ "exception.message": reason instanceof Error ? reason.message : String(reason)
9154
9353
  },
9155
9354
  "Sandbox network policy restore failed; discarding sandbox instance"
9156
9355
  );
@@ -10144,7 +10343,7 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
10144
10343
  "gen_ai.tool.name": toolName,
10145
10344
  ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
10146
10345
  "error.type": errorType,
10147
- "error.message": errorMessage
10346
+ "exception.message": errorMessage
10148
10347
  },
10149
10348
  "Agent tool call failed"
10150
10349
  );
@@ -11390,8 +11589,6 @@ async function generateAssistantReply(messageText, context = {}) {
11390
11589
  const shouldTrace = shouldEmitDevAgentTrace();
11391
11590
  const spanContext = {
11392
11591
  conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
11393
- turnId: context.correlation?.turnId,
11394
- agentId: context.correlation?.turnId,
11395
11592
  slackThreadId: context.correlation?.threadId,
11396
11593
  slackUserId: context.correlation?.requesterId,
11397
11594
  slackChannelId: context.correlation?.channelId,
@@ -11414,7 +11611,7 @@ async function generateAssistantReply(messageText, context = {}) {
11414
11611
  {
11415
11612
  "app.skill.count": availableSkills.length,
11416
11613
  "app.skill.names": availableSkills.map((skill) => skill.name).sort(),
11417
- "file.directories": roots,
11614
+ "app.file.directories": roots,
11418
11615
  "app.plugin.count": plugins.length,
11419
11616
  "app.plugin.names": plugins.map((plugin) => plugin.manifest.name).sort()
11420
11617
  },
@@ -11654,8 +11851,6 @@ async function generateAssistantReply(messageText, context = {}) {
11654
11851
  };
11655
11852
  setTags({
11656
11853
  conversationId: spanContext.conversationId,
11657
- turnId: spanContext.turnId,
11658
- agentId: spanContext.agentId,
11659
11854
  slackThreadId: context.correlation?.threadId,
11660
11855
  slackUserId: context.correlation?.requesterId,
11661
11856
  slackChannelId: context.correlation?.channelId,
@@ -11800,7 +11995,7 @@ async function generateAssistantReply(messageText, context = {}) {
11800
11995
  spanContext,
11801
11996
  {
11802
11997
  "gen_ai.tool.name": toolName,
11803
- "error.message": error instanceof Error ? error.message : String(error)
11998
+ "exception.message": error instanceof Error ? error.message : String(error)
11804
11999
  },
11805
12000
  "Tool invocation observer failed"
11806
12001
  );
@@ -11844,7 +12039,7 @@ async function generateAssistantReply(messageText, context = {}) {
11844
12039
  "streaming_message_start_error",
11845
12040
  {},
11846
12041
  {
11847
- "error.message": error instanceof Error ? error.message : String(error)
12042
+ "exception.message": error instanceof Error ? error.message : String(error)
11848
12043
  },
11849
12044
  "Failed to deliver assistant message start to stream coordinator"
11850
12045
  );
@@ -11866,7 +12061,7 @@ async function generateAssistantReply(messageText, context = {}) {
11866
12061
  "streaming_text_delta_error",
11867
12062
  {},
11868
12063
  {
11869
- "error.message": error instanceof Error ? error.message : String(error)
12064
+ "exception.message": error instanceof Error ? error.message : String(error)
11870
12065
  },
11871
12066
  "Failed to deliver text delta to stream"
11872
12067
  );
@@ -11952,8 +12147,7 @@ async function generateAssistantReply(messageText, context = {}) {
11952
12147
  ) ? usageSummary : void 0;
11953
12148
  setSpanAttributes({
11954
12149
  ...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 } : {}
12150
+ ...extractGenAiUsageAttributes(usageSummary)
11957
12151
  });
11958
12152
  if (getPendingAuthPause()) {
11959
12153
  timeoutResumeMessages = [...agent.state.messages];
@@ -12118,7 +12312,7 @@ async function generateAssistantReply(messageText, context = {}) {
12118
12312
  "mcp_tool_manager_close_failed",
12119
12313
  {},
12120
12314
  {
12121
- "error.message": closeError instanceof Error ? closeError.message : String(closeError)
12315
+ "exception.message": closeError instanceof Error ? closeError.message : String(closeError)
12122
12316
  },
12123
12317
  "Failed to close MCP tool manager"
12124
12318
  );
@@ -12183,7 +12377,7 @@ function getAgentTurnDiagnosticsAttributes(reply) {
12183
12377
  ...reply.diagnostics.stopReason ? {
12184
12378
  "gen_ai.response.finish_reasons": [reply.diagnostics.stopReason]
12185
12379
  } : {},
12186
- ...reply.diagnostics.errorMessage ? { "error.message": reply.diagnostics.errorMessage } : {}
12380
+ ...reply.diagnostics.errorMessage ? { "exception.message": reply.diagnostics.errorMessage } : {}
12187
12381
  };
12188
12382
  }
12189
12383
  function finalizeFailedTurnReply(args) {
@@ -12539,7 +12733,7 @@ function logAssistantStatusFailure(args) {
12539
12733
  "app.slack.channel_id_raw": args.channelId,
12540
12734
  "app.slack.channel_id": args.normalizedChannelId,
12541
12735
  "app.slack.thread_ts": args.threadTs,
12542
- "error.message": args.error instanceof Error ? args.error.message : String(args.error)
12736
+ "exception.message": args.error instanceof Error ? args.error.message : String(args.error)
12543
12737
  },
12544
12738
  `Failed to update assistant status channel=${args.normalizedChannelId} raw=${args.channelId} thread=${args.threadTs}`
12545
12739
  );
@@ -12577,24 +12771,18 @@ function createSlackWebApiAssistantStatusSession(args) {
12577
12771
 
12578
12772
  // src/chat/slack/footer.ts
12579
12773
  var SENTRY_CONVERSATION_SEARCH_STATS_PERIOD = "14d";
12580
- var ORG_ID_HOST_RE = /^o(\d+)\./;
12581
12774
  function escapeSlackMrkdwn(text) {
12582
12775
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
12583
12776
  }
12584
12777
  function escapeSlackLinkUrl(url) {
12585
12778
  return url.replaceAll("&", "&amp;").replaceAll("<", "%3C").replaceAll(">", "%3E");
12586
12779
  }
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
12780
  function quoteSentrySearchValue(value) {
12594
12781
  return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
12595
12782
  }
12596
- function getDsnOrgId(host) {
12597
- return host?.match(ORG_ID_HOST_RE)?.[1];
12783
+ function getSentryOrgSlug() {
12784
+ const slug = process.env.SENTRY_ORG_SLUG?.trim();
12785
+ return slug || void 0;
12598
12786
  }
12599
12787
  function isSentrySaasDsnHost(host) {
12600
12788
  return host === "sentry.io" || host.endsWith(".sentry.io");
@@ -12613,8 +12801,8 @@ function getSentryConversationSearchUrl(conversationId) {
12613
12801
  if (!dsn?.host || !dsn.projectId) {
12614
12802
  return void 0;
12615
12803
  }
12616
- const orgId = toOptionalString2(client2?.getOptions().orgId) ?? getDsnOrgId(dsn.host);
12617
- if (!orgId) {
12804
+ const orgSlug = getSentryOrgSlug();
12805
+ if (!orgSlug) {
12618
12806
  return void 0;
12619
12807
  }
12620
12808
  const params = new URLSearchParams();
@@ -12624,7 +12812,11 @@ function getSentryConversationSearchUrl(conversationId) {
12624
12812
  );
12625
12813
  params.set("project", dsn.projectId);
12626
12814
  params.set("statsPeriod", SENTRY_CONVERSATION_SEARCH_STATS_PERIOD);
12627
- return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgId}/explore/traces/?${params.toString()}`;
12815
+ const search = `explore/traces/?${params.toString()}`;
12816
+ if (isSentrySaasDsnHost(dsn.host)) {
12817
+ return `https://${orgSlug}.sentry.io/${search}`;
12818
+ }
12819
+ return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${search}`;
12628
12820
  }
12629
12821
  function formatSlackTokenCount(value) {
12630
12822
  if (value >= 1e6) {
@@ -12875,7 +13067,7 @@ async function postSlackApiReplyPosts(args) {
12875
13067
  return lastPostedMessageTs;
12876
13068
  }
12877
13069
 
12878
- // src/chat/slack/resume.ts
13070
+ // src/chat/runtime/slack-resume.ts
12879
13071
  function resolveReplyTimeoutMs(explicitTimeoutMs) {
12880
13072
  if (typeof explicitTimeoutMs === "number" && explicitTimeoutMs > 0) {
12881
13073
  return explicitTimeoutMs;
@@ -12975,20 +13167,12 @@ async function handleResumeFailure(args) {
12975
13167
  );
12976
13168
  await args.resumeArgs.onFailure?.(args.error);
12977
13169
  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
- }
13170
+ await postResumeFailureReply({
13171
+ channelId: args.resumeArgs.channelId,
13172
+ threadTs: args.resumeArgs.threadTs,
13173
+ eventId,
13174
+ logContext
13175
+ });
12992
13176
  }
12993
13177
  function createResumeReplyContext(args, statusSession) {
12994
13178
  const replyContext = args.replyContext ?? {};
@@ -13022,8 +13206,7 @@ function createResumeReplyContext(args, statusSession) {
13022
13206
  };
13023
13207
  }
13024
13208
  async function resumeSlackTurn(args) {
13025
- const requesterUserId = args.replyContext?.requester?.userId;
13026
- if (!requesterUserId) {
13209
+ if (!args.replyContext?.requester?.userId) {
13027
13210
  throw new Error("Resumed turn requires replyContext.requester.userId");
13028
13211
  }
13029
13212
  const stateAdapter = getStateAdapter();
@@ -13053,9 +13236,7 @@ async function resumeSlackTurn(args) {
13053
13236
  status.start();
13054
13237
  const generateReply = args.generateReply ?? generateAssistantReply;
13055
13238
  const replyContext = createResumeReplyContext(args, status);
13056
- const replyPromise = generateReply(args.messageText, {
13057
- ...replyContext
13058
- });
13239
+ const replyPromise = generateReply(args.messageText, replyContext);
13059
13240
  const replyTimeoutMs = resolveReplyTimeoutMs(args.replyTimeoutMs);
13060
13241
  let reply = typeof replyTimeoutMs === "number" ? await Promise.race([
13061
13242
  replyPromise,
@@ -13092,13 +13273,15 @@ async function resumeSlackTurn(args) {
13092
13273
  await args.onSuccess?.(reply);
13093
13274
  } catch (error) {
13094
13275
  await status.stop();
13095
- if ((isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) && args.onAuthPause) {
13276
+ const onAuthPause = args.onAuthPause;
13277
+ const onTimeoutPause = args.onTimeoutPause;
13278
+ if ((isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) && onAuthPause) {
13096
13279
  deferredPauseHandler = async () => {
13097
- await args.onAuthPause?.(error);
13280
+ await onAuthPause(error);
13098
13281
  };
13099
- } else if (isRetryableTurnError(error, "turn_timeout_resume") && args.onTimeoutPause) {
13282
+ } else if (isRetryableTurnError(error, "turn_timeout_resume") && onTimeoutPause) {
13100
13283
  deferredPauseHandler = async () => {
13101
- await args.onTimeoutPause?.(error);
13284
+ await onTimeoutPause(error);
13102
13285
  };
13103
13286
  } else {
13104
13287
  deferredFailureHandler = async () => {
@@ -13524,7 +13707,7 @@ async function resumeAuthorizedMcpTurn(args) {
13524
13707
  {},
13525
13708
  {
13526
13709
  "app.credential.provider": provider,
13527
- ...isRetryableTurnError(error) ? { "app.turn.retryable_reason": error.reason } : {}
13710
+ ...isRetryableTurnError(error) ? { "app.ai.retryable_reason": error.reason } : {}
13528
13711
  },
13529
13712
  "Resumed MCP turn requested another authorization flow"
13530
13713
  );
@@ -14790,73 +14973,7 @@ async function decideSubscribedThreadReply(args) {
14790
14973
  }
14791
14974
  }
14792
14975
 
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
- }
14976
+ // src/chat/slack/errors.ts
14860
14977
  function getSlackApiErrorCode(error) {
14861
14978
  if (!error || typeof error !== "object") {
14862
14979
  return void 0;
@@ -15005,7 +15122,7 @@ function createSlackTurnRuntime(deps) {
15005
15122
  error,
15006
15123
  "mention_handler_auth_pause",
15007
15124
  errorContext,
15008
- { "app.turn.retryable_reason": error.reason },
15125
+ { "app.ai.retryable_reason": error.reason },
15009
15126
  "onNewMention parked turn for auth resume"
15010
15127
  );
15011
15128
  return;
@@ -15048,10 +15165,20 @@ function createSlackTurnRuntime(deps) {
15048
15165
  runId
15049
15166
  }),
15050
15167
  async () => {
15051
- const rawUserText = message.text;
15052
- const userText = deps.stripLeadingBotMention(rawUserText, {
15168
+ const legacyAttachmentText = renderSlackLegacyAttachmentText(
15169
+ message.raw
15170
+ );
15171
+ const rawUserText = appendSlackLegacyAttachmentText(
15172
+ message.text,
15173
+ message.raw
15174
+ );
15175
+ const strippedUserText = deps.stripLeadingBotMention(message.text, {
15053
15176
  stripLeadingSlackMentionToken: Boolean(message.isMention)
15054
15177
  });
15178
+ const userText = appendSlackLegacyAttachmentText(
15179
+ strippedUserText,
15180
+ message.raw
15181
+ );
15055
15182
  const context = {
15056
15183
  threadId,
15057
15184
  requesterId: message.author.userId,
@@ -15090,7 +15217,7 @@ function createSlackTurnRuntime(deps) {
15090
15217
  rawText: rawUserText,
15091
15218
  text: userText,
15092
15219
  conversationContext: deps.getPreparedConversationContext(preparedState),
15093
- hasAttachments: message.attachments.length > 0,
15220
+ hasAttachments: message.attachments.length > 0 || legacyAttachmentText !== "",
15094
15221
  isExplicitMention: Boolean(message.isMention),
15095
15222
  context
15096
15223
  });
@@ -15140,7 +15267,7 @@ function createSlackTurnRuntime(deps) {
15140
15267
  error,
15141
15268
  "subscribed_message_handler_auth_pause",
15142
15269
  errorContext,
15143
- { "app.turn.retryable_reason": error.reason },
15270
+ { "app.ai.retryable_reason": error.reason },
15144
15271
  "onSubscribedMessage parked turn for auth resume"
15145
15272
  );
15146
15273
  return;
@@ -15287,7 +15414,7 @@ async function lookupSlackUser(userId) {
15287
15414
  {},
15288
15415
  {
15289
15416
  "enduser.id": userId,
15290
- "error.message": error instanceof Error ? error.message : String(error)
15417
+ "exception.message": error instanceof Error ? error.message : String(error)
15291
15418
  },
15292
15419
  "Slack user lookup failed with exception"
15293
15420
  );
@@ -15315,7 +15442,7 @@ function createSubscribedReplyPolicy(deps) {
15315
15442
  modelId: botConfig.fastModelId
15316
15443
  },
15317
15444
  {
15318
- "error.message": error instanceof Error ? error.message : String(error)
15445
+ "exception.message": error instanceof Error ? error.message : String(error)
15319
15446
  },
15320
15447
  "Subscribed-message classifier failed; skipping reply"
15321
15448
  );
@@ -15527,7 +15654,7 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
15527
15654
  },
15528
15655
  {
15529
15656
  "file.size": data.byteLength,
15530
- "file.mime_type": mediaType
15657
+ "app.file.mime_type": mediaType
15531
15658
  },
15532
15659
  "Skipping user attachment that exceeds size limit"
15533
15660
  );
@@ -15551,8 +15678,8 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
15551
15678
  modelId: botConfig.visionModelId ?? botConfig.modelId
15552
15679
  },
15553
15680
  {
15554
- "error.message": error instanceof Error ? error.message : String(error),
15555
- "file.mime_type": mediaType,
15681
+ "exception.message": error instanceof Error ? error.message : String(error),
15682
+ "app.file.mime_type": mediaType,
15556
15683
  ...attachment.name ? { "file.name": attachment.name } : {}
15557
15684
  },
15558
15685
  "Image attachment processing failed"
@@ -15570,8 +15697,8 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
15570
15697
  modelId: botConfig.modelId
15571
15698
  },
15572
15699
  {
15573
- "error.message": error instanceof Error ? error.message : String(error),
15574
- "file.mime_type": mediaType
15700
+ "exception.message": error instanceof Error ? error.message : String(error),
15701
+ "app.file.mime_type": mediaType
15575
15702
  },
15576
15703
  "Failed to resolve user attachment"
15577
15704
  );
@@ -15620,9 +15747,9 @@ async function summarizeConversationImage(args, deps) {
15620
15747
  modelId: visionModelId
15621
15748
  },
15622
15749
  {
15623
- "error.message": error instanceof Error ? error.message : String(error),
15624
- "file.id": args.fileId,
15625
- "file.mime_type": args.mimeType
15750
+ "exception.message": error instanceof Error ? error.message : String(error),
15751
+ "app.file.id": args.fileId,
15752
+ "app.file.mime_type": args.mimeType
15626
15753
  },
15627
15754
  "Image analysis failed while hydrating conversation context"
15628
15755
  );
@@ -15668,7 +15795,7 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15668
15795
  modelId: botConfig.modelId
15669
15796
  },
15670
15797
  {
15671
- "error.message": error instanceof Error ? error.message : String(error)
15798
+ "exception.message": error instanceof Error ? error.message : String(error)
15672
15799
  },
15673
15800
  "Failed to fetch thread replies for image context hydration"
15674
15801
  );
@@ -15735,9 +15862,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15735
15862
  modelId: botConfig.modelId
15736
15863
  },
15737
15864
  {
15738
- "file.id": fileId,
15865
+ "app.file.id": fileId,
15739
15866
  "file.size": fileSize,
15740
- "file.mime_type": mimeType
15867
+ "app.file.mime_type": mimeType
15741
15868
  },
15742
15869
  "Skipping thread image that exceeds size limit"
15743
15870
  );
@@ -15749,7 +15876,7 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15749
15876
  }
15750
15877
  let imageData;
15751
15878
  try {
15752
- imageData = await deps.downloadPrivateSlackFile(downloadUrl);
15879
+ imageData = await deps.downloadFile(downloadUrl);
15753
15880
  } catch (error) {
15754
15881
  logWarn(
15755
15882
  "conversation_image_download_failed",
@@ -15762,9 +15889,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15762
15889
  modelId: botConfig.modelId
15763
15890
  },
15764
15891
  {
15765
- "error.message": error instanceof Error ? error.message : String(error),
15766
- "file.id": fileId,
15767
- "file.mime_type": mimeType
15892
+ "exception.message": error instanceof Error ? error.message : String(error),
15893
+ "app.file.id": fileId,
15894
+ "app.file.mime_type": mimeType
15768
15895
  },
15769
15896
  "Failed to download thread image for context hydration"
15770
15897
  );
@@ -15782,9 +15909,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
15782
15909
  modelId: botConfig.modelId
15783
15910
  },
15784
15911
  {
15785
- "file.id": fileId,
15912
+ "app.file.id": fileId,
15786
15913
  "file.size": imageData.byteLength,
15787
- "file.mime_type": mimeType
15914
+ "app.file.mime_type": mimeType
15788
15915
  },
15789
15916
  "Skipping downloaded thread image that exceeds size limit"
15790
15917
  );
@@ -15847,12 +15974,6 @@ function createVisionContextService(deps) {
15847
15974
  )
15848
15975
  };
15849
15976
  }
15850
- var defaultVisionContextService = createVisionContextService({
15851
- completeText,
15852
- downloadPrivateSlackFile,
15853
- listThreadReplies
15854
- });
15855
- var hydrateConversationVisionContext = defaultVisionContextService.hydrateConversationVisionContext;
15856
15977
 
15857
15978
  // src/chat/app/services.ts
15858
15979
  function createJuniorRuntimeServices(overrides = {}) {
@@ -15862,7 +15983,7 @@ function createJuniorRuntimeServices(overrides = {}) {
15862
15983
  const visionContext = createVisionContextService({
15863
15984
  completeText: overrides.visionContext?.completeText ?? completeText,
15864
15985
  listThreadReplies: overrides.visionContext?.listThreadReplies ?? listThreadReplies,
15865
- downloadPrivateSlackFile: overrides.visionContext?.downloadPrivateSlackFile ?? downloadPrivateSlackFile
15986
+ downloadFile: overrides.visionContext?.downloadFile ?? downloadPrivateSlackFile
15866
15987
  });
15867
15988
  return {
15868
15989
  conversationMemory,
@@ -15879,6 +16000,85 @@ function createJuniorRuntimeServices(overrides = {}) {
15879
16000
  };
15880
16001
  }
15881
16002
 
16003
+ // src/chat/slack/message.ts
16004
+ function getSlackMessageTs(message) {
16005
+ if (message.id.endsWith(":message_changed_mention") && message.raw && typeof message.raw === "object") {
16006
+ const ts = message.raw.ts;
16007
+ if (typeof ts === "string" && ts.length > 0) {
16008
+ return ts;
16009
+ }
16010
+ }
16011
+ return message.id;
16012
+ }
16013
+
16014
+ // src/chat/runtime/thread-context.ts
16015
+ function escapeRegExp3(value) {
16016
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16017
+ }
16018
+ function stripLeadingBotMention(text, options = {}) {
16019
+ if (!text.trim()) return text;
16020
+ let next = text;
16021
+ if (options.stripLeadingSlackMentionToken) {
16022
+ next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
16023
+ }
16024
+ const mentionByNameRe = new RegExp(
16025
+ `^\\s*@${escapeRegExp3(botConfig.userName)}\\b[\\s,:-]*`,
16026
+ "i"
16027
+ );
16028
+ next = next.replace(mentionByNameRe, "").trim();
16029
+ const mentionByLabeledEntityRe = new RegExp(
16030
+ `^\\s*<@[^>|]+\\|${escapeRegExp3(botConfig.userName)}>[\\s,:-]*`,
16031
+ "i"
16032
+ );
16033
+ next = next.replace(mentionByLabeledEntityRe, "").trim();
16034
+ return next;
16035
+ }
16036
+ function getThreadId(thread, _message) {
16037
+ return toOptionalString(thread.id);
16038
+ }
16039
+ function getRunId(thread, message) {
16040
+ return toOptionalString(thread.runId) ?? toOptionalString(message.runId);
16041
+ }
16042
+ function getChannelId(thread, message) {
16043
+ return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
16044
+ }
16045
+ function getThreadTs(threadId) {
16046
+ return parseSlackThreadId(threadId)?.threadTs;
16047
+ }
16048
+ function getAssistantThreadContext(message) {
16049
+ const raw = message.raw;
16050
+ const rawRecord = raw && typeof raw === "object" ? raw : void 0;
16051
+ const channelId = toOptionalString(rawRecord?.channel);
16052
+ if (channelId) {
16053
+ const rawThreadTs = toOptionalString(rawRecord?.thread_ts);
16054
+ const threadTs = isDmChannel(channelId) ? rawThreadTs : rawThreadTs ?? toOptionalString(rawRecord?.ts);
16055
+ if (threadTs) {
16056
+ return { channelId, threadTs };
16057
+ }
16058
+ }
16059
+ const parsedThreadId = parseSlackThreadId(
16060
+ toOptionalString(message.threadId)
16061
+ );
16062
+ if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) {
16063
+ return void 0;
16064
+ }
16065
+ return parsedThreadId;
16066
+ }
16067
+ function getMessageTs(message) {
16068
+ const directTs = toOptionalString(
16069
+ message.ts
16070
+ );
16071
+ if (directTs) {
16072
+ return directTs;
16073
+ }
16074
+ const raw = message.raw;
16075
+ if (!raw || typeof raw !== "object") {
16076
+ return void 0;
16077
+ }
16078
+ const rawRecord = raw;
16079
+ return toOptionalString(rawRecord.ts) ?? toOptionalString(rawRecord.event_ts) ?? toOptionalString(rawRecord.message?.ts);
16080
+ }
16081
+
15882
16082
  // src/chat/slack/assistant-thread/title.ts
15883
16083
  function maybeUpdateAssistantTitle(args) {
15884
16084
  const assistantThreadContext = args.assistantThreadContext;
@@ -15937,7 +16137,7 @@ function maybeUpdateAssistantTitle(args) {
15937
16137
  modelId: args.modelId
15938
16138
  },
15939
16139
  {
15940
- "error.message": error instanceof Error ? error.message : String(error)
16140
+ "exception.message": error instanceof Error ? error.message : String(error)
15941
16141
  },
15942
16142
  "Thread title generation failed"
15943
16143
  );
@@ -15972,9 +16172,13 @@ function createReplyToThread(deps) {
15972
16172
  modelId: botConfig.modelId
15973
16173
  },
15974
16174
  async () => {
15975
- const userText = stripLeadingBotMention(message.text, {
16175
+ const strippedUserText = stripLeadingBotMention(message.text, {
15976
16176
  stripLeadingSlackMentionToken: options.explicitMention || Boolean(message.isMention)
15977
16177
  });
16178
+ const userText = appendSlackLegacyAttachmentText(
16179
+ strippedUserText,
16180
+ message.raw
16181
+ );
15978
16182
  const preparedState = options.preparedState ?? await deps.prepareTurnState({
15979
16183
  thread,
15980
16184
  message,
@@ -15996,11 +16200,8 @@ function createReplyToThread(deps) {
15996
16200
  nextTurnId: turnId,
15997
16201
  updateConversationStats
15998
16202
  });
15999
- const turnStartedAtMs = Date.now();
16000
16203
  const turnTraceContext = {
16001
16204
  conversationId,
16002
- turnId,
16003
- agentId: turnId,
16004
16205
  slackThreadId: threadId,
16005
16206
  slackUserId: message.author.userId,
16006
16207
  slackChannelId: channelId,
@@ -16009,9 +16210,7 @@ function createReplyToThread(deps) {
16009
16210
  modelId: botConfig.modelId
16010
16211
  };
16011
16212
  setTags({
16012
- conversationId,
16013
- turnId,
16014
- agentId: turnId
16213
+ conversationId
16015
16214
  });
16016
16215
  if (shouldEmitDevAgentTrace()) {
16017
16216
  logInfo(
@@ -16279,7 +16478,6 @@ function createReplyToThread(deps) {
16279
16478
  "agent_turn_completed",
16280
16479
  turnTraceContext,
16281
16480
  {
16282
- "app.turn.duration_ms": Date.now() - turnStartedAtMs,
16283
16481
  "app.ai.outcome": reply.diagnostics.outcome,
16284
16482
  "app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
16285
16483
  "app.ai.tool_error_results": reply.diagnostics.toolErrorCount
@@ -16365,9 +16563,7 @@ function createReplyToThread(deps) {
16365
16563
  logWarn(
16366
16564
  "agent_turn_failed",
16367
16565
  turnTraceContext,
16368
- {
16369
- "app.turn.duration_ms": Date.now() - turnStartedAtMs
16370
- },
16566
+ {},
16371
16567
  "Agent turn failed"
16372
16568
  );
16373
16569
  }
@@ -16380,7 +16576,6 @@ function createReplyToThread(deps) {
16380
16576
  }
16381
16577
 
16382
16578
  // src/chat/slack/assistant-thread/lifecycle.ts
16383
- import { ThreadImpl } from "chat";
16384
16579
  async function syncAssistantThreadContext(event, options) {
16385
16580
  const channelId = normalizeSlackConversationId(event.channelId);
16386
16581
  if (!channelId) {
@@ -16405,20 +16600,7 @@ async function syncAssistantThreadContext(event, options) {
16405
16600
  if (!sourceChannelId) {
16406
16601
  return;
16407
16602
  }
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
- });
16603
+ await event.onContextChannelResolved(sourceChannelId);
16422
16604
  }
16423
16605
  async function initializeAssistantThread(event) {
16424
16606
  await syncAssistantThreadContext(event, { setInitialTitle: true });
@@ -16428,11 +16610,97 @@ async function refreshAssistantThreadContext(event) {
16428
16610
  }
16429
16611
 
16430
16612
  // src/chat/runtime/turn-preparation.ts
16613
+ var BACKFILL_MESSAGE_LIMIT = 80;
16431
16614
  function hasPendingImageHydration(conversation) {
16432
16615
  return conversation.messages.some(
16433
16616
  (message) => isHumanConversationMessage(message) && !message.meta?.imagesHydrated
16434
16617
  );
16435
16618
  }
16619
+ function createConversationMessageFromSdkMessage(entry) {
16620
+ const enrichedText = appendSlackLegacyAttachmentText(entry.text, entry.raw);
16621
+ const rawText = normalizeConversationText(enrichedText);
16622
+ if (!rawText) {
16623
+ return null;
16624
+ }
16625
+ return {
16626
+ id: entry.id,
16627
+ role: entry.author.isMe ? "assistant" : "user",
16628
+ text: rawText,
16629
+ createdAtMs: entry.metadata.dateSent.getTime(),
16630
+ author: {
16631
+ userId: entry.author.userId,
16632
+ userName: entry.author.userName,
16633
+ fullName: entry.author.fullName,
16634
+ isBot: typeof entry.author.isBot === "boolean" ? entry.author.isBot : void 0
16635
+ },
16636
+ meta: {
16637
+ slackTs: getSlackMessageTs(entry)
16638
+ }
16639
+ };
16640
+ }
16641
+ async function seedConversationBackfill(thread, conversation, currentTurn) {
16642
+ if (conversation.backfill.completedAtMs) {
16643
+ return;
16644
+ }
16645
+ if (conversation.messages.length > 0 || conversation.compactions.length > 0) {
16646
+ conversation.backfill = {
16647
+ completedAtMs: Date.now(),
16648
+ source: "recent_messages"
16649
+ };
16650
+ updateConversationStats(conversation);
16651
+ return;
16652
+ }
16653
+ const seeded = [];
16654
+ let source = "recent_messages";
16655
+ try {
16656
+ const fetchedNewestFirst = [];
16657
+ for await (const entry of thread.messages) {
16658
+ fetchedNewestFirst.push(entry);
16659
+ if (fetchedNewestFirst.length >= BACKFILL_MESSAGE_LIMIT) {
16660
+ break;
16661
+ }
16662
+ }
16663
+ fetchedNewestFirst.reverse();
16664
+ for (const entry of fetchedNewestFirst) {
16665
+ const message = createConversationMessageFromSdkMessage(entry);
16666
+ if (message) {
16667
+ seeded.push(message);
16668
+ }
16669
+ }
16670
+ if (seeded.length > 0) {
16671
+ source = "thread_fetch";
16672
+ }
16673
+ } catch {
16674
+ }
16675
+ if (seeded.length === 0) {
16676
+ try {
16677
+ await thread.refresh();
16678
+ } catch {
16679
+ }
16680
+ const fromRecent = thread.recentMessages.slice(-BACKFILL_MESSAGE_LIMIT);
16681
+ for (const entry of fromRecent) {
16682
+ const message = createConversationMessageFromSdkMessage(entry);
16683
+ if (message) {
16684
+ seeded.push(message);
16685
+ }
16686
+ }
16687
+ source = "recent_messages";
16688
+ }
16689
+ for (const message of seeded) {
16690
+ if (message.id !== currentTurn.messageId && message.createdAtMs > currentTurn.messageCreatedAtMs) {
16691
+ continue;
16692
+ }
16693
+ if (message.id !== currentTurn.messageId && message.createdAtMs === currentTurn.messageCreatedAtMs && message.id > currentTurn.messageId) {
16694
+ continue;
16695
+ }
16696
+ upsertConversationMessage(conversation, message);
16697
+ }
16698
+ conversation.backfill = {
16699
+ completedAtMs: Date.now(),
16700
+ source
16701
+ };
16702
+ updateConversationStats(conversation);
16703
+ }
16436
16704
  function createPrepareTurnState(deps) {
16437
16705
  return async function prepareTurnState(args) {
16438
16706
  const existingState = await args.thread.state;
@@ -16518,6 +16786,17 @@ function createPrepareTurnState(deps) {
16518
16786
  }
16519
16787
 
16520
16788
  // src/chat/app/factory.ts
16789
+ async function persistAssistantContextChannelId(args) {
16790
+ const currentArtifacts = coerceThreadArtifactsState(
16791
+ await getPersistedThreadState(args.threadId)
16792
+ );
16793
+ const nextArtifacts = mergeArtifactsState(currentArtifacts, {
16794
+ assistantContextChannelId: args.sourceChannelId
16795
+ });
16796
+ await persistThreadStateById(args.threadId, {
16797
+ artifacts: nextArtifacts
16798
+ });
16799
+ }
16521
16800
  function createSlackRuntime(options) {
16522
16801
  const services = createJuniorRuntimeServices(options.services);
16523
16802
  const prepareTurnState = createPrepareTurnState({
@@ -16617,11 +16896,14 @@ function createSlackRuntime(options) {
16617
16896
  sourceChannelId
16618
16897
  }) => {
16619
16898
  await initializeAssistantThread({
16620
- threadId,
16621
16899
  channelId,
16622
16900
  threadTs,
16623
16901
  sourceChannelId,
16624
- getSlackAdapter: options.getSlackAdapter
16902
+ getSlackAdapter: options.getSlackAdapter,
16903
+ onContextChannelResolved: (resolvedSourceChannelId) => persistAssistantContextChannelId({
16904
+ sourceChannelId: resolvedSourceChannelId,
16905
+ threadId
16906
+ })
16625
16907
  });
16626
16908
  },
16627
16909
  refreshAssistantThreadContext: async ({
@@ -16631,11 +16913,14 @@ function createSlackRuntime(options) {
16631
16913
  sourceChannelId
16632
16914
  }) => {
16633
16915
  await refreshAssistantThreadContext({
16634
- threadId,
16635
16916
  channelId,
16636
16917
  threadTs,
16637
16918
  sourceChannelId,
16638
- getSlackAdapter: options.getSlackAdapter
16919
+ getSlackAdapter: options.getSlackAdapter,
16920
+ onContextChannelResolved: (resolvedSourceChannelId) => persistAssistantContextChannelId({
16921
+ sourceChannelId: resolvedSourceChannelId,
16922
+ threadId
16923
+ })
16639
16924
  });
16640
16925
  }
16641
16926
  });