@burdenoff/microfe-vibecontrols 2026.602.5 → 2026.602.7
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/components/ai/PluginHarnessPicker.js +50 -46
- package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
- package/dist/components/security/SecurityRunButton.js +60 -0
- package/dist/components/security/SecurityRunButton.js.map +1 -0
- package/dist/components/security/SecurityTabPanel.js +42 -26
- package/dist/components/security/SecurityTabPanel.js.map +1 -1
- package/dist/components/security/hooks.js +28 -11
- package/dist/components/security/hooks.js.map +1 -1
- package/dist/components/security/index.js +1 -0
- package/dist/constants/pluginCatalog.js +18 -3
- package/dist/constants/pluginCatalog.js.map +1 -1
- package/dist/generated/wspace-operations.js +9 -1
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/pages/SessionDetailsPage.js +497 -432
- package/dist/pages/SessionDetailsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,64 +6,64 @@ import { useCallback as i, useEffect as a, useRef as o, useState as s } from "re
|
|
|
6
6
|
import { Check as c, CheckCircle2 as l, Download as u, Loader2 as d, Puzzle as f, Trash2 as p } from "lucide-react";
|
|
7
7
|
import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
8
8
|
//#region src/components/ai/PluginHarnessPicker.tsx
|
|
9
|
-
function _({ packages: _, agentId: v, title: y, description: b, onChange: x }) {
|
|
10
|
-
let { installed:
|
|
9
|
+
function _({ packages: _, agentId: v, title: y, description: b, onChange: x, renderInstalledAction: S }) {
|
|
10
|
+
let { installed: C, installing: w, install: T, refetch: E, loading: D, error: O } = n(_, v), [k] = e(), [A, j] = s(null), [M, N] = s({}), [P, F] = s({}), I = o(/* @__PURE__ */ new Map());
|
|
11
11
|
a(() => {
|
|
12
|
-
let e =
|
|
12
|
+
let e = I.current;
|
|
13
13
|
return () => {
|
|
14
14
|
for (let t of e.values()) clearTimeout(t);
|
|
15
15
|
e.clear();
|
|
16
16
|
};
|
|
17
17
|
}, []);
|
|
18
|
-
let
|
|
19
|
-
if (await
|
|
20
|
-
|
|
18
|
+
let L = i(async (e) => {
|
|
19
|
+
if (await T(e)) {
|
|
20
|
+
F((t) => ({
|
|
21
21
|
...t,
|
|
22
22
|
[e]: !0
|
|
23
23
|
}));
|
|
24
|
-
let t =
|
|
24
|
+
let t = I.current.get(e);
|
|
25
25
|
t && clearTimeout(t);
|
|
26
26
|
let n = setTimeout(() => {
|
|
27
|
-
|
|
27
|
+
F((t) => {
|
|
28
28
|
if (!t[e]) return t;
|
|
29
29
|
let n = { ...t };
|
|
30
30
|
return delete n[e], n;
|
|
31
|
-
}),
|
|
31
|
+
}), I.current.delete(e);
|
|
32
32
|
}, 1500);
|
|
33
|
-
|
|
33
|
+
I.current.set(e, n), x?.();
|
|
34
34
|
}
|
|
35
|
-
}, [
|
|
35
|
+
}, [T, x]), R = i(async (e) => {
|
|
36
36
|
if (v) {
|
|
37
|
-
|
|
37
|
+
j(e), N((t) => ({
|
|
38
38
|
...t,
|
|
39
39
|
[e]: ""
|
|
40
40
|
}));
|
|
41
41
|
try {
|
|
42
|
-
let { data: t } = await
|
|
42
|
+
let { data: t } = await k({ variables: {
|
|
43
43
|
agentId: v,
|
|
44
44
|
packageName: e
|
|
45
45
|
} });
|
|
46
46
|
if (!t?.removeAgentPlugin.success) {
|
|
47
|
-
|
|
47
|
+
N((n) => ({
|
|
48
48
|
...n,
|
|
49
49
|
[e]: t?.removeAgentPlugin.error || "Remove failed"
|
|
50
50
|
}));
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
|
-
await
|
|
53
|
+
await E(), x?.();
|
|
54
54
|
} catch (t) {
|
|
55
|
-
|
|
55
|
+
N((n) => ({
|
|
56
56
|
...n,
|
|
57
57
|
[e]: t instanceof Error ? t.message : String(t)
|
|
58
58
|
}));
|
|
59
59
|
} finally {
|
|
60
|
-
|
|
60
|
+
j(null);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}, [
|
|
64
64
|
v,
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
k,
|
|
66
|
+
E,
|
|
67
67
|
x
|
|
68
68
|
]);
|
|
69
69
|
return _.length === 0 ? null : /* @__PURE__ */ g("section", {
|
|
@@ -77,15 +77,15 @@ function _({ packages: _, agentId: v, title: y, description: b, onChange: x }) {
|
|
|
77
77
|
className: "text-sm text-text-secondary mt-1",
|
|
78
78
|
children: b
|
|
79
79
|
})] }),
|
|
80
|
-
|
|
80
|
+
O && /* @__PURE__ */ h("div", {
|
|
81
81
|
role: "alert",
|
|
82
82
|
className: "rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text",
|
|
83
|
-
children:
|
|
83
|
+
children: O
|
|
84
84
|
}),
|
|
85
85
|
/* @__PURE__ */ h("div", {
|
|
86
86
|
className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3",
|
|
87
87
|
children: _.map((e) => {
|
|
88
|
-
let n = r(e), i = !!
|
|
88
|
+
let n = r(e), i = !!C[e], a = w === e, o = A === e, s = a || o || D, _ = !!P[e], y = M[e];
|
|
89
89
|
return /* @__PURE__ */ g(t, {
|
|
90
90
|
padding: "md",
|
|
91
91
|
className: "flex flex-col gap-3",
|
|
@@ -120,31 +120,35 @@ function _({ packages: _, agentId: v, title: y, description: b, onChange: x }) {
|
|
|
120
120
|
]
|
|
121
121
|
})]
|
|
122
122
|
}),
|
|
123
|
-
/* @__PURE__ */
|
|
123
|
+
/* @__PURE__ */ g("div", {
|
|
124
124
|
className: "flex items-center gap-2",
|
|
125
|
-
children:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
125
|
+
children: [
|
|
126
|
+
i && _ ? /* @__PURE__ */ g("button", {
|
|
127
|
+
type: "button",
|
|
128
|
+
disabled: !0,
|
|
129
|
+
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg text-status-success-text border border-status-success-border cursor-default",
|
|
130
|
+
"data-testid": `plugin-installed-${e}`,
|
|
131
|
+
"aria-live": "polite",
|
|
132
|
+
children: [/* @__PURE__ */ h(l, { className: "size-3.5" }), "Installed"]
|
|
133
|
+
}) : i ? /* @__PURE__ */ g("button", {
|
|
134
|
+
type: "button",
|
|
135
|
+
disabled: s || !v,
|
|
136
|
+
onClick: () => R(e),
|
|
137
|
+
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md border border-border-default text-text-secondary hover:border-status-error-border hover:text-status-error-text transition disabled:opacity-60 disabled:cursor-not-allowed",
|
|
138
|
+
"data-testid": `plugin-remove-${e}`,
|
|
139
|
+
children: [o ? /* @__PURE__ */ h(d, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ h(p, { className: "size-3.5" }), "Remove"]
|
|
140
|
+
}) : null,
|
|
141
|
+
i && !_ && S?.(e),
|
|
142
|
+
!i && /* @__PURE__ */ h("button", {
|
|
143
|
+
type: "button",
|
|
144
|
+
disabled: s || !v,
|
|
145
|
+
onClick: () => L(e),
|
|
146
|
+
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed",
|
|
147
|
+
"data-testid": `plugin-install-${e}`,
|
|
148
|
+
"aria-busy": a,
|
|
149
|
+
children: a ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(d, { className: "size-3.5 animate-spin" }), "Installing…"] }) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(u, { className: "size-3.5" }), "Install"] })
|
|
150
|
+
})
|
|
151
|
+
]
|
|
148
152
|
}),
|
|
149
153
|
y && /* @__PURE__ */ h("p", {
|
|
150
154
|
className: "text-xs text-status-error-text",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginHarnessPicker.js","names":[],"sources":["../../../src/components/ai/PluginHarnessPicker.tsx"],"sourcesContent":["/**\n * <PluginHarnessPicker />\n *\n * Grid of installable VibeControls agent plugins with per-card\n * Install / Installed / Remove state. Driven by the existing\n * `usePluginAvailability` hook (same code path as `<PluginGate>`), so\n * the Plugins tab on the Agent details page and this picker stay in\n * sync automatically.\n *\n * The default use-case is the AI workbench: once the base\n * `@vibecontrols/vibe-plugin-ai` plugin is installed, this picker lists\n * every AI provider harness (claude, codex, copilot, gemini, …) so the\n * user can add / remove harnesses without leaving the AI page.\n *\n * The component is feature-neutral — pass any `packages` array (e.g.\n * session providers, tunnel providers) to reuse the same UI.\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { Check, CheckCircle2, Download, Loader2, Puzzle, Trash2 } from 'lucide-react';\nimport { useRemoveAgentPluginMutation } from '@/generated/wspace-operations';\nimport { usePluginAvailability } from '@/hooks/usePluginAvailability';\nimport { getPluginMeta } from '@/constants/pluginCatalog';\nimport { Card } from '../shared/Card';\n\nexport interface PluginHarnessPickerProps {\n /** Package names to offer. Metadata is resolved via `getPluginMeta`. */\n packages: readonly string[];\n /** Active agent id. When null, the picker renders in a disabled state. */\n agentId: string | null;\n /** Optional section heading (rendered above the grid). */\n title?: string;\n /** Optional section description. */\n description?: string;\n /** Fired after a successful install or remove so parents can refetch. */\n onChange?: () => void;\n}\n\nexport function PluginHarnessPicker({\n packages,\n agentId,\n title,\n description,\n onChange,\n}: PluginHarnessPickerProps) {\n // Plugins are agent-wide (BOFF-2612); no profile threading needed.\n const { installed, installing, install, refetch, loading, error } = usePluginAvailability(\n packages,\n agentId\n );\n const [removePlugin] = useRemoveAgentPluginMutation();\n const [removingPkg, setRemovingPkg] = useState<string | null>(null);\n const [removeError, setRemoveError] = useState<Record<string, string>>({});\n // Per-package \"just installed\" pulse — flips on for ~1.5s after a\n // successful install so the button shows a clear success affordance\n // (checkmark + \"Installed\") before the card transitions to its\n // installed/Remove state. Without this the button silently flips from\n // spinner to Remove and users miss the success signal.\n const [justInstalled, setJustInstalled] = useState<Record<string, boolean>>({});\n const justInstalledTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n\n useEffect(() => {\n const timers = justInstalledTimers.current;\n return () => {\n for (const t of timers.values()) clearTimeout(t);\n timers.clear();\n };\n }, []);\n\n const handleInstall = useCallback(\n async (pkg: string) => {\n const ok = await install(pkg);\n if (ok) {\n setJustInstalled((prev) => ({ ...prev, [pkg]: true }));\n const existing = justInstalledTimers.current.get(pkg);\n if (existing) clearTimeout(existing);\n const timer = setTimeout(() => {\n setJustInstalled((prev) => {\n if (!prev[pkg]) return prev;\n const next = { ...prev };\n delete next[pkg];\n return next;\n });\n justInstalledTimers.current.delete(pkg);\n }, 1500);\n justInstalledTimers.current.set(pkg, timer);\n onChange?.();\n }\n },\n [install, onChange]\n );\n\n const handleRemove = useCallback(\n async (pkg: string) => {\n if (!agentId) return;\n setRemovingPkg(pkg);\n setRemoveError((e) => ({ ...e, [pkg]: '' }));\n try {\n const { data } = await removePlugin({\n variables: { agentId, packageName: pkg },\n });\n if (!data?.removeAgentPlugin.success) {\n setRemoveError((e) => ({\n ...e,\n [pkg]: data?.removeAgentPlugin.error || 'Remove failed',\n }));\n return;\n }\n await refetch();\n onChange?.();\n } catch (e) {\n setRemoveError((err) => ({\n ...err,\n [pkg]: e instanceof Error ? e.message : String(e),\n }));\n } finally {\n setRemovingPkg(null);\n }\n },\n [agentId, removePlugin, refetch, onChange]\n );\n\n if (packages.length === 0) return null;\n\n return (\n <section className=\"flex flex-col gap-3\" data-testid=\"plugin-harness-picker\">\n {(title || description) && (\n <div>\n {title && <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>}\n {description && <p className=\"text-sm text-text-secondary mt-1\">{description}</p>}\n </div>\n )}\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text\"\n >\n {error}\n </div>\n )}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {packages.map((pkg) => {\n const meta = getPluginMeta(pkg);\n const isInstalled = !!installed[pkg];\n const isInstalling = installing === pkg;\n const isRemoving = removingPkg === pkg;\n const isBusy = isInstalling || isRemoving || loading;\n const showJustInstalled = !!justInstalled[pkg];\n const err = removeError[pkg];\n return (\n <Card key={pkg} padding=\"md\" className=\"flex flex-col gap-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-shrink-0 size-9 rounded-md bg-bg-sunken flex items-center justify-center\">\n <Puzzle className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-start gap-2 flex-wrap\">\n {/*\n Plugin display name must wrap freely up to a\n reasonable hard cap so the column never blows up\n the modal layout. break-words allows wrapping on\n arbitrary characters when a name has no spaces;\n max-w bound corresponds to ~100 char-cells (a\n generous upper bound on real-world display names).\n */}\n <h4 className=\"font-medium text-text-primary break-words max-w-[100ch]\">\n {meta.displayName}\n </h4>\n {isInstalled && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs rounded bg-status-success-bg text-status-success-text shrink-0\">\n <Check className=\"size-3\" /> installed\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mt-0.5\">{meta.description}</p>\n <p\n className=\"text-[10px] font-mono text-text-placeholder mt-1 break-all\"\n title={pkg}\n >\n {pkg}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {isInstalled && showJustInstalled ? (\n <button\n type=\"button\"\n disabled\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg text-status-success-text border border-status-success-border cursor-default\"\n data-testid={`plugin-installed-${pkg}`}\n aria-live=\"polite\"\n >\n <CheckCircle2 className=\"size-3.5\" />\n Installed\n </button>\n ) : isInstalled ? (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleRemove(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md border border-border-default text-text-secondary hover:border-status-error-border hover:text-status-error-text transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-remove-${pkg}`}\n >\n {isRemoving ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"size-3.5\" />\n )}\n Remove\n </button>\n ) : (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleInstall(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-install-${pkg}`}\n aria-busy={isInstalling}\n >\n {isInstalling ? (\n <>\n <Loader2 className=\"size-3.5 animate-spin\" />\n Installing…\n </>\n ) : (\n <>\n <Download className=\"size-3.5\" />\n Install\n </>\n )}\n </button>\n )}\n </div>\n {err && <p className=\"text-xs text-status-error-text\">{err}</p>}\n </Card>\n );\n })}\n </div>\n {!agentId && <p className=\"text-xs text-text-muted\">Select an agent to manage plugins.</p>}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PluginHarnessPicker.js","names":[],"sources":["../../../src/components/ai/PluginHarnessPicker.tsx"],"sourcesContent":["/**\n * <PluginHarnessPicker />\n *\n * Grid of installable VibeControls agent plugins with per-card\n * Install / Installed / Remove state. Driven by the existing\n * `usePluginAvailability` hook (same code path as `<PluginGate>`), so\n * the Plugins tab on the Agent details page and this picker stay in\n * sync automatically.\n *\n * The default use-case is the AI workbench: once the base\n * `@vibecontrols/vibe-plugin-ai` plugin is installed, this picker lists\n * every AI provider harness (claude, codex, copilot, gemini, …) so the\n * user can add / remove harnesses without leaving the AI page.\n *\n * The component is feature-neutral — pass any `packages` array (e.g.\n * session providers, tunnel providers) to reuse the same UI.\n */\n\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Check, CheckCircle2, Download, Loader2, Puzzle, Trash2 } from 'lucide-react';\nimport { useRemoveAgentPluginMutation } from '@/generated/wspace-operations';\nimport { usePluginAvailability } from '@/hooks/usePluginAvailability';\nimport { getPluginMeta } from '@/constants/pluginCatalog';\nimport { Card } from '../shared/Card';\n\nexport interface PluginHarnessPickerProps {\n /** Package names to offer. Metadata is resolved via `getPluginMeta`. */\n packages: readonly string[];\n /** Active agent id. When null, the picker renders in a disabled state. */\n agentId: string | null;\n /** Optional section heading (rendered above the grid). */\n title?: string;\n /** Optional section description. */\n description?: string;\n /** Fired after a successful install or remove so parents can refetch. */\n onChange?: () => void;\n /**\n * Optional per-card action slot, rendered alongside the Remove button when\n * a package is installed. Lets a feature (e.g. the Security tab) attach a\n * contextual action — like \"Run scan\" — without coupling this generic\n * picker to that feature. Receives the installed package name.\n */\n renderInstalledAction?: (pkg: string) => ReactNode;\n}\n\nexport function PluginHarnessPicker({\n packages,\n agentId,\n title,\n description,\n onChange,\n renderInstalledAction,\n}: PluginHarnessPickerProps) {\n // Plugins are agent-wide (BOFF-2612); no profile threading needed.\n const { installed, installing, install, refetch, loading, error } = usePluginAvailability(\n packages,\n agentId\n );\n const [removePlugin] = useRemoveAgentPluginMutation();\n const [removingPkg, setRemovingPkg] = useState<string | null>(null);\n const [removeError, setRemoveError] = useState<Record<string, string>>({});\n // Per-package \"just installed\" pulse — flips on for ~1.5s after a\n // successful install so the button shows a clear success affordance\n // (checkmark + \"Installed\") before the card transitions to its\n // installed/Remove state. Without this the button silently flips from\n // spinner to Remove and users miss the success signal.\n const [justInstalled, setJustInstalled] = useState<Record<string, boolean>>({});\n const justInstalledTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n\n useEffect(() => {\n const timers = justInstalledTimers.current;\n return () => {\n for (const t of timers.values()) clearTimeout(t);\n timers.clear();\n };\n }, []);\n\n const handleInstall = useCallback(\n async (pkg: string) => {\n const ok = await install(pkg);\n if (ok) {\n setJustInstalled((prev) => ({ ...prev, [pkg]: true }));\n const existing = justInstalledTimers.current.get(pkg);\n if (existing) clearTimeout(existing);\n const timer = setTimeout(() => {\n setJustInstalled((prev) => {\n if (!prev[pkg]) return prev;\n const next = { ...prev };\n delete next[pkg];\n return next;\n });\n justInstalledTimers.current.delete(pkg);\n }, 1500);\n justInstalledTimers.current.set(pkg, timer);\n onChange?.();\n }\n },\n [install, onChange]\n );\n\n const handleRemove = useCallback(\n async (pkg: string) => {\n if (!agentId) return;\n setRemovingPkg(pkg);\n setRemoveError((e) => ({ ...e, [pkg]: '' }));\n try {\n const { data } = await removePlugin({\n variables: { agentId, packageName: pkg },\n });\n if (!data?.removeAgentPlugin.success) {\n setRemoveError((e) => ({\n ...e,\n [pkg]: data?.removeAgentPlugin.error || 'Remove failed',\n }));\n return;\n }\n await refetch();\n onChange?.();\n } catch (e) {\n setRemoveError((err) => ({\n ...err,\n [pkg]: e instanceof Error ? e.message : String(e),\n }));\n } finally {\n setRemovingPkg(null);\n }\n },\n [agentId, removePlugin, refetch, onChange]\n );\n\n if (packages.length === 0) return null;\n\n return (\n <section className=\"flex flex-col gap-3\" data-testid=\"plugin-harness-picker\">\n {(title || description) && (\n <div>\n {title && <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>}\n {description && <p className=\"text-sm text-text-secondary mt-1\">{description}</p>}\n </div>\n )}\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text\"\n >\n {error}\n </div>\n )}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {packages.map((pkg) => {\n const meta = getPluginMeta(pkg);\n const isInstalled = !!installed[pkg];\n const isInstalling = installing === pkg;\n const isRemoving = removingPkg === pkg;\n const isBusy = isInstalling || isRemoving || loading;\n const showJustInstalled = !!justInstalled[pkg];\n const err = removeError[pkg];\n return (\n <Card key={pkg} padding=\"md\" className=\"flex flex-col gap-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-shrink-0 size-9 rounded-md bg-bg-sunken flex items-center justify-center\">\n <Puzzle className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-start gap-2 flex-wrap\">\n {/*\n Plugin display name must wrap freely up to a\n reasonable hard cap so the column never blows up\n the modal layout. break-words allows wrapping on\n arbitrary characters when a name has no spaces;\n max-w bound corresponds to ~100 char-cells (a\n generous upper bound on real-world display names).\n */}\n <h4 className=\"font-medium text-text-primary break-words max-w-[100ch]\">\n {meta.displayName}\n </h4>\n {isInstalled && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs rounded bg-status-success-bg text-status-success-text shrink-0\">\n <Check className=\"size-3\" /> installed\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mt-0.5\">{meta.description}</p>\n <p\n className=\"text-[10px] font-mono text-text-placeholder mt-1 break-all\"\n title={pkg}\n >\n {pkg}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {isInstalled && showJustInstalled ? (\n <button\n type=\"button\"\n disabled\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg text-status-success-text border border-status-success-border cursor-default\"\n data-testid={`plugin-installed-${pkg}`}\n aria-live=\"polite\"\n >\n <CheckCircle2 className=\"size-3.5\" />\n Installed\n </button>\n ) : isInstalled ? (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleRemove(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md border border-border-default text-text-secondary hover:border-status-error-border hover:text-status-error-text transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-remove-${pkg}`}\n >\n {isRemoving ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"size-3.5\" />\n )}\n Remove\n </button>\n ) : null}\n {isInstalled && !showJustInstalled && renderInstalledAction?.(pkg)}\n {!isInstalled && (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleInstall(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-install-${pkg}`}\n aria-busy={isInstalling}\n >\n {isInstalling ? (\n <>\n <Loader2 className=\"size-3.5 animate-spin\" />\n Installing…\n </>\n ) : (\n <>\n <Download className=\"size-3.5\" />\n Install\n </>\n )}\n </button>\n )}\n </div>\n {err && <p className=\"text-xs text-status-error-text\">{err}</p>}\n </Card>\n );\n })}\n </div>\n {!agentId && <p className=\"text-xs text-text-muted\">Select an agent to manage plugins.</p>}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { SECURITY_STAGE_BY_PACKAGE as e } from "../../constants/pluginCatalog.js";
|
|
2
|
+
import { SecurityAgentError as t } from "./agentClient.js";
|
|
3
|
+
import { useStartSecurityScanMutation as n } from "./hooks.js";
|
|
4
|
+
import { useCallback as r, useState as i } from "react";
|
|
5
|
+
import { AlertCircle as a, Loader2 as o, Play as s } from "lucide-react";
|
|
6
|
+
import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
7
|
+
//#region src/components/security/SecurityRunButton.tsx
|
|
8
|
+
function d({ pkg: d, vibeId: f, workspaceId: p, repoUrl: m, repoLocalPath: h, commit: g, agentId: _, profile: v }) {
|
|
9
|
+
let y = e[d], { mutateAsync: b, isPending: x } = n({
|
|
10
|
+
agentId: _,
|
|
11
|
+
profile: v
|
|
12
|
+
}), [S, C] = i(null), w = r(async () => {
|
|
13
|
+
if (!(!y || !_)) {
|
|
14
|
+
C(null);
|
|
15
|
+
try {
|
|
16
|
+
await b({
|
|
17
|
+
vibeId: f,
|
|
18
|
+
workspaceId: p,
|
|
19
|
+
repoUrl: m,
|
|
20
|
+
repoLocalPath: h,
|
|
21
|
+
commit: g,
|
|
22
|
+
stage: y
|
|
23
|
+
});
|
|
24
|
+
} catch (e) {
|
|
25
|
+
C(e instanceof t || e instanceof Error ? e.message : "Failed to start scan");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}, [
|
|
29
|
+
y,
|
|
30
|
+
_,
|
|
31
|
+
b,
|
|
32
|
+
f,
|
|
33
|
+
p,
|
|
34
|
+
m,
|
|
35
|
+
h,
|
|
36
|
+
g
|
|
37
|
+
]);
|
|
38
|
+
return y ? /* @__PURE__ */ u("div", {
|
|
39
|
+
className: "flex flex-col gap-1",
|
|
40
|
+
children: [/* @__PURE__ */ l("button", {
|
|
41
|
+
type: "button",
|
|
42
|
+
disabled: x || !_,
|
|
43
|
+
onClick: () => void w(),
|
|
44
|
+
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed",
|
|
45
|
+
"data-testid": `security-run-${d}`,
|
|
46
|
+
"aria-busy": x,
|
|
47
|
+
children: x ? /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l(o, { className: "size-3.5 animate-spin" }), "Running…"] }) : /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l(s, { className: "size-3.5" }), "Run scan"] })
|
|
48
|
+
}), S && /* @__PURE__ */ u("p", {
|
|
49
|
+
className: "inline-flex items-start gap-1 text-xs text-status-error-text",
|
|
50
|
+
children: [/* @__PURE__ */ l(a, {
|
|
51
|
+
className: "size-3.5 mt-px shrink-0",
|
|
52
|
+
"aria-hidden": "true"
|
|
53
|
+
}), /* @__PURE__ */ l("span", { children: S })]
|
|
54
|
+
})]
|
|
55
|
+
}) : null;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { d as SecurityRunButton };
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=SecurityRunButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecurityRunButton.js","names":[],"sources":["../../../src/components/security/SecurityRunButton.tsx"],"sourcesContent":["/**\n * Per-stage \"Run scan\" control for an installed security provider plugin.\n *\n * Mounted as the `renderInstalledAction` slot of `<PluginHarnessPicker>` on\n * the Security tab: each installed provider card gets a Run button that\n * kicks off a scan for that provider's lifecycle stage against the current\n * vibe. The scan runs on the agent (over the svc GraphQL proxy); on success\n * the start mutation invalidates the `['security', agentId, profile, …]`\n * query family — which includes `useSecurityRecentScansQuery` — so the new\n * run surfaces in `<SecurityScanList>` with its conclusion + severity counts.\n *\n * Errors are surfaced inline (a small chip under the button), matching how\n * `<PluginHarnessPicker>` and `<SecurityScanList>` render their failures —\n * never a raw error toast.\n */\nimport { useCallback, useState, type ReactElement } from 'react';\nimport { AlertCircle, Loader2, Play } from 'lucide-react';\n\nimport { SECURITY_STAGE_BY_PACKAGE } from '@/constants/pluginCatalog';\nimport { useStartSecurityScanMutation, type UseSecurityAgentContext } from './hooks';\nimport { SecurityAgentError, type AgentStage } from './agentClient';\n\nexport interface SecurityRunButtonProps extends UseSecurityAgentContext {\n /** The installed security provider package this button runs. */\n pkg: string;\n /** Current vibe id. */\n vibeId: string;\n /** Workspace the vibe belongs to (required by the agent scan contract). */\n workspaceId: string;\n /** Git remote → recorded as `repoUrl` on the run. */\n repoUrl: string;\n /** Local checkout path the provider tooling runs against. */\n repoLocalPath: string;\n /** Best-effort commit ref (the vibe's git branch). */\n commit: string;\n}\n\nexport function SecurityRunButton({\n pkg,\n vibeId,\n workspaceId,\n repoUrl,\n repoLocalPath,\n commit,\n agentId,\n profile,\n}: SecurityRunButtonProps): ReactElement | null {\n const stage = SECURITY_STAGE_BY_PACKAGE[pkg] as AgentStage | undefined;\n const { mutateAsync, isPending } = useStartSecurityScanMutation({ agentId, profile });\n const [error, setError] = useState<string | null>(null);\n\n const handleRun = useCallback(async () => {\n if (!stage || !agentId) return;\n setError(null);\n try {\n await mutateAsync({\n vibeId,\n workspaceId,\n repoUrl,\n repoLocalPath,\n commit,\n stage,\n });\n // On success the mutation invalidates the security query family, which\n // refetches the recent-scans list — no manual refetch needed here.\n } catch (err) {\n const message =\n err instanceof SecurityAgentError || err instanceof Error\n ? err.message\n : 'Failed to start scan';\n setError(message);\n }\n }, [stage, agentId, mutateAsync, vibeId, workspaceId, repoUrl, repoLocalPath, commit]);\n\n // Provider package isn't mapped to a known stage — can't run it.\n if (!stage) return null;\n\n const disabled = isPending || !agentId;\n\n return (\n <div className=\"flex flex-col gap-1\">\n <button\n type=\"button\"\n disabled={disabled}\n onClick={() => void handleRun()}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`security-run-${pkg}`}\n aria-busy={isPending}\n >\n {isPending ? (\n <>\n <Loader2 className=\"size-3.5 animate-spin\" />\n Running…\n </>\n ) : (\n <>\n <Play className=\"size-3.5\" />\n Run scan\n </>\n )}\n </button>\n {error && (\n <p className=\"inline-flex items-start gap-1 text-xs text-status-error-text\">\n <AlertCircle className=\"size-3.5 mt-px shrink-0\" aria-hidden=\"true\" />\n <span>{error}</span>\n </p>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;AAqCA,SAAgB,EAAkB,EAChC,QACA,WACA,gBACA,YACA,kBACA,WACA,YACA,cAC8C;CAC9C,IAAM,IAAQ,EAA0B,IAClC,EAAE,gBAAa,iBAAc,EAA6B;EAAE;EAAS;EAAS,CAAC,EAC/E,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,IAAY,EAAY,YAAY;AACpC,SAAC,KAAS,CAAC,IACf;KAAS,KAAK;AACd,OAAI;AACF,UAAM,EAAY;KAChB;KACA;KACA;KACA;KACA;KACA;KACD,CAAC;YAGK,GAAK;AAKZ,MAHE,aAAe,KAAsB,aAAe,QAChD,EAAI,UACJ,uBACW;;;IAElB;EAAC;EAAO;EAAS;EAAa;EAAQ;EAAa;EAAS;EAAe;EAAO,CAAC;AAOtF,QAJK,IAKH,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,UAAD;GACE,MAAK;GACK,UANC,KAAa,CAAC;GAOzB,eAAe,KAAK,GAAW;GAC/B,WAAU;GACV,eAAa,gBAAgB;GAC7B,aAAW;aAEV,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAS,WAAU,yBAA0B,CAAA,EAAA,WAE5C,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,YAAa,CAAA,EAAA,WAE5B,EAAA,CAAA;GAEE,CAAA,EACR,KACC,kBAAC,KAAD;GAAG,WAAU;aAAb,CACE,kBAAC,GAAD;IAAa,WAAU;IAA0B,eAAY;IAAS,CAAA,EACtE,kBAAC,QAAD,EAAA,UAAO,GAAa,CAAA,CAClB;KAEF;MAhCW"}
|
|
@@ -1,50 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { useGetVibeQuery as e } from "../../generated/wspace-operations.js";
|
|
2
|
+
import { SECURITY_META_PACKAGE as t, SECURITY_PROVIDER_PACKAGES as n } from "../../constants/pluginCatalog.js";
|
|
3
|
+
import { PluginGate as r } from "../shared/PluginGate.js";
|
|
4
|
+
import { PluginHarnessPicker as i } from "../ai/PluginHarnessPicker.js";
|
|
5
|
+
import { SecurityScanList as a } from "./SecurityScanList.js";
|
|
6
|
+
import { SecurityRunButton as o } from "./SecurityRunButton.js";
|
|
7
|
+
import { Shield as s } from "lucide-react";
|
|
8
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
7
9
|
//#region src/components/security/SecurityTabPanel.tsx
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
+
function u({ vibeId: u, agentId: d, profile: f = "default" }) {
|
|
11
|
+
let { data: p } = e({
|
|
12
|
+
variables: { id: u },
|
|
13
|
+
skip: !u
|
|
14
|
+
}), m = p?.vibe, h = m?.workspaceId ?? "", g = m?.gitRemote ?? "", _ = m?.path ?? "", v = m?.gitBranch ?? "";
|
|
15
|
+
return /* @__PURE__ */ l("div", {
|
|
10
16
|
"data-testid": "security-tab-panel",
|
|
11
17
|
className: "space-y-4",
|
|
12
|
-
children: [/* @__PURE__ */
|
|
18
|
+
children: [/* @__PURE__ */ l("header", {
|
|
13
19
|
className: "flex items-start gap-3",
|
|
14
|
-
children: [/* @__PURE__ */
|
|
20
|
+
children: [/* @__PURE__ */ c("div", {
|
|
15
21
|
className: "size-9 rounded-md bg-bg-sunken flex items-center justify-center shrink-0",
|
|
16
|
-
children: /* @__PURE__ */
|
|
17
|
-
}), /* @__PURE__ */
|
|
22
|
+
children: /* @__PURE__ */ c(s, { className: "size-4 text-text-secondary" })
|
|
23
|
+
}), /* @__PURE__ */ l("div", {
|
|
18
24
|
className: "min-w-0",
|
|
19
|
-
children: [/* @__PURE__ */
|
|
25
|
+
children: [/* @__PURE__ */ c("h2", {
|
|
20
26
|
className: "text-base font-semibold text-text-primary",
|
|
21
27
|
children: "Security"
|
|
22
|
-
}), /* @__PURE__ */
|
|
28
|
+
}), /* @__PURE__ */ c("p", {
|
|
23
29
|
className: "text-sm text-text-secondary mt-0.5",
|
|
24
30
|
children: "Install + run security scans across the 14-stage lifecycle. Plugins run on the agent; findings live in its local SQLite cache."
|
|
25
31
|
})]
|
|
26
32
|
})]
|
|
27
|
-
}), /* @__PURE__ */
|
|
28
|
-
agentId:
|
|
29
|
-
required: [
|
|
33
|
+
}), /* @__PURE__ */ l(r, {
|
|
34
|
+
agentId: d,
|
|
35
|
+
required: [t],
|
|
30
36
|
mode: "all",
|
|
31
37
|
eyebrow: "Security plugin required",
|
|
32
38
|
title: "Install the Security Orchestrator",
|
|
33
39
|
description: "@vibecontrols/vibe-plugin-security routes scans across per-stage providers. Required before installing individual stage providers.",
|
|
34
|
-
children: [/* @__PURE__ */
|
|
35
|
-
packages:
|
|
36
|
-
agentId:
|
|
40
|
+
children: [/* @__PURE__ */ c(i, {
|
|
41
|
+
packages: n,
|
|
42
|
+
agentId: d,
|
|
37
43
|
title: "Security Providers",
|
|
38
|
-
description: "One plugin per stage in the security lifecycle. Install the providers you want to use; uninstalled stages stay skipped."
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
description: "One plugin per stage in the security lifecycle. Install the providers you want to use; uninstalled stages stay skipped. Run a stage's scan from its card once installed.",
|
|
45
|
+
renderInstalledAction: (e) => /* @__PURE__ */ c(o, {
|
|
46
|
+
pkg: e,
|
|
47
|
+
vibeId: u,
|
|
48
|
+
workspaceId: h,
|
|
49
|
+
repoUrl: g,
|
|
50
|
+
repoLocalPath: _,
|
|
51
|
+
commit: v,
|
|
52
|
+
agentId: d,
|
|
53
|
+
profile: f
|
|
54
|
+
})
|
|
55
|
+
}), /* @__PURE__ */ c(a, {
|
|
56
|
+
vibeId: u,
|
|
57
|
+
agentId: d,
|
|
58
|
+
profile: f
|
|
43
59
|
})]
|
|
44
60
|
})]
|
|
45
61
|
});
|
|
46
62
|
}
|
|
47
63
|
//#endregion
|
|
48
|
-
export {
|
|
64
|
+
export { u as SecurityTabPanel };
|
|
49
65
|
|
|
50
66
|
//# sourceMappingURL=SecurityTabPanel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityTabPanel.js","names":[],"sources":["../../../src/components/security/SecurityTabPanel.tsx"],"sourcesContent":["/**\n * Security tab on Vibe detail.\n *\n * Wave 1: surface the security plugins so the user can find +\n * install them from the UI (same pattern as the AI page).\n *\n * Wave 2 (this commit): once the meta plugin is installed, mount\n * `SecurityScanList` which reads recent scan runs straight from the\n * agent over the tunnel (`/api/profiles/<profile>/security/*`). No\n * backend GraphQL involvement — all per-vibe scan history lives in\n * the agent's local SQLite cache.\n */\n\nimport { Shield } from 'lucide-react';\nimport { PluginGate } from '../shared/PluginGate';\nimport { PluginHarnessPicker } from '../ai/PluginHarnessPicker';\nimport { SECURITY_META_PACKAGE, SECURITY_PROVIDER_PACKAGES } from '@/constants/pluginCatalog';\nimport { SecurityScanList } from './SecurityScanList';\n\ninterface PanelProps {\n vibeId: string;\n agentId: string | null;\n /**\n * Canonical agent profile name. Threaded through from VibeDetailsPage's\n * `useActiveAgentProfile` so the tab targets the same profile the rest\n * of the page is talking to.\n */\n profile?: string;\n}\n\nexport function SecurityTabPanel({\n vibeId,\n agentId,\n profile = 'default',\n}: PanelProps): React.ReactElement {\n return (\n <div data-testid=\"security-tab-panel\" className=\"space-y-4\">\n <header className=\"flex items-start gap-3\">\n <div className=\"size-9 rounded-md bg-bg-sunken flex items-center justify-center shrink-0\">\n <Shield className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"min-w-0\">\n <h2 className=\"text-base font-semibold text-text-primary\">Security</h2>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n Install + run security scans across the 14-stage lifecycle. Plugins run on the agent;\n findings live in its local SQLite cache.\n </p>\n </div>\n </header>\n\n <PluginGate\n agentId={agentId}\n required={[SECURITY_META_PACKAGE]}\n mode=\"all\"\n eyebrow=\"Security plugin required\"\n title=\"Install the Security Orchestrator\"\n description=\"@vibecontrols/vibe-plugin-security routes scans across per-stage providers. Required before installing individual stage providers.\"\n >\n <PluginHarnessPicker\n packages={SECURITY_PROVIDER_PACKAGES}\n agentId={agentId}\n title=\"Security Providers\"\n description=\"One plugin per stage in the security lifecycle. Install the providers you want to use; uninstalled stages stay skipped.\"\n />\n <SecurityScanList vibeId={vibeId} agentId={agentId} profile={profile} />\n </PluginGate>\n </div>\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"SecurityTabPanel.js","names":[],"sources":["../../../src/components/security/SecurityTabPanel.tsx"],"sourcesContent":["/**\n * Security tab on Vibe detail.\n *\n * Wave 1: surface the security plugins so the user can find +\n * install them from the UI (same pattern as the AI page).\n *\n * Wave 2 (this commit): once the meta plugin is installed, mount\n * `SecurityScanList` which reads recent scan runs straight from the\n * agent over the tunnel (`/api/profiles/<profile>/security/*`). No\n * backend GraphQL involvement — all per-vibe scan history lives in\n * the agent's local SQLite cache.\n */\n\nimport { Shield } from 'lucide-react';\nimport { useGetVibeQuery } from '@/generated/wspace-operations';\nimport { PluginGate } from '../shared/PluginGate';\nimport { PluginHarnessPicker } from '../ai/PluginHarnessPicker';\nimport { SECURITY_META_PACKAGE, SECURITY_PROVIDER_PACKAGES } from '@/constants/pluginCatalog';\nimport { SecurityScanList } from './SecurityScanList';\nimport { SecurityRunButton } from './SecurityRunButton';\n\ninterface PanelProps {\n vibeId: string;\n agentId: string | null;\n /**\n * Canonical agent profile name. Threaded through from VibeDetailsPage's\n * `useActiveAgentProfile` so the tab targets the same profile the rest\n * of the page is talking to.\n */\n profile?: string;\n}\n\nexport function SecurityTabPanel({\n vibeId,\n agentId,\n profile = 'default',\n}: PanelProps): React.ReactElement {\n // Repo metadata the agent scan contract needs. Shares the Apollo cache\n // entry the details page already primed via its own `useGetVibeQuery`, so\n // this is a cache read in practice — no extra network round-trip.\n const { data: vibeData } = useGetVibeQuery({\n variables: { id: vibeId },\n skip: !vibeId,\n });\n const vibe = vibeData?.vibe;\n const workspaceId = vibe?.workspaceId ?? '';\n const repoUrl = vibe?.gitRemote ?? '';\n const repoLocalPath = vibe?.path ?? '';\n const commit = vibe?.gitBranch ?? '';\n\n return (\n <div data-testid=\"security-tab-panel\" className=\"space-y-4\">\n <header className=\"flex items-start gap-3\">\n <div className=\"size-9 rounded-md bg-bg-sunken flex items-center justify-center shrink-0\">\n <Shield className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"min-w-0\">\n <h2 className=\"text-base font-semibold text-text-primary\">Security</h2>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n Install + run security scans across the 14-stage lifecycle. Plugins run on the agent;\n findings live in its local SQLite cache.\n </p>\n </div>\n </header>\n\n <PluginGate\n agentId={agentId}\n required={[SECURITY_META_PACKAGE]}\n mode=\"all\"\n eyebrow=\"Security plugin required\"\n title=\"Install the Security Orchestrator\"\n description=\"@vibecontrols/vibe-plugin-security routes scans across per-stage providers. Required before installing individual stage providers.\"\n >\n <PluginHarnessPicker\n packages={SECURITY_PROVIDER_PACKAGES}\n agentId={agentId}\n title=\"Security Providers\"\n description=\"One plugin per stage in the security lifecycle. Install the providers you want to use; uninstalled stages stay skipped. Run a stage's scan from its card once installed.\"\n renderInstalledAction={(pkg) => (\n <SecurityRunButton\n pkg={pkg}\n vibeId={vibeId}\n workspaceId={workspaceId}\n repoUrl={repoUrl}\n repoLocalPath={repoLocalPath}\n commit={commit}\n agentId={agentId}\n profile={profile}\n />\n )}\n />\n <SecurityScanList vibeId={vibeId} agentId={agentId} profile={profile} />\n </PluginGate>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAgCA,SAAgB,EAAiB,EAC/B,WACA,YACA,aAAU,aACuB;CAIjC,IAAM,EAAE,MAAM,MAAa,EAAgB;EACzC,WAAW,EAAE,IAAI,GAAQ;EACzB,MAAM,CAAC;EACR,CAAC,EACI,IAAO,GAAU,MACjB,IAAc,GAAM,eAAe,IACnC,IAAU,GAAM,aAAa,IAC7B,IAAgB,GAAM,QAAQ,IAC9B,IAAS,GAAM,aAAa;AAElC,QACE,kBAAC,OAAD;EAAK,eAAY;EAAqB,WAAU;YAAhD,CACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAQ,WAAU,8BAA+B,CAAA;IAC7C,CAAA,EACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAA4C;KAAa,CAAA,EACvE,kBAAC,KAAD;KAAG,WAAU;eAAqC;KAG9C,CAAA,CACA;MACC;MAET,kBAAC,GAAD;GACW;GACT,UAAU,CAAC,EAAsB;GACjC,MAAK;GACL,SAAQ;GACR,OAAM;GACN,aAAY;aANd,CAQE,kBAAC,GAAD;IACE,UAAU;IACD;IACT,OAAM;IACN,aAAY;IACZ,wBAAwB,MACtB,kBAAC,GAAD;KACO;KACG;KACK;KACJ;KACM;KACP;KACC;KACA;KACT,CAAA;IAEJ,CAAA,EACF,kBAAC,GAAD;IAA0B;IAAiB;IAAkB;IAAW,CAAA,CAC7D;KACT"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { SecurityAgentError as e, securityAgent as t } from "./agentClient.js";
|
|
2
2
|
import { useApolloClient as n } from "@apollo/client/react";
|
|
3
|
-
import {
|
|
3
|
+
import { useMutation as r, useQuery as i, useQueryClient as a } from "@tanstack/react-query";
|
|
4
4
|
//#region src/components/security/hooks.ts
|
|
5
|
-
function
|
|
5
|
+
function o(e, ...t) {
|
|
6
6
|
return [
|
|
7
7
|
"security",
|
|
8
8
|
e.agentId ?? "_",
|
|
@@ -10,23 +10,40 @@ function i(e, ...t) {
|
|
|
10
10
|
...t
|
|
11
11
|
];
|
|
12
12
|
}
|
|
13
|
-
function
|
|
13
|
+
function s(r, a, s = 20) {
|
|
14
14
|
let c = n();
|
|
15
|
-
return
|
|
16
|
-
queryKey:
|
|
17
|
-
enabled: !!
|
|
15
|
+
return i({
|
|
16
|
+
queryKey: o(r, "recent-scans", a, s),
|
|
17
|
+
enabled: !!r.agentId && !!a,
|
|
18
18
|
queryFn: async () => {
|
|
19
|
-
if (!
|
|
19
|
+
if (!r.agentId) throw new e("INVALID", 0, "no agent opts");
|
|
20
20
|
let n = {
|
|
21
|
-
agentId:
|
|
22
|
-
profile:
|
|
21
|
+
agentId: r.agentId,
|
|
22
|
+
profile: r.profile,
|
|
23
|
+
apolloClient: c
|
|
24
|
+
};
|
|
25
|
+
return t.listRecentScanRuns(n, a, s);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function c(i) {
|
|
30
|
+
let s = a(), c = n();
|
|
31
|
+
return r({
|
|
32
|
+
mutationFn: async (n) => {
|
|
33
|
+
if (!i.agentId) throw new e("INVALID", 0, "no agent opts");
|
|
34
|
+
let r = {
|
|
35
|
+
agentId: i.agentId,
|
|
36
|
+
profile: i.profile,
|
|
23
37
|
apolloClient: c
|
|
24
38
|
};
|
|
25
|
-
return t.
|
|
39
|
+
return t.startScan(r, n);
|
|
40
|
+
},
|
|
41
|
+
onSuccess: () => {
|
|
42
|
+
s.invalidateQueries({ queryKey: o(i) });
|
|
26
43
|
}
|
|
27
44
|
});
|
|
28
45
|
}
|
|
29
46
|
//#endregion
|
|
30
|
-
export {
|
|
47
|
+
export { s as useSecurityRecentScansQuery, c as useStartSecurityScanMutation };
|
|
31
48
|
|
|
32
49
|
//# sourceMappingURL=hooks.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.js","names":[],"sources":["../../../src/components/security/hooks.ts"],"sourcesContent":["/**\n * React Query hooks over the security agent GraphQL proxy.\n *\n * F4 Wave C: rewired off the direct browser→agent tunnel onto the svc\n * GraphQL proxy (`agentSecurityScanProxy` / `agentSecurityScanMutate`).\n * Cache keys still include the agent identity (agentId + profile) so\n * two simultaneously-active vibes against different agents stay\n * isolated. Mirrors the layout of `src/hooks/useGitops.ts`.\n */\nimport {\n useMutation,\n useQuery,\n useQueryClient,\n type UseMutationResult,\n type UseQueryResult,\n} from '@tanstack/react-query';\nimport { useApolloClient } from '@apollo/client/react';\n\nimport {\n securityAgent,\n SecurityAgentError,\n type FindingsFilter,\n type FindingsPage,\n type SecurityAgentOpts,\n type SecurityProvidersByStage,\n type SecurityScanRun,\n type StartScanInput,\n type StartScanResult,\n} from './agentClient';\n\nexport interface UseSecurityAgentContext {\n /** Required: canonical profile name. */\n profile: string;\n /** Required: agent id — scopes cache + passes through to svc proxy. */\n agentId: string | null;\n}\n\nfunction key(ctx: UseSecurityAgentContext, ...rest: unknown[]): unknown[] {\n return ['security', ctx.agentId ?? '_', ctx.profile, ...rest];\n}\n\nexport function useSecurityProvidersQuery(\n ctx: UseSecurityAgentContext\n): UseQueryResult<SecurityProvidersByStage, SecurityAgentError> {\n const apolloClient = useApolloClient();\n return useQuery({\n queryKey: key(ctx, 'providers'),\n enabled: !!ctx.agentId,\n queryFn: async () => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.listProviders(opts);\n },\n });\n}\n\nexport function useSecurityFindingsQuery(\n ctx: UseSecurityAgentContext,\n filter: FindingsFilter\n): UseQueryResult<FindingsPage, SecurityAgentError> {\n const apolloClient = useApolloClient();\n return useQuery({\n queryKey: key(ctx, 'findings', filter.vibeId, filter.severity, filter.status, filter.limit),\n enabled: !!ctx.agentId,\n queryFn: async () => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.listFindings(opts, filter);\n },\n });\n}\n\nexport function useSecurityRecentScansQuery(\n ctx: UseSecurityAgentContext,\n vibeId: string,\n limit = 20\n): UseQueryResult<SecurityScanRun[], SecurityAgentError> {\n const apolloClient = useApolloClient();\n return useQuery({\n queryKey: key(ctx, 'recent-scans', vibeId, limit),\n enabled: !!ctx.agentId && !!vibeId,\n queryFn: async () => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.listRecentScanRuns(opts, vibeId, limit);\n },\n });\n}\n\nexport function useStartSecurityScanMutation(\n ctx: UseSecurityAgentContext\n): UseMutationResult<StartScanResult, SecurityAgentError, StartScanInput> {\n const qc = useQueryClient();\n const apolloClient = useApolloClient();\n return useMutation({\n mutationFn: async (input: StartScanInput) => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.startScan(opts, input);\n },\n onSuccess: () => {\n void qc.invalidateQueries({ queryKey: key(ctx) });\n },\n });\n}\n"],"mappings":";;;;AAqCA,SAAS,EAAI,GAA8B,GAAG,GAA4B;AACxE,QAAO;EAAC;EAAY,EAAI,WAAW;EAAK,EAAI;EAAS,GAAG;EAAK;;AAkC/D,SAAgB,EACd,GACA,GACA,IAAQ,IAC+C;CACvD,IAAM,IAAe,GAAiB;AACtC,QAAO,EAAS;EACd,UAAU,EAAI,GAAK,gBAAgB,GAAQ,EAAM;EACjD,SAAS,CAAC,CAAC,EAAI,WAAW,CAAC,CAAC;EAC5B,SAAS,YAAY;AACnB,OAAI,CAAC,EAAI,QAAS,OAAM,IAAI,EAAmB,WAAW,GAAG,gBAAgB;GAC7E,IAAM,IAA0B;IAAE,SAAS,EAAI;IAAS,SAAS,EAAI;IAAS;IAAc;AAC5F,UAAO,EAAc,mBAAmB,GAAM,GAAQ,EAAM;;EAE/D,CAAC"}
|
|
1
|
+
{"version":3,"file":"hooks.js","names":[],"sources":["../../../src/components/security/hooks.ts"],"sourcesContent":["/**\n * React Query hooks over the security agent GraphQL proxy.\n *\n * F4 Wave C: rewired off the direct browser→agent tunnel onto the svc\n * GraphQL proxy (`agentSecurityScanProxy` / `agentSecurityScanMutate`).\n * Cache keys still include the agent identity (agentId + profile) so\n * two simultaneously-active vibes against different agents stay\n * isolated. Mirrors the layout of `src/hooks/useGitops.ts`.\n */\nimport {\n useMutation,\n useQuery,\n useQueryClient,\n type UseMutationResult,\n type UseQueryResult,\n} from '@tanstack/react-query';\nimport { useApolloClient } from '@apollo/client/react';\n\nimport {\n securityAgent,\n SecurityAgentError,\n type FindingsFilter,\n type FindingsPage,\n type SecurityAgentOpts,\n type SecurityProvidersByStage,\n type SecurityScanRun,\n type StartScanInput,\n type StartScanResult,\n} from './agentClient';\n\nexport interface UseSecurityAgentContext {\n /** Required: canonical profile name. */\n profile: string;\n /** Required: agent id — scopes cache + passes through to svc proxy. */\n agentId: string | null;\n}\n\nfunction key(ctx: UseSecurityAgentContext, ...rest: unknown[]): unknown[] {\n return ['security', ctx.agentId ?? '_', ctx.profile, ...rest];\n}\n\nexport function useSecurityProvidersQuery(\n ctx: UseSecurityAgentContext\n): UseQueryResult<SecurityProvidersByStage, SecurityAgentError> {\n const apolloClient = useApolloClient();\n return useQuery({\n queryKey: key(ctx, 'providers'),\n enabled: !!ctx.agentId,\n queryFn: async () => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.listProviders(opts);\n },\n });\n}\n\nexport function useSecurityFindingsQuery(\n ctx: UseSecurityAgentContext,\n filter: FindingsFilter\n): UseQueryResult<FindingsPage, SecurityAgentError> {\n const apolloClient = useApolloClient();\n return useQuery({\n queryKey: key(ctx, 'findings', filter.vibeId, filter.severity, filter.status, filter.limit),\n enabled: !!ctx.agentId,\n queryFn: async () => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.listFindings(opts, filter);\n },\n });\n}\n\nexport function useSecurityRecentScansQuery(\n ctx: UseSecurityAgentContext,\n vibeId: string,\n limit = 20\n): UseQueryResult<SecurityScanRun[], SecurityAgentError> {\n const apolloClient = useApolloClient();\n return useQuery({\n queryKey: key(ctx, 'recent-scans', vibeId, limit),\n enabled: !!ctx.agentId && !!vibeId,\n queryFn: async () => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.listRecentScanRuns(opts, vibeId, limit);\n },\n });\n}\n\nexport function useStartSecurityScanMutation(\n ctx: UseSecurityAgentContext\n): UseMutationResult<StartScanResult, SecurityAgentError, StartScanInput> {\n const qc = useQueryClient();\n const apolloClient = useApolloClient();\n return useMutation({\n mutationFn: async (input: StartScanInput) => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.startScan(opts, input);\n },\n onSuccess: () => {\n void qc.invalidateQueries({ queryKey: key(ctx) });\n },\n });\n}\n"],"mappings":";;;;AAqCA,SAAS,EAAI,GAA8B,GAAG,GAA4B;AACxE,QAAO;EAAC;EAAY,EAAI,WAAW;EAAK,EAAI;EAAS,GAAG;EAAK;;AAkC/D,SAAgB,EACd,GACA,GACA,IAAQ,IAC+C;CACvD,IAAM,IAAe,GAAiB;AACtC,QAAO,EAAS;EACd,UAAU,EAAI,GAAK,gBAAgB,GAAQ,EAAM;EACjD,SAAS,CAAC,CAAC,EAAI,WAAW,CAAC,CAAC;EAC5B,SAAS,YAAY;AACnB,OAAI,CAAC,EAAI,QAAS,OAAM,IAAI,EAAmB,WAAW,GAAG,gBAAgB;GAC7E,IAAM,IAA0B;IAAE,SAAS,EAAI;IAAS,SAAS,EAAI;IAAS;IAAc;AAC5F,UAAO,EAAc,mBAAmB,GAAM,GAAQ,EAAM;;EAE/D,CAAC;;AAGJ,SAAgB,EACd,GACwE;CACxE,IAAM,IAAK,GAAgB,EACrB,IAAe,GAAiB;AACtC,QAAO,EAAY;EACjB,YAAY,OAAO,MAA0B;AAC3C,OAAI,CAAC,EAAI,QAAS,OAAM,IAAI,EAAmB,WAAW,GAAG,gBAAgB;GAC7E,IAAM,IAA0B;IAAE,SAAS,EAAI;IAAS,SAAS,EAAI;IAAS;IAAc;AAC5F,UAAO,EAAc,UAAU,GAAM,EAAM;;EAE7C,iBAAiB;AACV,KAAG,kBAAkB,EAAE,UAAU,EAAI,EAAI,EAAE,CAAC;;EAEpD,CAAC"}
|
|
@@ -30,6 +30,21 @@ var n = "@vibecontrols/vibe-plugin-session-manager", r = [
|
|
|
30
30
|
"@vibecontrols/vibe-plugin-security-incident",
|
|
31
31
|
"@vibecontrols/vibe-plugin-security-archive"
|
|
32
32
|
], d = {
|
|
33
|
+
"@vibecontrols/vibe-plugin-security-onboard": "repo.onboard",
|
|
34
|
+
"@vibecontrols/vibe-plugin-security-developer-local": "developer.local",
|
|
35
|
+
"@vibecontrols/vibe-plugin-security-secrets-pr": "pull_request.fast",
|
|
36
|
+
"@vibecontrols/vibe-plugin-security-sast-deep": "pull_request.deep",
|
|
37
|
+
"@vibecontrols/vibe-plugin-security-scorecard": "main.merge",
|
|
38
|
+
"@vibecontrols/vibe-plugin-security-sbom-build": "build",
|
|
39
|
+
"@vibecontrols/vibe-plugin-security-package-publish": "package.publish",
|
|
40
|
+
"@vibecontrols/vibe-plugin-security-dast-preview": "deploy.preview",
|
|
41
|
+
"@vibecontrols/vibe-plugin-security-deploy-alpha": "deploy.alpha",
|
|
42
|
+
"@vibecontrols/vibe-plugin-security-release-gate": "promote.prod",
|
|
43
|
+
"@vibecontrols/vibe-plugin-security-runtime": "runtime.continuous",
|
|
44
|
+
"@vibecontrols/vibe-plugin-security-rescan": "scheduled.rescan",
|
|
45
|
+
"@vibecontrols/vibe-plugin-security-incident": "incident.response",
|
|
46
|
+
"@vibecontrols/vibe-plugin-security-archive": "archive.offboard"
|
|
47
|
+
}, f = {
|
|
33
48
|
[e]: {
|
|
34
49
|
packageName: e,
|
|
35
50
|
displayName: "AI Orchestrator",
|
|
@@ -301,8 +316,8 @@ var n = "@vibecontrols/vibe-plugin-session-manager", r = [
|
|
|
301
316
|
category: "security-provider"
|
|
302
317
|
}
|
|
303
318
|
};
|
|
304
|
-
function
|
|
305
|
-
return
|
|
319
|
+
function p(e) {
|
|
320
|
+
return f[e] ?? {
|
|
306
321
|
packageName: e,
|
|
307
322
|
displayName: e.replace("@vibecontrols/vibe-plugin-", ""),
|
|
308
323
|
description: "VibeControls agent plugin.",
|
|
@@ -310,6 +325,6 @@ function f(e) {
|
|
|
310
325
|
};
|
|
311
326
|
}
|
|
312
327
|
//#endregion
|
|
313
|
-
export { e as AI_CORE_PACKAGE, t as AI_PROVIDER_PACKAGES, o as GIT_PACKAGE, s as GRAPHQL_PLAYGROUND_PACKAGE, i as PLAN_META_PACKAGE, a as PLAN_PLANNOTATOR_PACKAGE,
|
|
328
|
+
export { e as AI_CORE_PACKAGE, t as AI_PROVIDER_PACKAGES, o as GIT_PACKAGE, s as GRAPHQL_PLAYGROUND_PACKAGE, i as PLAN_META_PACKAGE, a as PLAN_PLANNOTATOR_PACKAGE, f as PLUGIN_CATALOG, l as SECURITY_META_PACKAGE, u as SECURITY_PROVIDER_PACKAGES, d as SECURITY_STAGE_BY_PACKAGE, n as SESSION_MANAGER_PACKAGE, r as SESSION_PROVIDER_PACKAGES, p as getPluginMeta };
|
|
314
329
|
|
|
315
330
|
//# sourceMappingURL=pluginCatalog.js.map
|