@sentry/junior-plugin-api 0.72.0 → 0.73.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/index.d.ts CHANGED
@@ -1,25 +1,84 @@
1
1
  import { z } from "zod";
2
+ /** Runtime-owned Slack address for routing future work or side effects. */
3
+ export declare const slackDestinationSchema: z.ZodObject<{
4
+ platform: z.ZodLiteral<"slack">;
5
+ teamId: z.ZodString;
6
+ channelId: z.ZodString;
7
+ }, z.core.$strict>;
8
+ /** Runtime-owned local CLI conversation address. */
9
+ export declare const localDestinationSchema: z.ZodObject<{
10
+ platform: z.ZodLiteral<"local">;
11
+ conversationId: z.ZodString;
12
+ }, z.core.$strict>;
2
13
  /** Runtime-owned provider-neutral address for routing future work or side effects. */
3
- export declare const destinationSchema: z.ZodObject<{
14
+ export declare const destinationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4
15
  platform: z.ZodLiteral<"slack">;
5
16
  teamId: z.ZodString;
6
17
  channelId: z.ZodString;
18
+ }, z.core.$strict>, z.ZodObject<{
19
+ platform: z.ZodLiteral<"local">;
20
+ conversationId: z.ZodString;
21
+ }, z.core.$strict>], "platform">;
22
+ /** Runtime-owned Slack coordinates for the inbound invocation. */
23
+ export declare const slackSourceSchema: z.ZodObject<{
24
+ platform: z.ZodLiteral<"slack">;
25
+ teamId: z.ZodString;
26
+ channelId: z.ZodString;
27
+ messageTs: z.ZodOptional<z.ZodString>;
28
+ threadTs: z.ZodOptional<z.ZodString>;
29
+ }, z.core.$strict>;
30
+ /** Runtime-owned local CLI coordinates for the inbound invocation. */
31
+ export declare const localSourceSchema: z.ZodObject<{
32
+ platform: z.ZodLiteral<"local">;
33
+ conversationId: z.ZodString;
7
34
  }, z.core.$strict>;
35
+ /** Runtime-owned provider-neutral coordinates for the inbound invocation. */
36
+ export declare const sourceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
37
+ platform: z.ZodLiteral<"slack">;
38
+ teamId: z.ZodString;
39
+ channelId: z.ZodString;
40
+ messageTs: z.ZodOptional<z.ZodString>;
41
+ threadTs: z.ZodOptional<z.ZodString>;
42
+ }, z.core.$strict>, z.ZodObject<{
43
+ platform: z.ZodLiteral<"local">;
44
+ conversationId: z.ZodString;
45
+ }, z.core.$strict>], "platform">;
8
46
  /** Stable user credential subject shape accepted from plugins. */
9
47
  export declare const agentPluginCredentialSubjectSchema: z.ZodObject<{
10
48
  type: z.ZodLiteral<"user">;
11
49
  userId: z.ZodString;
12
50
  allowedWhen: z.ZodLiteral<"private-direct-conversation">;
13
51
  }, z.core.$strict>;
14
- /** Runtime-provided requester identity visible to plugin hooks. */
15
- export declare const requesterSchema: z.ZodObject<{
52
+ export declare const slackRequesterSchema: z.ZodObject<{
16
53
  platform: z.ZodLiteral<"slack">;
17
54
  teamId: z.ZodString;
55
+ email: z.ZodOptional<z.ZodString>;
56
+ fullName: z.ZodOptional<z.ZodString>;
18
57
  userId: z.ZodString;
19
58
  userName: z.ZodOptional<z.ZodString>;
20
- fullName: z.ZodOptional<z.ZodString>;
59
+ }, z.core.$strict>;
60
+ export declare const localRequesterSchema: z.ZodObject<{
61
+ platform: z.ZodLiteral<"local">;
21
62
  email: z.ZodOptional<z.ZodString>;
63
+ fullName: z.ZodOptional<z.ZodString>;
64
+ userId: z.ZodString;
65
+ userName: z.ZodOptional<z.ZodString>;
22
66
  }, z.core.$strict>;
67
+ /** Runtime-provided requester identity visible to plugin hooks. */
68
+ export declare const requesterSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
69
+ platform: z.ZodLiteral<"slack">;
70
+ teamId: z.ZodString;
71
+ email: z.ZodOptional<z.ZodString>;
72
+ fullName: z.ZodOptional<z.ZodString>;
73
+ userId: z.ZodString;
74
+ userName: z.ZodOptional<z.ZodString>;
75
+ }, z.core.$strict>, z.ZodObject<{
76
+ platform: z.ZodLiteral<"local">;
77
+ email: z.ZodOptional<z.ZodString>;
78
+ fullName: z.ZodOptional<z.ZodString>;
79
+ userId: z.ZodString;
80
+ userName: z.ZodOptional<z.ZodString>;
81
+ }, z.core.$strict>], "platform">;
23
82
  /** Plugin dispatch request accepted by Junior core. */
24
83
  export declare const dispatchOptionsSchema: z.ZodObject<{
25
84
  idempotencyKey: z.ZodPipe<z.ZodString, z.ZodString>;
@@ -37,6 +96,15 @@ export declare const dispatchOptionsSchema: z.ZodObject<{
37
96
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
38
97
  }, z.core.$strict>;
39
98
  export type Requester = z.output<typeof requesterSchema>;
99
+ export type SlackRequester = z.output<typeof slackRequesterSchema>;
100
+ export type LocalRequester = z.output<typeof localRequesterSchema>;
101
+ export type Source = z.output<typeof sourceSchema>;
102
+ export type SlackSource = Extract<Source, {
103
+ platform: "slack";
104
+ }>;
105
+ export type LocalSource = Extract<Source, {
106
+ platform: "local";
107
+ }>;
40
108
  export interface AgentPluginMetadata {
41
109
  name: string;
42
110
  }
@@ -63,6 +131,28 @@ export interface AgentPluginContext {
63
131
  log: AgentPluginLogger;
64
132
  plugin: AgentPluginMetadata;
65
133
  }
134
+ interface BaseInvocationContext {
135
+ /**
136
+ * Opaque Junior conversation/session identity for this invocation.
137
+ * Interactive Slack turns use `slack:{channelId}:{threadTs}`.
138
+ */
139
+ conversationId?: string;
140
+ }
141
+ export interface SlackInvocationContext extends BaseInvocationContext {
142
+ /** Runtime-owned default outbound destination for this invocation, if any. */
143
+ destination?: SlackDestination;
144
+ requester?: SlackRequester;
145
+ /** Runtime-owned source where the invocation came from. */
146
+ source: SlackSource;
147
+ }
148
+ export interface LocalInvocationContext extends BaseInvocationContext {
149
+ /** Runtime-owned default outbound destination for this invocation, if any. */
150
+ destination?: LocalDestination;
151
+ requester?: LocalRequester;
152
+ /** Runtime-owned source where the invocation came from. */
153
+ source: LocalSource;
154
+ }
155
+ export type InvocationContext = LocalInvocationContext | SlackInvocationContext;
66
156
  export interface AgentPluginSandbox {
67
157
  juniorRoot: string;
68
158
  root: string;
@@ -122,23 +212,19 @@ export interface AgentPluginToolDefinition<TInput = unknown> {
122
212
  promptSnippet?: string;
123
213
  execute?: AgentPluginToolExecute<TInput>;
124
214
  }
125
- export interface ToolRegistrationHookContext extends AgentPluginContext {
215
+ export interface SlackToolRegistrationHookContext {
126
216
  /**
127
- * Capabilities of `channelId` the raw conversation channel exposed to
128
- * this plugin. Recomputed from `channelId`, not from `destination`.
217
+ * Capabilities of the source Slack conversation exposed to this plugin.
218
+ * Recomputed from `source.channelId`, not from `destination`.
129
219
  */
130
- channelCapabilities?: {
220
+ channelCapabilities: {
131
221
  canAddReactions: boolean;
132
222
  canCreateCanvas: boolean;
133
223
  canPostToChannel: boolean;
134
224
  };
135
- /**
136
- * The raw Slack channel ID for this conversation — the DM or channel where
137
- * this turn is happening, without any assistant-context-source override.
138
- * Use this as the stable binding key for state scoped to a Slack conversation.
139
- * `channelCapabilities` describes this channel.
140
- */
141
- channelId?: string;
225
+ credentialSubject?: AgentPluginCredentialSubject;
226
+ }
227
+ interface BaseToolRegistrationHookContext extends AgentPluginContext {
142
228
  /**
143
229
  * Opaque Junior conversation/session identity for this turn.
144
230
  * Interactive Slack turns use `slack:{channelId}:{threadTs}`.
@@ -146,22 +232,26 @@ export interface ToolRegistrationHookContext extends AgentPluginContext {
146
232
  * Do not parse as Slack unless the value starts with `slack:`.
147
233
  */
148
234
  conversationId?: string;
149
- credentialSubject?: AgentPluginCredentialSubject;
150
- /**
151
- * Runtime-owned destination suitable for future autonomous dispatch. For
152
- * Slack, this is the raw conversation channel, not a thread timestamp or
153
- * assistant-context source channel.
154
- */
155
- destination?: Destination;
156
- messageTs?: string;
157
- requester?: Requester;
158
235
  state: AgentPluginState;
159
- teamId?: string;
160
- threadTs?: string;
161
236
  userText?: string;
162
237
  }
238
+ interface SlackToolRegistrationContext extends BaseToolRegistrationHookContext, SlackInvocationContext {
239
+ slack: SlackToolRegistrationHookContext;
240
+ }
241
+ interface LocalToolRegistrationContext extends BaseToolRegistrationHookContext, LocalInvocationContext {
242
+ slack?: never;
243
+ }
244
+ export type ToolRegistrationHookContext = LocalToolRegistrationContext | SlackToolRegistrationContext;
163
245
  export type AgentPluginCredentialSubject = z.output<typeof agentPluginCredentialSubjectSchema>;
164
246
  export type Destination = z.output<typeof destinationSchema>;
247
+ export type SlackDestination = Extract<Destination, {
248
+ platform: "slack";
249
+ }>;
250
+ export type LocalDestination = Extract<Destination, {
251
+ platform: "local";
252
+ }>;
253
+ /** Narrow a runtime destination to the Slack-specific address shape. */
254
+ export declare function isSlackDestination(destination: Destination | undefined): destination is SlackDestination;
165
255
  export type DispatchOptions = z.output<typeof dispatchOptionsSchema>;
166
256
  export interface DispatchResult {
167
257
  id: string;
@@ -481,6 +571,7 @@ export interface JuniorPluginManifest {
481
571
  configKeys?: string[];
482
572
  credentials?: JuniorPluginCredentials;
483
573
  description: string;
574
+ displayName: string;
484
575
  domains?: string[];
485
576
  envVars?: Record<string, JuniorPluginEnvVarDeclaration>;
486
577
  mcp?: JuniorPluginMcpConfig;
package/dist/index.js CHANGED
@@ -2,28 +2,60 @@
2
2
  import { z } from "zod";
3
3
  var slackTeamIdSchema = z.string().regex(/^T[A-Z0-9]+$/);
4
4
  var slackConversationIdSchema = z.string().regex(/^(C|G|D)[A-Z0-9]+$/);
5
+ var localConversationIdSchema = z.string().regex(/^local:[a-z0-9_-]+:[a-z0-9][a-z0-9_-]*$/);
5
6
  var exactActorUserIdSchema = z.string().min(1).refine(
6
7
  (value) => value === value.trim() && value.toLowerCase() !== "unknown"
7
8
  );
8
9
  var nonBlankStringSchema = z.string().refine((value) => value.trim().length > 0);
9
- var destinationSchema = z.object({
10
+ var slackDestinationSchema = z.object({
10
11
  platform: z.literal("slack"),
11
12
  teamId: slackTeamIdSchema,
12
13
  channelId: slackConversationIdSchema
13
14
  }).strict();
15
+ var localDestinationSchema = z.object({
16
+ platform: z.literal("local"),
17
+ conversationId: localConversationIdSchema
18
+ }).strict();
19
+ var destinationSchema = z.discriminatedUnion("platform", [
20
+ slackDestinationSchema,
21
+ localDestinationSchema
22
+ ]);
23
+ var slackSourceSchema = z.object({
24
+ platform: z.literal("slack"),
25
+ teamId: slackTeamIdSchema,
26
+ channelId: slackConversationIdSchema,
27
+ messageTs: nonBlankStringSchema.optional(),
28
+ threadTs: nonBlankStringSchema.optional()
29
+ }).strict();
30
+ var localSourceSchema = localDestinationSchema;
31
+ var sourceSchema = z.discriminatedUnion("platform", [
32
+ slackSourceSchema,
33
+ localSourceSchema
34
+ ]);
14
35
  var agentPluginCredentialSubjectSchema = z.object({
15
36
  type: z.literal("user"),
16
37
  userId: exactActorUserIdSchema,
17
38
  allowedWhen: z.literal("private-direct-conversation")
18
39
  }).strict();
19
- var requesterSchema = z.object({
20
- platform: z.literal("slack"),
21
- teamId: slackTeamIdSchema,
22
- userId: exactActorUserIdSchema,
23
- userName: nonBlankStringSchema.optional(),
40
+ var requesterProfileSchema = {
41
+ email: nonBlankStringSchema.optional(),
24
42
  fullName: nonBlankStringSchema.optional(),
25
- email: nonBlankStringSchema.optional()
43
+ userId: exactActorUserIdSchema,
44
+ userName: nonBlankStringSchema.optional()
45
+ };
46
+ var slackRequesterSchema = z.object({
47
+ ...requesterProfileSchema,
48
+ platform: z.literal("slack"),
49
+ teamId: slackTeamIdSchema
26
50
  }).strict();
51
+ var localRequesterSchema = z.object({
52
+ ...requesterProfileSchema,
53
+ platform: z.literal("local")
54
+ }).strict();
55
+ var requesterSchema = z.discriminatedUnion("platform", [
56
+ slackRequesterSchema,
57
+ localRequesterSchema
58
+ ]);
27
59
  var dispatchMetadataSchema = z.record(z.string(), z.string()).superRefine((metadata, ctx) => {
28
60
  const entries = Object.entries(metadata);
29
61
  if (entries.length > 20) {
@@ -61,7 +93,7 @@ var dispatchMetadataSchema = z.record(z.string(), z.string()).superRefine((metad
61
93
  var dispatchOptionsSchema = z.object({
62
94
  idempotencyKey: nonBlankStringSchema.pipe(z.string().max(512)),
63
95
  credentialSubject: agentPluginCredentialSubjectSchema.optional(),
64
- destination: destinationSchema,
96
+ destination: slackDestinationSchema,
65
97
  input: nonBlankStringSchema.pipe(z.string().max(32e3)),
66
98
  metadata: dispatchMetadataSchema.optional()
67
99
  }).strict();
@@ -71,6 +103,9 @@ var AgentPluginToolInputError = class extends Error {
71
103
  this.name = "AgentPluginToolInputError";
72
104
  }
73
105
  };
106
+ function isSlackDestination(destination) {
107
+ return destination?.platform === "slack";
108
+ }
74
109
  var agentPluginProviderNameSchema = z.string().regex(/^[a-z][a-z0-9-]*$/);
75
110
  var agentPluginGrantNameSchema = z.string().regex(/^[a-z][a-z0-9.-]*$/);
76
111
  var agentPluginGrantAccessSchema = z.union([
@@ -150,6 +185,11 @@ function defineJuniorPlugin(plugin) {
150
185
  `Junior plugin registration name "${name}" must be a lowercase plugin identifier.`
151
186
  );
152
187
  }
188
+ if (typeof manifest.displayName !== "string" || !manifest.displayName.trim()) {
189
+ throw new Error(
190
+ `Junior plugin "${name}" manifest.displayName is required.`
191
+ );
192
+ }
153
193
  if (typeof manifest.description !== "string" || !manifest.description.trim()) {
154
194
  throw new Error(
155
195
  `Junior plugin "${name}" manifest.description is required.`
@@ -178,5 +218,13 @@ export {
178
218
  defineJuniorPlugin,
179
219
  destinationSchema,
180
220
  dispatchOptionsSchema,
181
- requesterSchema
221
+ isSlackDestination,
222
+ localDestinationSchema,
223
+ localRequesterSchema,
224
+ localSourceSchema,
225
+ requesterSchema,
226
+ slackDestinationSchema,
227
+ slackRequesterSchema,
228
+ slackSourceSchema,
229
+ sourceSchema
182
230
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-plugin-api",
3
- "version": "0.72.0",
3
+ "version": "0.73.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/index.ts CHANGED
@@ -2,6 +2,9 @@ import { z } from "zod";
2
2
 
3
3
  const slackTeamIdSchema = z.string().regex(/^T[A-Z0-9]+$/);
4
4
  const slackConversationIdSchema = z.string().regex(/^(C|G|D)[A-Z0-9]+$/);
5
+ const localConversationIdSchema = z
6
+ .string()
7
+ .regex(/^local:[a-z0-9_-]+:[a-z0-9][a-z0-9_-]*$/);
5
8
  const exactActorUserIdSchema = z
6
9
  .string()
7
10
  .min(1)
@@ -12,15 +15,49 @@ const nonBlankStringSchema = z
12
15
  .string()
13
16
  .refine((value) => value.trim().length > 0);
14
17
 
18
+ /** Runtime-owned Slack address for routing future work or side effects. */
19
+ export const slackDestinationSchema = z
20
+ .object({
21
+ platform: z.literal("slack"),
22
+ teamId: slackTeamIdSchema,
23
+ channelId: slackConversationIdSchema,
24
+ })
25
+ .strict();
26
+
27
+ /** Runtime-owned local CLI conversation address. */
28
+ export const localDestinationSchema = z
29
+ .object({
30
+ platform: z.literal("local"),
31
+ conversationId: localConversationIdSchema,
32
+ })
33
+ .strict();
34
+
15
35
  /** Runtime-owned provider-neutral address for routing future work or side effects. */
16
- export const destinationSchema = z
36
+ export const destinationSchema = z.discriminatedUnion("platform", [
37
+ slackDestinationSchema,
38
+ localDestinationSchema,
39
+ ]);
40
+
41
+ /** Runtime-owned Slack coordinates for the inbound invocation. */
42
+ export const slackSourceSchema = z
17
43
  .object({
18
44
  platform: z.literal("slack"),
19
45
  teamId: slackTeamIdSchema,
20
46
  channelId: slackConversationIdSchema,
47
+ messageTs: nonBlankStringSchema.optional(),
48
+ threadTs: nonBlankStringSchema.optional(),
21
49
  })
22
50
  .strict();
23
51
 
52
+ /** Runtime-owned local CLI coordinates for the inbound invocation. */
53
+ export const localSourceSchema = localDestinationSchema;
54
+
55
+ /** Runtime-owned provider-neutral coordinates for the inbound invocation. */
56
+ export const sourceSchema = z.discriminatedUnion("platform", [
57
+ slackSourceSchema,
58
+ localSourceSchema,
59
+ ]);
60
+
24
61
  /** Stable user credential subject shape accepted from plugins. */
25
62
  export const agentPluginCredentialSubjectSchema = z
26
63
  .object({
@@ -30,18 +67,35 @@ export const agentPluginCredentialSubjectSchema = z
30
67
  })
31
68
  .strict();
32
69
 
33
- /** Runtime-provided requester identity visible to plugin hooks. */
34
- export const requesterSchema = z
70
+ /** Shared exact actor profile fields for platform-scoped requesters. */
71
+ const requesterProfileSchema = {
72
+ email: nonBlankStringSchema.optional(),
73
+ fullName: nonBlankStringSchema.optional(),
74
+ userId: exactActorUserIdSchema,
75
+ userName: nonBlankStringSchema.optional(),
76
+ };
77
+
78
+ export const slackRequesterSchema = z
35
79
  .object({
80
+ ...requesterProfileSchema,
36
81
  platform: z.literal("slack"),
37
82
  teamId: slackTeamIdSchema,
38
- userId: exactActorUserIdSchema,
39
- userName: nonBlankStringSchema.optional(),
40
- fullName: nonBlankStringSchema.optional(),
41
- email: nonBlankStringSchema.optional(),
42
83
  })
43
84
  .strict();
44
85
 
86
+ export const localRequesterSchema = z
87
+ .object({
88
+ ...requesterProfileSchema,
89
+ platform: z.literal("local"),
90
+ })
91
+ .strict();
92
+
93
+ /** Runtime-provided requester identity visible to plugin hooks. */
94
+ export const requesterSchema = z.discriminatedUnion("platform", [
95
+ slackRequesterSchema,
96
+ localRequesterSchema,
97
+ ]);
98
+
45
99
  const dispatchMetadataSchema = z
46
100
  .record(z.string(), z.string())
47
101
  .superRefine((metadata, ctx) => {
@@ -84,13 +138,18 @@ export const dispatchOptionsSchema = z
84
138
  .object({
85
139
  idempotencyKey: nonBlankStringSchema.pipe(z.string().max(512)),
86
140
  credentialSubject: agentPluginCredentialSubjectSchema.optional(),
87
- destination: destinationSchema,
141
+ destination: slackDestinationSchema,
88
142
  input: nonBlankStringSchema.pipe(z.string().max(32_000)),
89
143
  metadata: dispatchMetadataSchema.optional(),
90
144
  })
91
145
  .strict();
92
146
 
93
147
  export type Requester = z.output<typeof requesterSchema>;
148
+ export type SlackRequester = z.output<typeof slackRequesterSchema>;
149
+ export type LocalRequester = z.output<typeof localRequesterSchema>;
150
+ export type Source = z.output<typeof sourceSchema>;
151
+ export type SlackSource = Extract<Source, { platform: "slack" }>;
152
+ export type LocalSource = Extract<Source, { platform: "local" }>;
94
153
 
95
154
  export interface AgentPluginMetadata {
96
155
  name: string;
@@ -125,6 +184,32 @@ export interface AgentPluginContext {
125
184
  plugin: AgentPluginMetadata;
126
185
  }
127
186
 
187
+ interface BaseInvocationContext {
188
+ /**
189
+ * Opaque Junior conversation/session identity for this invocation.
190
+ * Interactive Slack turns use `slack:{channelId}:{threadTs}`.
191
+ */
192
+ conversationId?: string;
193
+ }
194
+
195
+ export interface SlackInvocationContext extends BaseInvocationContext {
196
+ /** Runtime-owned default outbound destination for this invocation, if any. */
197
+ destination?: SlackDestination;
198
+ requester?: SlackRequester;
199
+ /** Runtime-owned source where the invocation came from. */
200
+ source: SlackSource;
201
+ }
202
+
203
+ export interface LocalInvocationContext extends BaseInvocationContext {
204
+ /** Runtime-owned default outbound destination for this invocation, if any. */
205
+ destination?: LocalDestination;
206
+ requester?: LocalRequester;
207
+ /** Runtime-owned source where the invocation came from. */
208
+ source: LocalSource;
209
+ }
210
+
211
+ export type InvocationContext = LocalInvocationContext | SlackInvocationContext;
212
+
128
213
  export interface AgentPluginSandbox {
129
214
  juniorRoot: string;
130
215
  root: string;
@@ -190,23 +275,20 @@ export interface AgentPluginToolDefinition<TInput = unknown> {
190
275
  execute?: AgentPluginToolExecute<TInput>;
191
276
  }
192
277
 
193
- export interface ToolRegistrationHookContext extends AgentPluginContext {
278
+ export interface SlackToolRegistrationHookContext {
194
279
  /**
195
- * Capabilities of `channelId` the raw conversation channel exposed to
196
- * this plugin. Recomputed from `channelId`, not from `destination`.
280
+ * Capabilities of the source Slack conversation exposed to this plugin.
281
+ * Recomputed from `source.channelId`, not from `destination`.
197
282
  */
198
- channelCapabilities?: {
283
+ channelCapabilities: {
199
284
  canAddReactions: boolean;
200
285
  canCreateCanvas: boolean;
201
286
  canPostToChannel: boolean;
202
287
  };
203
- /**
204
- * The raw Slack channel ID for this conversation — the DM or channel where
205
- * this turn is happening, without any assistant-context-source override.
206
- * Use this as the stable binding key for state scoped to a Slack conversation.
207
- * `channelCapabilities` describes this channel.
208
- */
209
- channelId?: string;
288
+ credentialSubject?: AgentPluginCredentialSubject;
289
+ }
290
+
291
+ interface BaseToolRegistrationHookContext extends AgentPluginContext {
210
292
  /**
211
293
  * Opaque Junior conversation/session identity for this turn.
212
294
  * Interactive Slack turns use `slack:{channelId}:{threadTs}`.
@@ -214,27 +296,41 @@ export interface ToolRegistrationHookContext extends AgentPluginContext {
214
296
  * Do not parse as Slack unless the value starts with `slack:`.
215
297
  */
216
298
  conversationId?: string;
217
- credentialSubject?: AgentPluginCredentialSubject;
218
- /**
219
- * Runtime-owned destination suitable for future autonomous dispatch. For
220
- * Slack, this is the raw conversation channel, not a thread timestamp or
221
- * assistant-context source channel.
222
- */
223
- destination?: Destination;
224
- messageTs?: string;
225
- requester?: Requester;
226
299
  state: AgentPluginState;
227
- teamId?: string;
228
- threadTs?: string;
229
300
  userText?: string;
230
301
  }
231
302
 
303
+ interface SlackToolRegistrationContext
304
+ extends BaseToolRegistrationHookContext, SlackInvocationContext {
305
+ slack: SlackToolRegistrationHookContext;
306
+ }
307
+
308
+ interface LocalToolRegistrationContext
309
+ extends BaseToolRegistrationHookContext, LocalInvocationContext {
310
+ slack?: never;
311
+ }
312
+
313
+ export type ToolRegistrationHookContext =
314
+ | LocalToolRegistrationContext
315
+ | SlackToolRegistrationContext;
316
+
232
317
  export type AgentPluginCredentialSubject = z.output<
233
318
  typeof agentPluginCredentialSubjectSchema
234
319
  >;
235
320
 
236
321
  export type Destination = z.output<typeof destinationSchema>;
237
322
 
323
+ export type SlackDestination = Extract<Destination, { platform: "slack" }>;
324
+
325
+ export type LocalDestination = Extract<Destination, { platform: "local" }>;
326
+
327
+ /** Narrow a runtime destination to the Slack-specific address shape. */
328
+ export function isSlackDestination(
329
+ destination: Destination | undefined,
330
+ ): destination is SlackDestination {
331
+ return destination?.platform === "slack";
332
+ }
333
+
238
334
  export type DispatchOptions = z.output<typeof dispatchOptionsSchema>;
239
335
 
240
336
  export interface DispatchResult {
@@ -672,6 +768,7 @@ export interface JuniorPluginManifest {
672
768
  configKeys?: string[];
673
769
  credentials?: JuniorPluginCredentials;
674
770
  description: string;
771
+ displayName: string;
675
772
  domains?: string[];
676
773
  envVars?: Record<string, JuniorPluginEnvVarDeclaration>;
677
774
  mcp?: JuniorPluginMcpConfig;
@@ -726,6 +823,14 @@ export function defineJuniorPlugin(
726
823
  `Junior plugin registration name "${name}" must be a lowercase plugin identifier.`,
727
824
  );
728
825
  }
826
+ if (
827
+ typeof manifest.displayName !== "string" ||
828
+ !manifest.displayName.trim()
829
+ ) {
830
+ throw new Error(
831
+ `Junior plugin "${name}" manifest.displayName is required.`,
832
+ );
833
+ }
729
834
  if (
730
835
  typeof manifest.description !== "string" ||
731
836
  !manifest.description.trim()