@sentry/junior-scheduler 0.67.0 → 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.
@@ -0,0 +1,5 @@
1
+ import type { ScheduledTaskPrincipal } from "./types";
2
+ /** Keep scheduler creator metadata from promoting Slack IDs into display names. */
3
+ export declare function sanitizeScheduledTaskPrincipal(principal: ScheduledTaskPrincipal): ScheduledTaskPrincipal;
4
+ /** Render scheduler creator metadata without inventing human profile fields. */
5
+ export declare function scheduledTaskPrincipalLabel(principal: ScheduledTaskPrincipal): string;
package/dist/index.js CHANGED
@@ -9,6 +9,50 @@ var SCHEDULED_TASK_SYSTEM_ACTOR = Object.freeze({
9
9
  id: "scheduled-task"
10
10
  });
11
11
 
12
+ // src/identity.ts
13
+ var SLACK_USER_ID_PATTERN = /^[UW][A-Z0-9]{5,}$/;
14
+ function clean(value) {
15
+ const normalized = value?.trim();
16
+ return normalized ? normalized : void 0;
17
+ }
18
+ function cleanSlackUserId(value) {
19
+ const normalized = clean(value);
20
+ return normalized && normalized.toLowerCase() !== "unknown" ? normalized : void 0;
21
+ }
22
+ function cleanDisplay(value, slackUserId) {
23
+ const normalized = clean(value);
24
+ if (!normalized || normalized.toLowerCase() === "unknown" || normalized === slackUserId) {
25
+ return void 0;
26
+ }
27
+ return SLACK_USER_ID_PATTERN.test(normalized) ? void 0 : normalized;
28
+ }
29
+ function sanitizeScheduledTaskPrincipal(principal) {
30
+ const slackUserId = cleanSlackUserId(principal.slackUserId);
31
+ if (!slackUserId) {
32
+ throw new Error("Scheduled task principal requires a Slack user id");
33
+ }
34
+ const fullName = cleanDisplay(principal.fullName, slackUserId);
35
+ const userName = cleanDisplay(principal.userName, slackUserId);
36
+ return {
37
+ slackUserId,
38
+ ...fullName ? { fullName } : {},
39
+ ...userName ? { userName } : {}
40
+ };
41
+ }
42
+ function scheduledTaskPrincipalLabel(principal) {
43
+ const author = sanitizeScheduledTaskPrincipal(principal);
44
+ if (author.fullName && author.userName) {
45
+ return `${author.fullName} (@${author.userName})`;
46
+ }
47
+ if (author.fullName) {
48
+ return author.fullName;
49
+ }
50
+ if (author.userName) {
51
+ return `@${author.userName}`;
52
+ }
53
+ return `Slack User ${author.slackUserId}`;
54
+ }
55
+
12
56
  // src/prompt.ts
13
57
  function escapeXml(value) {
14
58
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
@@ -28,7 +72,7 @@ function renderOptionalLine(name, value) {
28
72
  function buildScheduledTaskRunPrompt(args) {
29
73
  const { run, task } = args;
30
74
  const destination = task.destination;
31
- const creator = task.createdBy;
75
+ const creator = sanitizeScheduledTaskPrincipal(task.createdBy);
32
76
  const executionActor = task.executionActor ?? SCHEDULED_TASK_SYSTEM_ACTOR;
33
77
  if (!task.task.text?.trim()) {
34
78
  throw new Error("Scheduled task text is required");
@@ -997,15 +1041,15 @@ function requireActiveDestination(context) {
997
1041
  };
998
1042
  }
999
1043
  function requireRequester(context) {
1000
- const userId = context.requester?.userId;
1001
- if (!userId) {
1044
+ const userId = context.requester?.userId?.trim();
1045
+ if (!userId || userId.toLowerCase() === "unknown") {
1002
1046
  throwToolInputError("No active Slack requester context is available.");
1003
1047
  }
1004
- return {
1048
+ return sanitizeScheduledTaskPrincipal({
1005
1049
  slackUserId: userId,
1006
1050
  ...context.requester?.userName ? { userName: context.requester.userName } : {},
1007
1051
  ...context.requester?.fullName ? { fullName: context.requester.fullName } : {}
1008
- };
1052
+ });
1009
1053
  }
1010
1054
  function tool(definition) {
1011
1055
  return definition;
@@ -1578,17 +1622,12 @@ function destinationLabel(destination) {
1578
1622
  }
1579
1623
  return destination.channelId;
1580
1624
  }
1581
- function authorLabel(author) {
1582
- if (author.fullName && author.userName) {
1583
- return `${author.fullName} (@${author.userName})`;
1584
- }
1585
- if (author.fullName) {
1586
- return author.fullName;
1587
- }
1588
- if (author.userName) {
1589
- return `@${author.userName}`;
1625
+ function operationalAuthorLabel(author) {
1626
+ try {
1627
+ return scheduledTaskPrincipalLabel(author);
1628
+ } catch {
1629
+ return "Invalid Slack creator metadata";
1590
1630
  }
1591
- return `Slack User ${author.slackUserId}`;
1592
1631
  }
1593
1632
  function cadenceLabel(task) {
1594
1633
  if (task.schedule.kind === "one_off") {
@@ -1660,7 +1699,7 @@ async function buildSchedulerOperationalReport(args) {
1660
1699
  id: task.id,
1661
1700
  values: {
1662
1701
  task: task.id,
1663
- author: authorLabel(task.createdBy),
1702
+ author: operationalAuthorLabel(task.createdBy),
1664
1703
  destination: destinationLabel(task.destination),
1665
1704
  nextRun: formatTimestamp(task.nextRunAtMs),
1666
1705
  cadence: cadenceLabel(task)
@@ -1681,7 +1720,7 @@ async function buildSchedulerOperationalReport(args) {
1681
1720
  tone: "danger",
1682
1721
  values: {
1683
1722
  task: task.id,
1684
- author: authorLabel(task.createdBy),
1723
+ author: operationalAuthorLabel(task.createdBy),
1685
1724
  destination: destinationLabel(task.destination),
1686
1725
  updated: formatTimestamp(task.updatedAtMs)
1687
1726
  }
@@ -1705,7 +1744,7 @@ async function buildSchedulerOperationalReport(args) {
1705
1744
  values: {
1706
1745
  run: run.id,
1707
1746
  task: run.taskId,
1708
- author: task ? authorLabel(task.createdBy) : "unknown",
1747
+ author: task ? operationalAuthorLabel(task.createdBy) : "Missing scheduled task",
1709
1748
  scheduledFor: formatTimestamp(run.scheduledForMs),
1710
1749
  status: run.status
1711
1750
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-scheduler",
3
- "version": "0.67.0",
3
+ "version": "0.67.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "@sinclair/typebox": "^0.34.49",
26
- "@sentry/junior-plugin-api": "0.67.0"
26
+ "@sentry/junior-plugin-api": "0.67.1"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^25.9.1",
@@ -0,0 +1,64 @@
1
+ import type { ScheduledTaskPrincipal } from "./types";
2
+
3
+ const SLACK_USER_ID_PATTERN = /^[UW][A-Z0-9]{5,}$/;
4
+
5
+ function clean(value: string | undefined): string | undefined {
6
+ const normalized = value?.trim();
7
+ return normalized ? normalized : undefined;
8
+ }
9
+
10
+ function cleanSlackUserId(value: string | undefined): string | undefined {
11
+ const normalized = clean(value);
12
+ return normalized && normalized.toLowerCase() !== "unknown"
13
+ ? normalized
14
+ : undefined;
15
+ }
16
+
17
+ function cleanDisplay(
18
+ value: string | undefined,
19
+ slackUserId: string,
20
+ ): string | undefined {
21
+ const normalized = clean(value);
22
+ if (
23
+ !normalized ||
24
+ normalized.toLowerCase() === "unknown" ||
25
+ normalized === slackUserId
26
+ ) {
27
+ return undefined;
28
+ }
29
+ return SLACK_USER_ID_PATTERN.test(normalized) ? undefined : normalized;
30
+ }
31
+
32
+ /** Keep scheduler creator metadata from promoting Slack IDs into display names. */
33
+ export function sanitizeScheduledTaskPrincipal(
34
+ principal: ScheduledTaskPrincipal,
35
+ ): ScheduledTaskPrincipal {
36
+ const slackUserId = cleanSlackUserId(principal.slackUserId);
37
+ if (!slackUserId) {
38
+ throw new Error("Scheduled task principal requires a Slack user id");
39
+ }
40
+ const fullName = cleanDisplay(principal.fullName, slackUserId);
41
+ const userName = cleanDisplay(principal.userName, slackUserId);
42
+ return {
43
+ slackUserId,
44
+ ...(fullName ? { fullName } : {}),
45
+ ...(userName ? { userName } : {}),
46
+ };
47
+ }
48
+
49
+ /** Render scheduler creator metadata without inventing human profile fields. */
50
+ export function scheduledTaskPrincipalLabel(
51
+ principal: ScheduledTaskPrincipal,
52
+ ): string {
53
+ const author = sanitizeScheduledTaskPrincipal(principal);
54
+ if (author.fullName && author.userName) {
55
+ return `${author.fullName} (@${author.userName})`;
56
+ }
57
+ if (author.fullName) {
58
+ return author.fullName;
59
+ }
60
+ if (author.userName) {
61
+ return `@${author.userName}`;
62
+ }
63
+ return `Slack User ${author.slackUserId}`;
64
+ }
package/src/plugin.ts CHANGED
@@ -12,7 +12,12 @@ import {
12
12
  type SchedulerOperationalStore,
13
13
  type SchedulerStore,
14
14
  } from "./store";
15
- import type { ScheduledRun, ScheduledTask } from "./types";
15
+ import { scheduledTaskPrincipalLabel } from "./identity";
16
+ import type {
17
+ ScheduledRun,
18
+ ScheduledTask,
19
+ ScheduledTaskPrincipal,
20
+ } from "./types";
16
21
  import {
17
22
  createSlackScheduleCreateTaskTool,
18
23
  createSlackScheduleDeleteTaskTool,
@@ -197,17 +202,12 @@ function destinationLabel(destination: ScheduledTask["destination"]): string {
197
202
  return destination.channelId;
198
203
  }
199
204
 
200
- function authorLabel(author: ScheduledTask["createdBy"]): string {
201
- if (author.fullName && author.userName) {
202
- return `${author.fullName} (@${author.userName})`;
205
+ function operationalAuthorLabel(author: ScheduledTaskPrincipal): string {
206
+ try {
207
+ return scheduledTaskPrincipalLabel(author);
208
+ } catch {
209
+ return "Invalid Slack creator metadata";
203
210
  }
204
- if (author.fullName) {
205
- return author.fullName;
206
- }
207
- if (author.userName) {
208
- return `@${author.userName}`;
209
- }
210
- return `Slack User ${author.slackUserId}`;
211
211
  }
212
212
 
213
213
  function cadenceLabel(task: ScheduledTask): string {
@@ -301,7 +301,7 @@ async function buildSchedulerOperationalReport(args: {
301
301
  id: task.id,
302
302
  values: {
303
303
  task: task.id,
304
- author: authorLabel(task.createdBy),
304
+ author: operationalAuthorLabel(task.createdBy),
305
305
  destination: destinationLabel(task.destination),
306
306
  nextRun: formatTimestamp(task.nextRunAtMs),
307
307
  cadence: cadenceLabel(task),
@@ -322,7 +322,7 @@ async function buildSchedulerOperationalReport(args: {
322
322
  tone: "danger",
323
323
  values: {
324
324
  task: task.id,
325
- author: authorLabel(task.createdBy),
325
+ author: operationalAuthorLabel(task.createdBy),
326
326
  destination: destinationLabel(task.destination),
327
327
  updated: formatTimestamp(task.updatedAtMs),
328
328
  },
@@ -346,7 +346,9 @@ async function buildSchedulerOperationalReport(args: {
346
346
  values: {
347
347
  run: run.id,
348
348
  task: run.taskId,
349
- author: task ? authorLabel(task.createdBy) : "unknown",
349
+ author: task
350
+ ? operationalAuthorLabel(task.createdBy)
351
+ : "Missing scheduled task",
350
352
  scheduledFor: formatTimestamp(run.scheduledForMs),
351
353
  status: run.status,
352
354
  },
package/src/prompt.ts CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  type ScheduledRun,
4
4
  type ScheduledTask,
5
5
  } from "./types";
6
+ import { sanitizeScheduledTaskPrincipal } from "./identity";
6
7
 
7
8
  function escapeXml(value: string): string {
8
9
  return value
@@ -35,7 +36,7 @@ export function buildScheduledTaskRunPrompt(args: {
35
36
  }): string {
36
37
  const { run, task } = args;
37
38
  const destination = task.destination;
38
- const creator = task.createdBy;
39
+ const creator = sanitizeScheduledTaskPrincipal(task.createdBy);
39
40
  const executionActor = task.executionActor ?? SCHEDULED_TASK_SYSTEM_ACTOR;
40
41
  if (!task.task.text?.trim()) {
41
42
  throw new Error("Scheduled task text is required");
@@ -7,6 +7,7 @@ import {
7
7
  type AgentPluginToolDefinition,
8
8
  } from "@sentry/junior-plugin-api";
9
9
  import { buildCalendarRecurrence, parseScheduleTimestamp } from "./cadence";
10
+ import { sanitizeScheduledTaskPrincipal } from "./identity";
10
11
  import { createSchedulerStore } from "./store";
11
12
  import { SCHEDULED_TASK_SYSTEM_ACTOR } from "./types";
12
13
  import type {
@@ -68,12 +69,12 @@ function requireActiveDestination(
68
69
  function requireRequester(
69
70
  context: SchedulerToolContext,
70
71
  ): ScheduledTaskPrincipal {
71
- const userId = context.requester?.userId;
72
- if (!userId) {
72
+ const userId = context.requester?.userId?.trim();
73
+ if (!userId || userId.toLowerCase() === "unknown") {
73
74
  throwToolInputError("No active Slack requester context is available.");
74
75
  }
75
76
 
76
- return {
77
+ return sanitizeScheduledTaskPrincipal({
77
78
  slackUserId: userId,
78
79
  ...(context.requester?.userName
79
80
  ? { userName: context.requester.userName }
@@ -81,7 +82,7 @@ function requireRequester(
81
82
  ...(context.requester?.fullName
82
83
  ? { fullName: context.requester.fullName }
83
84
  : {}),
84
- };
85
+ });
85
86
  }
86
87
 
87
88
  function tool<TInput = any>(