@sentry/junior 0.31.0 → 0.32.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.d.ts CHANGED
@@ -4,6 +4,8 @@ import { Hono } from 'hono';
4
4
  type WaitUntilFn = (task: Promise<unknown> | (() => Promise<unknown>)) => void;
5
5
 
6
6
  interface JuniorAppOptions {
7
+ /** Install-wide provider defaults (`provider.key` format). Channel overrides take precedence. */
8
+ configDefaults?: Record<string, unknown>;
7
9
  pluginPackages?: string[];
8
10
  waitUntil?: WaitUntilFn;
9
11
  }
package/dist/app.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  findSkillByName,
4
4
  loadSkillsByName,
5
5
  parseSkillInvocation
6
- } from "./chunk-3SH2A7VQ.js";
6
+ } from "./chunk-EHXMTKBA.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-LEYD42MR.js";
33
+ } from "./chunk-3M7ZD6FF.js";
34
34
  import {
35
35
  CredentialUnavailableError,
36
36
  buildOAuthTokenRequest,
@@ -46,6 +46,7 @@ import {
46
46
  getPluginOAuthConfig,
47
47
  getPluginProviders,
48
48
  hasRequiredOAuthScope,
49
+ isPluginConfigKey,
49
50
  isPluginProvider,
50
51
  isRecord,
51
52
  logError,
@@ -63,7 +64,7 @@ import {
63
64
  toOptionalString,
64
65
  withContext,
65
66
  withSpan
66
- } from "./chunk-RZJDO55D.js";
67
+ } from "./chunk-XARRBRQV.js";
67
68
  import "./chunk-Z3YD6NHK.js";
68
69
  import {
69
70
  discoverInstalledPluginPackageContent,
@@ -78,6 +79,26 @@ import "./chunk-2KG3PWR4.js";
78
79
  // src/app.ts
79
80
  import { Hono } from "hono";
80
81
 
82
+ // src/chat/configuration/defaults.ts
83
+ var installDefaults = {};
84
+ function setConfigDefaults(defaults) {
85
+ if (!defaults) {
86
+ installDefaults = {};
87
+ return;
88
+ }
89
+ for (const key of Object.keys(defaults)) {
90
+ if (!isPluginConfigKey(key)) {
91
+ throw new Error(
92
+ `configDefaults: "${key}" is not a registered plugin config key`
93
+ );
94
+ }
95
+ }
96
+ installDefaults = { ...defaults };
97
+ }
98
+ function getConfigDefaults() {
99
+ return installDefaults;
100
+ }
101
+
81
102
  // src/handlers/diagnostics.ts
82
103
  import { readFileSync } from "fs";
83
104
  import path from "path";
@@ -2925,7 +2946,9 @@ function formatLoadedSkillsForPrompt(skills) {
2925
2946
  lines.push(
2926
2947
  ` <skill name="${escapeXml(skill.name)}" location="${escapeXml(`${skillDir}/SKILL.md`)}">`
2927
2948
  );
2928
- lines.push(`References are relative to ${escapeXml(skillDir)}.`);
2949
+ lines.push(
2950
+ `Skill directory: ${escapeXml(skillDir)}. Resolve relative paths there; for skill-owned bash commands, cd there first or use absolute paths.`
2951
+ );
2929
2952
  lines.push("");
2930
2953
  lines.push(skill.body);
2931
2954
  lines.push(" </skill>");
@@ -3031,31 +3054,111 @@ function formatThreadParticipantsLines(participants) {
3031
3054
  return `- ${parts.join(", ")}`;
3032
3055
  });
3033
3056
  }
3057
+ function formatSlackCapabilityNames(capabilities) {
3058
+ const names = [
3059
+ capabilities?.canCreateCanvas ? "canvas_create" : "",
3060
+ capabilities?.canPostToChannel ? "channel_post" : "",
3061
+ capabilities?.canAddReactions ? "reaction_add" : ""
3062
+ ].filter(Boolean);
3063
+ return names.length > 0 ? names.join(", ") : "none";
3064
+ }
3034
3065
  var HEADER = "You are a Slack-based helper assistant. The behavior and output blocks below are authoritative; the personality block sets voice only.";
3035
- var BEHAVIOR_RULES = [
3036
- "- Load the best-matching skill/tool when relevant, then use it before answering; do not preload multiple skills or claim tool use that did not happen.",
3037
- "- After `loadSkill`, resolve references under `skill_dir`; for active MCP catalogs, use `searchMcpTools` then `callMcpTool` with exact returned tool names.",
3038
- "- Default to acting in-turn: use relevant available skills/tools to satisfy the request, continue until done or blocked, and only ask the user when access or required input is missing. If a fact cannot be verified, say so.",
3066
+ var TOOL_POLICY_RULES = [
3067
+ "- Tool schemas are the source of truth for parameters; tool names are case-sensitive, so call tools exactly by their exposed names and do not invent arguments.",
3068
+ "- Use tools for actionable work and for facts that are mutable, external, repository-backed, provider-backed, or requested as verified/current. Stable general knowledge and already-provided context may be answered directly.",
3069
+ "- Verification source order: conversation/thread context; user-provided attachments, links, and reference files; local/sandbox files when present; loaded skill references; repository/provider tools; public web. Use the nearest authoritative available source before weaker sources.",
3070
+ "- For repository or implementation questions, inspect the target repository first: local checkout when present, otherwise the configured GitHub/source provider. Do not treat loaded skill files as repo source unless the user asks about the skill. Cite file paths, symbols, PRs/issues, commits, or URLs that support the answer.",
3071
+ `- Sandbox-backed file and shell tools operate in an isolated workspace rooted at ${SANDBOX_WORKSPACE_ROOT}; readFile/writeFile paths are sandbox-workspace paths, bash runs inside that workspace, and attachFile accepts absolute or workspace-relative sandbox paths.`,
3072
+ "- If a sandbox-backed tool reports that sandbox execution is unavailable, treat that as a blocker for local file/shell inspection; do not pretend host files were inspected.",
3073
+ "- For user-provided URLs, use `webFetch`; for discovery, use `webSearch` then fetch/read promising sources; for current time/date context, use `systemTime`.",
3074
+ "- If the first result is empty, stale, ambiguous, or incomplete, try a focused alternate query, path, command, or source before concluding the answer cannot be verified."
3075
+ ];
3076
+ var TOOL_CALL_STYLE_RULES = [
3077
+ "- For routine low-risk tool use, call the tool directly without narrating the obvious step first.",
3078
+ "- Briefly narrate only when it helps the user understand multi-step work, sensitive actions, destructive actions, or a notable change in approach.",
3079
+ "- When a first-class tool exists for an action, use it directly instead of asking the user to run an equivalent command, slash command, or manual lookup.",
3080
+ "- Keep tool-call explanations separate from final answers; final answers should report results, evidence, or blockers."
3081
+ ];
3082
+ var SKILL_POLICY_RULES = [
3083
+ "- Before answering, scan `<available-skills>`. For matching operational or conceptual provider/repository workflow questions, load the most specific skill; do not answer from memory first. If none fits, do not load a skill.",
3084
+ "- Never load multiple skills up front. After `loadSkill`, follow `<loaded-skills>` and resolve relative references under that skill's location.",
3085
+ "- For explicit `/skill` triggers, treat that skill as selected unless the tool says it is unavailable.",
3086
+ "- For active MCP catalogs, use `searchMcpTools` to inspect descriptors before `callMcpTool`; pass exact returned `tool_name` values and put provider fields inside `arguments`.",
3087
+ "- Run authenticated provider commands directly after resolving target defaults; let the runtime handle auth pauses/resumes.",
3088
+ "- Run `jr-rpc config get|set|unset|list` as standalone bash commands for conversation-scoped provider defaults; do not chain them with `cd`, `&&`, pipes, or provider commands."
3089
+ ];
3090
+ var EXECUTION_CONTRACT_RULES = [
3091
+ "- Actionable request: act in this turn.",
3092
+ "- Continue until done or genuinely blocked. Do not finish with a plan, promise, or offer to check next when an available tool or source can move the request forward.",
3093
+ "- Completion means the final answer covers the user's actual ask, including requested follow-up checks, and is grounded in the best evidence you could access.",
3094
+ "- Ask the user only for missing access, approval, or a decision that blocks safe progress. Ask one focused question; otherwise infer conservatively and continue.",
3095
+ "- For conflicting evidence, compare sources and state which source is authoritative for the answer.",
3096
+ "- For non-trivial or long-running work, call `reportProgress` early when available, then only when the major phase changes. Routine tool calls should stay silent."
3097
+ ];
3098
+ var CONVERSATION_RULES = [
3039
3099
  "- In thread follow-ups, answer from prior thread context; do not repeat resolved clarifying questions.",
3040
- "- Keep work silent and post one result-focused reply unless blocked or waiting on user input; do not use reactions as progress.",
3041
- "- Do not claim an attachment, canvas, or channel post succeeded unless the tool returned success this turn; when it did, include any link the tool returned.",
3042
- "- Run authenticated provider commands directly; resolve target defaults first and let the runtime handle auth pauses/resumes.",
3043
- "- On resumed turns, post a brief continuation notice, then the resumed answer as a separate message.",
3100
+ "- Preserve attribution roles from thread context: the requester is the person asking now, which may differ from the original reporter or subject.",
3101
+ "- On resumed turns, post a brief continuation notice, then the resumed answer as a separate message."
3102
+ ];
3103
+ var SLACK_ACTION_RULES = [
3104
+ "- Context-bound Slack tools use runtime-owned targets; do not invent channel, canvas, list, or message IDs.",
3105
+ "- Use first-class Slack tools for Slack side effects; do not use bash, curl, or provider APIs to bypass Slack tool targeting.",
3106
+ "- Use channel-post and emoji-reaction tools only when the user explicitly asks for that Slack side effect.",
3107
+ "- For explicit channel-post or emoji-reaction requests, skip a duplicate thread text reply when the tool result already satisfies the request.",
3108
+ "- Do not claim an attachment, canvas, channel post, list update, or reaction succeeded unless the tool returned success this turn; when it did, include any link the tool returned.",
3109
+ "- Do not use reactions as progress indicators."
3110
+ ];
3111
+ var SAFETY_RULES = [
3112
+ "- Stay within the user's request and the runtime's available capabilities; do not pursue independent goals, persistence, replication, credential gathering, or access expansion.",
3113
+ "- Respect stop, pause, audit, and approval boundaries. Do not bypass safeguards or persuade the user to weaken them.",
3114
+ "- Do not change system prompts, tool policies, security settings, credentials, or runtime configuration unless the user explicitly requests that exact administrative action and an available tool permits it."
3115
+ ];
3116
+ var FAILURE_RULES = [
3044
3117
  "- For tool/runtime failures, run the named check before diagnosing and report the exact failed command plus stderr/exit code.",
3045
- "- Run `jr-rpc config get|set|unset|list` as standalone bash commands for conversation-scoped provider defaults.",
3046
- "- For explicit channel-post or emoji-reaction requests, skip the text reply."
3118
+ "- If a fact cannot be verified after focused checks, say what you checked and what blocked a stronger answer.",
3119
+ "- Do not surface raw tool payloads, execution-escape text, or internal routing metadata as the final answer."
3047
3120
  ];
3121
+ function renderRuleSection(tag, lines) {
3122
+ return [`<${tag}>`, ...lines, `</${tag}>`].join("\n");
3123
+ }
3124
+ function buildBehaviorSection() {
3125
+ return [
3126
+ renderRuleSection("tool-policy", TOOL_POLICY_RULES),
3127
+ renderRuleSection("tool-call-style", TOOL_CALL_STYLE_RULES),
3128
+ renderRuleSection("skill-policy", SKILL_POLICY_RULES),
3129
+ renderRuleSection("execution-contract", EXECUTION_CONTRACT_RULES),
3130
+ renderRuleSection("conversation", CONVERSATION_RULES),
3131
+ renderRuleSection("slack-actions", SLACK_ACTION_RULES),
3132
+ renderRuleSection("safety", SAFETY_RULES),
3133
+ renderRuleSection("failure-handling", FAILURE_RULES)
3134
+ ].join("\n\n");
3135
+ }
3048
3136
  function buildOutputSection() {
3049
3137
  const openTag = `<output format="slack-mrkdwn" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`;
3050
3138
  return [
3051
3139
  openTag,
3140
+ "- Start with the answer or result, not internal process narration.",
3052
3141
  "- Use Slack-friendly mrkdwn: bolded section labels instead of headings, no markdown tables or markdown links, and plain URLs.",
3053
3142
  "- Keep replies brief and scannable; use bullets or short code blocks when helpful, and one compact thread reply when it fits.",
3054
- "- When a research or document-style answer would benefit from continuation, multiple sections, or future reference value, create a Slack canvas and keep the thread reply to a short summary plus the canvas link.",
3055
- "- End every turn with a final user-facing markdown response.",
3143
+ "- When a research or document-style answer would benefit from continuation, multiple sections, or future reference value, create a Slack canvas and keep the thread reply to one or two short sentences plus the link; do not recap the canvas contents.",
3144
+ "- Unless a successful Slack side-effect tool intentionally satisfied the request by itself, end every turn with a final user-facing markdown response.",
3056
3145
  "</output>"
3057
3146
  ].join("\n");
3058
3147
  }
3148
+ function buildRuntimeSection(params) {
3149
+ const lines = [
3150
+ `- version: ${escapeXml(getRuntimeMetadata().version ?? "unknown")}`,
3151
+ params.modelId ? `- model: ${escapeXml(params.modelId)}` : "",
3152
+ params.fastModelId ? `- fast_model: ${escapeXml(params.fastModelId)}` : "",
3153
+ params.thinkingLevel ? `- thinking: ${escapeXml(params.thinkingLevel)}` : "",
3154
+ params.channelId ? "- channel: slack" : "",
3155
+ params.channelId ? `- slack_capabilities: ${escapeXml(
3156
+ formatSlackCapabilityNames(params.slackCapabilities)
3157
+ )}` : "",
3158
+ `- sandbox_workspace: ${escapeXml(SANDBOX_WORKSPACE_ROOT)}`
3159
+ ].filter(Boolean);
3160
+ return renderTagBlock("runtime", lines.join("\n"));
3161
+ }
3059
3162
  function buildContextSection(params) {
3060
3163
  const blocks = [];
3061
3164
  if (JUNIOR_WORLD) {
@@ -3070,10 +3173,6 @@ function buildContextSection(params) {
3070
3173
  ])
3071
3174
  );
3072
3175
  }
3073
- const runtimeVersion = getRuntimeMetadata().version;
3074
- if (runtimeVersion) {
3075
- blocks.push([`<runtime version="${escapeXml(runtimeVersion)}" />`]);
3076
- }
3077
3176
  blocks.push(
3078
3177
  renderIdentityBlock("assistant", {
3079
3178
  user_name: params.assistant?.userName ?? botConfig.userName,
@@ -3106,7 +3205,7 @@ function buildContextSection(params) {
3106
3205
  if (configLines) {
3107
3206
  blocks.push(
3108
3207
  renderTag("configuration", [
3109
- "Conversation-scoped defaults. Follow explicit user input when it conflicts.",
3208
+ "Install and conversation-scoped defaults. Channel overrides take precedence; follow explicit user input when it conflicts.",
3110
3209
  ...configLines
3111
3210
  ])
3112
3211
  );
@@ -3145,6 +3244,13 @@ function buildSystemPrompt(params) {
3145
3244
  const sections = [
3146
3245
  HEADER,
3147
3246
  renderTagBlock("personality", JUNIOR_PERSONALITY.trim()),
3247
+ renderTagBlock("behavior", buildBehaviorSection()),
3248
+ buildOutputSection(),
3249
+ buildCapabilitiesSection({
3250
+ availableSkills: params.availableSkills,
3251
+ activeSkills: params.activeSkills,
3252
+ activeMcpCatalogs: params.activeMcpCatalogs ?? []
3253
+ }),
3148
3254
  buildContextSection({
3149
3255
  assistant: params.assistant,
3150
3256
  requester: params.requester,
@@ -3154,13 +3260,7 @@ function buildSystemPrompt(params) {
3154
3260
  invocation: params.invocation,
3155
3261
  turnState: params.turnState
3156
3262
  }),
3157
- buildCapabilitiesSection({
3158
- availableSkills: params.availableSkills,
3159
- activeSkills: params.activeSkills,
3160
- activeMcpCatalogs: params.activeMcpCatalogs ?? []
3161
- }),
3162
- renderTagBlock("behavior", BEHAVIOR_RULES.join("\n")),
3163
- buildOutputSection()
3263
+ buildRuntimeSection(params.runtime ?? {})
3164
3264
  ];
3165
3265
  return sections.join("\n\n");
3166
3266
  }
@@ -4808,7 +4908,7 @@ function resolveMcpArguments(input) {
4808
4908
  }
4809
4909
  function createCallMcpToolTool(mcpToolManager, getActiveSkills) {
4810
4910
  return tool({
4811
- description: "Call an active MCP tool by exact tool_name. Use loadSkill to activate the provider, then searchMcpTools to discover tool names and schemas; authorization is handled by the runtime when required.",
4911
+ description: "Call an active MCP tool by exact tool_name. Use loadSkill to activate the provider, then searchMcpTools to discover tool names and schemas; copy required provider fields into arguments. Do not call with only tool_name unless the discovered tool has no arguments. Authorization is handled by the runtime when required.",
4812
4912
  inputSchema: Type4.Object(
4813
4913
  {
4814
4914
  tool_name: Type4.String({
@@ -4817,7 +4917,7 @@ function createCallMcpToolTool(mcpToolManager, getActiveSkills) {
4817
4917
  }),
4818
4918
  arguments: Type4.Optional(
4819
4919
  Type4.Record(Type4.String(), Type4.Unknown(), {
4820
- description: "Arguments matching the disclosed MCP tool schema."
4920
+ description: 'Arguments matching the disclosed MCP tool schema, for example { "query": "..." } when searchMcpTools shows query is required.'
4821
4921
  })
4822
4922
  )
4823
4923
  },
@@ -4875,13 +4975,15 @@ async function loadSkillFromHost(availableSkills, skillName) {
4875
4975
  skill_name: skill.name,
4876
4976
  description: skill.description,
4877
4977
  skill_dir: skillDir,
4978
+ working_directory: skillDir,
4878
4979
  location: skillFilePath,
4980
+ path_resolution: `Resolve relative paths in this skill against ${skillDir}. For bash commands from this skill, cd to ${skillDir} first or use absolute paths.`,
4879
4981
  instructions: loaded.body
4880
4982
  };
4881
4983
  }
4882
4984
  function createLoadSkillTool(availableSkills, options) {
4883
4985
  return tool({
4884
- description: "Load a skill by name so its instructions and provider tool catalog are available for this turn. When the result includes mcp_provider and available_tool_count, use searchMcpTools to list or search descriptors before callMcpTool. Use when a request clearly matches a known skill.",
4986
+ description: "Load a skill by name for this turn. The result includes working_directory; resolve skill paths there and run skill-owned bash commands from there or with absolute paths. When the result includes mcp_provider, use searchMcpTools before callMcpTool. Use when a request clearly matches a known skill.",
4885
4987
  inputSchema: Type5.Object({
4886
4988
  skill_name: Type5.String({
4887
4989
  minLength: 1,
@@ -5694,7 +5796,7 @@ function mergeRecentCanvases(existing, created) {
5694
5796
  }
5695
5797
  function createSlackCanvasCreateTool(context, state) {
5696
5798
  return tool({
5697
- description: "Create a Slack canvas for long-form output in the active assistant context channel. Use when the answer is better as a reusable document than a thread reply: long-form research, timelines, bios/profiles, structured notes, plans, comparisons, or anything likely to exceed one compact Slack reply. After creating it, keep the thread reply brief and include the canvas link. Do not use for short answers that fit cleanly in one normal thread reply.",
5799
+ description: "Create a Slack canvas for long-form output in the active assistant context channel. Use when the answer is better as a reusable document than a thread reply: long-form research, timelines, bios/profiles, structured notes, plans, comparisons, or anything likely to exceed one compact Slack reply. After creating it, reply with one or two short sentences plus the canvas link; do not recap the canvas contents. Do not use for short answers that fit cleanly in one normal thread reply.",
5698
5800
  inputSchema: Type12.Object({
5699
5801
  title: Type12.String({
5700
5802
  minLength: 1,
@@ -6435,25 +6537,24 @@ async function fetchWithPinnedLookup(url, resolved, signal) {
6435
6537
  chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
6436
6538
  });
6437
6539
  response.on("end", () => {
6438
- signal.removeEventListener("abort", onAbort);
6439
- const headers = new Headers();
6440
- for (const [name, value] of Object.entries(response.headers)) {
6441
- if (Array.isArray(value)) {
6442
- for (const item of value) {
6443
- headers.append(name, item);
6444
- }
6445
- continue;
6446
- }
6447
- if (typeof value === "string") {
6448
- headers.set(name, value);
6540
+ try {
6541
+ signal.removeEventListener("abort", onAbort);
6542
+ const headers = new Headers();
6543
+ const rawHeaders = response.rawHeaders;
6544
+ for (let i = 0; i < rawHeaders.length; i += 2) {
6545
+ headers.append(rawHeaders[i], rawHeaders[i + 1]);
6449
6546
  }
6547
+ const rawStatus = response.statusCode ?? 500;
6548
+ const status = rawStatus >= 200 && rawStatus <= 599 ? rawStatus : 502;
6549
+ resolve(
6550
+ new Response(Buffer.concat(chunks), {
6551
+ status,
6552
+ headers
6553
+ })
6554
+ );
6555
+ } catch (error) {
6556
+ reject(error);
6450
6557
  }
6451
- resolve(
6452
- new Response(Buffer.concat(chunks), {
6453
- status: response.statusCode ?? 500,
6454
- headers
6455
- })
6456
- );
6457
6558
  });
6458
6559
  }
6459
6560
  );
@@ -6544,9 +6645,7 @@ async function readResponseBody(response, maxBytes) {
6544
6645
  }
6545
6646
  chunks.push(value);
6546
6647
  }
6547
- return new TextDecoder().decode(
6548
- Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)))
6549
- );
6648
+ return new TextDecoder().decode(Buffer.concat(chunks));
6550
6649
  }
6551
6650
 
6552
6651
  // src/chat/tools/web/fetch-content.ts
@@ -7484,7 +7583,17 @@ if (args.length === 0 || args[0] === "--version" || args[0] === "version") {
7484
7583
  process.exit(0);
7485
7584
  }
7486
7585
 
7487
- if (args[0] === "issues" && args[1] === "list") {
7586
+ if (args[0] === "--help" || args[0] === "help") {
7587
+ outputText("USAGE\\n sentry issue list|view|events ...\\n sentry org list|view ...\\n sentry log list|view ...\\n sentry trace list|view|logs ...\\n sentry api ...\\n");
7588
+ process.exit(0);
7589
+ }
7590
+
7591
+ if (args.includes("--help")) {
7592
+ outputText("sentry eval stub help\\n");
7593
+ process.exit(0);
7594
+ }
7595
+
7596
+ if (args[0] === "issue" && args[1] === "list") {
7488
7597
  if (hasFlag("--json")) {
7489
7598
  outputJson([]);
7490
7599
  } else {
@@ -7493,7 +7602,7 @@ if (args[0] === "issues" && args[1] === "list") {
7493
7602
  process.exit(0);
7494
7603
  }
7495
7604
 
7496
- if (args[0] === "organizations" && args[1] === "list") {
7605
+ if (args[0] === "org" && args[1] === "list") {
7497
7606
  if (hasFlag("--json")) {
7498
7607
  outputJson([{ slug: "getsentry", name: "Sentry" }]);
7499
7608
  } else {
@@ -8948,6 +9057,8 @@ function trimTrailingAssistantMessages(messages) {
8948
9057
  // src/chat/services/reply-delivery-plan.ts
8949
9058
  var REACTION_ONLY_ACK_RE = /^(?::[a-z0-9_+-]+:|[\p{Extended_Pictographic}\uFE0F\u200D]+)$/u;
8950
9059
  var REDUNDANT_REACTION_ACK_TEXT = ["done", "got it", "ok", "okay"];
9060
+ var REACTION_INTENT_RE = /\b(react|reaction|emoji|thumbs?\s*up|acknowledge)\b/i;
9061
+ var REACTION_WITH_REPLY_INTENT_RE = /\b(confirm|explain|reply|respond|say|tell|summari[sz]e|why)\b/i;
8951
9062
  function normalizeReactionAckText(text) {
8952
9063
  return text.trim().toLowerCase().replace(/[!.]+$/g, "");
8953
9064
  }
@@ -8964,6 +9075,13 @@ function isRedundantReactionAckText(text) {
8964
9075
  normalized
8965
9076
  );
8966
9077
  }
9078
+ function isReactionOnlyIntent(text) {
9079
+ const normalized = text.trim();
9080
+ if (!normalized) {
9081
+ return false;
9082
+ }
9083
+ return REACTION_INTENT_RE.test(normalized) && !REACTION_WITH_REPLY_INTENT_RE.test(normalized);
9084
+ }
8967
9085
  function buildReplyDeliveryPlan(args) {
8968
9086
  const mode = args.explicitChannelPostIntent && args.channelPostPerformed ? "channel_only" : "thread";
8969
9087
  let attachFiles = "none";
@@ -9024,6 +9142,22 @@ Note: No file was attached in this turn. I need to attach the file before claimi
9024
9142
  }
9025
9143
 
9026
9144
  // src/chat/services/turn-result.ts
9145
+ var POST_CANVAS_REPLY_MAX_CHARS = 700;
9146
+ var POST_CANVAS_REPLY_MAX_LINES = 8;
9147
+ function isVerbosePostCanvasReply(text) {
9148
+ const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
9149
+ return text.length > POST_CANVAS_REPLY_MAX_CHARS || lines.length > POST_CANVAS_REPLY_MAX_LINES;
9150
+ }
9151
+ function getCreatedCanvasUrl(artifactStatePatch) {
9152
+ if (artifactStatePatch.lastCanvasUrl) {
9153
+ return artifactStatePatch.lastCanvasUrl;
9154
+ }
9155
+ return artifactStatePatch.recentCanvases?.find((canvas) => canvas.url)?.url;
9156
+ }
9157
+ function buildBriefPostCanvasReply(artifactStatePatch) {
9158
+ const canvasUrl = getCreatedCanvasUrl(artifactStatePatch);
9159
+ return canvasUrl ? `I created a canvas with the full reference: ${canvasUrl}` : "I created a canvas with the full reference.";
9160
+ }
9027
9161
  function buildTurnResult(input) {
9028
9162
  const {
9029
9163
  newMessages,
@@ -9053,6 +9187,7 @@ function buildTurnResult(input) {
9053
9187
  const channelPostPerformed = successfulToolNames.has(
9054
9188
  "slackChannelPostMessage"
9055
9189
  );
9190
+ const canvasCreated = successfulToolNames.has("slackCanvasCreate");
9056
9191
  const reactionPerformed = successfulToolNames.has("slackMessageAddReaction");
9057
9192
  const baseDeliveryPlan = buildReplyDeliveryPlan({
9058
9193
  explicitChannelPostIntent,
@@ -9085,7 +9220,9 @@ function buildTurnResult(input) {
9085
9220
  const usedPrimaryText = Boolean(primaryText);
9086
9221
  const outcome = primaryText ? stopReason === "error" ? "provider_error" : "success" : sideEffectOnlySuccess ? "success" : "execution_failure";
9087
9222
  const fallbackText = buildExecutionFailureMessage(toolErrorCount);
9088
- const responseText = primaryText || (sideEffectOnlySuccess ? "" : fallbackText);
9223
+ const suppressReactionOnlyText = reactionPerformed && !channelPostPerformed && replyFiles.length === 0 && Boolean(primaryText) && isReactionOnlyIntent(userInput);
9224
+ const rawResponseText = suppressReactionOnlyText ? "" : primaryText || (sideEffectOnlySuccess ? "" : fallbackText);
9225
+ const responseText = canvasCreated && isVerbosePostCanvasReply(rawResponseText) ? buildBriefPostCanvasReply(artifactStatePatch) : rawResponseText;
9089
9226
  const escapedOrRawPayload = Boolean(primaryText) && (isExecutionEscapeResponse(primaryText) || isRawToolPayloadResponse(primaryText));
9090
9227
  const resolvedText = escapedOrRawPayload ? fallbackText : enforceAttachmentClaimTruth(responseText, replyFiles.length > 0);
9091
9228
  const deliveryPlan = reactionPerformed && !resolvedText && replyFiles.length === 0 && !channelPostPerformed ? {
@@ -9149,7 +9286,13 @@ var turnExecutionProfileSchema = z.object({
9149
9286
  confidence: z.number().min(0).max(1),
9150
9287
  reason: z.string().min(1)
9151
9288
  });
9152
- var DEFAULT_THINKING_LEVEL = "low";
9289
+ var DEFAULT_THINKING_LEVEL = "medium";
9290
+ var THINKING_LEVEL_RANK = {
9291
+ none: 0,
9292
+ low: 1,
9293
+ medium: 2,
9294
+ high: 3
9295
+ };
9153
9296
  function trimContextForRouter(text) {
9154
9297
  const trimmed = text?.trim();
9155
9298
  if (!trimmed) {
@@ -9172,13 +9315,14 @@ function trimContextForRouter(text) {
9172
9315
  }
9173
9316
  function buildClassifierSystemPrompt() {
9174
9317
  return [
9175
- "You route assistant turns to the cheapest thinking level that is still likely to succeed.",
9318
+ "You route assistant turns to the thinking level most likely to produce a complete, source-grounded answer.",
9176
9319
  "Choose exactly one bucket: none, low, medium, or high.",
9177
9320
  "",
9178
- "Use none for greetings, acknowledgments, and trivial single-step asks.",
9179
- "Use low for straightforward explanations or simple one-step work.",
9180
- "Use medium for investigations, ambiguous asks, multi-step analysis, or likely multi-tool work.",
9321
+ "Use none only for greetings, acknowledgments, and turns that need no substantive assistant work.",
9322
+ "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.",
9323
+ "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.",
9181
9324
  "Use high for code changes, debugging/root-cause analysis, research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
9325
+ "When unsure between two non-none buckets, choose the higher bucket. Do not use low as the default.",
9182
9326
  "",
9183
9327
  "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.",
9184
9328
  "",
@@ -9251,15 +9395,31 @@ async function selectTurnThinkingLevel(args) {
9251
9395
  },
9252
9396
  prompt
9253
9397
  });
9398
+ const normalizedSelection = applyThinkingFloor(selection, {
9399
+ minimum: trimmedContext || turnBlockCount > 0 ? "medium" : void 0
9400
+ });
9254
9401
  setSpanAttributes({
9255
- "app.ai.thinking_level": selection.thinkingLevel,
9256
- "app.ai.thinking_level_reason": selection.reason,
9257
- ...selection.confidence !== void 0 ? { "app.ai.thinking_level_confidence": selection.confidence } : {}
9402
+ "app.ai.thinking_level": normalizedSelection.thinkingLevel,
9403
+ "app.ai.thinking_level_reason": normalizedSelection.reason,
9404
+ ...normalizedSelection.confidence !== void 0 ? {
9405
+ "app.ai.thinking_level_confidence": normalizedSelection.confidence
9406
+ } : {}
9258
9407
  });
9259
- return selection;
9408
+ return normalizedSelection;
9260
9409
  }
9261
9410
  );
9262
9411
  }
9412
+ function applyThinkingFloor(selection, args) {
9413
+ const minimum = args.minimum;
9414
+ if (!minimum || selection.thinkingLevel === "none" || THINKING_LEVEL_RANK[selection.thinkingLevel] >= THINKING_LEVEL_RANK[minimum]) {
9415
+ return selection;
9416
+ }
9417
+ return {
9418
+ ...selection,
9419
+ thinkingLevel: minimum,
9420
+ reason: `thinking_floor:${minimum}:${selection.reason}`
9421
+ };
9422
+ }
9263
9423
  async function classifyTurn(args) {
9264
9424
  try {
9265
9425
  const result = await args.completeObject({
@@ -9278,7 +9438,7 @@ async function classifyTurn(args) {
9278
9438
  return {
9279
9439
  confidence: parsed.confidence,
9280
9440
  thinkingLevel: DEFAULT_THINKING_LEVEL,
9281
- reason: `low_confidence_default:${reason}`
9441
+ reason: `low_confidence_medium_default:${reason}`
9282
9442
  };
9283
9443
  }
9284
9444
  return {
@@ -10013,6 +10173,7 @@ async function generateAssistantReply(messageText, context = {}) {
10013
10173
  timeoutResumeSliceId = currentSliceId;
10014
10174
  const persistedConfigurationValues = context.channelConfiguration ? await context.channelConfiguration.resolveValues() : {};
10015
10175
  configurationValues = {
10176
+ ...getConfigDefaults(),
10016
10177
  ...context.configuration ?? {},
10017
10178
  ...persistedConfigurationValues
10018
10179
  };
@@ -10215,6 +10376,8 @@ async function generateAssistantReply(messageText, context = {}) {
10215
10376
  assistantUserName: context.assistant?.userName,
10216
10377
  modelId: botConfig.modelId
10217
10378
  });
10379
+ const toolChannelId = context.toolChannelId ?? context.correlation?.channelId;
10380
+ const channelCapabilities = resolveChannelCapabilities(toolChannelId);
10218
10381
  const tools = createTools(
10219
10382
  availableSkills,
10220
10383
  {
@@ -10268,10 +10431,8 @@ async function generateAssistantReply(messageText, context = {}) {
10268
10431
  }
10269
10432
  },
10270
10433
  {
10271
- channelId: context.toolChannelId ?? context.correlation?.channelId,
10272
- channelCapabilities: resolveChannelCapabilities(
10273
- context.toolChannelId ?? context.correlation?.channelId
10274
- ),
10434
+ channelId: toolChannelId,
10435
+ channelCapabilities,
10275
10436
  messageTs: context.correlation?.messageTs,
10276
10437
  threadTs: context.correlation?.threadTs,
10277
10438
  userText: userInput,
@@ -10300,6 +10461,13 @@ async function generateAssistantReply(messageText, context = {}) {
10300
10461
  availableSkills,
10301
10462
  activeSkills,
10302
10463
  activeMcpCatalogs,
10464
+ runtime: {
10465
+ channelId: toolChannelId,
10466
+ fastModelId: botConfig.fastModelId,
10467
+ modelId: botConfig.modelId,
10468
+ slackCapabilities: channelCapabilities,
10469
+ thinkingLevel: thinkingSelection.thinkingLevel
10470
+ },
10303
10471
  invocation: skillInvocation,
10304
10472
  assistant: context.assistant,
10305
10473
  requester: context.requester,
@@ -15881,6 +16049,7 @@ async function createApp(options) {
15881
16049
  setPluginPackages(
15882
16050
  options?.pluginPackages ?? await resolveBuildPluginPackages()
15883
16051
  );
16052
+ setConfigDefaults(options?.configDefaults);
15884
16053
  const waitUntil = options?.waitUntil ?? await defaultWaitUntil();
15885
16054
  const app = new Hono();
15886
16055
  app.onError((err, c) => {
@@ -7,7 +7,7 @@ import {
7
7
  serializeGenAiAttribute,
8
8
  setSpanAttributes,
9
9
  withSpan
10
- } from "./chunk-RZJDO55D.js";
10
+ } from "./chunk-XARRBRQV.js";
11
11
 
12
12
  // src/chat/state/adapter.ts
13
13
  import { createMemoryState } from "@chat-adapter/state-memory";
@@ -2,7 +2,7 @@ import {
2
2
  getPluginForSkillPath,
3
3
  getPluginSkillRoots,
4
4
  logWarn
5
- } from "./chunk-RZJDO55D.js";
5
+ } from "./chunk-XARRBRQV.js";
6
6
  import {
7
7
  skillRoots
8
8
  } from "./chunk-XPXD3FCE.js";
@@ -2725,6 +2725,9 @@ function getPluginDefinition(provider) {
2725
2725
  function isPluginProvider(provider) {
2726
2726
  return ensurePluginsLoaded().pluginsByName.has(provider);
2727
2727
  }
2728
+ function isPluginConfigKey(key) {
2729
+ return ensurePluginsLoaded().pluginConfigKeys.has(key);
2730
+ }
2728
2731
  function createPluginBroker(provider, deps) {
2729
2732
  const plugin = ensurePluginsLoaded().pluginsByName.get(provider);
2730
2733
  if (!plugin) {
@@ -2787,5 +2790,6 @@ export {
2787
2790
  getPluginForSkillPath,
2788
2791
  getPluginDefinition,
2789
2792
  isPluginProvider,
2793
+ isPluginConfigKey,
2790
2794
  createPluginBroker
2791
2795
  };
package/dist/cli/check.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  parseSkillFile
3
- } from "../chunk-3SH2A7VQ.js";
3
+ } from "../chunk-EHXMTKBA.js";
4
4
  import {
5
5
  parsePluginManifest
6
- } from "../chunk-RZJDO55D.js";
6
+ } from "../chunk-XARRBRQV.js";
7
7
  import "../chunk-Z3YD6NHK.js";
8
8
  import "../chunk-XPXD3FCE.js";
9
9
  import "../chunk-2KG3PWR4.js";
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  disconnectStateAdapter,
3
3
  resolveRuntimeDependencySnapshot
4
- } from "../chunk-LEYD42MR.js";
4
+ } from "../chunk-3M7ZD6FF.js";
5
5
  import {
6
6
  getPluginProviders,
7
7
  getPluginRuntimeDependencies,
8
8
  getPluginRuntimePostinstall
9
- } from "../chunk-RZJDO55D.js";
9
+ } from "../chunk-XARRBRQV.js";
10
10
  import "../chunk-Z3YD6NHK.js";
11
11
  import "../chunk-XPXD3FCE.js";
12
12
  import "../chunk-2KG3PWR4.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"