@sentry/junior-plugin-api 0.116.1 → 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/hooks.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { EgressHookContext, EgressResponseHookContext, IssueCredentialHookContext, PluginCredentialResult, PluginGrant, PluginProviderAccount, ResolveOAuthAccountHookContext } from "./credentials";
2
2
  import type { HeartbeatHookContext, HeartbeatResult, OperationalReportHookContext, ApiRouteRegistrationHookContext, PluginOperationalReportContent, PluginRoute, PluginRouteApp, RouteRegistrationHookContext, SlackConversationLink, SlackConversationLinkHookContext } from "./operations";
3
3
  import type { BeforeToolExecuteHookContext, PluginToolDefinition, SandboxPrepareHookContext, ToolRegistrationHookContext } from "./tools";
4
- import type { PromptMessage, SystemPromptContext, UserPromptContext } from "./prompt";
4
+ import type { PromptMessage, SystemPromptContext, UserPromptContext, UserPromptContribution } from "./prompt";
5
5
  export interface PluginHooks {
6
6
  systemPrompt?(ctx: SystemPromptContext): Promise<PromptMessage[]> | PromptMessage[];
7
- userPrompt?(ctx: UserPromptContext): Promise<PromptMessage[] | undefined> | PromptMessage[] | undefined;
7
+ userPrompt?(ctx: UserPromptContext): Promise<UserPromptContribution[] | undefined> | UserPromptContribution[] | undefined;
8
8
  beforeToolExecute?(ctx: BeforeToolExecuteHookContext): Promise<void> | void;
9
9
  grantForEgress?(ctx: EgressHookContext): Promise<PluginGrant | undefined> | PluginGrant | undefined;
10
10
  heartbeat?(ctx: HeartbeatHookContext): Promise<HeartbeatResult | void> | HeartbeatResult | void;
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export * from "./annotations";
2
2
  export * from "./schemas";
3
3
  export * from "./context";
4
4
  export * from "./state";
5
- export { promptMessageSchema, type PromptMessage, type SystemPromptContext, type UserPromptContext, } from "./prompt";
5
+ export { definePromptContext, promptContextSchema, promptMessageSchema, type PromptContext, type PromptContextContribution, type PromptMessage, type SystemPromptContext, type UserPromptContribution, type UserPromptContext, } from "./prompt";
6
6
  export * from "./dispatch";
7
7
  export * from "./resource-events";
8
8
  export * from "./tasks";
@@ -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
@@ -198,9 +198,25 @@ function isSlackDestination(destination) {
198
198
 
199
199
  // src/prompt.ts
200
200
  import { z as z3 } from "zod";
201
+ var promptContextKindSchema = z3.string().trim().min(1).max(64).regex(/^[a-z][a-z0-9_-]*$/);
201
202
  var promptMessageSchema = z3.object({
202
203
  text: z3.string().trim().min(1).max(8e3)
203
204
  }).strict();
205
+ var promptContextSchema = z3.object({
206
+ kind: promptContextKindSchema,
207
+ version: z3.number().int().positive(),
208
+ content: z3.record(z3.string(), z3.unknown())
209
+ }).strict();
210
+ function definePromptContext(definition) {
211
+ const identity = promptContextSchema.pick({ kind: true, version: true }).parse({ kind: definition.kind, version: definition.version });
212
+ return (content) => {
213
+ const parsed = definition.schema.parse(content);
214
+ return {
215
+ context: { ...identity, content: parsed },
216
+ renderPrompt: () => definition.renderPrompt(parsed)
217
+ };
218
+ };
219
+ }
204
220
 
205
221
  // src/resource-events.ts
206
222
  import { z as z4 } from "zod";
@@ -446,6 +462,7 @@ var EgressPolicyDenied = class extends Error {
446
462
 
447
463
  // src/registration.ts
448
464
  var PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
465
+ var USER_PAGE_ID_RE = /^[a-z][a-z0-9-]*$/;
449
466
  function defineJuniorPlugin(plugin) {
450
467
  if ("pluginConfig" in plugin) {
451
468
  throw new Error(
@@ -480,10 +497,69 @@ function defineJuniorPlugin(plugin) {
480
497
  `Junior plugin "${name}" manifest.description is required.`
481
498
  );
482
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
+ }
483
530
  return {
484
531
  ...plugin
485
532
  };
486
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);
487
563
  export {
488
564
  EgressAuthRequired,
489
565
  EgressPolicyDenied,
@@ -494,6 +570,7 @@ export {
494
570
  createSlackSource,
495
571
  defineJuniorPlugin,
496
572
  definePluginTool,
573
+ definePromptContext,
497
574
  destinationSchema,
498
575
  destinationVisibilitySchema,
499
576
  dispatchOptionsSchema,
@@ -520,6 +597,10 @@ export {
520
597
  pluginToolContinuationSchema,
521
598
  pluginToolErrorSchema,
522
599
  pluginToolResultSchema,
600
+ pluginUserPageContentSchema,
601
+ pluginUserPageLinkSchema,
602
+ pluginUserPageLinksSchema,
603
+ promptContextSchema,
523
604
  promptMessageSchema,
524
605
  resourceEventSchema,
525
606
  resourceLinkAnnotationSchema,
package/dist/prompt.d.ts CHANGED
@@ -6,6 +6,27 @@ export declare const promptMessageSchema: z.ZodObject<{
6
6
  }, z.core.$strict>;
7
7
  /** Small plugin-owned prompt text block rendered by Junior core. */
8
8
  export type PromptMessage = z.output<typeof promptMessageSchema>;
9
+ export declare const promptContextSchema: z.ZodObject<{
10
+ kind: z.ZodString;
11
+ version: z.ZodNumber;
12
+ content: z.ZodRecord<z.ZodString, z.ZodUnknown>;
13
+ }, z.core.$strict>;
14
+ /** Structured plugin context retained alongside its model-visible rendering. */
15
+ export type PromptContext = z.output<typeof promptContextSchema>;
16
+ /** Runtime contribution produced from one validated plugin context value. */
17
+ export interface PromptContextContribution {
18
+ context: PromptContext;
19
+ renderPrompt(): string;
20
+ }
21
+ /** Define one typed, versioned plugin context contribution. */
22
+ export declare function definePromptContext<TSchema extends z.ZodType<Record<string, unknown>>>(definition: {
23
+ kind: string;
24
+ version: number;
25
+ schema: TSchema;
26
+ renderPrompt(content: z.output<TSchema>): string;
27
+ }): (content: z.input<TSchema>) => PromptContextContribution;
28
+ /** One request-scoped plugin contribution to the model-visible user prompt. */
29
+ export type UserPromptContribution = PromptMessage | PromptContextContribution;
9
30
  /** Stable platform context for plugin system prompt guidance. */
10
31
  export type SystemPromptContext = Pick<PluginContext, "db" | "log" | "plugin"> & {
11
32
  platform: Platform;
@@ -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.116.1",
3
+ "version": "0.118.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/hooks.ts CHANGED
@@ -29,6 +29,7 @@ import type {
29
29
  PromptMessage,
30
30
  SystemPromptContext,
31
31
  UserPromptContext,
32
+ UserPromptContribution,
32
33
  } from "./prompt";
33
34
 
34
35
  export interface PluginHooks {
@@ -37,7 +38,10 @@ export interface PluginHooks {
37
38
  ): Promise<PromptMessage[]> | PromptMessage[];
38
39
  userPrompt?(
39
40
  ctx: UserPromptContext,
40
- ): Promise<PromptMessage[] | undefined> | PromptMessage[] | undefined;
41
+ ):
42
+ | Promise<UserPromptContribution[] | undefined>
43
+ | UserPromptContribution[]
44
+ | undefined;
41
45
  beforeToolExecute?(ctx: BeforeToolExecuteHookContext): Promise<void> | void;
42
46
  grantForEgress?(
43
47
  ctx: EgressHookContext,
package/src/index.ts CHANGED
@@ -3,9 +3,14 @@ export * from "./schemas";
3
3
  export * from "./context";
4
4
  export * from "./state";
5
5
  export {
6
+ definePromptContext,
7
+ promptContextSchema,
6
8
  promptMessageSchema,
9
+ type PromptContext,
10
+ type PromptContextContribution,
7
11
  type PromptMessage,
8
12
  type SystemPromptContext,
13
+ type UserPromptContribution,
9
14
  type UserPromptContext,
10
15
  } from "./prompt";
11
16
  export * from "./dispatch";
@@ -18,3 +23,4 @@ export * from "./hooks";
18
23
  export * from "./cli";
19
24
  export * from "./manifest";
20
25
  export * from "./registration";
26
+ export * from "./user-pages";
package/src/prompt.ts CHANGED
@@ -9,6 +9,13 @@ import type {
9
9
  } from "./context";
10
10
  import type { PluginState } from "./state";
11
11
 
12
+ const promptContextKindSchema = z
13
+ .string()
14
+ .trim()
15
+ .min(1)
16
+ .max(64)
17
+ .regex(/^[a-z][a-z0-9_-]*$/);
18
+
12
19
  export const promptMessageSchema = z
13
20
  .object({
14
21
  text: z.string().trim().min(1).max(8_000),
@@ -18,6 +25,47 @@ export const promptMessageSchema = z
18
25
  /** Small plugin-owned prompt text block rendered by Junior core. */
19
26
  export type PromptMessage = z.output<typeof promptMessageSchema>;
20
27
 
28
+ export const promptContextSchema = z
29
+ .object({
30
+ kind: promptContextKindSchema,
31
+ version: z.number().int().positive(),
32
+ content: z.record(z.string(), z.unknown()),
33
+ })
34
+ .strict();
35
+
36
+ /** Structured plugin context retained alongside its model-visible rendering. */
37
+ export type PromptContext = z.output<typeof promptContextSchema>;
38
+
39
+ /** Runtime contribution produced from one validated plugin context value. */
40
+ export interface PromptContextContribution {
41
+ context: PromptContext;
42
+ renderPrompt(): string;
43
+ }
44
+
45
+ /** Define one typed, versioned plugin context contribution. */
46
+ export function definePromptContext<
47
+ TSchema extends z.ZodType<Record<string, unknown>>,
48
+ >(definition: {
49
+ kind: string;
50
+ version: number;
51
+ schema: TSchema;
52
+ renderPrompt(content: z.output<TSchema>): string;
53
+ }): (content: z.input<TSchema>) => PromptContextContribution {
54
+ const identity = promptContextSchema
55
+ .pick({ kind: true, version: true })
56
+ .parse({ kind: definition.kind, version: definition.version });
57
+ return (content) => {
58
+ const parsed = definition.schema.parse(content);
59
+ return {
60
+ context: { ...identity, content: parsed },
61
+ renderPrompt: () => definition.renderPrompt(parsed),
62
+ };
63
+ };
64
+ }
65
+
66
+ /** One request-scoped plugin contribution to the model-visible user prompt. */
67
+ export type UserPromptContribution = PromptMessage | PromptContextContribution;
68
+
21
69
  /** Stable platform context for plugin system prompt guidance. */
22
70
  export type SystemPromptContext = Pick<
23
71
  PluginContext,
@@ -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
+ }