@sentry/junior-plugin-api 0.74.0 → 0.75.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/context.d.ts +59 -0
- package/dist/credentials.d.ts +158 -0
- package/dist/database.d.ts +13 -0
- package/dist/dispatch.d.ts +13 -0
- package/dist/hooks.d.ts +17 -0
- package/dist/index.d.ts +11 -616
- package/dist/index.js +79 -59
- package/dist/manifest.d.ts +74 -0
- package/dist/operations.d.ts +93 -0
- package/dist/prompt.d.ts +42 -0
- package/dist/registration.d.ts +13 -0
- package/dist/schemas.d.ts +108 -0
- package/dist/state.d.ts +20 -0
- package/dist/tools.d.ts +107 -0
- package/package.json +2 -1
- package/src/context.ts +72 -0
- package/src/credentials.ts +200 -0
- package/src/database.ts +22 -0
- package/src/dispatch.ts +22 -0
- package/src/hooks.ts +67 -0
- package/src/index.ts +11 -875
- package/src/manifest.ts +87 -0
- package/src/operations.ts +126 -0
- package/src/prompt.ts +59 -0
- package/src/registration.ts +62 -0
- package/src/schemas.ts +161 -0
- package/src/state.ts +26 -0
- package/src/tools.ts +130 -0
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export interface PluginOAuthConfig {
|
|
2
|
+
authorizeEndpoint: string;
|
|
3
|
+
authorizeParams?: Record<string, string>;
|
|
4
|
+
clientIdEnv: string;
|
|
5
|
+
clientSecretEnv: string;
|
|
6
|
+
scope?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Treat a provider token response with `scope: ""` like an omitted scope and
|
|
9
|
+
* fall back to the requested scope string when storing the token.
|
|
10
|
+
*/
|
|
11
|
+
treatEmptyScopeAsUnreported?: boolean;
|
|
12
|
+
tokenAuthMethod?: "body" | "basic";
|
|
13
|
+
tokenEndpoint: string;
|
|
14
|
+
tokenExtraHeaders?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PluginOAuthBearerCredentials {
|
|
18
|
+
apiHeaders?: Record<string, string>;
|
|
19
|
+
authTokenEnv: string;
|
|
20
|
+
authTokenPlaceholder?: string;
|
|
21
|
+
domains: string[];
|
|
22
|
+
type: "oauth-bearer";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type PluginCredentials = PluginOAuthBearerCredentials;
|
|
26
|
+
|
|
27
|
+
export interface PluginNpmRuntimeDependency {
|
|
28
|
+
package: string;
|
|
29
|
+
type: "npm";
|
|
30
|
+
version: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface PluginSystemRuntimeDependency {
|
|
34
|
+
package: string;
|
|
35
|
+
type: "system";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PluginSystemRuntimeDependencyFromUrl {
|
|
39
|
+
sha256: string;
|
|
40
|
+
type: "system";
|
|
41
|
+
url: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type PluginRuntimeDependency =
|
|
45
|
+
| PluginNpmRuntimeDependency
|
|
46
|
+
| PluginSystemRuntimeDependency
|
|
47
|
+
| PluginSystemRuntimeDependencyFromUrl;
|
|
48
|
+
|
|
49
|
+
export interface PluginRuntimePostinstallCommand {
|
|
50
|
+
args?: string[];
|
|
51
|
+
cmd: string;
|
|
52
|
+
sudo?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface PluginMcpConfig {
|
|
56
|
+
allowedTools?: string[];
|
|
57
|
+
headers?: Record<string, string>;
|
|
58
|
+
transport: "http";
|
|
59
|
+
url: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface PluginEnvVarDeclaration {
|
|
63
|
+
default?: string;
|
|
64
|
+
exposeToCommandEnv?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface PluginManifest {
|
|
68
|
+
apiHeaders?: Record<string, string>;
|
|
69
|
+
capabilities?: string[];
|
|
70
|
+
commandEnv?: Record<string, string>;
|
|
71
|
+
configKeys?: string[];
|
|
72
|
+
credentials?: PluginCredentials;
|
|
73
|
+
description: string;
|
|
74
|
+
displayName: string;
|
|
75
|
+
domains?: string[];
|
|
76
|
+
envVars?: Record<string, PluginEnvVarDeclaration>;
|
|
77
|
+
mcp?: PluginMcpConfig;
|
|
78
|
+
name: string;
|
|
79
|
+
oauth?: PluginOAuthConfig;
|
|
80
|
+
runtimeDependencies?: PluginRuntimeDependency[];
|
|
81
|
+
runtimePostinstall?: PluginRuntimePostinstallCommand[];
|
|
82
|
+
target?: {
|
|
83
|
+
commandFlags?: string[];
|
|
84
|
+
configKey: string;
|
|
85
|
+
type: string;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { PluginContext } from "./context";
|
|
2
|
+
import type { PluginDb } from "./database";
|
|
3
|
+
import type { Dispatch, DispatchOptions, DispatchResult } from "./dispatch";
|
|
4
|
+
import type { PluginReadState, PluginState } from "./state";
|
|
5
|
+
|
|
6
|
+
export type PluginConversationStatus =
|
|
7
|
+
| "active"
|
|
8
|
+
| "completed"
|
|
9
|
+
| "failed"
|
|
10
|
+
| "hung"
|
|
11
|
+
| "superseded";
|
|
12
|
+
|
|
13
|
+
export interface PluginConversationSummary {
|
|
14
|
+
channelName?: string;
|
|
15
|
+
conversationId: string;
|
|
16
|
+
displayTitle: string;
|
|
17
|
+
lastActivityAt: string;
|
|
18
|
+
lastUpdatedAt: string;
|
|
19
|
+
source?: "api" | "internal" | "local" | "plugin" | "scheduler" | "slack";
|
|
20
|
+
status: PluginConversationStatus;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PluginConversations {
|
|
24
|
+
listRecent(options?: {
|
|
25
|
+
limit?: number;
|
|
26
|
+
}): Promise<PluginConversationSummary[]>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface HeartbeatHookContext extends PluginContext {
|
|
30
|
+
agent: {
|
|
31
|
+
dispatch(options: DispatchOptions): Promise<DispatchResult>;
|
|
32
|
+
get(id: string): Promise<Dispatch | undefined>;
|
|
33
|
+
};
|
|
34
|
+
nowMs: number;
|
|
35
|
+
state: PluginState;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface HeartbeatResult {
|
|
39
|
+
dispatchCount?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface StorageMigrationResult {
|
|
43
|
+
existing: number;
|
|
44
|
+
migrated: number;
|
|
45
|
+
missing: number;
|
|
46
|
+
scanned: number;
|
|
47
|
+
skipped?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface StorageMigrationContext extends PluginContext {
|
|
51
|
+
db: PluginDb;
|
|
52
|
+
state: PluginState;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type PluginOperationalTone = "danger" | "good" | "neutral" | "warning";
|
|
56
|
+
|
|
57
|
+
export interface PluginOperationalMetric {
|
|
58
|
+
label: string;
|
|
59
|
+
tone?: PluginOperationalTone;
|
|
60
|
+
value: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface PluginOperationalField {
|
|
64
|
+
key: string;
|
|
65
|
+
label: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface PluginOperationalRecord {
|
|
69
|
+
id: string;
|
|
70
|
+
tone?: PluginOperationalTone;
|
|
71
|
+
values: Record<string, string>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface PluginOperationalRecordSet {
|
|
75
|
+
fields?: PluginOperationalField[];
|
|
76
|
+
emptyText?: string;
|
|
77
|
+
records?: PluginOperationalRecord[];
|
|
78
|
+
title: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface PluginOperationalReportContent {
|
|
82
|
+
generatedAt?: string;
|
|
83
|
+
metrics?: PluginOperationalMetric[];
|
|
84
|
+
recordSets?: PluginOperationalRecordSet[];
|
|
85
|
+
title?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface PluginOperationalReport extends PluginOperationalReportContent {
|
|
89
|
+
pluginName: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface OperationalReportHookContext extends PluginContext {
|
|
93
|
+
conversations: PluginConversations;
|
|
94
|
+
nowMs: number;
|
|
95
|
+
state: PluginReadState;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type PluginRouteMethod =
|
|
99
|
+
| "GET"
|
|
100
|
+
| "POST"
|
|
101
|
+
| "PUT"
|
|
102
|
+
| "PATCH"
|
|
103
|
+
| "DELETE"
|
|
104
|
+
| "HEAD"
|
|
105
|
+
| "OPTIONS"
|
|
106
|
+
| "ALL";
|
|
107
|
+
|
|
108
|
+
export type PluginRouteHandler = {
|
|
109
|
+
bivarianceHack(request: Request): Promise<Response> | Response;
|
|
110
|
+
}["bivarianceHack"];
|
|
111
|
+
|
|
112
|
+
export interface PluginRoute {
|
|
113
|
+
handler: PluginRouteHandler;
|
|
114
|
+
method?: PluginRouteMethod | PluginRouteMethod[];
|
|
115
|
+
path: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface RouteRegistrationHookContext extends PluginContext {}
|
|
119
|
+
|
|
120
|
+
export interface SlackConversationLink {
|
|
121
|
+
url: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface SlackConversationLinkHookContext extends PluginContext {
|
|
125
|
+
conversationId: string;
|
|
126
|
+
}
|
package/src/prompt.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { InvocationContext, PluginContext } from "./context";
|
|
2
|
+
import type {
|
|
3
|
+
PluginSessionState,
|
|
4
|
+
PluginSessionStateAppend,
|
|
5
|
+
PluginState,
|
|
6
|
+
} from "./state";
|
|
7
|
+
|
|
8
|
+
export interface UserPromptContribution {
|
|
9
|
+
id: string;
|
|
10
|
+
text: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UserPromptContributionResult {
|
|
14
|
+
contributions?: UserPromptContribution[];
|
|
15
|
+
sessionState?: PluginSessionStateAppend[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type UserPromptHookContext = PluginContext &
|
|
19
|
+
InvocationContext & {
|
|
20
|
+
isFirstPrompt: boolean;
|
|
21
|
+
session: PluginSessionState;
|
|
22
|
+
state: PluginState;
|
|
23
|
+
userText: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export interface PluginTaskEnqueueOptions {
|
|
27
|
+
idempotencyKey: string;
|
|
28
|
+
name: string;
|
|
29
|
+
payload?: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface PluginTaskEnqueueResult {
|
|
33
|
+
id: string;
|
|
34
|
+
status: "created" | "already_exists";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface PluginTaskQueue {
|
|
38
|
+
enqueue(options: PluginTaskEnqueueOptions): Promise<PluginTaskEnqueueResult>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type TurnObservationHookContext = PluginContext &
|
|
42
|
+
InvocationContext & {
|
|
43
|
+
observationId: string;
|
|
44
|
+
tasks: PluginTaskQueue;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export interface PluginTaskContext extends PluginContext {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
observation?: {
|
|
51
|
+
load(): Promise<unknown | undefined>;
|
|
52
|
+
};
|
|
53
|
+
payload?: unknown;
|
|
54
|
+
state: PluginState;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type PluginTaskHandler = (
|
|
58
|
+
ctx: PluginTaskContext,
|
|
59
|
+
) => Promise<void> | void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { PluginDatabaseConfig } from "./database";
|
|
2
|
+
import type { PluginHooks } from "./hooks";
|
|
3
|
+
import type { PluginManifest } from "./manifest";
|
|
4
|
+
|
|
5
|
+
export type PluginRegistrationInput = {
|
|
6
|
+
database?: PluginDatabaseConfig;
|
|
7
|
+
hooks?: PluginHooks;
|
|
8
|
+
manifest: PluginManifest;
|
|
9
|
+
packageName?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export interface PluginRegistration extends PluginRegistrationInput {}
|
|
13
|
+
|
|
14
|
+
const PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
15
|
+
|
|
16
|
+
/** Define one Junior plugin registration for app and build-time wiring. */
|
|
17
|
+
export function defineJuniorPlugin(
|
|
18
|
+
plugin: PluginRegistrationInput,
|
|
19
|
+
): PluginRegistration {
|
|
20
|
+
if ("pluginConfig" in plugin) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"pluginConfig is no longer supported. Put runtime metadata in manifest or plugin registration fields.",
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
if ("name" in plugin) {
|
|
26
|
+
throw new Error("defineJuniorPlugin() uses manifest.name for identity.");
|
|
27
|
+
}
|
|
28
|
+
const manifest = plugin.manifest;
|
|
29
|
+
if (!manifest) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"defineJuniorPlugin() requires a manifest. Use a package name string in defineJuniorPlugins([...]) for plugin.yaml packages.",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const name = manifest.name;
|
|
35
|
+
if (!name) {
|
|
36
|
+
throw new Error("Junior plugin manifest.name is required.");
|
|
37
|
+
}
|
|
38
|
+
if (!PLUGIN_NAME_RE.test(name)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Junior plugin registration name "${name}" must be a lowercase plugin identifier.`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (
|
|
44
|
+
typeof manifest.displayName !== "string" ||
|
|
45
|
+
!manifest.displayName.trim()
|
|
46
|
+
) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Junior plugin "${name}" manifest.displayName is required.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (
|
|
52
|
+
typeof manifest.description !== "string" ||
|
|
53
|
+
!manifest.description.trim()
|
|
54
|
+
) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Junior plugin "${name}" manifest.description is required.`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
...plugin,
|
|
61
|
+
};
|
|
62
|
+
}
|
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const slackTeamIdSchema = z.string().regex(/^T[A-Z0-9]+$/);
|
|
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_-]*$/);
|
|
8
|
+
const exactActorUserIdSchema = z
|
|
9
|
+
.string()
|
|
10
|
+
.min(1)
|
|
11
|
+
.refine(
|
|
12
|
+
(value) => value === value.trim() && value.toLowerCase() !== "unknown",
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const nonBlankStringSchema = z
|
|
16
|
+
.string()
|
|
17
|
+
.refine((value) => value.trim().length > 0);
|
|
18
|
+
|
|
19
|
+
/** Runtime-owned Slack address for routing future work or side effects. */
|
|
20
|
+
export const slackDestinationSchema = z
|
|
21
|
+
.object({
|
|
22
|
+
platform: z.literal("slack"),
|
|
23
|
+
teamId: slackTeamIdSchema,
|
|
24
|
+
channelId: slackConversationIdSchema,
|
|
25
|
+
})
|
|
26
|
+
.strict();
|
|
27
|
+
|
|
28
|
+
/** Runtime-owned local CLI conversation address. */
|
|
29
|
+
export const localDestinationSchema = z
|
|
30
|
+
.object({
|
|
31
|
+
platform: z.literal("local"),
|
|
32
|
+
conversationId: localConversationIdSchema,
|
|
33
|
+
})
|
|
34
|
+
.strict();
|
|
35
|
+
|
|
36
|
+
/** Runtime-owned provider-neutral address for routing future work or side effects. */
|
|
37
|
+
export const destinationSchema = z.discriminatedUnion("platform", [
|
|
38
|
+
slackDestinationSchema,
|
|
39
|
+
localDestinationSchema,
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
/** Runtime-owned Slack coordinates for the inbound invocation. */
|
|
43
|
+
export const slackSourceSchema = z
|
|
44
|
+
.object({
|
|
45
|
+
platform: z.literal("slack"),
|
|
46
|
+
teamId: slackTeamIdSchema,
|
|
47
|
+
channelId: slackConversationIdSchema,
|
|
48
|
+
messageTs: nonBlankStringSchema.optional(),
|
|
49
|
+
threadTs: nonBlankStringSchema.optional(),
|
|
50
|
+
})
|
|
51
|
+
.strict();
|
|
52
|
+
|
|
53
|
+
/** Runtime-owned local CLI coordinates for the inbound invocation. */
|
|
54
|
+
export const localSourceSchema = localDestinationSchema;
|
|
55
|
+
|
|
56
|
+
/** Runtime-owned provider-neutral coordinates for the inbound invocation. */
|
|
57
|
+
export const sourceSchema = z.discriminatedUnion("platform", [
|
|
58
|
+
slackSourceSchema,
|
|
59
|
+
localSourceSchema,
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
/** Stable user credential subject shape accepted from plugins. */
|
|
63
|
+
export const pluginCredentialSubjectSchema = z
|
|
64
|
+
.object({
|
|
65
|
+
type: z.literal("user"),
|
|
66
|
+
userId: exactActorUserIdSchema,
|
|
67
|
+
allowedWhen: z.literal("private-direct-conversation"),
|
|
68
|
+
})
|
|
69
|
+
.strict();
|
|
70
|
+
|
|
71
|
+
/** Shared exact actor profile fields for platform-scoped requesters. */
|
|
72
|
+
const requesterProfileSchema = {
|
|
73
|
+
email: nonBlankStringSchema.optional(),
|
|
74
|
+
fullName: nonBlankStringSchema.optional(),
|
|
75
|
+
userId: exactActorUserIdSchema,
|
|
76
|
+
userName: nonBlankStringSchema.optional(),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const slackRequesterSchema = z
|
|
80
|
+
.object({
|
|
81
|
+
...requesterProfileSchema,
|
|
82
|
+
platform: z.literal("slack"),
|
|
83
|
+
teamId: slackTeamIdSchema,
|
|
84
|
+
})
|
|
85
|
+
.strict();
|
|
86
|
+
|
|
87
|
+
export const localRequesterSchema = z
|
|
88
|
+
.object({
|
|
89
|
+
...requesterProfileSchema,
|
|
90
|
+
platform: z.literal("local"),
|
|
91
|
+
})
|
|
92
|
+
.strict();
|
|
93
|
+
|
|
94
|
+
/** Runtime-provided requester identity visible to plugin hooks. */
|
|
95
|
+
export const requesterSchema = z.discriminatedUnion("platform", [
|
|
96
|
+
slackRequesterSchema,
|
|
97
|
+
localRequesterSchema,
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
const dispatchMetadataSchema = z
|
|
101
|
+
.record(z.string(), z.string())
|
|
102
|
+
.superRefine((metadata, ctx) => {
|
|
103
|
+
const entries = Object.entries(metadata);
|
|
104
|
+
if (entries.length > 20) {
|
|
105
|
+
ctx.addIssue({
|
|
106
|
+
code: z.ZodIssueCode.custom,
|
|
107
|
+
message: "Dispatch metadata has too many keys",
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
for (const [key, value] of entries) {
|
|
112
|
+
if (!key.trim()) {
|
|
113
|
+
ctx.addIssue({
|
|
114
|
+
code: z.ZodIssueCode.custom,
|
|
115
|
+
message: "Dispatch metadata values must be strings",
|
|
116
|
+
path: [key],
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (key.length > 128) {
|
|
121
|
+
ctx.addIssue({
|
|
122
|
+
code: z.ZodIssueCode.custom,
|
|
123
|
+
message: "Dispatch metadata key exceeds the maximum length",
|
|
124
|
+
path: [key],
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (/[\r\n]/.test(key)) {
|
|
128
|
+
ctx.addIssue({
|
|
129
|
+
code: z.ZodIssueCode.custom,
|
|
130
|
+
message: "Dispatch metadata keys must be single-line strings",
|
|
131
|
+
path: [key],
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (/[\r\n]/.test(value)) {
|
|
135
|
+
ctx.addIssue({
|
|
136
|
+
code: z.ZodIssueCode.custom,
|
|
137
|
+
message: "Dispatch metadata values must be single-line strings",
|
|
138
|
+
path: [key],
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (value.length > 512) {
|
|
142
|
+
ctx.addIssue({
|
|
143
|
+
code: z.ZodIssueCode.custom,
|
|
144
|
+
message: "Dispatch metadata value exceeds the maximum length",
|
|
145
|
+
path: [key],
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
/** Plugin dispatch request accepted by Junior core. */
|
|
152
|
+
export const dispatchOptionsSchema = z
|
|
153
|
+
.object({
|
|
154
|
+
idempotencyKey: nonBlankStringSchema.pipe(z.string().max(512)),
|
|
155
|
+
credentialSubject: pluginCredentialSubjectSchema.optional(),
|
|
156
|
+
destination: slackDestinationSchema,
|
|
157
|
+
input: nonBlankStringSchema.pipe(z.string().max(32_000)),
|
|
158
|
+
metadata: dispatchMetadataSchema.optional(),
|
|
159
|
+
source: sourceSchema,
|
|
160
|
+
})
|
|
161
|
+
.strict();
|
package/src/state.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface PluginState {
|
|
2
|
+
delete(key: string): Promise<void>;
|
|
3
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
4
|
+
set(key: string, value: unknown, ttlMs?: number): Promise<void>;
|
|
5
|
+
setIfNotExists(key: string, value: unknown, ttlMs?: number): Promise<boolean>;
|
|
6
|
+
withLock<T>(
|
|
7
|
+
key: string,
|
|
8
|
+
ttlMs: number,
|
|
9
|
+
callback: () => Promise<T>,
|
|
10
|
+
): Promise<T>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PluginReadState {
|
|
14
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PluginSessionStateAppend {
|
|
18
|
+
key: string;
|
|
19
|
+
value: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PluginSessionState {
|
|
23
|
+
list<T = unknown>(
|
|
24
|
+
key: string,
|
|
25
|
+
): Promise<Array<{ createdAtMs: number; value: T }>>;
|
|
26
|
+
}
|
package/src/tools.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PluginContext,
|
|
3
|
+
LocalInvocationContext,
|
|
4
|
+
Requester,
|
|
5
|
+
SlackInvocationContext,
|
|
6
|
+
} from "./context";
|
|
7
|
+
import type { PluginCredentialSubject } from "./credentials";
|
|
8
|
+
import type { PluginState } from "./state";
|
|
9
|
+
|
|
10
|
+
export interface PluginEnv {
|
|
11
|
+
get(key: string): string | undefined;
|
|
12
|
+
set(key: string, value: string): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PluginDecision {
|
|
16
|
+
deny(message: string): void;
|
|
17
|
+
replaceInput(input: Record<string, unknown>): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Thrown when a plugin tool rejects invalid model or user input. */
|
|
21
|
+
export class PluginToolInputError extends Error {
|
|
22
|
+
constructor(message: string, options?: { cause?: unknown }) {
|
|
23
|
+
super(message, options);
|
|
24
|
+
this.name = "PluginToolInputError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PluginSandbox {
|
|
29
|
+
juniorRoot: string;
|
|
30
|
+
root: string;
|
|
31
|
+
readFile(path: string): Promise<Uint8Array | null>;
|
|
32
|
+
run(input: {
|
|
33
|
+
args?: string[];
|
|
34
|
+
cmd: string;
|
|
35
|
+
cwd?: string;
|
|
36
|
+
env?: Record<string, string>;
|
|
37
|
+
sudo?: boolean;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
exitCode: number;
|
|
40
|
+
stderr: string;
|
|
41
|
+
stdout: string;
|
|
42
|
+
}>;
|
|
43
|
+
writeFile(input: {
|
|
44
|
+
content: string | Uint8Array;
|
|
45
|
+
mode?: number;
|
|
46
|
+
path: string;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SandboxPrepareHookContext extends PluginContext {
|
|
51
|
+
requester?: Requester;
|
|
52
|
+
sandbox: PluginSandbox;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface BeforeToolExecuteHookContext extends PluginContext {
|
|
56
|
+
decision: PluginDecision;
|
|
57
|
+
env: PluginEnv;
|
|
58
|
+
requester?: Requester;
|
|
59
|
+
tool: {
|
|
60
|
+
input: Record<string, unknown>;
|
|
61
|
+
name: string;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type PluginToolExecute<TInput = unknown> = {
|
|
66
|
+
bivarianceHack(
|
|
67
|
+
input: TInput,
|
|
68
|
+
options: { experimental_context?: unknown },
|
|
69
|
+
): Promise<unknown> | unknown;
|
|
70
|
+
}["bivarianceHack"];
|
|
71
|
+
|
|
72
|
+
export interface PluginToolDefinition<TInput = unknown> {
|
|
73
|
+
annotations?: unknown;
|
|
74
|
+
description: string;
|
|
75
|
+
executionMode?: unknown;
|
|
76
|
+
inputSchema: unknown;
|
|
77
|
+
prepareArguments?: (args: unknown) => unknown;
|
|
78
|
+
/**
|
|
79
|
+
* @deprecated Put tool-selection and usage guidance directly in `description`
|
|
80
|
+
* and parameter descriptions. Retained for compatibility; may be removed in a
|
|
81
|
+
* future major version.
|
|
82
|
+
*/
|
|
83
|
+
promptGuidelines?: string[];
|
|
84
|
+
/**
|
|
85
|
+
* @deprecated Put tool-selection and usage guidance directly in `description`
|
|
86
|
+
* and parameter descriptions. Retained for compatibility; may be removed in a
|
|
87
|
+
* future major version.
|
|
88
|
+
*/
|
|
89
|
+
promptSnippet?: string;
|
|
90
|
+
execute?: PluginToolExecute<TInput>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface SlackToolRegistrationHookContext {
|
|
94
|
+
/**
|
|
95
|
+
* Capabilities of the source Slack conversation exposed to this plugin.
|
|
96
|
+
* Recomputed from `source.channelId`, not from `destination`.
|
|
97
|
+
*/
|
|
98
|
+
channelCapabilities: {
|
|
99
|
+
canAddReactions: boolean;
|
|
100
|
+
canCreateCanvas: boolean;
|
|
101
|
+
canPostToChannel: boolean;
|
|
102
|
+
};
|
|
103
|
+
credentialSubject?: PluginCredentialSubject;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface BaseToolRegistrationHookContext extends PluginContext {
|
|
107
|
+
/**
|
|
108
|
+
* Opaque Junior conversation/session identity for this turn.
|
|
109
|
+
* Interactive Slack turns use `slack:{channelId}:{threadTs}`.
|
|
110
|
+
* Scheduled/API turns use an internal id such as `agent-dispatch:{id}`.
|
|
111
|
+
* Do not parse as Slack unless the value starts with `slack:`.
|
|
112
|
+
*/
|
|
113
|
+
conversationId?: string;
|
|
114
|
+
state: PluginState;
|
|
115
|
+
userText?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
interface SlackToolRegistrationContext
|
|
119
|
+
extends BaseToolRegistrationHookContext, SlackInvocationContext {
|
|
120
|
+
slack: SlackToolRegistrationHookContext;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface LocalToolRegistrationContext
|
|
124
|
+
extends BaseToolRegistrationHookContext, LocalInvocationContext {
|
|
125
|
+
slack?: never;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type ToolRegistrationHookContext =
|
|
129
|
+
| LocalToolRegistrationContext
|
|
130
|
+
| SlackToolRegistrationContext;
|