@sentry/junior 0.60.1 → 0.62.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
@@ -2969,13 +2969,7 @@ var editReplacementSchema = Type2.Object(
2969
2969
  );
2970
2970
  function createEditFileTool() {
2971
2971
  return tool({
2972
- description: "Edit one sandbox workspace file with exact text replacements. Use for precise changes to existing files. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff.",
2973
- promptSnippet: "existing-file exact edits; returns diff",
2974
- promptGuidelines: [
2975
- "prefer over writeFile for targeted changes",
2976
- "oldText exact, unique, non-overlapping",
2977
- "multiple same-file changes: one edits[] call"
2978
- ],
2972
+ description: "Edit one sandbox workspace file with exact text replacements. Use for precise changes to existing files; prefer this over writeFile for targeted changes. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff. Multiple changes to the same file: use one edits[] call.",
2979
2973
  prepareArguments: prepareEditFileArguments,
2980
2974
  executionMode: "sequential",
2981
2975
  inputSchema: Type2.Object(
@@ -5406,13 +5400,7 @@ function createSlackCanvasReadTool() {
5406
5400
  }
5407
5401
  function createSlackCanvasEditTool(state) {
5408
5402
  return tool({
5409
- description: "Edit one Slack canvas with exact markdown replacements. Use for precise changes to existing Canvas content. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff.",
5410
- promptSnippet: "existing-canvas exact edits; returns diff",
5411
- promptGuidelines: [
5412
- "prefer over slackCanvasWrite for targeted changes",
5413
- "oldText exact, unique, non-overlapping",
5414
- "multiple same-canvas changes: one edits[] call"
5415
- ],
5403
+ description: "Edit one Slack canvas with exact markdown replacements. Use for precise changes to existing Canvas content; prefer this over slackCanvasWrite for targeted changes. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff. Multiple changes to the same canvas: use one edits[] call.",
5416
5404
  prepareArguments: prepareCanvasEditArguments,
5417
5405
  executionMode: "sequential",
5418
5406
  inputSchema: Type16.Object(
@@ -5499,9 +5487,7 @@ function createSlackCanvasEditTool(state) {
5499
5487
  }
5500
5488
  function createSlackCanvasWriteTool(state) {
5501
5489
  return tool({
5502
- description: "Write UTF-8 markdown content to a Slack canvas. Use for deliberate full-Canvas replacement after validation. Do not use for targeted edits.",
5503
- promptSnippet: "deliberate full-canvas replacement",
5504
- promptGuidelines: ["targeted existing-canvas changes: slackCanvasEdit"],
5490
+ description: "Write UTF-8 markdown content to a Slack canvas. Use for deliberate full-Canvas replacement after validation; use slackCanvasEdit for targeted changes to existing canvas content.",
5505
5491
  executionMode: "sequential",
5506
5492
  inputSchema: Type16.Object(
5507
5493
  {
@@ -6599,15 +6585,22 @@ function summarizeMessageText(text) {
6599
6585
  }
6600
6586
  return normalized.length > 1200 ? `${normalized.slice(0, 1200)}...` : normalized;
6601
6587
  }
6588
+ function isStructuredThreadContext(context) {
6589
+ return /^<thread-(compactions|transcript)>/.test(context);
6590
+ }
6591
+ function renderThreadContextForPrompt(context) {
6592
+ if (isStructuredThreadContext(context)) {
6593
+ return context;
6594
+ }
6595
+ return ["<thread-background>", context, "</thread-background>"].join("\n");
6596
+ }
6602
6597
  function buildUserTurnText(userInput, conversationContext) {
6603
6598
  const trimmedContext = conversationContext?.trim();
6604
6599
  if (!trimmedContext) {
6605
6600
  return userInput;
6606
6601
  }
6607
6602
  return [
6608
- "<thread-background>",
6609
- trimmedContext,
6610
- "</thread-background>",
6603
+ renderThreadContextForPrompt(trimmedContext),
6611
6604
  "",
6612
6605
  "<current-instruction>",
6613
6606
  userInput,
@@ -6816,7 +6809,7 @@ function createStateAdvisorSessionStore() {
6816
6809
  }
6817
6810
 
6818
6811
  // src/chat/tools/advisor/tool.ts
6819
- var ADVISOR_TOOL_DESCRIPTION = "Ask a stronger advisor for deep technical guidance. Call this when the task has a hard reasoning core: algorithm design, architecture, concurrency, security-sensitive logic, data modeling, unclear requirements, repeated failures, difficult debugging, broad refactors, or final review of nontrivial work. Pass a focused question plus curated context containing the exact evidence, constraints, current plan, alternatives, command output, code snippets, or diffs the advisor should start from. The advisor does not automatically receive the parent transcript, keeps its own advisor history for this parent conversation, can use read-only inspection tools to verify evidence, can reason deeply, and returns guidance for you to apply and verify. Follow-up calls can build on prior advisor guidance but must include any new evidence or changed constraints. Use it after initial orientation reads when repository context matters, before committing to a non-obvious implementation plan, when changing approach, when stuck, and before declaring complex work complete. Do not use it for greetings, simple deterministic edits, routine formatting, or tasks where the next action is already obvious from fresh tool output.";
6812
+ var ADVISOR_TOOL_DESCRIPTION = "Use this before committing to a non-obvious technical plan or declaring complex work complete. Call proactively after initial orientation when repository context matters \u2014 especially for architecture, algorithms, data modeling, concurrency, security-sensitive logic, unclear requirements, broad refactors, difficult debugging, repeated failures, or high-risk changes. Provide a focused question plus curated context: exact evidence, constraints, relevant code snippets, command output, diffs, current plan, and alternatives considered. The advisor does not automatically receive the parent transcript, keeps its own advisor history for this parent conversation, and can use read-only inspection tools to verify evidence. Follow-up calls can build on prior advisor guidance but must include new evidence or changed constraints. Do not use for greetings, simple deterministic edits, routine formatting, or when the next action is obvious from fresh tool output.";
6820
6813
  var ADVISOR_SYSTEM_PROMPT = [
6821
6814
  "You are a senior technical advisor for the executor.",
6822
6815
  "Analyze the executor-supplied context deeply. Use read-only tools when direct inspection or verification would materially improve the advice.",
@@ -7660,9 +7653,7 @@ function createWebSearchTool(override) {
7660
7653
  import { Type as Type24 } from "@sinclair/typebox";
7661
7654
  function createWriteFileTool() {
7662
7655
  return tool({
7663
- 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.",
7664
- promptSnippet: "new file or deliberate full-file replacement",
7665
- promptGuidelines: ["targeted existing-file changes: editFile"],
7656
+ description: "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or deliberate full-file replacement after validation; use editFile instead for targeted changes to existing files. Do not use for exploratory analysis-only turns.",
7666
7657
  executionMode: "sequential",
7667
7658
  inputSchema: Type24.Object(
7668
7659
  {
@@ -8635,6 +8626,7 @@ async function syncSkillsToSandbox(params) {
8635
8626
 
8636
8627
  // src/chat/sandbox/session.ts
8637
8628
  var DEFAULT_MAX_OUTPUT_LENGTH = 3e4;
8629
+ var DEFAULT_BASH_COMMAND_TIMEOUT_MS = 5 * 60 * 1e3;
8638
8630
  var SANDBOX_RUNTIME = "node22";
8639
8631
  var SANDBOX_RUNTIME_BIN_DIR = `${SANDBOX_WORKSPACE_ROOT}/.junior/bin`;
8640
8632
  var SNAPSHOT_BOOT_RETRY_COUNT = 3;
@@ -8707,6 +8699,16 @@ function getCommandStreamInterruptedResult() {
8707
8699
  stderrTruncated: false
8708
8700
  };
8709
8701
  }
8702
+ function getCommandAbortedResult() {
8703
+ return {
8704
+ stdout: "",
8705
+ stderr: "Command aborted because the agent turn was cancelled.",
8706
+ exitCode: 130,
8707
+ stdoutTruncated: false,
8708
+ stderrTruncated: false,
8709
+ aborted: true
8710
+ };
8711
+ }
8710
8712
  function createSandboxSessionManager(options) {
8711
8713
  let sandbox = null;
8712
8714
  let sandboxIdHint = options?.sandboxId;
@@ -8962,7 +8964,9 @@ function createSandboxSessionManager(options) {
8962
8964
  "sandbox_hint_discarded_profile_mismatch",
8963
8965
  traceContext,
8964
8966
  {
8965
- ...options?.sandboxDependencyProfileHash ? { "app.sandbox.previous_profile_hash": options.sandboxDependencyProfileHash } : {},
8967
+ ...options?.sandboxDependencyProfileHash ? {
8968
+ "app.sandbox.previous_profile_hash": options.sandboxDependencyProfileHash
8969
+ } : {},
8966
8970
  ...dependencyProfileHash ? { "app.sandbox.current_profile_hash": dependencyProfileHash } : {}
8967
8971
  },
8968
8972
  "Dependency profile changed; discarding sandbox hint and creating fresh session"
@@ -9130,37 +9134,63 @@ function createSandboxSessionManager(options) {
9130
9134
  return {
9131
9135
  bash: async (input) => {
9132
9136
  let timedOut = false;
9137
+ let aborted = false;
9133
9138
  let timeoutId;
9139
+ let onAbort;
9134
9140
  try {
9141
+ if (input.signal?.aborted) {
9142
+ return getCommandAbortedResult();
9143
+ }
9135
9144
  await refreshNetworkPolicy(sandboxInstance);
9145
+ if (input.signal?.aborted) {
9146
+ return getCommandAbortedResult();
9147
+ }
9136
9148
  const sandboxCommandEnv = await resolveCommandEnv();
9149
+ if (input.signal?.aborted) {
9150
+ return getCommandAbortedResult();
9151
+ }
9137
9152
  const script = buildNonInteractiveShellScript(input.command, {
9138
9153
  env: { ...sandboxCommandEnv, ...input.env ?? {} },
9139
9154
  pathPrefix: `${SANDBOX_RUNTIME_BIN_DIR}:$PATH`
9140
9155
  });
9141
- const controller = input.timeoutMs && input.timeoutMs > 0 ? new AbortController() : void 0;
9142
- timeoutId = controller ? setTimeout(() => {
9156
+ const controller = new AbortController();
9157
+ const timeoutMs2 = input.timeoutMs && input.timeoutMs > 0 ? input.timeoutMs : DEFAULT_BASH_COMMAND_TIMEOUT_MS;
9158
+ onAbort = () => {
9159
+ aborted = true;
9160
+ controller.abort(input.signal?.reason);
9161
+ };
9162
+ if (input.signal) {
9163
+ input.signal.addEventListener("abort", onAbort, { once: true });
9164
+ if (input.signal.aborted) {
9165
+ onAbort();
9166
+ }
9167
+ }
9168
+ timeoutId = setTimeout(() => {
9143
9169
  timedOut = true;
9144
9170
  controller.abort();
9145
- }, input.timeoutMs) : void 0;
9171
+ }, timeoutMs2);
9172
+ timeoutId.unref?.();
9146
9173
  const commandResult2 = await sandboxInstance.runCommand({
9147
9174
  cmd: "bash",
9148
9175
  args: ["-c", script],
9149
9176
  cwd: SANDBOX_WORKSPACE_ROOT,
9150
- ...controller ? { signal: controller.signal } : {}
9177
+ signal: controller.signal
9151
9178
  });
9152
9179
  return await readCommandOutput(commandResult2);
9153
9180
  } catch (error) {
9154
9181
  if (timedOut) {
9155
9182
  return {
9156
9183
  stdout: "",
9157
- stderr: `Command timed out after ${input.timeoutMs}ms`,
9184
+ stderr: `Command timed out after ${input.timeoutMs && input.timeoutMs > 0 ? input.timeoutMs : DEFAULT_BASH_COMMAND_TIMEOUT_MS}ms`,
9158
9185
  exitCode: 124,
9159
9186
  stdoutTruncated: false,
9160
9187
  stderrTruncated: false,
9161
9188
  timedOut: true
9162
9189
  };
9163
9190
  }
9191
+ if (aborted || input.signal?.aborted) {
9192
+ return getCommandAbortedResult();
9193
+ }
9164
9194
  if (isSandboxCommandStreamInterruptedError(error)) {
9165
9195
  return getCommandStreamInterruptedResult();
9166
9196
  }
@@ -9169,6 +9199,9 @@ function createSandboxSessionManager(options) {
9169
9199
  if (timeoutId) {
9170
9200
  clearTimeout(timeoutId);
9171
9201
  }
9202
+ if (input.signal && onAbort) {
9203
+ input.signal.removeEventListener("abort", onAbort);
9204
+ }
9172
9205
  }
9173
9206
  },
9174
9207
  readFile: async (input) => await executeReadFile(input, {
@@ -9344,7 +9377,7 @@ function createSandboxExecutor(options) {
9344
9377
  "Sandbox boot requested"
9345
9378
  );
9346
9379
  };
9347
- const executeBashTool = async (rawInput, command) => {
9380
+ const executeBashTool = async (rawInput, command, signal) => {
9348
9381
  const env = parseEnv(rawInput.env);
9349
9382
  const timeoutMs = positiveInteger(rawInput.timeoutMs);
9350
9383
  logSandboxBootRequest("tool.bash", {
@@ -9362,7 +9395,8 @@ function createSandboxExecutor(options) {
9362
9395
  const response = await executeBash({
9363
9396
  command,
9364
9397
  ...env ? { env } : {},
9365
- ...timeoutMs ? { timeoutMs } : {}
9398
+ ...timeoutMs ? { timeoutMs } : {},
9399
+ ...signal ? { signal } : {}
9366
9400
  });
9367
9401
  setSpanAttributes({
9368
9402
  "process.exit.code": response.exitCode,
@@ -9632,7 +9666,7 @@ function createSandboxExecutor(options) {
9632
9666
  return { result: custom.result };
9633
9667
  }
9634
9668
  }
9635
- return await executeBashTool(rawInput, bashCommand);
9669
+ return await executeBashTool(rawInput, bashCommand, params.signal);
9636
9670
  }
9637
9671
  try {
9638
9672
  if (params.toolName === "readFile") {
@@ -10471,7 +10505,7 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
10471
10505
  parameters: toolDef.inputSchema,
10472
10506
  prepareArguments: toolDef.prepareArguments,
10473
10507
  executionMode: toolDef.executionMode,
10474
- execute: async (toolCallId, params) => {
10508
+ execute: async (toolCallId, params, signal) => {
10475
10509
  const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
10476
10510
  const toolArgumentsAttribute = serializeToolPayload(params);
10477
10511
  const toolArgumentsMetadata = toGenAiPayloadTraceAttributes(
@@ -10519,9 +10553,11 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
10519
10553
  const isSandbox = Boolean(sandboxExecutor?.canExecute(toolName));
10520
10554
  const result = isSandbox ? await sandboxExecutor.execute({
10521
10555
  toolName,
10522
- input: sandboxInput
10556
+ input: sandboxInput,
10557
+ ...signal ? { signal } : {}
10523
10558
  }) : await toolDef.execute(toolInput, {
10524
- experimental_context: sandbox
10559
+ experimental_context: sandbox,
10560
+ ...signal ? { signal } : {}
10525
10561
  });
10526
10562
  const normalized = normalizeToolResult(result, isSandbox);
10527
10563
  if (bashCommand && pluginAuthOrchestration) {
@@ -11500,13 +11536,13 @@ function buildClassifierSystemPrompt() {
11500
11536
  "Choose exactly one bucket: none, low, medium, high, or xhigh.",
11501
11537
  "",
11502
11538
  "Use none only for greetings, acknowledgments, and turns that need no substantive assistant work.",
11503
- "Use low rarely: only for deterministic one-step answers or transformations with no tools, no current/external facts, no thread-background interpretation, and no source verification.",
11539
+ "Use low rarely: only for deterministic one-step answers or transformations with no tools, no current/external facts, no prior thread-context interpretation, and no source verification.",
11504
11540
  "Use medium for normal assistant work: explanations, source-backed checks, thread follow-ups, tool choice, likely tool use, ambiguous asks, multi-step analysis, or anything where a confident but shallow answer would be risky.",
11505
11541
  "Use high for research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
11506
11542
  "Use xhigh for the most complex tasks: code changes, debugging/root-cause analysis, broad refactors, architecture decisions, multi-file implementation, or any task where deep reasoning across multiple systems or files is required.",
11507
11543
  "When unsure between two non-none buckets, choose the higher bucket. Do not use low as the default.",
11508
11544
  "",
11509
- "Classify based on the substance of the task, not the length of the current message. When the current instruction is a short affirmation (for example: 'go', 'do it', 'yes please', 'proceed') and the thread-background contains a pending task, classify the pending task \u2014 not the affirmation.",
11545
+ "Classify based on the substance of the task, not the length of the current message. When the current instruction is a short affirmation (for example: 'go', 'do it', 'yes please', 'proceed') and prior thread context contains a pending task, classify the pending task \u2014 not the affirmation.",
11510
11546
  "",
11511
11547
  "Return JSON only with thinking_level, confidence, and reason.",
11512
11548
  "confidence must be a number from 0 to 1, not a word label."
@@ -11515,12 +11551,17 @@ function buildClassifierSystemPrompt() {
11515
11551
  function buildClassifierPrompt(args) {
11516
11552
  const sections = [];
11517
11553
  if (args.conversationContext) {
11518
- sections.push(
11519
- "<thread-background>",
11520
- args.conversationContext.text,
11521
- "</thread-background>",
11522
- ""
11523
- );
11554
+ const contextText = args.conversationContext.text;
11555
+ if (/^<thread-(compactions|transcript)>/.test(contextText)) {
11556
+ sections.push(contextText, "");
11557
+ } else {
11558
+ sections.push(
11559
+ "<thread-background>",
11560
+ contextText,
11561
+ "</thread-background>",
11562
+ ""
11563
+ );
11564
+ }
11524
11565
  }
11525
11566
  sections.push(
11526
11567
  "<current-instruction>",
@@ -12312,9 +12353,38 @@ function createMcpAuthOrchestration(deps, abortAgent) {
12312
12353
 
12313
12354
  // src/chat/respond.ts
12314
12355
  var PROVIDER_RETRY_DELAYS_MS = [1e3, 2e3];
12356
+ var AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
12315
12357
  function sleep3(ms) {
12316
12358
  return new Promise((resolve) => setTimeout(resolve, ms));
12317
12359
  }
12360
+ function waitForAbortSettlement(promise, timeoutMs) {
12361
+ return new Promise((resolve) => {
12362
+ let done = false;
12363
+ const timeoutId = setTimeout(() => {
12364
+ if (!done) {
12365
+ done = true;
12366
+ resolve(false);
12367
+ }
12368
+ }, timeoutMs);
12369
+ timeoutId.unref?.();
12370
+ promise.then(
12371
+ () => {
12372
+ if (!done) {
12373
+ done = true;
12374
+ clearTimeout(timeoutId);
12375
+ resolve(true);
12376
+ }
12377
+ },
12378
+ () => {
12379
+ if (!done) {
12380
+ done = true;
12381
+ clearTimeout(timeoutId);
12382
+ resolve(true);
12383
+ }
12384
+ }
12385
+ );
12386
+ });
12387
+ }
12318
12388
  var startupDiscoveryLogged = false;
12319
12389
  var MAX_ROUTER_ATTACHMENT_PREVIEW_CHARS = 2e3;
12320
12390
  function buildOmittedImageAttachmentNotice(count) {
@@ -13070,8 +13140,20 @@ async function generateAssistantReply(messageText2, context = {}) {
13070
13140
  },
13071
13141
  "Agent turn timed out and was aborted"
13072
13142
  );
13073
- await run2.catch(() => {
13074
- });
13143
+ const settled = await waitForAbortSettlement(
13144
+ run2,
13145
+ AGENT_ABORT_SETTLE_GRACE_MS
13146
+ );
13147
+ if (!settled) {
13148
+ logWarn(
13149
+ "agent_turn_abort_settle_timeout",
13150
+ {},
13151
+ {
13152
+ "app.ai.abort_settle_grace_ms": AGENT_ABORT_SETTLE_GRACE_MS
13153
+ },
13154
+ "Timed-out agent run did not settle after abort before resume snapshot"
13155
+ );
13156
+ }
13075
13157
  timeoutResumeMessages = [...agent.state.messages];
13076
13158
  }
13077
13159
  if (getPendingAuthPause()) {
@@ -13463,20 +13545,25 @@ function buildConversationContext(conversation, options = {}) {
13463
13545
  " </compaction>"
13464
13546
  );
13465
13547
  }
13466
- lines.push("</thread-compactions>", "");
13548
+ lines.push("</thread-compactions>");
13467
13549
  }
13468
- lines.push("<thread-transcript>");
13469
- for (const [index, message] of messages.entries()) {
13470
- const author = escapeXml(message.author?.userName ?? message.role);
13471
- const ts = new Date(message.createdAtMs).toISOString();
13472
- const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
13473
- lines.push(
13474
- ` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${slackTsAttr}>`,
13475
- renderConversationMessageLine(message, conversation),
13476
- " </message>"
13477
- );
13550
+ if (messages.length > 0) {
13551
+ if (lines.length > 0) {
13552
+ lines.push("");
13553
+ }
13554
+ lines.push("<thread-transcript>");
13555
+ for (const [index, message] of messages.entries()) {
13556
+ const author = escapeXml(message.author?.userName ?? message.role);
13557
+ const ts = new Date(message.createdAtMs).toISOString();
13558
+ const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
13559
+ lines.push(
13560
+ ` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${slackTsAttr}>`,
13561
+ renderConversationMessageLine(message, conversation),
13562
+ " </message>"
13563
+ );
13564
+ }
13565
+ lines.push("</thread-transcript>");
13478
13566
  }
13479
- lines.push("</thread-transcript>");
13480
13567
  return lines.join("\n");
13481
13568
  }
13482
13569
  function pruneCompactions(compactions) {
@@ -19637,7 +19724,7 @@ function getSlackMessageTs(message) {
19637
19724
  // src/chat/slack/assistant-thread/title.ts
19638
19725
  function maybeUpdateAssistantTitle(args) {
19639
19726
  const assistantThreadContext = args.assistantThreadContext;
19640
- if (!assistantThreadContext?.channelId || !assistantThreadContext.threadTs || !isDmChannel(assistantThreadContext.channelId)) {
19727
+ if (!assistantThreadContext?.channelId || !assistantThreadContext.threadTs) {
19641
19728
  return Promise.resolve(void 0);
19642
19729
  }
19643
19730
  const titleSourceMessage = getThreadTitleSourceMessage(args.conversation);
@@ -19647,40 +19734,12 @@ function maybeUpdateAssistantTitle(args) {
19647
19734
  if (args.artifacts.assistantTitleSourceMessageId === titleSourceMessage.id) {
19648
19735
  return Promise.resolve(void 0);
19649
19736
  }
19737
+ const isDm = isDmChannel(assistantThreadContext.channelId);
19650
19738
  return (async () => {
19739
+ let title;
19651
19740
  try {
19652
- const title = await args.generateThreadTitle(titleSourceMessage.text);
19653
- await args.getSlackAdapter().setAssistantTitle(
19654
- assistantThreadContext.channelId,
19655
- assistantThreadContext.threadTs,
19656
- title
19657
- );
19658
- return { sourceMessageId: titleSourceMessage.id, title };
19741
+ title = await args.generateThreadTitle(titleSourceMessage.text);
19659
19742
  } catch (error) {
19660
- const slackErrorCode = getSlackApiErrorCode(error);
19661
- const assistantTitleErrorAttributes = {
19662
- "app.slack.assistant_title.outcome": "permission_denied",
19663
- ...slackErrorCode ? {
19664
- "app.slack.assistant_title.error_code": slackErrorCode
19665
- } : {}
19666
- };
19667
- if (isSlackTitlePermissionError(error)) {
19668
- setSpanAttributes(assistantTitleErrorAttributes);
19669
- logError(
19670
- "thread_title_generation_permission_denied",
19671
- {
19672
- slackThreadId: args.threadId,
19673
- slackUserId: args.requesterId,
19674
- slackChannelId: args.channelId,
19675
- runId: args.runId,
19676
- assistantUserName: args.assistantUserName,
19677
- modelId: args.modelId
19678
- },
19679
- assistantTitleErrorAttributes,
19680
- "Skipping thread title update due to Slack permission error"
19681
- );
19682
- return { sourceMessageId: titleSourceMessage.id };
19683
- }
19684
19743
  logWarn(
19685
19744
  "thread_title_generation_failed",
19686
19745
  {
@@ -19698,6 +19757,56 @@ function maybeUpdateAssistantTitle(args) {
19698
19757
  );
19699
19758
  return void 0;
19700
19759
  }
19760
+ if (isDm) {
19761
+ try {
19762
+ await args.getSlackAdapter().setAssistantTitle(
19763
+ assistantThreadContext.channelId,
19764
+ assistantThreadContext.threadTs,
19765
+ title
19766
+ );
19767
+ } catch (error) {
19768
+ const slackErrorCode = getSlackApiErrorCode(error);
19769
+ const assistantTitleErrorAttributes = {
19770
+ "app.slack.assistant_title.outcome": "permission_denied",
19771
+ ...slackErrorCode ? {
19772
+ "app.slack.assistant_title.error_code": slackErrorCode
19773
+ } : {}
19774
+ };
19775
+ if (isSlackTitlePermissionError(error)) {
19776
+ setSpanAttributes(assistantTitleErrorAttributes);
19777
+ logError(
19778
+ "thread_title_generation_permission_denied",
19779
+ {
19780
+ slackThreadId: args.threadId,
19781
+ slackUserId: args.requesterId,
19782
+ slackChannelId: args.channelId,
19783
+ runId: args.runId,
19784
+ assistantUserName: args.assistantUserName,
19785
+ modelId: args.modelId
19786
+ },
19787
+ assistantTitleErrorAttributes,
19788
+ "Skipping Slack thread title update due to permission error"
19789
+ );
19790
+ } else {
19791
+ logWarn(
19792
+ "thread_title_slack_update_failed",
19793
+ {
19794
+ slackThreadId: args.threadId,
19795
+ slackUserId: args.requesterId,
19796
+ slackChannelId: args.channelId,
19797
+ runId: args.runId,
19798
+ assistantUserName: args.assistantUserName,
19799
+ modelId: args.modelId
19800
+ },
19801
+ {
19802
+ "exception.message": error instanceof Error ? error.message : String(error)
19803
+ },
19804
+ "Slack thread title update failed"
19805
+ );
19806
+ }
19807
+ }
19808
+ }
19809
+ return { sourceMessageId: titleSourceMessage.id, title };
19701
19810
  })();
19702
19811
  }
19703
19812
 
@@ -20111,7 +20220,7 @@ function createReplyToThread(deps) {
20111
20220
  if (conversationId && loadedPiMessages.canCompact && piMessages?.length) {
20112
20221
  const compaction = await deps.services.contextCompactor.maybeCompact({
20113
20222
  conversation: preparedState.conversation,
20114
- conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
20223
+ conversationContext: preparedState.conversationContext,
20115
20224
  conversationId,
20116
20225
  metadata: {
20117
20226
  threadId,
@@ -20153,7 +20262,7 @@ function createReplyToThread(deps) {
20153
20262
  fullName: message.author.fullName ?? fallbackIdentity?.fullName,
20154
20263
  email: fallbackIdentity?.email
20155
20264
  },
20156
- conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
20265
+ conversationContext: preparedState.conversationContext,
20157
20266
  artifactState: preparedState.artifacts,
20158
20267
  piMessages,
20159
20268
  pendingAuth: preparedState.conversation.processing.pendingAuth,
@@ -20714,8 +20823,7 @@ function createPrepareTurnState(deps) {
20714
20823
  requesterId: args.context.requesterId,
20715
20824
  runId: args.context.runId
20716
20825
  });
20717
- const conversationContext = buildConversationContext(conversation);
20718
- const routingContext = buildConversationContext(conversation, {
20826
+ const conversationContext = buildConversationContext(conversation, {
20719
20827
  excludeMessageId: userMessageId
20720
20828
  });
20721
20829
  setSpanAttributes({
@@ -20730,7 +20838,6 @@ function createPrepareTurnState(deps) {
20730
20838
  sandboxId: existingSandboxId,
20731
20839
  sandboxDependencyProfileHash: existingSandboxDependencyProfileHash,
20732
20840
  conversationContext,
20733
- routingContext,
20734
20841
  userMessageId
20735
20842
  };
20736
20843
  };
@@ -20777,7 +20884,7 @@ function createSlackRuntime(options) {
20777
20884
  conversation: preparedState.conversation
20778
20885
  });
20779
20886
  },
20780
- getPreparedConversationContext: (preparedState) => preparedState.routingContext ?? preparedState.conversationContext,
20887
+ getPreparedConversationContext: (preparedState) => preparedState.conversationContext,
20781
20888
  decideSubscribedReply: services.subscribedReplyPolicy,
20782
20889
  recordSkippedSubscribedMessage: async ({
20783
20890
  thread,
@@ -44,7 +44,7 @@ export declare function toObservablePromptPart(part: {
44
44
  export declare function summarizeMessageText(text: string): string;
45
45
  /**
46
46
  * Put prior thread text before the current instruction when no Pi history
47
- * exists. These are top-level sibling blocks in the user message.
47
+ * exists. Structured thread XML is already a top-level prompt block.
48
48
  */
49
49
  export declare function buildUserTurnText(userInput: string, conversationContext?: string): string;
50
50
  /** Encode a non-image attachment as base64 XML for the prompt. */
@@ -8,7 +8,6 @@ export interface PreparedTurnState {
8
8
  channelConfiguration?: ChannelConfigurationService;
9
9
  conversation: ThreadConversationState;
10
10
  conversationContext?: string;
11
- routingContext?: string;
12
11
  sandboxId?: string;
13
12
  sandboxDependencyProfileHash?: string;
14
13
  userMessageId?: string;
@@ -5,6 +5,7 @@ import type { SkillMetadata } from "@/chat/skills";
5
5
  interface SandboxExecutionInput {
6
6
  toolName: string;
7
7
  input: unknown;
8
+ signal?: AbortSignal;
8
9
  }
9
10
  export interface SandboxExecutionEnvelope<T = unknown> {
10
11
  result: T;
@@ -6,6 +6,7 @@ interface SandboxToolExecutors {
6
6
  bash: (input: {
7
7
  command: string;
8
8
  env?: Record<string, string>;
9
+ signal?: AbortSignal;
9
10
  timeoutMs?: number;
10
11
  }) => Promise<{
11
12
  stdout: string;
@@ -13,6 +14,7 @@ interface SandboxToolExecutors {
13
14
  exitCode: number;
14
15
  stdoutTruncated: boolean;
15
16
  stderrTruncated: boolean;
17
+ aborted?: boolean;
16
18
  timedOut?: boolean;
17
19
  }>;
18
20
  readFile: (input: {
@@ -3,12 +3,17 @@ import { type ConversationMemoryService } from "@/chat/services/conversation-mem
3
3
  import type { ThreadArtifactsState } from "@/chat/state/artifacts";
4
4
  import type { ThreadConversationState } from "@/chat/state/conversation";
5
5
  /**
6
- * Best-effort assistant-thread title update for DM assistant threads.
6
+ * Best-effort conversation title generation for all Slack conversations.
7
7
  *
8
8
  * Title generation is intentionally detached from reply generation and visible
9
- * reply delivery. Stable Slack permission failures are treated as a terminal
10
- * skip for the current source message so later turns do not keep paying for
11
- * the same fast-model title generation call.
9
+ * reply delivery. For DM assistant threads the generated title is also pushed
10
+ * to Slack via `setAssistantTitle`. For channel conversations the title is
11
+ * generated and returned for dashboard reporting only — the Slack API for
12
+ * setting thread titles is DM-only and is not called.
13
+ *
14
+ * Stable Slack permission failures on DM title updates are treated as a
15
+ * terminal skip for the current source message so later turns do not keep
16
+ * paying for the same fast-model call that Slack will reject.
12
17
  */
13
18
  export declare function maybeUpdateAssistantTitle(args: {
14
19
  assistantThreadContext?: {
@@ -5,12 +5,23 @@ export interface ToolDefinition<TInputSchema extends TSchema = TSchema> {
5
5
  description: string;
6
6
  inputSchema: TInputSchema;
7
7
  annotations?: ToolAnnotations;
8
+ /**
9
+ * @deprecated Put tool-selection and usage guidance directly in `description`
10
+ * and parameter descriptions. Retained for plugin compatibility; may be
11
+ * removed in a future major version.
12
+ */
8
13
  promptSnippet?: string;
14
+ /**
15
+ * @deprecated Put tool-selection and usage guidance directly in `description`
16
+ * and parameter descriptions. Retained for plugin compatibility; may be
17
+ * removed in a future major version.
18
+ */
9
19
  promptGuidelines?: string[];
10
20
  prepareArguments?: (args: unknown) => Static<TInputSchema>;
11
21
  executionMode?: ToolExecutionMode;
12
22
  execute?: (input: Static<TInputSchema>, options: {
13
23
  experimental_context?: unknown;
24
+ signal?: AbortSignal;
14
25
  }) => Promise<unknown> | unknown;
15
26
  }
16
27
  /** Infer execute parameter types from the inputSchema via generic binding. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.60.1",
3
+ "version": "0.62.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -64,7 +64,7 @@
64
64
  "node-html-markdown": "^2.0.0",
65
65
  "yaml": "^2.9.0",
66
66
  "zod": "^4.4.3",
67
- "@sentry/junior-plugin-api": "0.60.1"
67
+ "@sentry/junior-plugin-api": "0.62.0"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/node": "^25.9.1",
@@ -76,7 +76,7 @@
76
76
  "typescript": "^6.0.3",
77
77
  "vercel": "^54.4.0",
78
78
  "vitest": "^4.1.7",
79
- "@sentry/junior-scheduler": "0.60.1"
79
+ "@sentry/junior-scheduler": "0.62.0"
80
80
  },
81
81
  "scripts": {
82
82
  "build": "tsup && tsc -p tsconfig.build.json --emitDeclarationOnly",