@sentry/junior-plugin-api 0.117.0 → 0.118.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/README.md CHANGED
@@ -21,7 +21,8 @@ The manifest declares runtime metadata such as:
21
21
  - provider domains, grants, OAuth, API-header transformations, and safe command
22
22
  environment placeholders;
23
23
  - runtime dependencies and snapshot installation steps;
24
- - configuration fields.
24
+ - configuration fields;
25
+ - signed-in user pages backed by core-owned discovery and read routes.
25
26
 
26
27
  Manifest values are validated before runtime activation. Secret deployment
27
28
  values remain host-only; sandbox-exposed command environment must be explicitly
@@ -39,6 +40,18 @@ reports, and other typed hook surfaces exported by this package.
39
40
  - Tool hooks return model-visible schemas aligned with their executor inputs.
40
41
  - Host-owned structured model and embedding calls do not expose provider
41
42
  credentials to plugins.
43
+ - User page readers receive the signed-in email plus runtime-owned actors linked
44
+ to that user. Plugins return bounded data and do not mount their own page
45
+ routes or browser code.
46
+
47
+ ## User Pages
48
+
49
+ Register signed-in pages through `userPages` beside `hooks`, `tasks`, and `cli`.
50
+ Each definition owns its navigation metadata and `read(ctx)` function, so a
51
+ page cannot be advertised without an implementation. Core owns discovery,
52
+ authentication, actor resolution, routing, response validation, and rendering.
53
+ The initial response type is the bounded `list` page; add another core-rendered
54
+ type only when a concrete product need proves it.
42
55
 
43
56
  ## Durable Work
44
57
 
package/dist/index.d.ts CHANGED
@@ -13,3 +13,4 @@ export * from "./hooks";
13
13
  export * from "./cli";
14
14
  export * from "./manifest";
15
15
  export * from "./registration";
16
+ export * from "./user-pages";
package/dist/index.js CHANGED
@@ -462,6 +462,7 @@ var EgressPolicyDenied = class extends Error {
462
462
 
463
463
  // src/registration.ts
464
464
  var PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
465
+ var USER_PAGE_ID_RE = /^[a-z][a-z0-9-]*$/;
465
466
  function defineJuniorPlugin(plugin) {
466
467
  if ("pluginConfig" in plugin) {
467
468
  throw new Error(
@@ -496,10 +497,69 @@ function defineJuniorPlugin(plugin) {
496
497
  `Junior plugin "${name}" manifest.description is required.`
497
498
  );
498
499
  }
500
+ if (plugin.userPages !== void 0 && !Array.isArray(plugin.userPages)) {
501
+ throw new Error(`Junior plugin "${name}" userPages must be an array.`);
502
+ }
503
+ const userPageIds = /* @__PURE__ */ new Set();
504
+ for (const page of plugin.userPages ?? []) {
505
+ if (!page || typeof page !== "object") {
506
+ throw new Error(`Junior plugin "${name}" user pages must be objects.`);
507
+ }
508
+ if (typeof page.id !== "string" || page.id.length > 64 || !USER_PAGE_ID_RE.test(page.id)) {
509
+ throw new Error(
510
+ `Junior plugin "${name}" user page id "${page.id}" must be a lowercase identifier.`
511
+ );
512
+ }
513
+ if (userPageIds.has(page.id)) {
514
+ throw new Error(
515
+ `Junior plugin "${name}" has duplicate user page id "${page.id}".`
516
+ );
517
+ }
518
+ if (typeof page.label !== "string" || !page.label.trim() || page.label.length > 80 || typeof page.description !== "string" || !page.description.trim() || page.description.length > 500) {
519
+ throw new Error(
520
+ `Junior plugin "${name}" user page "${page.id}" requires label and description.`
521
+ );
522
+ }
523
+ if (typeof page.read !== "function") {
524
+ throw new Error(
525
+ `Junior plugin "${name}" user page "${page.id}" requires a read function.`
526
+ );
527
+ }
528
+ userPageIds.add(page.id);
529
+ }
499
530
  return {
500
531
  ...plugin
501
532
  };
502
533
  }
534
+
535
+ // src/user-pages.ts
536
+ import { z as z9 } from "zod";
537
+ var userPageIdSchema = nonBlankStringSchema.max(64).regex(/^[a-z][a-z0-9-]*$/);
538
+ var userPageLabelSchema = nonBlankStringSchema.max(80);
539
+ var userPageDescriptionSchema = nonBlankStringSchema.max(500);
540
+ var pluginUserPageMetadataSchema = z9.object({
541
+ label: nonBlankStringSchema.max(80),
542
+ value: nonBlankStringSchema.max(500)
543
+ }).strict();
544
+ var pluginUserPageRecordSchema = z9.object({
545
+ description: nonBlankStringSchema.max(1e3).optional(),
546
+ id: nonBlankStringSchema.max(128),
547
+ metadata: z9.array(pluginUserPageMetadataSchema).max(8).optional(),
548
+ title: nonBlankStringSchema.max(4e3)
549
+ }).strict();
550
+ var pluginUserPageContentSchema = z9.object({
551
+ emptyText: nonBlankStringSchema.max(500).optional(),
552
+ records: z9.array(pluginUserPageRecordSchema).max(100),
553
+ type: z9.literal("list")
554
+ }).strict();
555
+ var pluginUserPageLinkSchema = z9.object({
556
+ description: userPageDescriptionSchema,
557
+ id: userPageIdSchema,
558
+ label: userPageLabelSchema,
559
+ pluginDisplayName: nonBlankStringSchema.max(200),
560
+ pluginName: nonBlankStringSchema.max(100)
561
+ }).strict();
562
+ var pluginUserPageLinksSchema = z9.array(pluginUserPageLinkSchema);
503
563
  export {
504
564
  EgressAuthRequired,
505
565
  EgressPolicyDenied,
@@ -537,6 +597,9 @@ export {
537
597
  pluginToolContinuationSchema,
538
598
  pluginToolErrorSchema,
539
599
  pluginToolResultSchema,
600
+ pluginUserPageContentSchema,
601
+ pluginUserPageLinkSchema,
602
+ pluginUserPageLinksSchema,
540
603
  promptContextSchema,
541
604
  promptMessageSchema,
542
605
  resourceEventSchema,
@@ -2,6 +2,7 @@ import type { PluginCliDefinition } from "./cli";
2
2
  import type { PluginHooks } from "./hooks";
3
3
  import type { PluginManifest } from "./manifest";
4
4
  import type { PluginTasks } from "./tasks";
5
+ import type { PluginUserPageDefinition } from "./user-pages";
5
6
  export interface PluginModelConfig {
6
7
  /** Host model family used when no explicit structured model id is configured. */
7
8
  structuredModel?: "default" | "fast";
@@ -15,6 +16,7 @@ export type PluginRegistrationInput = {
15
16
  model?: PluginModelConfig;
16
17
  packageName?: string;
17
18
  tasks?: PluginTasks;
19
+ userPages?: PluginUserPageDefinition[];
18
20
  };
19
21
  export interface PluginRegistration extends PluginRegistrationInput {
20
22
  }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Core-rendered plugin user pages.
3
+ *
4
+ * Plugins own bounded data projection; Junior owns viewer authorization,
5
+ * routing, response validation, and browser rendering.
6
+ */
7
+ import { z } from "zod";
8
+ import type { LocalActor, PluginContext, SlackActor } from "./context";
9
+ /** Bounded list content returned by a plugin-owned user page. */
10
+ export declare const pluginUserPageContentSchema: z.ZodObject<{
11
+ emptyText: z.ZodOptional<z.ZodString>;
12
+ records: z.ZodArray<z.ZodObject<{
13
+ description: z.ZodOptional<z.ZodString>;
14
+ id: z.ZodString;
15
+ metadata: z.ZodOptional<z.ZodArray<z.ZodObject<{
16
+ label: z.ZodString;
17
+ value: z.ZodString;
18
+ }, z.core.$strict>>>;
19
+ title: z.ZodString;
20
+ }, z.core.$strict>>;
21
+ type: z.ZodLiteral<"list">;
22
+ }, z.core.$strict>;
23
+ /** Validated content for the current core-rendered list page type. */
24
+ export type PluginUserPageContent = z.output<typeof pluginUserPageContentSchema>;
25
+ export declare const pluginUserPageLinkSchema: z.ZodObject<{
26
+ description: z.ZodString;
27
+ id: z.ZodString;
28
+ label: z.ZodString;
29
+ pluginDisplayName: z.ZodString;
30
+ pluginName: z.ZodString;
31
+ }, z.core.$strict>;
32
+ export declare const pluginUserPageLinksSchema: z.ZodArray<z.ZodObject<{
33
+ description: z.ZodString;
34
+ id: z.ZodString;
35
+ label: z.ZodString;
36
+ pluginDisplayName: z.ZodString;
37
+ pluginName: z.ZodString;
38
+ }, z.core.$strict>>;
39
+ /** Safe navigation metadata for one registered plugin user page. */
40
+ export type PluginUserPageLink = z.output<typeof pluginUserPageLinkSchema>;
41
+ /** Runtime-owned actor shapes that may belong to an authenticated viewer. */
42
+ export type PluginUserPageActor = SlackActor | LocalActor;
43
+ /** Trusted host context supplied while reading one plugin user page. */
44
+ export interface PluginUserPageContext extends PluginContext {
45
+ viewer: {
46
+ actors: PluginUserPageActor[];
47
+ email: string;
48
+ };
49
+ }
50
+ /** Navigation metadata and reader for one core-rendered plugin user page. */
51
+ export interface PluginUserPageDefinition {
52
+ description: string;
53
+ id: string;
54
+ label: string;
55
+ read(ctx: PluginUserPageContext): Promise<PluginUserPageContent> | PluginUserPageContent;
56
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-plugin-api",
3
- "version": "0.117.0",
3
+ "version": "0.118.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/index.ts CHANGED
@@ -23,3 +23,4 @@ export * from "./hooks";
23
23
  export * from "./cli";
24
24
  export * from "./manifest";
25
25
  export * from "./registration";
26
+ export * from "./user-pages";
@@ -2,6 +2,7 @@ import type { PluginCliDefinition } from "./cli";
2
2
  import type { PluginHooks } from "./hooks";
3
3
  import type { PluginManifest } from "./manifest";
4
4
  import type { PluginTasks } from "./tasks";
5
+ import type { PluginUserPageDefinition } from "./user-pages";
5
6
 
6
7
  export interface PluginModelConfig {
7
8
  /** Host model family used when no explicit structured model id is configured. */
@@ -17,11 +18,13 @@ export type PluginRegistrationInput = {
17
18
  model?: PluginModelConfig;
18
19
  packageName?: string;
19
20
  tasks?: PluginTasks;
21
+ userPages?: PluginUserPageDefinition[];
20
22
  };
21
23
 
22
24
  export interface PluginRegistration extends PluginRegistrationInput {}
23
25
 
24
26
  const PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
27
+ const USER_PAGE_ID_RE = /^[a-z][a-z0-9-]*$/;
25
28
 
26
29
  /** Define one Junior plugin registration for app and build-time wiring. */
27
30
  export function defineJuniorPlugin(
@@ -66,6 +69,47 @@ export function defineJuniorPlugin(
66
69
  `Junior plugin "${name}" manifest.description is required.`,
67
70
  );
68
71
  }
72
+ if (plugin.userPages !== undefined && !Array.isArray(plugin.userPages)) {
73
+ throw new Error(`Junior plugin "${name}" userPages must be an array.`);
74
+ }
75
+ const userPageIds = new Set<string>();
76
+ for (const page of plugin.userPages ?? []) {
77
+ if (!page || typeof page !== "object") {
78
+ throw new Error(`Junior plugin "${name}" user pages must be objects.`);
79
+ }
80
+ if (
81
+ typeof page.id !== "string" ||
82
+ page.id.length > 64 ||
83
+ !USER_PAGE_ID_RE.test(page.id)
84
+ ) {
85
+ throw new Error(
86
+ `Junior plugin "${name}" user page id "${page.id}" must be a lowercase identifier.`,
87
+ );
88
+ }
89
+ if (userPageIds.has(page.id)) {
90
+ throw new Error(
91
+ `Junior plugin "${name}" has duplicate user page id "${page.id}".`,
92
+ );
93
+ }
94
+ if (
95
+ typeof page.label !== "string" ||
96
+ !page.label.trim() ||
97
+ page.label.length > 80 ||
98
+ typeof page.description !== "string" ||
99
+ !page.description.trim() ||
100
+ page.description.length > 500
101
+ ) {
102
+ throw new Error(
103
+ `Junior plugin "${name}" user page "${page.id}" requires label and description.`,
104
+ );
105
+ }
106
+ if (typeof page.read !== "function") {
107
+ throw new Error(
108
+ `Junior plugin "${name}" user page "${page.id}" requires a read function.`,
109
+ );
110
+ }
111
+ userPageIds.add(page.id);
112
+ }
69
113
  return {
70
114
  ...plugin,
71
115
  };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Core-rendered plugin user pages.
3
+ *
4
+ * Plugins own bounded data projection; Junior owns viewer authorization,
5
+ * routing, response validation, and browser rendering.
6
+ */
7
+ import { z } from "zod";
8
+ import type { LocalActor, PluginContext, SlackActor } from "./context";
9
+ import { nonBlankStringSchema } from "./schemas";
10
+
11
+ const userPageIdSchema = nonBlankStringSchema
12
+ .max(64)
13
+ .regex(/^[a-z][a-z0-9-]*$/);
14
+ const userPageLabelSchema = nonBlankStringSchema.max(80);
15
+ const userPageDescriptionSchema = nonBlankStringSchema.max(500);
16
+
17
+ const pluginUserPageMetadataSchema = z
18
+ .object({
19
+ label: nonBlankStringSchema.max(80),
20
+ value: nonBlankStringSchema.max(500),
21
+ })
22
+ .strict();
23
+
24
+ const pluginUserPageRecordSchema = z
25
+ .object({
26
+ description: nonBlankStringSchema.max(1_000).optional(),
27
+ id: nonBlankStringSchema.max(128),
28
+ metadata: z.array(pluginUserPageMetadataSchema).max(8).optional(),
29
+ title: nonBlankStringSchema.max(4_000),
30
+ })
31
+ .strict();
32
+
33
+ /** Bounded list content returned by a plugin-owned user page. */
34
+ export const pluginUserPageContentSchema = z
35
+ .object({
36
+ emptyText: nonBlankStringSchema.max(500).optional(),
37
+ records: z.array(pluginUserPageRecordSchema).max(100),
38
+ type: z.literal("list"),
39
+ })
40
+ .strict();
41
+
42
+ /** Validated content for the current core-rendered list page type. */
43
+ export type PluginUserPageContent = z.output<
44
+ typeof pluginUserPageContentSchema
45
+ >;
46
+
47
+ export const pluginUserPageLinkSchema = z
48
+ .object({
49
+ description: userPageDescriptionSchema,
50
+ id: userPageIdSchema,
51
+ label: userPageLabelSchema,
52
+ pluginDisplayName: nonBlankStringSchema.max(200),
53
+ pluginName: nonBlankStringSchema.max(100),
54
+ })
55
+ .strict();
56
+
57
+ export const pluginUserPageLinksSchema = z.array(pluginUserPageLinkSchema);
58
+
59
+ /** Safe navigation metadata for one registered plugin user page. */
60
+ export type PluginUserPageLink = z.output<typeof pluginUserPageLinkSchema>;
61
+
62
+ /** Runtime-owned actor shapes that may belong to an authenticated viewer. */
63
+ export type PluginUserPageActor = SlackActor | LocalActor;
64
+
65
+ /** Trusted host context supplied while reading one plugin user page. */
66
+ export interface PluginUserPageContext extends PluginContext {
67
+ viewer: {
68
+ actors: PluginUserPageActor[];
69
+ email: string;
70
+ };
71
+ }
72
+
73
+ /** Navigation metadata and reader for one core-rendered plugin user page. */
74
+ export interface PluginUserPageDefinition {
75
+ description: string;
76
+ id: string;
77
+ label: string;
78
+ read(
79
+ ctx: PluginUserPageContext,
80
+ ): Promise<PluginUserPageContent> | PluginUserPageContent;
81
+ }