@sentry/junior-plugin-api 0.72.0 → 0.74.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;
@@ -183,6 +273,21 @@ export interface AgentPluginState {
183
273
  export interface AgentPluginReadState {
184
274
  get<T = unknown>(key: string): Promise<T | undefined>;
185
275
  }
276
+ export type AgentPluginConversationStatus = "active" | "completed" | "failed" | "hung" | "superseded";
277
+ export interface AgentPluginConversationSummary {
278
+ channelName?: string;
279
+ conversationId: string;
280
+ displayTitle: string;
281
+ lastActivityAt: string;
282
+ lastUpdatedAt: string;
283
+ source?: "api" | "internal" | "local" | "plugin" | "scheduler" | "slack";
284
+ status: AgentPluginConversationStatus;
285
+ }
286
+ export interface AgentPluginConversations {
287
+ listRecent(options?: {
288
+ limit?: number;
289
+ }): Promise<AgentPluginConversationSummary[]>;
290
+ }
186
291
  export interface HeartbeatHookContext extends AgentPluginContext {
187
292
  agent: {
188
293
  dispatch(options: DispatchOptions): Promise<DispatchResult>;
@@ -225,6 +330,7 @@ export interface PluginOperationalReport extends PluginOperationalReportContent
225
330
  pluginName: string;
226
331
  }
227
332
  export interface OperationalReportHookContext extends AgentPluginContext {
333
+ conversations: AgentPluginConversations;
228
334
  nowMs: number;
229
335
  state: AgentPluginReadState;
230
336
  }
@@ -481,6 +587,7 @@ export interface JuniorPluginManifest {
481
587
  configKeys?: string[];
482
588
  credentials?: JuniorPluginCredentials;
483
589
  description: string;
590
+ displayName: string;
484
591
  domains?: string[];
485
592
  envVars?: Record<string, JuniorPluginEnvVarDeclaration>;
486
593
  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.74.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 {
@@ -271,6 +367,29 @@ export interface AgentPluginReadState {
271
367
  get<T = unknown>(key: string): Promise<T | undefined>;
272
368
  }
273
369
 
370
+ export type AgentPluginConversationStatus =
371
+ | "active"
372
+ | "completed"
373
+ | "failed"
374
+ | "hung"
375
+ | "superseded";
376
+
377
+ export interface AgentPluginConversationSummary {
378
+ channelName?: string;
379
+ conversationId: string;
380
+ displayTitle: string;
381
+ lastActivityAt: string;
382
+ lastUpdatedAt: string;
383
+ source?: "api" | "internal" | "local" | "plugin" | "scheduler" | "slack";
384
+ status: AgentPluginConversationStatus;
385
+ }
386
+
387
+ export interface AgentPluginConversations {
388
+ listRecent(options?: {
389
+ limit?: number;
390
+ }): Promise<AgentPluginConversationSummary[]>;
391
+ }
392
+
274
393
  export interface HeartbeatHookContext extends AgentPluginContext {
275
394
  agent: {
276
395
  dispatch(options: DispatchOptions): Promise<DispatchResult>;
@@ -322,6 +441,7 @@ export interface PluginOperationalReport extends PluginOperationalReportContent
322
441
  }
323
442
 
324
443
  export interface OperationalReportHookContext extends AgentPluginContext {
444
+ conversations: AgentPluginConversations;
325
445
  nowMs: number;
326
446
  state: AgentPluginReadState;
327
447
  }
@@ -672,6 +792,7 @@ export interface JuniorPluginManifest {
672
792
  configKeys?: string[];
673
793
  credentials?: JuniorPluginCredentials;
674
794
  description: string;
795
+ displayName: string;
675
796
  domains?: string[];
676
797
  envVars?: Record<string, JuniorPluginEnvVarDeclaration>;
677
798
  mcp?: JuniorPluginMcpConfig;
@@ -726,6 +847,14 @@ export function defineJuniorPlugin(
726
847
  `Junior plugin registration name "${name}" must be a lowercase plugin identifier.`,
727
848
  );
728
849
  }
850
+ if (
851
+ typeof manifest.displayName !== "string" ||
852
+ !manifest.displayName.trim()
853
+ ) {
854
+ throw new Error(
855
+ `Junior plugin "${name}" manifest.displayName is required.`,
856
+ );
857
+ }
729
858
  if (
730
859
  typeof manifest.description !== "string" ||
731
860
  !manifest.description.trim()