@checkstack/automation-frontend 0.5.0 → 0.6.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,76 @@
1
1
  # @checkstack/automation-frontend
2
2
 
3
+ ## 0.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - @checkstack/script-packages-frontend@0.3.11
8
+
9
+ ## 0.6.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 6005271: Add AI "skills" - reusable prompt templates for the chat assistant and the
14
+ `ai_analyze` automation action. A skill bundles a system-prompt fragment, an
15
+ optional starter prompt, and (for analyze) suggested output fields, tagged with
16
+ the surfaces it targets.
17
+
18
+ Skills come from two sources merged into one catalogue: builtin skills
19
+ contributed by core/plugins via the new `aiSkillExtensionPoint`, and GLOBAL
20
+ user skills authored by operators (new `ai_skill` table) and visible to everyone
21
+ who can read skills. New access rules `ai.skill.read`, `ai.skill-create.manage`
22
+ (a dedicated create permission), and `ai.skill.manage` (edit/delete, author-only
23
+ with admin moderation) gate the feature - all default-on, admin-revocable.
24
+
25
+ The chat composer gains a skill picker (its system prompt seeds the turn, its
26
+ starter prompt seeds the message box); the `ai_analyze` action gains an optional
27
+ `skillId` that seeds the system prompt, prompt (when blank), and output fields
28
+ (when none) - explicit config always wins. A new "AI skills" settings page lets
29
+ operators browse, view full details (prompts + output fields), publish, edit,
30
+ and delete their global skills. Ships six builtin skills across chat and analyze.
31
+
32
+ To support rich pickers, `@checkstack/ui`'s `DynamicForm` gains a `catalog`
33
+ options style (`x-options-style: "catalog"`, with resolver options carrying an
34
+ optional `description`) that renders a browsable modal of cards instead of a
35
+ plain Select, and `@checkstack/backend-api` propagates the new annotation. The
36
+ shared `PageHeader` now wraps a long subtitle beside its actions instead of
37
+ letting them overlap.
38
+
39
+ - 748268c: Add an example-automation template catalogue. Creating a new automation now
40
+ opens a picker (`/automation/new`) with curated, ready-to-use starting points
41
+ grouped by category, plus a "Blank automation" option. Selecting a template
42
+ seeds the editor (the operator still chooses a service account and saves).
43
+
44
+ Templates are an extensible registry: external plugins contribute their own via
45
+ the new `automationTemplateExtensionPoint`, exactly like actions / triggers /
46
+ artifact types. Every registered template is validated against the LIVE
47
+ trigger/action/artifact registries at server startup - a template that
48
+ references a capability that is not installed is withheld with a console
49
+ warning, and one whose definition no longer validates (interface drift) is
50
+ withheld with a console error - so a template can never silently drift when an
51
+ action, trigger, condition, or artifact interface changes.
52
+
53
+ Ships five built-in templates spanning incident response and alerting
54
+ (AI-triage-and-file-Jira-bug, close-Jira-on-recovery, AI-summarize-incident,
55
+ page-on-call-on-sustained-degradation, AI-severity-escalation).
56
+
57
+ ### Patch Changes
58
+
59
+ - Updated dependencies [4134ed9]
60
+ - Updated dependencies [6005271]
61
+ - Updated dependencies [748268c]
62
+ - Updated dependencies [4134ed9]
63
+ - Updated dependencies [079369a]
64
+ - @checkstack/ai-common@0.4.0
65
+ - @checkstack/ui@1.16.0
66
+ - @checkstack/automation-common@0.6.0
67
+ - @checkstack/auth-common@0.9.1
68
+ - @checkstack/template-engine@0.4.4
69
+ - @checkstack/gitops-frontend@0.5.7
70
+ - @checkstack/script-packages-frontend@0.3.10
71
+ - @checkstack/secrets-frontend@0.2.6
72
+ - @checkstack/catalog-common@2.3.6
73
+
3
74
  ## 0.5.0
4
75
 
5
76
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/automation-frontend",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "main": "src/index.tsx",
@@ -13,18 +13,19 @@
13
13
  "lint:code": "eslint . --max-warnings 0"
14
14
  },
15
15
  "dependencies": {
16
- "@checkstack/auth-common": "0.9.0",
17
- "@checkstack/automation-common": "0.5.0",
18
- "@checkstack/catalog-common": "2.3.5",
16
+ "@checkstack/auth-common": "0.9.1",
17
+ "@checkstack/automation-common": "0.6.0",
18
+ "@checkstack/ai-common": "0.4.0",
19
+ "@checkstack/catalog-common": "2.3.6",
19
20
  "@checkstack/common": "0.15.0",
20
21
  "@checkstack/frontend-api": "0.9.0",
21
- "@checkstack/gitops-frontend": "0.5.6",
22
+ "@checkstack/gitops-frontend": "0.5.7",
22
23
  "@checkstack/integration-common": "0.9.0",
23
- "@checkstack/script-packages-frontend": "0.3.9",
24
- "@checkstack/secrets-frontend": "0.2.5",
24
+ "@checkstack/script-packages-frontend": "0.3.11",
25
+ "@checkstack/secrets-frontend": "0.2.6",
25
26
  "@checkstack/signal-frontend": "0.2.4",
26
- "@checkstack/template-engine": "0.4.3",
27
- "@checkstack/ui": "1.15.1",
27
+ "@checkstack/template-engine": "0.4.4",
28
+ "@checkstack/ui": "1.16.0",
28
29
  "@dnd-kit/core": "^6.3.1",
29
30
  "@dnd-kit/sortable": "^8.0.0",
30
31
  "@dnd-kit/utilities": "^3.2.2",
@@ -32,7 +32,7 @@ import type {
32
32
  import { useAutomationRegistry } from "./registry-context";
33
33
  import { ItemPicker } from "./ItemPicker";
34
34
  import { ConditionEditor } from "./ConditionEditor";
35
- import { useConnectionOptionResolvers } from "./useConnectionOptionResolvers";
35
+ import { useActionOptionResolvers } from "./useActionOptionResolvers";
36
36
  import { automationScriptTestRenderer } from "./ScriptTestRenderer";
37
37
  import {
38
38
  useScriptPackageTypeAcquisition,
@@ -127,7 +127,7 @@ export const ProviderActionBody: React.FC<{
127
127
  // `connectionProviderId`; the bridge turns their `x-options-resolver`
128
128
  // fields into a live connection picker + cascading provider dropdowns.
129
129
  // Non-connection actions get an empty map (no-op) since the id is absent.
130
- const optionsResolvers = useConnectionOptionResolvers(
130
+ const optionsResolvers = useActionOptionResolvers(
131
131
  selected?.connectionProviderId,
132
132
  );
133
133
 
@@ -0,0 +1,51 @@
1
+ import React from "react";
2
+ import { usePluginClient } from "@checkstack/frontend-api";
3
+ import { AiApi } from "@checkstack/ai-common";
4
+ import type { OptionsResolver } from "@checkstack/ui";
5
+ import { useConnectionOptionResolvers } from "./useConnectionOptionResolvers";
6
+
7
+ /**
8
+ * The `x-options-resolver` name the `ai_analyze` action declares for its
9
+ * `skillId` field. Resolved here (NOT through the connection bridge) because
10
+ * skills are not connection-scoped: the connection bridge returns `[]` whenever
11
+ * no `connectionId` is selected, which would wrongly empty the skill dropdown.
12
+ */
13
+ const AI_SKILL_RESOLVER_NAME = "aiSkillOptions";
14
+
15
+ /**
16
+ * Resolver map for an action editor's `x-options-resolver` config fields.
17
+ *
18
+ * Composes the connection-backed resolvers (connection picker + cascading
19
+ * provider dropdowns) with non-connection resolvers that need their own data
20
+ * source. Today the only non-connection resolver is `aiSkillOptions`, which
21
+ * lists the AI skills targeting the `ai_analyze` action via `listSkills` -
22
+ * independent of any selected connection.
23
+ */
24
+ export function useActionOptionResolvers(
25
+ connectionProviderId?: string,
26
+ ): Record<string, OptionsResolver> {
27
+ const connectionResolvers = useConnectionOptionResolvers(connectionProviderId);
28
+ const aiClient = usePluginClient(AiApi);
29
+
30
+ return React.useMemo(() => {
31
+ const skillResolver: OptionsResolver = async () => {
32
+ const { skills } = await aiClient.listSkills.call({ target: "ai_analyze" });
33
+ return skills.map((skill) => ({
34
+ value: skill.id,
35
+ label: skill.name,
36
+ description: skill.description,
37
+ }));
38
+ };
39
+
40
+ return new Proxy<Record<string, OptionsResolver>>(
41
+ {},
42
+ {
43
+ get: (_target, prop) => {
44
+ if (prop === AI_SKILL_RESOLVER_NAME) return skillResolver;
45
+ if (typeof prop !== "string") return;
46
+ return connectionResolvers[prop];
47
+ },
48
+ },
49
+ );
50
+ }, [connectionResolvers, aiClient]);
51
+ }
package/src/index.tsx CHANGED
@@ -20,7 +20,8 @@ export {
20
20
  * Routes:
21
21
  *
22
22
  * - `/automation/` → list view
23
- * - `/automation/new` → blank edit page (create)
23
+ * - `/automation/new` → template picker (create)
24
+ * - `/automation/new/blank` → blank / template-seeded editor
24
25
  * - `/automation/:automationId` → edit page
25
26
  * - `/automation/:automationId/runs` → run history
26
27
  * - `/automation/:automationId/runs/:runId` → single run drill-down
@@ -53,6 +54,15 @@ export default createFrontendPlugin({
53
54
  },
54
55
  {
55
56
  route: automationRoutes.routes.create,
57
+ load: () =>
58
+ import("./pages/AutomationTemplatePickerPage").then((m) => ({
59
+ default: m.AutomationTemplatePickerPage,
60
+ })),
61
+ title: "New automation",
62
+ accessRule: automationAccess.manage,
63
+ },
64
+ {
65
+ route: automationRoutes.routes.createBlank,
56
66
  load: () =>
57
67
  import("./pages/AutomationEditPage").then((m) => ({
58
68
  default: m.AutomationEditPage,
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Link, useNavigate, useParams } from "react-router-dom";
2
+ import { Link, useNavigate, useParams, useSearchParams } from "react-router-dom";
3
3
  import {
4
4
  ChevronLeft,
5
5
  History as HistoryIcon,
@@ -99,6 +99,7 @@ type EditTab = "visual" | "yaml";
99
99
  */
100
100
  const AutomationEditContent: React.FC = () => {
101
101
  const { automationId } = useParams<{ automationId: string }>();
102
+ const [searchParams] = useSearchParams();
102
103
  const isNew = !automationId || automationId === "new";
103
104
  const client = usePluginClient(AutomationApi);
104
105
  const accessApi = useApi(accessApiRef);
@@ -183,6 +184,35 @@ const AutomationEditContent: React.FC = () => {
183
184
  },
184
185
  );
185
186
 
187
+ // Template seeding (new automations only): `/automation/new/blank?template=<id>`
188
+ // pre-fills the editor from a curated example template. The template's
189
+ // `definition` seeds the canonical state once; the operator still picks a
190
+ // `runAs` service account and saves. `enabled: false` on the query keeps it
191
+ // off the wire for blank (no-template) and existing-automation editors.
192
+ const templateId = isNew ? searchParams.get("template") : null;
193
+ const templatesQuery = client.listAutomationTemplates.useQuery(undefined, {
194
+ enabled: Boolean(templateId),
195
+ gcTime: 0,
196
+ });
197
+ const selectedTemplate = templateId
198
+ ? templatesQuery.data?.items.find((t) => t.id === templateId)
199
+ : undefined;
200
+ useInitOnceForKey(
201
+ templatesQuery.isFetchedAfterMount ? selectedTemplate : undefined,
202
+ selectedTemplate?.id,
203
+ (template) => {
204
+ const normalized: AutomationDefinition = {
205
+ ...template.definition,
206
+ triggers: assignDefaultTriggerIds(template.definition.triggers),
207
+ actions: assignDefaultIds(template.definition.actions, new Set()),
208
+ };
209
+ setName(template.definition.name);
210
+ setDescription(template.definition.description ?? "");
211
+ setDefinition(normalized);
212
+ setYamlText(stringifyYaml(normalized));
213
+ },
214
+ );
215
+
186
216
  // Keep the YAML mirror in sync with definition while the visual editor
187
217
  // is active — the YAML tab needs a non-stale starting point when the
188
218
  // operator switches over.
@@ -0,0 +1,152 @@
1
+ import React from "react";
2
+ import { useNavigate } from "react-router-dom";
3
+ import { Workflow, FilePlus2, ArrowRight } from "lucide-react";
4
+ import {
5
+ usePluginClient,
6
+ accessApiRef,
7
+ useApi,
8
+ wrapInSuspense,
9
+ } from "@checkstack/frontend-api";
10
+ import {
11
+ AutomationApi,
12
+ automationAccess,
13
+ automationRoutes,
14
+ type AutomationTemplate,
15
+ } from "@checkstack/automation-common";
16
+ import {
17
+ PageLayout,
18
+ Card,
19
+ CardHeader,
20
+ CardTitle,
21
+ CardContent,
22
+ Button,
23
+ Badge,
24
+ DynamicIcon,
25
+ LoadingSpinner,
26
+ QueryErrorState,
27
+ } from "@checkstack/ui";
28
+ import { resolveRoute } from "@checkstack/common";
29
+
30
+ /**
31
+ * Landing page for "create a new automation". Presents a "Blank automation"
32
+ * option plus a gallery of curated example templates (grouped by category)
33
+ * contributed by core + plugins. Picking a template opens the blank editor
34
+ * pre-seeded from it (`/automation/new/blank?template=<id>`); the operator
35
+ * still chooses a service account and saves.
36
+ */
37
+ const AutomationTemplatePickerContent: React.FC = () => {
38
+ const client = usePluginClient(AutomationApi);
39
+ const accessApi = useApi(accessApiRef);
40
+ const navigate = useNavigate();
41
+
42
+ const { allowed: canManage, loading: accessLoading } = accessApi.useAccess(
43
+ automationAccess.manage,
44
+ );
45
+
46
+ const query = client.listAutomationTemplates.useQuery();
47
+
48
+ const startBlank = () =>
49
+ navigate(resolveRoute(automationRoutes.routes.createBlank));
50
+
51
+ const openTemplate = (template: AutomationTemplate) =>
52
+ navigate(
53
+ `${resolveRoute(automationRoutes.routes.createBlank)}?template=${encodeURIComponent(
54
+ template.id,
55
+ )}`,
56
+ );
57
+
58
+ // Group templates by category, preserving first-seen category order.
59
+ const grouped = React.useMemo(() => {
60
+ const byCategory = new Map<string, AutomationTemplate[]>();
61
+ for (const template of query.data?.items ?? []) {
62
+ const list = byCategory.get(template.category) ?? [];
63
+ list.push(template);
64
+ byCategory.set(template.category, list);
65
+ }
66
+ return [...byCategory.entries()];
67
+ }, [query.data]);
68
+
69
+ return (
70
+ <PageLayout
71
+ title="New automation"
72
+ subtitle="Start from a curated example or build your own from scratch"
73
+ icon={Workflow}
74
+ loading={accessLoading}
75
+ allowed={canManage}
76
+ >
77
+ <div className="space-y-6">
78
+ <Card>
79
+ <CardContent className="flex flex-col gap-3 p-4 sm:flex-row sm:items-center sm:justify-between">
80
+ <div className="flex items-center gap-3">
81
+ <div className="flex h-10 w-10 items-center justify-center rounded-md bg-muted">
82
+ <FilePlus2 className="h-5 w-5 text-muted-foreground" />
83
+ </div>
84
+ <div>
85
+ <div className="font-medium">Blank automation</div>
86
+ <div className="text-sm text-muted-foreground">
87
+ Start with an empty editor and add triggers and actions yourself.
88
+ </div>
89
+ </div>
90
+ </div>
91
+ <Button onClick={startBlank}>
92
+ Start blank
93
+ <ArrowRight className="ml-1 h-4 w-4" />
94
+ </Button>
95
+ </CardContent>
96
+ </Card>
97
+
98
+ {query.isLoading ? (
99
+ <div className="p-6">
100
+ <LoadingSpinner />
101
+ </div>
102
+ ) : query.isError ? (
103
+ <QueryErrorState error={query.error} onRetry={() => query.refetch()} />
104
+ ) : (
105
+ grouped.map(([category, items]) => (
106
+ <section key={category} className="space-y-3">
107
+ <h2 className="text-sm font-semibold text-muted-foreground">
108
+ {category}
109
+ </h2>
110
+ <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
111
+ {items.map((template) => (
112
+ <Card
113
+ key={template.id}
114
+ className="flex h-full cursor-pointer flex-col transition-colors hover:border-primary"
115
+ onClick={() => openTemplate(template)}
116
+ >
117
+ <CardHeader className="flex-row items-start gap-3 space-y-0">
118
+ <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
119
+ <DynamicIcon name={template.icon} className="h-5 w-5" />
120
+ </div>
121
+ <CardTitle className="text-base leading-snug">
122
+ {template.name}
123
+ </CardTitle>
124
+ </CardHeader>
125
+ <CardContent className="flex flex-1 flex-col justify-between gap-3">
126
+ <p className="text-sm text-muted-foreground">
127
+ {template.description}
128
+ </p>
129
+ <div className="flex items-center justify-between">
130
+ <Badge variant="secondary" className="font-normal">
131
+ {template.ownerPluginId}
132
+ </Badge>
133
+ <span className="flex items-center text-sm font-medium text-primary">
134
+ Use template
135
+ <ArrowRight className="ml-1 h-4 w-4" />
136
+ </span>
137
+ </div>
138
+ </CardContent>
139
+ </Card>
140
+ ))}
141
+ </div>
142
+ </section>
143
+ ))
144
+ )}
145
+ </div>
146
+ </PageLayout>
147
+ );
148
+ };
149
+
150
+ export const AutomationTemplatePickerPage = wrapInSuspense(
151
+ AutomationTemplatePickerContent,
152
+ );
package/tsconfig.json CHANGED
@@ -4,6 +4,9 @@
4
4
  "src"
5
5
  ],
6
6
  "references": [
7
+ {
8
+ "path": "../ai-common"
9
+ },
7
10
  {
8
11
  "path": "../auth-common"
9
12
  },