@sentry/junior 0.31.0 → 0.33.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";
@@ -1110,7 +1131,6 @@ async function postSlackMessage(input) {
1110
1131
  () => getSlackClient().chat.postMessage({
1111
1132
  channel: channelId,
1112
1133
  text,
1113
- mrkdwn: true,
1114
1134
  ...input.blocks?.length ? {
1115
1135
  blocks: input.blocks
1116
1136
  } : {},
@@ -2925,7 +2945,9 @@ function formatLoadedSkillsForPrompt(skills) {
2925
2945
  lines.push(
2926
2946
  ` <skill name="${escapeXml(skill.name)}" location="${escapeXml(`${skillDir}/SKILL.md`)}">`
2927
2947
  );
2928
- lines.push(`References are relative to ${escapeXml(skillDir)}.`);
2948
+ lines.push(
2949
+ `Skill directory: ${escapeXml(skillDir)}. Resolve relative paths there; for skill-owned bash commands, cd there first or use absolute paths.`
2950
+ );
2929
2951
  lines.push("");
2930
2952
  lines.push(skill.body);
2931
2953
  lines.push(" </skill>");
@@ -3031,31 +3053,111 @@ function formatThreadParticipantsLines(participants) {
3031
3053
  return `- ${parts.join(", ")}`;
3032
3054
  });
3033
3055
  }
3056
+ function formatSlackCapabilityNames(capabilities) {
3057
+ const names = [
3058
+ capabilities?.canCreateCanvas ? "canvas_create" : "",
3059
+ capabilities?.canPostToChannel ? "channel_post" : "",
3060
+ capabilities?.canAddReactions ? "reaction_add" : ""
3061
+ ].filter(Boolean);
3062
+ return names.length > 0 ? names.join(", ") : "none";
3063
+ }
3034
3064
  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.",
3065
+ var TOOL_POLICY_RULES = [
3066
+ "- 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.",
3067
+ "- 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.",
3068
+ "- 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.",
3069
+ "- 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.",
3070
+ `- 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.`,
3071
+ "- 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.",
3072
+ "- For user-provided URLs, use `webFetch`; for discovery, use `webSearch` then fetch/read promising sources; for current time/date context, use `systemTime`.",
3073
+ "- 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."
3074
+ ];
3075
+ var TOOL_CALL_STYLE_RULES = [
3076
+ "- For routine low-risk tool use, call the tool directly without narrating the obvious step first.",
3077
+ "- Briefly narrate only when it helps the user understand multi-step work, sensitive actions, destructive actions, or a notable change in approach.",
3078
+ "- 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.",
3079
+ "- Keep tool-call explanations separate from final answers; final answers should report results, evidence, or blockers."
3080
+ ];
3081
+ var SKILL_POLICY_RULES = [
3082
+ "- 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.",
3083
+ "- Never load multiple skills up front. After `loadSkill`, follow `<loaded-skills>` and resolve relative references under that skill's location.",
3084
+ "- For explicit `/skill` triggers, treat that skill as selected unless the tool says it is unavailable.",
3085
+ "- For active MCP catalogs, use `searchMcpTools` to inspect descriptors before `callMcpTool`; pass exact returned `tool_name` values and put provider fields inside `arguments`.",
3086
+ "- Run authenticated provider commands directly after resolving target defaults; let the runtime handle auth pauses/resumes.",
3087
+ "- 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."
3088
+ ];
3089
+ var EXECUTION_CONTRACT_RULES = [
3090
+ "- Actionable request: act in this turn.",
3091
+ "- 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.",
3092
+ "- 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.",
3093
+ "- Ask the user only for missing access, approval, or a decision that blocks safe progress. Ask one focused question; otherwise infer conservatively and continue.",
3094
+ "- For conflicting evidence, compare sources and state which source is authoritative for the answer.",
3095
+ "- 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."
3096
+ ];
3097
+ var CONVERSATION_RULES = [
3039
3098
  "- 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.",
3099
+ "- Preserve attribution roles from thread context: the requester is the person asking now, which may differ from the original reporter or subject.",
3100
+ "- On resumed turns, post a brief continuation notice, then the resumed answer as a separate message."
3101
+ ];
3102
+ var SLACK_ACTION_RULES = [
3103
+ "- Context-bound Slack tools use runtime-owned targets; do not invent channel, canvas, list, or message IDs.",
3104
+ "- Use first-class Slack tools for Slack side effects; do not use bash, curl, or provider APIs to bypass Slack tool targeting.",
3105
+ "- Use channel-post and emoji-reaction tools only when the user explicitly asks for that Slack side effect.",
3106
+ "- For explicit channel-post or emoji-reaction requests, skip a duplicate thread text reply when the tool result already satisfies the request.",
3107
+ "- 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.",
3108
+ "- Do not use reactions as progress indicators."
3109
+ ];
3110
+ var SAFETY_RULES = [
3111
+ "- Stay within the user's request and the runtime's available capabilities; do not pursue independent goals, persistence, replication, credential gathering, or access expansion.",
3112
+ "- Respect stop, pause, audit, and approval boundaries. Do not bypass safeguards or persuade the user to weaken them.",
3113
+ "- 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."
3114
+ ];
3115
+ var FAILURE_RULES = [
3044
3116
  "- 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."
3117
+ "- If a fact cannot be verified after focused checks, say what you checked and what blocked a stronger answer.",
3118
+ "- Do not surface raw tool payloads, execution-escape text, or internal routing metadata as the final answer."
3047
3119
  ];
3120
+ function renderRuleSection(tag, lines) {
3121
+ return [`<${tag}>`, ...lines, `</${tag}>`].join("\n");
3122
+ }
3123
+ function buildBehaviorSection() {
3124
+ return [
3125
+ renderRuleSection("tool-policy", TOOL_POLICY_RULES),
3126
+ renderRuleSection("tool-call-style", TOOL_CALL_STYLE_RULES),
3127
+ renderRuleSection("skill-policy", SKILL_POLICY_RULES),
3128
+ renderRuleSection("execution-contract", EXECUTION_CONTRACT_RULES),
3129
+ renderRuleSection("conversation", CONVERSATION_RULES),
3130
+ renderRuleSection("slack-actions", SLACK_ACTION_RULES),
3131
+ renderRuleSection("safety", SAFETY_RULES),
3132
+ renderRuleSection("failure-handling", FAILURE_RULES)
3133
+ ].join("\n\n");
3134
+ }
3048
3135
  function buildOutputSection() {
3049
- const openTag = `<output format="slack-mrkdwn" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`;
3136
+ const openTag = `<output format="slack-markdown" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`;
3050
3137
  return [
3051
3138
  openTag,
3052
- "- Use Slack-friendly mrkdwn: bolded section labels instead of headings, no markdown tables or markdown links, and plain URLs.",
3139
+ "- Start with the answer or result, not internal process narration.",
3140
+ "- Use Slack-flavored Markdown: **bold** section labels, `code`, [text](url) links, bullet lists, and fenced code blocks. No tables.",
3053
3141
  "- 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.",
3142
+ "- 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.",
3143
+ "- Unless a successful Slack side-effect tool intentionally satisfied the request by itself, end every turn with a final user-facing markdown response.",
3056
3144
  "</output>"
3057
3145
  ].join("\n");
3058
3146
  }
3147
+ function buildRuntimeSection(params) {
3148
+ const lines = [
3149
+ `- version: ${escapeXml(getRuntimeMetadata().version ?? "unknown")}`,
3150
+ params.modelId ? `- model: ${escapeXml(params.modelId)}` : "",
3151
+ params.fastModelId ? `- fast_model: ${escapeXml(params.fastModelId)}` : "",
3152
+ params.thinkingLevel ? `- thinking: ${escapeXml(params.thinkingLevel)}` : "",
3153
+ params.channelId ? "- channel: slack" : "",
3154
+ params.channelId ? `- slack_capabilities: ${escapeXml(
3155
+ formatSlackCapabilityNames(params.slackCapabilities)
3156
+ )}` : "",
3157
+ `- sandbox_workspace: ${escapeXml(SANDBOX_WORKSPACE_ROOT)}`
3158
+ ].filter(Boolean);
3159
+ return renderTagBlock("runtime", lines.join("\n"));
3160
+ }
3059
3161
  function buildContextSection(params) {
3060
3162
  const blocks = [];
3061
3163
  if (JUNIOR_WORLD) {
@@ -3070,10 +3172,6 @@ function buildContextSection(params) {
3070
3172
  ])
3071
3173
  );
3072
3174
  }
3073
- const runtimeVersion = getRuntimeMetadata().version;
3074
- if (runtimeVersion) {
3075
- blocks.push([`<runtime version="${escapeXml(runtimeVersion)}" />`]);
3076
- }
3077
3175
  blocks.push(
3078
3176
  renderIdentityBlock("assistant", {
3079
3177
  user_name: params.assistant?.userName ?? botConfig.userName,
@@ -3106,7 +3204,7 @@ function buildContextSection(params) {
3106
3204
  if (configLines) {
3107
3205
  blocks.push(
3108
3206
  renderTag("configuration", [
3109
- "Conversation-scoped defaults. Follow explicit user input when it conflicts.",
3207
+ "Install and conversation-scoped defaults. Channel overrides take precedence; follow explicit user input when it conflicts.",
3110
3208
  ...configLines
3111
3209
  ])
3112
3210
  );
@@ -3145,6 +3243,13 @@ function buildSystemPrompt(params) {
3145
3243
  const sections = [
3146
3244
  HEADER,
3147
3245
  renderTagBlock("personality", JUNIOR_PERSONALITY.trim()),
3246
+ renderTagBlock("behavior", buildBehaviorSection()),
3247
+ buildOutputSection(),
3248
+ buildCapabilitiesSection({
3249
+ availableSkills: params.availableSkills,
3250
+ activeSkills: params.activeSkills,
3251
+ activeMcpCatalogs: params.activeMcpCatalogs ?? []
3252
+ }),
3148
3253
  buildContextSection({
3149
3254
  assistant: params.assistant,
3150
3255
  requester: params.requester,
@@ -3154,13 +3259,7 @@ function buildSystemPrompt(params) {
3154
3259
  invocation: params.invocation,
3155
3260
  turnState: params.turnState
3156
3261
  }),
3157
- buildCapabilitiesSection({
3158
- availableSkills: params.availableSkills,
3159
- activeSkills: params.activeSkills,
3160
- activeMcpCatalogs: params.activeMcpCatalogs ?? []
3161
- }),
3162
- renderTagBlock("behavior", BEHAVIOR_RULES.join("\n")),
3163
- buildOutputSection()
3262
+ buildRuntimeSection(params.runtime ?? {})
3164
3263
  ];
3165
3264
  return sections.join("\n\n");
3166
3265
  }
@@ -4808,7 +4907,7 @@ function resolveMcpArguments(input) {
4808
4907
  }
4809
4908
  function createCallMcpToolTool(mcpToolManager, getActiveSkills) {
4810
4909
  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.",
4910
+ 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
4911
  inputSchema: Type4.Object(
4813
4912
  {
4814
4913
  tool_name: Type4.String({
@@ -4817,7 +4916,7 @@ function createCallMcpToolTool(mcpToolManager, getActiveSkills) {
4817
4916
  }),
4818
4917
  arguments: Type4.Optional(
4819
4918
  Type4.Record(Type4.String(), Type4.Unknown(), {
4820
- description: "Arguments matching the disclosed MCP tool schema."
4919
+ description: 'Arguments matching the disclosed MCP tool schema, for example { "query": "..." } when searchMcpTools shows query is required.'
4821
4920
  })
4822
4921
  )
4823
4922
  },
@@ -4875,13 +4974,15 @@ async function loadSkillFromHost(availableSkills, skillName) {
4875
4974
  skill_name: skill.name,
4876
4975
  description: skill.description,
4877
4976
  skill_dir: skillDir,
4977
+ working_directory: skillDir,
4878
4978
  location: skillFilePath,
4979
+ 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
4980
  instructions: loaded.body
4880
4981
  };
4881
4982
  }
4882
4983
  function createLoadSkillTool(availableSkills, options) {
4883
4984
  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.",
4985
+ 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
4986
  inputSchema: Type5.Object({
4886
4987
  skill_name: Type5.String({
4887
4988
  minLength: 1,
@@ -5694,7 +5795,7 @@ function mergeRecentCanvases(existing, created) {
5694
5795
  }
5695
5796
  function createSlackCanvasCreateTool(context, state) {
5696
5797
  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.",
5798
+ 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
5799
  inputSchema: Type12.Object({
5699
5800
  title: Type12.String({
5700
5801
  minLength: 1,
@@ -6435,25 +6536,24 @@ async function fetchWithPinnedLookup(url, resolved, signal) {
6435
6536
  chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
6436
6537
  });
6437
6538
  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);
6539
+ try {
6540
+ signal.removeEventListener("abort", onAbort);
6541
+ const headers = new Headers();
6542
+ const rawHeaders = response.rawHeaders;
6543
+ for (let i = 0; i < rawHeaders.length; i += 2) {
6544
+ headers.append(rawHeaders[i], rawHeaders[i + 1]);
6449
6545
  }
6546
+ const rawStatus = response.statusCode ?? 500;
6547
+ const status = rawStatus >= 200 && rawStatus <= 599 ? rawStatus : 502;
6548
+ resolve(
6549
+ new Response(Buffer.concat(chunks), {
6550
+ status,
6551
+ headers
6552
+ })
6553
+ );
6554
+ } catch (error) {
6555
+ reject(error);
6450
6556
  }
6451
- resolve(
6452
- new Response(Buffer.concat(chunks), {
6453
- status: response.statusCode ?? 500,
6454
- headers
6455
- })
6456
- );
6457
6557
  });
6458
6558
  }
6459
6559
  );
@@ -6544,9 +6644,7 @@ async function readResponseBody(response, maxBytes) {
6544
6644
  }
6545
6645
  chunks.push(value);
6546
6646
  }
6547
- return new TextDecoder().decode(
6548
- Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)))
6549
- );
6647
+ return new TextDecoder().decode(Buffer.concat(chunks));
6550
6648
  }
6551
6649
 
6552
6650
  // src/chat/tools/web/fetch-content.ts
@@ -7484,7 +7582,17 @@ if (args.length === 0 || args[0] === "--version" || args[0] === "version") {
7484
7582
  process.exit(0);
7485
7583
  }
7486
7584
 
7487
- if (args[0] === "issues" && args[1] === "list") {
7585
+ if (args[0] === "--help" || args[0] === "help") {
7586
+ 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");
7587
+ process.exit(0);
7588
+ }
7589
+
7590
+ if (args.includes("--help")) {
7591
+ outputText("sentry eval stub help\\n");
7592
+ process.exit(0);
7593
+ }
7594
+
7595
+ if (args[0] === "issue" && args[1] === "list") {
7488
7596
  if (hasFlag("--json")) {
7489
7597
  outputJson([]);
7490
7598
  } else {
@@ -7493,7 +7601,7 @@ if (args[0] === "issues" && args[1] === "list") {
7493
7601
  process.exit(0);
7494
7602
  }
7495
7603
 
7496
- if (args[0] === "organizations" && args[1] === "list") {
7604
+ if (args[0] === "org" && args[1] === "list") {
7497
7605
  if (hasFlag("--json")) {
7498
7606
  outputJson([{ slug: "getsentry", name: "Sentry" }]);
7499
7607
  } else {
@@ -8948,6 +9056,8 @@ function trimTrailingAssistantMessages(messages) {
8948
9056
  // src/chat/services/reply-delivery-plan.ts
8949
9057
  var REACTION_ONLY_ACK_RE = /^(?::[a-z0-9_+-]+:|[\p{Extended_Pictographic}\uFE0F\u200D]+)$/u;
8950
9058
  var REDUNDANT_REACTION_ACK_TEXT = ["done", "got it", "ok", "okay"];
9059
+ var REACTION_INTENT_RE = /\b(react|reaction|emoji|thumbs?\s*up|acknowledge)\b/i;
9060
+ var REACTION_WITH_REPLY_INTENT_RE = /\b(confirm|explain|reply|respond|say|tell|summari[sz]e|why)\b/i;
8951
9061
  function normalizeReactionAckText(text) {
8952
9062
  return text.trim().toLowerCase().replace(/[!.]+$/g, "");
8953
9063
  }
@@ -8964,6 +9074,13 @@ function isRedundantReactionAckText(text) {
8964
9074
  normalized
8965
9075
  );
8966
9076
  }
9077
+ function isReactionOnlyIntent(text) {
9078
+ const normalized = text.trim();
9079
+ if (!normalized) {
9080
+ return false;
9081
+ }
9082
+ return REACTION_INTENT_RE.test(normalized) && !REACTION_WITH_REPLY_INTENT_RE.test(normalized);
9083
+ }
8967
9084
  function buildReplyDeliveryPlan(args) {
8968
9085
  const mode = args.explicitChannelPostIntent && args.channelPostPerformed ? "channel_only" : "thread";
8969
9086
  let attachFiles = "none";
@@ -9024,6 +9141,22 @@ Note: No file was attached in this turn. I need to attach the file before claimi
9024
9141
  }
9025
9142
 
9026
9143
  // src/chat/services/turn-result.ts
9144
+ var POST_CANVAS_REPLY_MAX_CHARS = 700;
9145
+ var POST_CANVAS_REPLY_MAX_LINES = 8;
9146
+ function isVerbosePostCanvasReply(text) {
9147
+ const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
9148
+ return text.length > POST_CANVAS_REPLY_MAX_CHARS || lines.length > POST_CANVAS_REPLY_MAX_LINES;
9149
+ }
9150
+ function getCreatedCanvasUrl(artifactStatePatch) {
9151
+ if (artifactStatePatch.lastCanvasUrl) {
9152
+ return artifactStatePatch.lastCanvasUrl;
9153
+ }
9154
+ return artifactStatePatch.recentCanvases?.find((canvas) => canvas.url)?.url;
9155
+ }
9156
+ function buildBriefPostCanvasReply(artifactStatePatch) {
9157
+ const canvasUrl = getCreatedCanvasUrl(artifactStatePatch);
9158
+ return canvasUrl ? `I created a canvas with the full reference: ${canvasUrl}` : "I created a canvas with the full reference.";
9159
+ }
9027
9160
  function buildTurnResult(input) {
9028
9161
  const {
9029
9162
  newMessages,
@@ -9053,6 +9186,7 @@ function buildTurnResult(input) {
9053
9186
  const channelPostPerformed = successfulToolNames.has(
9054
9187
  "slackChannelPostMessage"
9055
9188
  );
9189
+ const canvasCreated = successfulToolNames.has("slackCanvasCreate");
9056
9190
  const reactionPerformed = successfulToolNames.has("slackMessageAddReaction");
9057
9191
  const baseDeliveryPlan = buildReplyDeliveryPlan({
9058
9192
  explicitChannelPostIntent,
@@ -9085,7 +9219,9 @@ function buildTurnResult(input) {
9085
9219
  const usedPrimaryText = Boolean(primaryText);
9086
9220
  const outcome = primaryText ? stopReason === "error" ? "provider_error" : "success" : sideEffectOnlySuccess ? "success" : "execution_failure";
9087
9221
  const fallbackText = buildExecutionFailureMessage(toolErrorCount);
9088
- const responseText = primaryText || (sideEffectOnlySuccess ? "" : fallbackText);
9222
+ const suppressReactionOnlyText = reactionPerformed && !channelPostPerformed && replyFiles.length === 0 && Boolean(primaryText) && isReactionOnlyIntent(userInput);
9223
+ const rawResponseText = suppressReactionOnlyText ? "" : primaryText || (sideEffectOnlySuccess ? "" : fallbackText);
9224
+ const responseText = canvasCreated && isVerbosePostCanvasReply(rawResponseText) ? buildBriefPostCanvasReply(artifactStatePatch) : rawResponseText;
9089
9225
  const escapedOrRawPayload = Boolean(primaryText) && (isExecutionEscapeResponse(primaryText) || isRawToolPayloadResponse(primaryText));
9090
9226
  const resolvedText = escapedOrRawPayload ? fallbackText : enforceAttachmentClaimTruth(responseText, replyFiles.length > 0);
9091
9227
  const deliveryPlan = reactionPerformed && !resolvedText && replyFiles.length === 0 && !channelPostPerformed ? {
@@ -9149,7 +9285,13 @@ var turnExecutionProfileSchema = z.object({
9149
9285
  confidence: z.number().min(0).max(1),
9150
9286
  reason: z.string().min(1)
9151
9287
  });
9152
- var DEFAULT_THINKING_LEVEL = "low";
9288
+ var DEFAULT_THINKING_LEVEL = "medium";
9289
+ var THINKING_LEVEL_RANK = {
9290
+ none: 0,
9291
+ low: 1,
9292
+ medium: 2,
9293
+ high: 3
9294
+ };
9153
9295
  function trimContextForRouter(text) {
9154
9296
  const trimmed = text?.trim();
9155
9297
  if (!trimmed) {
@@ -9172,13 +9314,14 @@ function trimContextForRouter(text) {
9172
9314
  }
9173
9315
  function buildClassifierSystemPrompt() {
9174
9316
  return [
9175
- "You route assistant turns to the cheapest thinking level that is still likely to succeed.",
9317
+ "You route assistant turns to the thinking level most likely to produce a complete, source-grounded answer.",
9176
9318
  "Choose exactly one bucket: none, low, medium, or high.",
9177
9319
  "",
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.",
9320
+ "Use none only for greetings, acknowledgments, and turns that need no substantive assistant work.",
9321
+ "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.",
9322
+ "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
9323
  "Use high for code changes, debugging/root-cause analysis, research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
9324
+ "When unsure between two non-none buckets, choose the higher bucket. Do not use low as the default.",
9182
9325
  "",
9183
9326
  "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
9327
  "",
@@ -9251,15 +9394,31 @@ async function selectTurnThinkingLevel(args) {
9251
9394
  },
9252
9395
  prompt
9253
9396
  });
9397
+ const normalizedSelection = applyThinkingFloor(selection, {
9398
+ minimum: trimmedContext || turnBlockCount > 0 ? "medium" : void 0
9399
+ });
9254
9400
  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 } : {}
9401
+ "app.ai.thinking_level": normalizedSelection.thinkingLevel,
9402
+ "app.ai.thinking_level_reason": normalizedSelection.reason,
9403
+ ...normalizedSelection.confidence !== void 0 ? {
9404
+ "app.ai.thinking_level_confidence": normalizedSelection.confidence
9405
+ } : {}
9258
9406
  });
9259
- return selection;
9407
+ return normalizedSelection;
9260
9408
  }
9261
9409
  );
9262
9410
  }
9411
+ function applyThinkingFloor(selection, args) {
9412
+ const minimum = args.minimum;
9413
+ if (!minimum || selection.thinkingLevel === "none" || THINKING_LEVEL_RANK[selection.thinkingLevel] >= THINKING_LEVEL_RANK[minimum]) {
9414
+ return selection;
9415
+ }
9416
+ return {
9417
+ ...selection,
9418
+ thinkingLevel: minimum,
9419
+ reason: `thinking_floor:${minimum}:${selection.reason}`
9420
+ };
9421
+ }
9263
9422
  async function classifyTurn(args) {
9264
9423
  try {
9265
9424
  const result = await args.completeObject({
@@ -9278,7 +9437,7 @@ async function classifyTurn(args) {
9278
9437
  return {
9279
9438
  confidence: parsed.confidence,
9280
9439
  thinkingLevel: DEFAULT_THINKING_LEVEL,
9281
- reason: `low_confidence_default:${reason}`
9440
+ reason: `low_confidence_medium_default:${reason}`
9282
9441
  };
9283
9442
  }
9284
9443
  return {
@@ -10013,6 +10172,7 @@ async function generateAssistantReply(messageText, context = {}) {
10013
10172
  timeoutResumeSliceId = currentSliceId;
10014
10173
  const persistedConfigurationValues = context.channelConfiguration ? await context.channelConfiguration.resolveValues() : {};
10015
10174
  configurationValues = {
10175
+ ...getConfigDefaults(),
10016
10176
  ...context.configuration ?? {},
10017
10177
  ...persistedConfigurationValues
10018
10178
  };
@@ -10215,6 +10375,8 @@ async function generateAssistantReply(messageText, context = {}) {
10215
10375
  assistantUserName: context.assistant?.userName,
10216
10376
  modelId: botConfig.modelId
10217
10377
  });
10378
+ const toolChannelId = context.toolChannelId ?? context.correlation?.channelId;
10379
+ const channelCapabilities = resolveChannelCapabilities(toolChannelId);
10218
10380
  const tools = createTools(
10219
10381
  availableSkills,
10220
10382
  {
@@ -10268,10 +10430,8 @@ async function generateAssistantReply(messageText, context = {}) {
10268
10430
  }
10269
10431
  },
10270
10432
  {
10271
- channelId: context.toolChannelId ?? context.correlation?.channelId,
10272
- channelCapabilities: resolveChannelCapabilities(
10273
- context.toolChannelId ?? context.correlation?.channelId
10274
- ),
10433
+ channelId: toolChannelId,
10434
+ channelCapabilities,
10275
10435
  messageTs: context.correlation?.messageTs,
10276
10436
  threadTs: context.correlation?.threadTs,
10277
10437
  userText: userInput,
@@ -10300,6 +10460,13 @@ async function generateAssistantReply(messageText, context = {}) {
10300
10460
  availableSkills,
10301
10461
  activeSkills,
10302
10462
  activeMcpCatalogs,
10463
+ runtime: {
10464
+ channelId: toolChannelId,
10465
+ fastModelId: botConfig.fastModelId,
10466
+ modelId: botConfig.modelId,
10467
+ slackCapabilities: channelCapabilities,
10468
+ thinkingLevel: thinkingSelection.thinkingLevel
10469
+ },
10303
10470
  invocation: skillInvocation,
10304
10471
  assistant: context.assistant,
10305
10472
  requester: context.requester,
@@ -11062,25 +11229,25 @@ function buildSlackReplyFooter(args) {
11062
11229
  return items.length > 0 ? { items } : void 0;
11063
11230
  }
11064
11231
  function buildSlackReplyBlocks(text, footer) {
11065
- if (!text.trim() || !footer?.items.length) {
11232
+ if (!text.trim()) {
11066
11233
  return void 0;
11067
11234
  }
11068
- return [
11069
- {
11070
- type: "section",
11071
- text: {
11072
- type: "mrkdwn",
11073
- text
11074
- }
11075
- },
11235
+ const blocks = [
11076
11236
  {
11237
+ type: "markdown",
11238
+ text
11239
+ }
11240
+ ];
11241
+ if (footer?.items.length) {
11242
+ blocks.push({
11077
11243
  type: "context",
11078
11244
  elements: footer.items.map((item) => ({
11079
11245
  type: "mrkdwn",
11080
11246
  text: `*${escapeSlackMrkdwn(item.label)}:* ${escapeSlackMrkdwn(item.value)}`
11081
11247
  }))
11082
- }
11083
- ];
11248
+ });
11249
+ }
11250
+ return blocks;
11084
11251
  }
11085
11252
 
11086
11253
  // src/chat/slack/reply.ts
@@ -11204,11 +11371,13 @@ async function postSlackApiReplyPosts(args) {
11204
11371
  let messageTs;
11205
11372
  try {
11206
11373
  if (post.text.trim().length > 0) {
11374
+ const footer = index === lastTextPostIndex ? args.footer : void 0;
11375
+ const blocks = buildSlackReplyBlocks(post.text, footer);
11207
11376
  const response = await postSlackMessage({
11208
11377
  channelId: args.channelId,
11209
11378
  threadTs: args.threadTs,
11210
11379
  text: post.text,
11211
- ...index === lastTextPostIndex && args.footer ? { blocks: buildSlackReplyBlocks(post.text, args.footer) } : {}
11380
+ ...blocks ? { blocks } : {}
11212
11381
  });
11213
11382
  messageTs = response.ts;
11214
11383
  lastPostedMessageTs = response.ts;
@@ -15149,6 +15318,24 @@ function nonEmptyString(value) {
15149
15318
  return trimmed || void 0;
15150
15319
  }
15151
15320
 
15321
+ // src/chat/ingress/workspace-membership.ts
15322
+ import { AsyncLocalStorage } from "async_hooks";
15323
+ var workspaceTeamIdStorage = new AsyncLocalStorage();
15324
+ function runWithWorkspaceTeamId(teamId, fn) {
15325
+ if (!teamId) return fn();
15326
+ return workspaceTeamIdStorage.run(teamId, fn);
15327
+ }
15328
+ function isExternalSlackUser(raw) {
15329
+ if (!raw) return false;
15330
+ const workspaceTeamId = workspaceTeamIdStorage.getStore();
15331
+ if (!workspaceTeamId) return false;
15332
+ const userTeam = typeof raw.user_team === "string" ? raw.user_team : void 0;
15333
+ if (userTeam) return userTeam !== workspaceTeamId;
15334
+ const sourceTeam = typeof raw.source_team === "string" ? raw.source_team : void 0;
15335
+ if (sourceTeam) return sourceTeam !== workspaceTeamId;
15336
+ return false;
15337
+ }
15338
+
15152
15339
  // src/chat/ingress/junior-chat.ts
15153
15340
  function enqueueBackgroundTask(options, task) {
15154
15341
  if (!options?.waitUntil) {
@@ -15187,6 +15374,9 @@ var JuniorChat = class extends Chat {
15187
15374
  (async () => {
15188
15375
  try {
15189
15376
  const message = await messageOrFactory();
15377
+ if (isExternalSlackUser(message.raw)) {
15378
+ return;
15379
+ }
15190
15380
  const normalized = normalizeIncomingSlackThreadId(
15191
15381
  threadId,
15192
15382
  message
@@ -15205,6 +15395,9 @@ var JuniorChat = class extends Chat {
15205
15395
  );
15206
15396
  return;
15207
15397
  }
15398
+ if (isExternalSlackUser(messageOrFactory.raw)) {
15399
+ return;
15400
+ }
15208
15401
  enqueueBackgroundTask(
15209
15402
  options,
15210
15403
  (async () => {
@@ -15656,12 +15849,16 @@ function extractMessageChangedMention(body, botUserId, adapter) {
15656
15849
  const userId = event.message.user ?? "unknown";
15657
15850
  const threadId = `slack:${channelId}:${threadTs}`;
15658
15851
  const teamId = typeof body.team_id === "string" ? body.team_id : void 0;
15852
+ const userTeam = typeof event.message.user_team === "string" ? event.message.user_team : void 0;
15853
+ const sourceTeam = typeof event.message.source_team === "string" ? event.message.source_team : void 0;
15659
15854
  const raw = {
15660
15855
  channel: channelId,
15661
15856
  ts: messageTs,
15662
15857
  thread_ts: threadTs,
15663
15858
  user: userId,
15664
- ...teamId ? { team_id: teamId } : {}
15859
+ ...teamId ? { team_id: teamId } : {},
15860
+ ...userTeam ? { user_team: userTeam } : {},
15861
+ ...sourceTeam ? { source_team: sourceTeam } : {}
15665
15862
  };
15666
15863
  const message = new Message({
15667
15864
  id: getEditedMentionMessageId(messageTs),
@@ -15763,6 +15960,7 @@ async function handlePlatformWebhook(request, platform, waitUntil, bot = getProd
15763
15960
  return new Response(`Unknown platform: ${platform}`, { status: 404 });
15764
15961
  }
15765
15962
  let rebuiltRequest = request;
15963
+ let slackWorkspaceTeamId;
15766
15964
  if (platform === "slack") {
15767
15965
  const rawBody = await request.text();
15768
15966
  let parsedBody;
@@ -15771,15 +15969,19 @@ async function handlePlatformWebhook(request, platform, waitUntil, bot = getProd
15771
15969
  } catch {
15772
15970
  parsedBody = void 0;
15773
15971
  }
15972
+ slackWorkspaceTeamId = getSlackPayloadTeamId(parsedBody);
15774
15973
  if (parsedBody && isMessageChangedEnvelope(parsedBody)) {
15775
15974
  try {
15776
- await handleAuthenticatedSlackMessageChangedMention({
15777
- body: parsedBody,
15778
- bot,
15779
- rawBody,
15780
- request,
15781
- waitUntil
15782
- });
15975
+ await runWithWorkspaceTeamId(
15976
+ slackWorkspaceTeamId,
15977
+ () => handleAuthenticatedSlackMessageChangedMention({
15978
+ body: parsedBody,
15979
+ bot,
15980
+ rawBody,
15981
+ request,
15982
+ waitUntil
15983
+ })
15984
+ );
15783
15985
  } catch (error) {
15784
15986
  logException(error, "slack_message_changed_side_channel_failed");
15785
15987
  }
@@ -15797,9 +15999,12 @@ async function handlePlatformWebhook(request, platform, waitUntil, bot = getProd
15797
15999
  requestContext,
15798
16000
  async () => {
15799
16001
  try {
15800
- const response = await handler(rebuiltRequest, {
15801
- waitUntil: (task) => waitUntil(task)
15802
- });
16002
+ const response = await runWithWorkspaceTeamId(
16003
+ slackWorkspaceTeamId,
16004
+ () => handler(rebuiltRequest, {
16005
+ waitUntil: (task) => waitUntil(task)
16006
+ })
16007
+ );
15803
16008
  if (response.status >= 400) {
15804
16009
  let responseBodySnippet;
15805
16010
  try {
@@ -15881,6 +16086,7 @@ async function createApp(options) {
15881
16086
  setPluginPackages(
15882
16087
  options?.pluginPackages ?? await resolveBuildPluginPackages()
15883
16088
  );
16089
+ setConfigDefaults(options?.configDefaults);
15884
16090
  const waitUntil = options?.waitUntil ?? await defaultWaitUntil();
15885
16091
  const app = new Hono();
15886
16092
  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.33.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"