@sentry/junior-plugin-api 0.74.1 → 0.76.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/cli.d.ts +39 -0
- package/dist/context.d.ts +97 -0
- package/dist/credentials.d.ts +167 -0
- package/dist/dispatch.d.ts +13 -0
- package/dist/hooks.d.ts +20 -0
- package/dist/index.d.ts +13 -616
- package/dist/index.js +178 -69
- package/dist/manifest.d.ts +74 -0
- package/dist/operations.d.ts +91 -0
- package/dist/prompt.d.ts +22 -0
- package/dist/registration.d.ts +22 -0
- package/dist/schemas.d.ts +133 -0
- package/dist/state.d.ts +10 -0
- package/dist/tasks.d.ts +98 -0
- package/dist/tools.d.ts +116 -0
- package/package.json +2 -1
- package/src/cli.ts +54 -0
- package/src/context.ts +146 -0
- package/src/credentials.ts +208 -0
- package/src/dispatch.ts +22 -0
- package/src/hooks.ts +78 -0
- package/src/index.ts +18 -875
- package/src/manifest.ts +87 -0
- package/src/operations.ts +124 -0
- package/src/prompt.ts +38 -0
- package/src/registration.ts +72 -0
- package/src/schemas.ts +173 -0
- package/src/state.ts +15 -0
- package/src/tasks.ts +68 -0
- package/src/tools.ts +144 -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,124 @@
|
|
|
1
|
+
import type { PluginContext } from "./context";
|
|
2
|
+
import type { Dispatch, DispatchOptions, DispatchResult } from "./dispatch";
|
|
3
|
+
import type { PluginReadState, PluginState } from "./state";
|
|
4
|
+
|
|
5
|
+
export type PluginConversationStatus =
|
|
6
|
+
| "active"
|
|
7
|
+
| "completed"
|
|
8
|
+
| "failed"
|
|
9
|
+
| "hung"
|
|
10
|
+
| "superseded";
|
|
11
|
+
|
|
12
|
+
export interface PluginConversationSummary {
|
|
13
|
+
channelName?: string;
|
|
14
|
+
conversationId: string;
|
|
15
|
+
displayTitle: string;
|
|
16
|
+
lastActivityAt: string;
|
|
17
|
+
lastUpdatedAt: string;
|
|
18
|
+
source?: "api" | "internal" | "local" | "plugin" | "scheduler" | "slack";
|
|
19
|
+
status: PluginConversationStatus;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PluginConversations {
|
|
23
|
+
listRecent(options?: {
|
|
24
|
+
limit?: number;
|
|
25
|
+
}): Promise<PluginConversationSummary[]>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface HeartbeatHookContext extends PluginContext {
|
|
29
|
+
agent: {
|
|
30
|
+
dispatch(options: DispatchOptions): Promise<DispatchResult>;
|
|
31
|
+
get(id: string): Promise<Dispatch | undefined>;
|
|
32
|
+
};
|
|
33
|
+
nowMs: number;
|
|
34
|
+
state: PluginState;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface HeartbeatResult {
|
|
38
|
+
dispatchCount?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface StorageMigrationResult {
|
|
42
|
+
existing: number;
|
|
43
|
+
migrated: number;
|
|
44
|
+
missing: number;
|
|
45
|
+
scanned: number;
|
|
46
|
+
skipped?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface StorageMigrationContext extends PluginContext {
|
|
50
|
+
state: PluginState;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type PluginOperationalTone = "danger" | "good" | "neutral" | "warning";
|
|
54
|
+
|
|
55
|
+
export interface PluginOperationalMetric {
|
|
56
|
+
label: string;
|
|
57
|
+
tone?: PluginOperationalTone;
|
|
58
|
+
value: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface PluginOperationalField {
|
|
62
|
+
key: string;
|
|
63
|
+
label: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PluginOperationalRecord {
|
|
67
|
+
id: string;
|
|
68
|
+
tone?: PluginOperationalTone;
|
|
69
|
+
values: Record<string, string>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface PluginOperationalRecordSet {
|
|
73
|
+
fields?: PluginOperationalField[];
|
|
74
|
+
emptyText?: string;
|
|
75
|
+
records?: PluginOperationalRecord[];
|
|
76
|
+
title: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface PluginOperationalReportContent {
|
|
80
|
+
generatedAt?: string;
|
|
81
|
+
metrics?: PluginOperationalMetric[];
|
|
82
|
+
recordSets?: PluginOperationalRecordSet[];
|
|
83
|
+
title?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface PluginOperationalReport extends PluginOperationalReportContent {
|
|
87
|
+
pluginName: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface OperationalReportHookContext extends PluginContext {
|
|
91
|
+
conversations: PluginConversations;
|
|
92
|
+
nowMs: number;
|
|
93
|
+
state: PluginReadState;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type PluginRouteMethod =
|
|
97
|
+
| "GET"
|
|
98
|
+
| "POST"
|
|
99
|
+
| "PUT"
|
|
100
|
+
| "PATCH"
|
|
101
|
+
| "DELETE"
|
|
102
|
+
| "HEAD"
|
|
103
|
+
| "OPTIONS"
|
|
104
|
+
| "ALL";
|
|
105
|
+
|
|
106
|
+
export type PluginRouteHandler = {
|
|
107
|
+
bivarianceHack(request: Request): Promise<Response> | Response;
|
|
108
|
+
}["bivarianceHack"];
|
|
109
|
+
|
|
110
|
+
export interface PluginRoute {
|
|
111
|
+
handler: PluginRouteHandler;
|
|
112
|
+
method?: PluginRouteMethod | PluginRouteMethod[];
|
|
113
|
+
path: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface RouteRegistrationHookContext extends PluginContext {}
|
|
117
|
+
|
|
118
|
+
export interface SlackConversationLink {
|
|
119
|
+
url: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface SlackConversationLinkHookContext extends PluginContext {
|
|
123
|
+
conversationId: string;
|
|
124
|
+
}
|
package/src/prompt.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type {
|
|
3
|
+
Destination,
|
|
4
|
+
Platform,
|
|
5
|
+
PluginContext,
|
|
6
|
+
PluginEmbedder,
|
|
7
|
+
Requester,
|
|
8
|
+
Source,
|
|
9
|
+
} from "./context";
|
|
10
|
+
import type { PluginState } from "./state";
|
|
11
|
+
|
|
12
|
+
export const promptMessageSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
text: z.string().trim().min(1).max(8_000),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
17
|
+
|
|
18
|
+
/** Small plugin-owned prompt text block rendered by Junior core. */
|
|
19
|
+
export type PromptMessage = z.output<typeof promptMessageSchema>;
|
|
20
|
+
|
|
21
|
+
/** Stable platform context for plugin system prompt guidance. */
|
|
22
|
+
export type SystemPromptContext = Pick<
|
|
23
|
+
PluginContext,
|
|
24
|
+
"db" | "log" | "plugin"
|
|
25
|
+
> & {
|
|
26
|
+
platform: Platform;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Runtime facts available while building plugin user prompt context. */
|
|
30
|
+
export type UserPromptContext = Pick<PluginContext, "db" | "log" | "plugin"> & {
|
|
31
|
+
conversationId?: string;
|
|
32
|
+
destination: Destination;
|
|
33
|
+
embedder: PluginEmbedder;
|
|
34
|
+
requester?: Requester;
|
|
35
|
+
source: Source;
|
|
36
|
+
state: PluginState;
|
|
37
|
+
text: string;
|
|
38
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { PluginCliDefinition } from "./cli";
|
|
2
|
+
import type { PluginHooks } from "./hooks";
|
|
3
|
+
import type { PluginManifest } from "./manifest";
|
|
4
|
+
import type { PluginTasks } from "./tasks";
|
|
5
|
+
|
|
6
|
+
export interface PluginModelConfig {
|
|
7
|
+
/** Host model family used when no explicit structured model id is configured. */
|
|
8
|
+
structuredModel?: "default" | "fast";
|
|
9
|
+
/** Host model id used for this plugin's structured model calls. */
|
|
10
|
+
structuredModelId?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type PluginRegistrationInput = {
|
|
14
|
+
cli?: PluginCliDefinition;
|
|
15
|
+
hooks?: PluginHooks;
|
|
16
|
+
manifest: PluginManifest;
|
|
17
|
+
model?: PluginModelConfig;
|
|
18
|
+
packageName?: string;
|
|
19
|
+
tasks?: PluginTasks;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export interface PluginRegistration extends PluginRegistrationInput {}
|
|
23
|
+
|
|
24
|
+
const PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
25
|
+
|
|
26
|
+
/** Define one Junior plugin registration for app and build-time wiring. */
|
|
27
|
+
export function defineJuniorPlugin(
|
|
28
|
+
plugin: PluginRegistrationInput,
|
|
29
|
+
): PluginRegistration {
|
|
30
|
+
if ("pluginConfig" in plugin) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
"pluginConfig is no longer supported. Put runtime metadata in manifest or plugin registration fields.",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if ("name" in plugin) {
|
|
36
|
+
throw new Error("defineJuniorPlugin() uses manifest.name for identity.");
|
|
37
|
+
}
|
|
38
|
+
const manifest = plugin.manifest;
|
|
39
|
+
if (!manifest) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
"defineJuniorPlugin() requires a manifest. Use a package name string in defineJuniorPlugins([...]) for plugin.yaml packages.",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const name = manifest.name;
|
|
45
|
+
if (!name) {
|
|
46
|
+
throw new Error("Junior plugin manifest.name is required.");
|
|
47
|
+
}
|
|
48
|
+
if (!PLUGIN_NAME_RE.test(name)) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Junior plugin registration name "${name}" must be a lowercase plugin identifier.`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (
|
|
54
|
+
typeof manifest.displayName !== "string" ||
|
|
55
|
+
!manifest.displayName.trim()
|
|
56
|
+
) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Junior plugin "${name}" manifest.displayName is required.`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (
|
|
62
|
+
typeof manifest.description !== "string" ||
|
|
63
|
+
!manifest.description.trim()
|
|
64
|
+
) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Junior plugin "${name}" manifest.description is required.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
...plugin,
|
|
71
|
+
};
|
|
72
|
+
}
|
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
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 platform names supported by plugin public contracts. */
|
|
20
|
+
export const platformSchema = z.enum(["slack", "local"]);
|
|
21
|
+
|
|
22
|
+
/** Runtime source visibility visible to plugins. */
|
|
23
|
+
export const sourceTypeSchema = z.enum(["pub", "priv"]);
|
|
24
|
+
|
|
25
|
+
const slackAddressSchema = z
|
|
26
|
+
.object({
|
|
27
|
+
platform: z.literal("slack"),
|
|
28
|
+
teamId: slackTeamIdSchema,
|
|
29
|
+
channelId: slackConversationIdSchema,
|
|
30
|
+
})
|
|
31
|
+
.strict();
|
|
32
|
+
|
|
33
|
+
/** Runtime-owned Slack address for routing future work or side effects. */
|
|
34
|
+
export const slackDestinationSchema = slackAddressSchema;
|
|
35
|
+
|
|
36
|
+
/** Runtime-owned local CLI conversation address. */
|
|
37
|
+
export const localDestinationSchema = z
|
|
38
|
+
.object({
|
|
39
|
+
platform: z.literal("local"),
|
|
40
|
+
conversationId: localConversationIdSchema,
|
|
41
|
+
})
|
|
42
|
+
.strict();
|
|
43
|
+
|
|
44
|
+
/** Runtime-owned provider-neutral address for routing future work or side effects. */
|
|
45
|
+
export const destinationSchema = z.discriminatedUnion("platform", [
|
|
46
|
+
slackDestinationSchema,
|
|
47
|
+
localDestinationSchema,
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
/** Runtime-owned Slack coordinates for the inbound invocation. */
|
|
51
|
+
export const slackSourceSchema = slackAddressSchema
|
|
52
|
+
.extend({
|
|
53
|
+
type: sourceTypeSchema,
|
|
54
|
+
messageTs: nonBlankStringSchema.optional(),
|
|
55
|
+
threadTs: nonBlankStringSchema.optional(),
|
|
56
|
+
})
|
|
57
|
+
.strict();
|
|
58
|
+
|
|
59
|
+
/** Runtime-owned local CLI coordinates for the inbound invocation. */
|
|
60
|
+
export const localSourceSchema = z
|
|
61
|
+
.object({
|
|
62
|
+
platform: z.literal("local"),
|
|
63
|
+
type: z.literal("priv"),
|
|
64
|
+
conversationId: localConversationIdSchema,
|
|
65
|
+
})
|
|
66
|
+
.strict();
|
|
67
|
+
|
|
68
|
+
/** Runtime-owned provider-neutral coordinates for the inbound invocation. */
|
|
69
|
+
export const sourceSchema = z.discriminatedUnion("platform", [
|
|
70
|
+
slackSourceSchema,
|
|
71
|
+
localSourceSchema,
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
/** Stable user credential subject shape accepted from plugins. */
|
|
75
|
+
export const pluginCredentialSubjectSchema = z
|
|
76
|
+
.object({
|
|
77
|
+
type: z.literal("user"),
|
|
78
|
+
userId: exactActorUserIdSchema,
|
|
79
|
+
allowedWhen: z.literal("private-direct-conversation"),
|
|
80
|
+
})
|
|
81
|
+
.strict();
|
|
82
|
+
|
|
83
|
+
/** Shared exact actor profile fields for platform-scoped requesters. */
|
|
84
|
+
const requesterProfileSchema = {
|
|
85
|
+
email: nonBlankStringSchema.optional(),
|
|
86
|
+
fullName: nonBlankStringSchema.optional(),
|
|
87
|
+
userId: exactActorUserIdSchema,
|
|
88
|
+
userName: nonBlankStringSchema.optional(),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const slackRequesterSchema = z
|
|
92
|
+
.object({
|
|
93
|
+
...requesterProfileSchema,
|
|
94
|
+
platform: z.literal("slack"),
|
|
95
|
+
teamId: slackTeamIdSchema,
|
|
96
|
+
})
|
|
97
|
+
.strict();
|
|
98
|
+
|
|
99
|
+
export const localRequesterSchema = z
|
|
100
|
+
.object({
|
|
101
|
+
...requesterProfileSchema,
|
|
102
|
+
platform: z.literal("local"),
|
|
103
|
+
})
|
|
104
|
+
.strict();
|
|
105
|
+
|
|
106
|
+
/** Runtime-provided requester identity visible to plugin hooks. */
|
|
107
|
+
export const requesterSchema = z.discriminatedUnion("platform", [
|
|
108
|
+
slackRequesterSchema,
|
|
109
|
+
localRequesterSchema,
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
const dispatchMetadataSchema = z
|
|
113
|
+
.record(z.string(), z.string())
|
|
114
|
+
.superRefine((metadata, ctx) => {
|
|
115
|
+
const entries = Object.entries(metadata);
|
|
116
|
+
if (entries.length > 20) {
|
|
117
|
+
ctx.addIssue({
|
|
118
|
+
code: z.ZodIssueCode.custom,
|
|
119
|
+
message: "Dispatch metadata has too many keys",
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
for (const [key, value] of entries) {
|
|
124
|
+
if (!key.trim()) {
|
|
125
|
+
ctx.addIssue({
|
|
126
|
+
code: z.ZodIssueCode.custom,
|
|
127
|
+
message: "Dispatch metadata values must be strings",
|
|
128
|
+
path: [key],
|
|
129
|
+
});
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (key.length > 128) {
|
|
133
|
+
ctx.addIssue({
|
|
134
|
+
code: z.ZodIssueCode.custom,
|
|
135
|
+
message: "Dispatch metadata key exceeds the maximum length",
|
|
136
|
+
path: [key],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (/[\r\n]/.test(key)) {
|
|
140
|
+
ctx.addIssue({
|
|
141
|
+
code: z.ZodIssueCode.custom,
|
|
142
|
+
message: "Dispatch metadata keys must be single-line strings",
|
|
143
|
+
path: [key],
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (/[\r\n]/.test(value)) {
|
|
147
|
+
ctx.addIssue({
|
|
148
|
+
code: z.ZodIssueCode.custom,
|
|
149
|
+
message: "Dispatch metadata values must be single-line strings",
|
|
150
|
+
path: [key],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (value.length > 512) {
|
|
154
|
+
ctx.addIssue({
|
|
155
|
+
code: z.ZodIssueCode.custom,
|
|
156
|
+
message: "Dispatch metadata value exceeds the maximum length",
|
|
157
|
+
path: [key],
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/** Plugin dispatch request accepted by Junior core. */
|
|
164
|
+
export const dispatchOptionsSchema = z
|
|
165
|
+
.object({
|
|
166
|
+
idempotencyKey: nonBlankStringSchema.pipe(z.string().max(512)),
|
|
167
|
+
credentialSubject: pluginCredentialSubjectSchema.optional(),
|
|
168
|
+
destination: slackDestinationSchema,
|
|
169
|
+
input: nonBlankStringSchema.pipe(z.string().max(32_000)),
|
|
170
|
+
metadata: dispatchMetadataSchema.optional(),
|
|
171
|
+
source: sourceSchema,
|
|
172
|
+
})
|
|
173
|
+
.strict();
|
package/src/state.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
}
|
package/src/tasks.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public plugin background-task contracts.
|
|
3
|
+
*
|
|
4
|
+
* Plugins register small task handlers, while Junior core owns durable
|
|
5
|
+
* scheduling, queue delivery, retries, and the bounded run projection.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import type { PluginContext, PluginEmbedder, PluginModel } from "./context";
|
|
9
|
+
import { destinationSchema, requesterSchema, sourceSchema } from "./schemas";
|
|
10
|
+
import type { PluginState } from "./state";
|
|
11
|
+
|
|
12
|
+
/** One normalized transcript entry from the completed run exposed to plugin tasks. */
|
|
13
|
+
export const pluginRunTranscriptEntrySchema = z.discriminatedUnion("type", [
|
|
14
|
+
z
|
|
15
|
+
.object({
|
|
16
|
+
type: z.literal("message"),
|
|
17
|
+
role: z.enum(["user", "assistant"]),
|
|
18
|
+
text: z.string().min(1),
|
|
19
|
+
})
|
|
20
|
+
.strict(),
|
|
21
|
+
z
|
|
22
|
+
.object({
|
|
23
|
+
type: z.literal("toolResult"),
|
|
24
|
+
toolName: z.string().min(1),
|
|
25
|
+
isError: z.boolean(),
|
|
26
|
+
text: z.string().min(1).optional(),
|
|
27
|
+
})
|
|
28
|
+
.strict(),
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
/** Runtime-owned completed-run projection exposed to plugin tasks. */
|
|
32
|
+
export const pluginRunContextSchema = z
|
|
33
|
+
.object({
|
|
34
|
+
completedAtMs: z.number().finite(),
|
|
35
|
+
conversationId: z.string().min(1),
|
|
36
|
+
destination: destinationSchema,
|
|
37
|
+
requester: requesterSchema.optional(),
|
|
38
|
+
runId: z.string().min(1),
|
|
39
|
+
source: sourceSchema,
|
|
40
|
+
transcript: z.array(pluginRunTranscriptEntrySchema),
|
|
41
|
+
})
|
|
42
|
+
.strict();
|
|
43
|
+
|
|
44
|
+
export type PluginRunTranscriptEntry = z.output<
|
|
45
|
+
typeof pluginRunTranscriptEntrySchema
|
|
46
|
+
>;
|
|
47
|
+
|
|
48
|
+
export type PluginRunContext = z.output<typeof pluginRunContextSchema>;
|
|
49
|
+
|
|
50
|
+
/** Runtime context passed to a plugin-owned background task. */
|
|
51
|
+
export interface PluginTaskContext extends PluginContext {
|
|
52
|
+
embedder: PluginEmbedder;
|
|
53
|
+
id: string;
|
|
54
|
+
model: PluginModel;
|
|
55
|
+
name: string;
|
|
56
|
+
run: {
|
|
57
|
+
load(): Promise<PluginRunContext>;
|
|
58
|
+
};
|
|
59
|
+
state: PluginState;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Plugin task handler registered by name in a plugin manifest module. */
|
|
63
|
+
export interface PluginTaskDefinition {
|
|
64
|
+
run(ctx: PluginTaskContext): Promise<void> | void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Task handlers keyed by the plugin-owned task name. */
|
|
68
|
+
export type PluginTasks = Record<string, PluginTaskDefinition>;
|