@sentry/junior 0.66.3 → 0.67.1

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.
@@ -6,7 +6,7 @@ import {
6
6
  setSpanAttributes,
7
7
  toOptionalString,
8
8
  withSpan
9
- } from "./chunk-6QWWMZCK.js";
9
+ } from "./chunk-OIIXZOOC.js";
10
10
 
11
11
  // src/chat/slack/context.ts
12
12
  function toTrimmedSlackString(value) {
@@ -496,10 +496,24 @@ async function completeObject(params) {
496
496
  };
497
497
  }
498
498
 
499
+ // src/chat/slack/emoji.ts
500
+ var SLACK_EMOJI_NAME_RE = /^(?:[a-z0-9_+-]+)(?:::(?:skin-tone-[2-6]))?$/;
501
+ function normalizeSlackEmojiName(value) {
502
+ const trimmed = value.trim().toLowerCase();
503
+ if (!trimmed) {
504
+ return null;
505
+ }
506
+ const normalized = trimmed.startsWith(":") && trimmed.endsWith(":") ? trimmed.slice(1, -1) : trimmed;
507
+ return SLACK_EMOJI_NAME_RE.test(normalized) ? normalized : null;
508
+ }
509
+
499
510
  // src/chat/config.ts
500
511
  var MIN_AGENT_TURN_TIMEOUT_MS = 10 * 1e3;
501
512
  var DEFAULT_AGENT_TURN_TIMEOUT_MS = 12 * 60 * 1e3;
502
513
  var DEFAULT_FUNCTION_MAX_DURATION_SECONDS = 300;
514
+ var DEFAULT_SLACK_SLASH_COMMAND = "/jr";
515
+ var DEFAULT_PROCESSING_REACTION_EMOJI = "eyes";
516
+ var DEFAULT_COMPLETED_REACTION_EMOJI = "white_check_mark";
503
517
  var ADVISOR_THINKING_LEVELS = [
504
518
  "minimal",
505
519
  "low",
@@ -589,6 +603,15 @@ function parseOptionalPositiveInteger(envName, rawValue) {
589
603
  }
590
604
  return value;
591
605
  }
606
+ function parseSlashCommand(rawValue) {
607
+ const command = toOptionalTrimmed(rawValue) ?? DEFAULT_SLACK_SLASH_COMMAND;
608
+ if (!command.startsWith("/") || /\s/.test(command)) {
609
+ throw new Error(
610
+ "JUNIOR_SLASH_COMMAND must start with / and contain no whitespace"
611
+ );
612
+ }
613
+ return command;
614
+ }
592
615
  var DEFAULT_MODEL_ID = getModel("vercel-ai-gateway", "openai/gpt-5.4").id;
593
616
  var DEFAULT_FAST_MODEL_ID = getModel(
594
617
  "vercel-ai-gateway",
@@ -610,6 +633,19 @@ function readAdvisorConfig(env) {
610
633
  thinkingLevel: parseAdvisorThinkingLevel(env.AI_ADVISOR_THINKING_LEVEL)
611
634
  };
612
635
  }
636
+ function parseReactionEmoji(envName, rawValue, defaultEmoji) {
637
+ const trimmed = toOptionalTrimmed(rawValue);
638
+ if (trimmed === void 0) {
639
+ return defaultEmoji;
640
+ }
641
+ const normalized = normalizeSlackEmojiName(trimmed);
642
+ if (!normalized) {
643
+ throw new Error(
644
+ `${envName} must be a valid Slack emoji name (for example "eyes" or ":white_check_mark:")`
645
+ );
646
+ }
647
+ return normalized;
648
+ }
613
649
  function readBotConfig(env) {
614
650
  const functionMaxDurationSeconds = resolveFunctionMaxDurationSeconds(env);
615
651
  const maxTurnTimeoutMs = resolveMaxTurnTimeoutMs(functionMaxDurationSeconds);
@@ -622,6 +658,16 @@ function readBotConfig(env) {
622
658
  ),
623
659
  fastModelId: validateGatewayModelId(env.AI_FAST_MODEL ?? env.AI_MODEL) ?? DEFAULT_FAST_MODEL_ID,
624
660
  loadingMessages: parseLoadingMessages(env.JUNIOR_LOADING_MESSAGES),
661
+ processingReactionEmoji: parseReactionEmoji(
662
+ "JUNIOR_PROCESSING_REACTION",
663
+ env.JUNIOR_PROCESSING_REACTION,
664
+ DEFAULT_PROCESSING_REACTION_EMOJI
665
+ ),
666
+ completedReactionEmoji: parseReactionEmoji(
667
+ "JUNIOR_COMPLETED_REACTION",
668
+ env.JUNIOR_COMPLETED_REACTION,
669
+ DEFAULT_COMPLETED_REACTION_EMOJI
670
+ ),
625
671
  visionModelId: validateGatewayModelId(env.AI_VISION_MODEL),
626
672
  turnTimeoutMs: parseAgentTurnTimeoutMs(
627
673
  env.AGENT_TURN_TIMEOUT_MS,
@@ -638,7 +684,8 @@ function readChatConfig(env = process.env) {
638
684
  botToken: toOptionalTrimmed(env.SLACK_BOT_TOKEN) ?? toOptionalTrimmed(env.SLACK_BOT_USER_TOKEN),
639
685
  signingSecret: toOptionalTrimmed(env.SLACK_SIGNING_SECRET),
640
686
  clientId: toOptionalTrimmed(env.SLACK_CLIENT_ID),
641
- clientSecret: toOptionalTrimmed(env.SLACK_CLIENT_SECRET)
687
+ clientSecret: toOptionalTrimmed(env.SLACK_CLIENT_SECRET),
688
+ slashCommand: parseSlashCommand(env.JUNIOR_SLASH_COMMAND)
642
689
  },
643
690
  state: {
644
691
  adapter: env.JUNIOR_STATE_ADAPTER?.trim().toLowerCase() === "memory" ? "memory" : "redis",
@@ -938,6 +985,7 @@ export {
938
985
  resolveGatewayModel,
939
986
  completeText,
940
987
  completeObject,
988
+ normalizeSlackEmojiName,
941
989
  FUNCTION_TIMEOUT_BUFFER_SECONDS,
942
990
  getChatConfig,
943
991
  botConfig,
@@ -2858,6 +2858,90 @@ var CredentialUnavailableError = class extends Error {
2858
2858
  }
2859
2859
  };
2860
2860
 
2861
+ // src/chat/services/requester-identity.ts
2862
+ var SLACK_USER_ID_PATTERN = /^[UW][A-Z0-9]{5,}$/;
2863
+ var EMAIL_PATTERN = /^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/;
2864
+ function clean(value) {
2865
+ const trimmed = value?.trim();
2866
+ return trimmed ? trimmed : void 0;
2867
+ }
2868
+ function isSyntheticActorUserId(value) {
2869
+ return value.toLowerCase() === "unknown";
2870
+ }
2871
+ function parseActorUserId(value) {
2872
+ if (typeof value !== "string" || value.length === 0) {
2873
+ return void 0;
2874
+ }
2875
+ if (value !== value.trim() || isSyntheticActorUserId(value)) {
2876
+ return void 0;
2877
+ }
2878
+ return value;
2879
+ }
2880
+ function isActorUserId(value) {
2881
+ return parseActorUserId(value) === value;
2882
+ }
2883
+ function isSlackUserId(value) {
2884
+ return SLACK_USER_ID_PATTERN.test(value);
2885
+ }
2886
+ function cleanActorDisplayName(value, userId) {
2887
+ const displayName = clean(value);
2888
+ if (!displayName) {
2889
+ return void 0;
2890
+ }
2891
+ if (displayName.toLowerCase() === "unknown") {
2892
+ return void 0;
2893
+ }
2894
+ if (userId && displayName === userId) {
2895
+ return void 0;
2896
+ }
2897
+ return isSlackUserId(displayName) ? void 0 : displayName;
2898
+ }
2899
+ function cleanActorEmail(value) {
2900
+ const email = clean(value);
2901
+ return email && EMAIL_PATTERN.test(email) ? email : void 0;
2902
+ }
2903
+ function buildActorIdentity(requester, requesterId) {
2904
+ const contextUserId = parseActorUserId(requesterId);
2905
+ if (requesterId !== void 0 && !contextUserId) {
2906
+ return void 0;
2907
+ }
2908
+ const requesterUserId = parseActorUserId(requester?.userId);
2909
+ if (requester?.userId !== void 0 && !requesterUserId) {
2910
+ return void 0;
2911
+ }
2912
+ const userId = contextUserId ?? requesterUserId;
2913
+ const canUseRequesterIdentity = !contextUserId || !requesterUserId || contextUserId === requesterUserId;
2914
+ const email = canUseRequesterIdentity ? cleanActorEmail(requester?.email) : void 0;
2915
+ const fullName = canUseRequesterIdentity ? cleanActorDisplayName(requester?.fullName, userId) : void 0;
2916
+ const userName = canUseRequesterIdentity ? cleanActorDisplayName(requester?.userName, userId) : void 0;
2917
+ const identity = {
2918
+ ...email ? { email } : {},
2919
+ ...fullName ? { fullName } : {},
2920
+ ...userId ? { userId } : {},
2921
+ ...userName ? { userName } : {}
2922
+ };
2923
+ return Object.keys(identity).length > 0 ? identity : void 0;
2924
+ }
2925
+ function slackActorIdentity(userId, profile) {
2926
+ const actorUserId = parseActorUserId(userId);
2927
+ if (!actorUserId) {
2928
+ throw new Error("Slack actor identity requires a user id");
2929
+ }
2930
+ const identity = buildActorIdentity(
2931
+ {
2932
+ email: profile?.email,
2933
+ fullName: profile?.fullName,
2934
+ userId: actorUserId,
2935
+ userName: profile?.userName
2936
+ },
2937
+ actorUserId
2938
+ );
2939
+ if (!identity?.userId) {
2940
+ throw new Error("Slack actor identity requires a user id");
2941
+ }
2942
+ return identity;
2943
+ }
2944
+
2861
2945
  // src/chat/credentials/context.ts
2862
2946
  function credentialUserSubjectId(context) {
2863
2947
  if (context.actor.type === "user") {
@@ -2895,11 +2979,15 @@ function parseCredentialContext(value) {
2895
2979
  function parseActor(value) {
2896
2980
  if (value && typeof value === "object") {
2897
2981
  const record = value;
2898
- if (record.type === "user" && typeof record.userId === "string" && record.userId) {
2899
- return { type: "user", userId: record.userId };
2982
+ const userId = parseActorUserId(
2983
+ typeof record.userId === "string" ? record.userId : void 0
2984
+ );
2985
+ if (record.type === "user" && userId) {
2986
+ return { type: "user", userId };
2900
2987
  }
2901
- if (record.type === "system" && typeof record.id === "string" && record.id) {
2902
- return { type: "system", id: record.id };
2988
+ const systemId = typeof record.id === "string" && record.id.length > 0 && record.id === record.id.trim() && record.id.toLowerCase() !== "unknown" ? record.id : void 0;
2989
+ if (record.type === "system" && systemId) {
2990
+ return { type: "system", id: systemId };
2903
2991
  }
2904
2992
  }
2905
2993
  return void 0;
@@ -2907,7 +2995,10 @@ function parseActor(value) {
2907
2995
  function parseSubject(value) {
2908
2996
  if (value && typeof value === "object") {
2909
2997
  const record = value;
2910
- if (record.type === "user" && typeof record.userId === "string" && record.userId && record.allowedWhen === "private-direct-conversation") {
2998
+ const userId = parseActorUserId(
2999
+ typeof record.userId === "string" ? record.userId : void 0
3000
+ );
3001
+ if (record.type === "user" && userId && record.allowedWhen === "private-direct-conversation") {
2911
3002
  if (!record.binding || typeof record.binding !== "object") {
2912
3003
  return void 0;
2913
3004
  }
@@ -2917,7 +3008,7 @@ function parseSubject(value) {
2917
3008
  }
2918
3009
  return {
2919
3010
  type: "user",
2920
- userId: record.userId,
3011
+ userId,
2921
3012
  allowedWhen: "private-direct-conversation",
2922
3013
  binding: {
2923
3014
  type: "slack-direct-conversation",
@@ -3618,6 +3709,10 @@ export {
3618
3709
  parsePluginManifest,
3619
3710
  parseInlinePluginManifest,
3620
3711
  CredentialUnavailableError,
3712
+ parseActorUserId,
3713
+ isActorUserId,
3714
+ buildActorIdentity,
3715
+ slackActorIdentity,
3621
3716
  parseCredentialContext,
3622
3717
  hasRequiredOAuthScope,
3623
3718
  buildOAuthTokenRequest,
@@ -2,7 +2,7 @@ import {
2
2
  getPluginForSkillPath,
3
3
  getPluginSkillRoots,
4
4
  logWarn
5
- } from "./chunk-6QWWMZCK.js";
5
+ } from "./chunk-OIIXZOOC.js";
6
6
  import {
7
7
  skillRoots
8
8
  } from "./chunk-KVZL5NZS.js";
@@ -2,12 +2,12 @@ import {
2
2
  SANDBOX_WORKSPACE_ROOT,
3
3
  getStateAdapter,
4
4
  toOptionalTrimmed
5
- } from "./chunk-SAYCFF7O.js";
5
+ } from "./chunk-MT23VNOH.js";
6
6
  import {
7
7
  getPluginRuntimeDependencies,
8
8
  getPluginRuntimePostinstall,
9
9
  withSpan
10
- } from "./chunk-6QWWMZCK.js";
10
+ } from "./chunk-OIIXZOOC.js";
11
11
 
12
12
  // src/chat/sandbox/runtime-dependency-snapshots.ts
13
13
  import { createHash } from "crypto";
package/dist/cli/check.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  parseSkillFile
3
- } from "../chunk-YL5G5YC4.js";
3
+ } from "../chunk-V47RLIO2.js";
4
4
  import {
5
5
  parseInlinePluginManifest,
6
6
  parsePluginManifest
7
- } from "../chunk-6QWWMZCK.js";
7
+ } from "../chunk-OIIXZOOC.js";
8
8
  import "../chunk-Z3YD6NHK.js";
9
9
  import {
10
10
  JUNIOR_CONVERSATION_WORK_CALLBACK_ROUTE,
package/dist/cli/init.js CHANGED
@@ -186,6 +186,7 @@ Operational context and domain knowledge for ${name}.
186
186
  SLACK_SIGNING_SECRET=
187
187
  JUNIOR_SECRET=
188
188
  JUNIOR_BOT_NAME=
189
+ JUNIOR_SLASH_COMMAND=
189
190
  AI_MODEL=
190
191
  AI_FAST_MODEL=
191
192
  AI_VISION_MODEL=
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  resolveRuntimeDependencySnapshot
3
- } from "../chunk-JA5QR3N4.js";
3
+ } from "../chunk-YGGH2742.js";
4
4
  import {
5
5
  disconnectStateAdapter
6
- } from "../chunk-SAYCFF7O.js";
6
+ } from "../chunk-MT23VNOH.js";
7
7
  import {
8
8
  getPluginProviders,
9
9
  getPluginRuntimeDependencies,
10
10
  getPluginRuntimePostinstall
11
- } from "../chunk-6QWWMZCK.js";
11
+ } from "../chunk-OIIXZOOC.js";
12
12
  import "../chunk-Z3YD6NHK.js";
13
13
  import "../chunk-KVZL5NZS.js";
14
14
  import "../chunk-2KG3PWR4.js";
@@ -1,4 +1,6 @@
1
1
  import { getPluginPackageContent } from "@/chat/plugins/registry";
2
+ import { type AgentTurnSurface } from "@/chat/state/turn-session";
3
+ import type { PluginOperationalReport } from "@sentry/junior-plugin-api";
2
4
  export interface HealthReport {
3
5
  status: "ok";
4
6
  service: string;
@@ -20,7 +22,7 @@ export interface RuntimeInfoReport {
20
22
  packagedContent: ReturnType<typeof getPluginPackageContent>;
21
23
  }
22
24
  export type DashboardSessionStatus = "active" | "completed" | "failed" | "hung" | "superseded";
23
- export type DashboardSurface = "slack" | "api" | "scheduler" | "internal";
25
+ export type DashboardSurface = AgentTurnSurface;
24
26
  export interface DashboardTurnUsage {
25
27
  inputTokens?: number;
26
28
  outputTokens?: number;
@@ -99,6 +101,40 @@ export interface DashboardSessionFeed {
99
101
  source: "turn_session_records";
100
102
  generatedAt: string;
101
103
  }
104
+ export interface DashboardConversationStatsItem {
105
+ active: number;
106
+ conversations: number;
107
+ durationMs: number;
108
+ failed: number;
109
+ hung: number;
110
+ label: string;
111
+ tokens?: number;
112
+ turns: number;
113
+ }
114
+ export interface DashboardConversationStatsReport {
115
+ active: number;
116
+ conversations: number;
117
+ durationMs: number;
118
+ failed: number;
119
+ generatedAt: string;
120
+ hung: number;
121
+ locations: DashboardConversationStatsItem[];
122
+ requesters: DashboardConversationStatsItem[];
123
+ sampleLimit: number;
124
+ sampleSize: number;
125
+ source: "turn_session_records";
126
+ tokens?: number;
127
+ truncated: boolean;
128
+ turns: number;
129
+ windowEnd: string;
130
+ windowStart: string;
131
+ }
132
+ export type { PluginOperationalReport } from "@sentry/junior-plugin-api";
133
+ export interface PluginOperationalReportFeed {
134
+ generatedAt: string;
135
+ reports: PluginOperationalReport[];
136
+ source: "trusted_plugins";
137
+ }
102
138
  export interface JuniorReporting {
103
139
  /** Read the public runtime health snapshot without exposing discovery data. */
104
140
  getHealth(): Promise<HealthReport>;
@@ -115,6 +151,10 @@ export interface JuniorReporting {
115
151
  * actor, route, usage, and links that can later be reconstructed from spans.
116
152
  */
117
153
  getSessions(): Promise<DashboardSessionFeed>;
154
+ /** Read aggregate conversation stats for authenticated dashboard views. */
155
+ getConversationStats?(): Promise<DashboardConversationStatsReport>;
156
+ /** Read sanitized operational summaries contributed by trusted plugins. */
157
+ getPluginOperationalReports?(): Promise<PluginOperationalReportFeed>;
118
158
  /**
119
159
  * Read one conversation transcript for the dashboard.
120
160
  *
@@ -125,4 +165,7 @@ export interface JuniorReporting {
125
165
  getConversation(conversationId: string): Promise<DashboardConversationReport>;
126
166
  }
127
167
  /** Create the read-only reporting boundary used by authenticated dashboard routes. */
128
- export declare function createJuniorReporting(): JuniorReporting;
168
+ export declare function createJuniorReporting(): JuniorReporting & {
169
+ getConversationStats(): Promise<DashboardConversationStatsReport>;
170
+ getPluginOperationalReports(): Promise<PluginOperationalReportFeed>;
171
+ };