@burdenoff/microfe-vibecontrols 2026.525.2 → 2026.525.3
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 +65 -64
- package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
- package/dist/components/shared/PluginGate.js +71 -70
- package/dist/components/shared/PluginGate.js.map +1 -1
- package/dist/generated/wspace-operations.js +4 -4
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/hooks/usePluginAvailability.js +97 -53
- package/dist/hooks/usePluginAvailability.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +525 -490
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +19 -19
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/ai/AIPage.js +10 -10
- package/dist/pages/ai/AISessionDetailPage.js +4 -4
- package/dist/pages/ai/AgentManagerPage.js +11 -11
- package/dist/pages/ai/AgentManagerPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,118 +1,119 @@
|
|
|
1
1
|
import { useRemoveAgentPluginMutation as e } from "../../generated/wspace-operations.js";
|
|
2
2
|
import { Card as t } from "../shared/Card.js";
|
|
3
3
|
import { usePluginAvailability as n } from "../../hooks/usePluginAvailability.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { useActiveAgentProfile as r } from "../../hooks/useActiveAgentProfile.js";
|
|
5
|
+
import { getPluginMeta as i } from "../../constants/pluginCatalog.js";
|
|
6
|
+
import { useCallback as a, useEffect as o, useRef as s, useState as c } from "react";
|
|
7
|
+
import { Check as l, CheckCircle2 as u, Download as d, Loader2 as f, Puzzle as p, Trash2 as m } from "lucide-react";
|
|
8
|
+
import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
8
9
|
//#region src/components/ai/PluginHarnessPicker.tsx
|
|
9
|
-
function
|
|
10
|
-
let { installed:
|
|
11
|
-
|
|
12
|
-
let e =
|
|
10
|
+
function v({ packages: v, agentId: y, title: b, description: x, onChange: S }) {
|
|
11
|
+
let { active: C } = r(y), { installed: w, installing: T, install: E, refetch: D, loading: O, error: k } = n(v, y, C), [A] = e(), [j, M] = c(null), [N, P] = c({}), [F, I] = c({}), L = s(/* @__PURE__ */ new Map());
|
|
12
|
+
o(() => {
|
|
13
|
+
let e = L.current;
|
|
13
14
|
return () => {
|
|
14
15
|
for (let t of e.values()) clearTimeout(t);
|
|
15
16
|
e.clear();
|
|
16
17
|
};
|
|
17
18
|
}, []);
|
|
18
|
-
let
|
|
19
|
-
if (await
|
|
20
|
-
|
|
19
|
+
let R = a(async (e) => {
|
|
20
|
+
if (await E(e)) {
|
|
21
|
+
I((t) => ({
|
|
21
22
|
...t,
|
|
22
23
|
[e]: !0
|
|
23
24
|
}));
|
|
24
|
-
let t =
|
|
25
|
+
let t = L.current.get(e);
|
|
25
26
|
t && clearTimeout(t);
|
|
26
27
|
let n = setTimeout(() => {
|
|
27
|
-
|
|
28
|
+
I((t) => {
|
|
28
29
|
if (!t[e]) return t;
|
|
29
30
|
let n = { ...t };
|
|
30
31
|
return delete n[e], n;
|
|
31
|
-
}),
|
|
32
|
+
}), L.current.delete(e);
|
|
32
33
|
}, 1500);
|
|
33
|
-
|
|
34
|
+
L.current.set(e, n), S?.();
|
|
34
35
|
}
|
|
35
|
-
}, [
|
|
36
|
-
if (
|
|
37
|
-
|
|
36
|
+
}, [E, S]), z = a(async (e) => {
|
|
37
|
+
if (y) {
|
|
38
|
+
M(e), P((t) => ({
|
|
38
39
|
...t,
|
|
39
40
|
[e]: ""
|
|
40
41
|
}));
|
|
41
42
|
try {
|
|
42
|
-
let { data: t } = await
|
|
43
|
-
agentId:
|
|
43
|
+
let { data: t } = await A({ variables: {
|
|
44
|
+
agentId: y,
|
|
44
45
|
packageName: e
|
|
45
46
|
} });
|
|
46
47
|
if (!t?.removeAgentPlugin.success) {
|
|
47
|
-
|
|
48
|
+
P((n) => ({
|
|
48
49
|
...n,
|
|
49
50
|
[e]: t?.removeAgentPlugin.error || "Remove failed"
|
|
50
51
|
}));
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
|
-
await
|
|
54
|
+
await D(), S?.();
|
|
54
55
|
} catch (t) {
|
|
55
|
-
|
|
56
|
+
P((n) => ({
|
|
56
57
|
...n,
|
|
57
58
|
[e]: t instanceof Error ? t.message : String(t)
|
|
58
59
|
}));
|
|
59
60
|
} finally {
|
|
60
|
-
|
|
61
|
+
M(null);
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
}, [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
y,
|
|
66
|
+
A,
|
|
67
|
+
D,
|
|
68
|
+
S
|
|
68
69
|
]);
|
|
69
|
-
return
|
|
70
|
+
return v.length === 0 ? null : /* @__PURE__ */ _("section", {
|
|
70
71
|
className: "flex flex-col gap-3",
|
|
71
72
|
"data-testid": "plugin-harness-picker",
|
|
72
73
|
children: [
|
|
73
|
-
(
|
|
74
|
+
(b || x) && /* @__PURE__ */ _("div", { children: [b && /* @__PURE__ */ g("h3", {
|
|
74
75
|
className: "text-base font-semibold text-text-primary",
|
|
75
|
-
children: y
|
|
76
|
-
}), b && /* @__PURE__ */ h("p", {
|
|
77
|
-
className: "text-sm text-text-secondary mt-1",
|
|
78
76
|
children: b
|
|
77
|
+
}), x && /* @__PURE__ */ g("p", {
|
|
78
|
+
className: "text-sm text-text-secondary mt-1",
|
|
79
|
+
children: x
|
|
79
80
|
})] }),
|
|
80
|
-
|
|
81
|
+
k && /* @__PURE__ */ g("div", {
|
|
81
82
|
role: "alert",
|
|
82
83
|
className: "rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text",
|
|
83
|
-
children:
|
|
84
|
+
children: k
|
|
84
85
|
}),
|
|
85
|
-
/* @__PURE__ */
|
|
86
|
+
/* @__PURE__ */ g("div", {
|
|
86
87
|
className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3",
|
|
87
|
-
children:
|
|
88
|
-
let n =
|
|
89
|
-
return /* @__PURE__ */
|
|
88
|
+
children: v.map((e) => {
|
|
89
|
+
let n = i(e), r = !!w[e], a = T === e, o = j === e, s = a || o || O, c = !!F[e], v = N[e];
|
|
90
|
+
return /* @__PURE__ */ _(t, {
|
|
90
91
|
padding: "md",
|
|
91
92
|
className: "flex flex-col gap-3",
|
|
92
93
|
children: [
|
|
93
|
-
/* @__PURE__ */
|
|
94
|
+
/* @__PURE__ */ _("div", {
|
|
94
95
|
className: "flex items-start gap-3",
|
|
95
|
-
children: [/* @__PURE__ */
|
|
96
|
+
children: [/* @__PURE__ */ g("div", {
|
|
96
97
|
className: "flex-shrink-0 w-9 h-9 rounded-md bg-bg-sunken flex items-center justify-center",
|
|
97
|
-
children: /* @__PURE__ */
|
|
98
|
-
}), /* @__PURE__ */
|
|
98
|
+
children: /* @__PURE__ */ g(p, { className: "w-4 h-4 text-text-secondary" })
|
|
99
|
+
}), /* @__PURE__ */ _("div", {
|
|
99
100
|
className: "min-w-0 flex-1",
|
|
100
101
|
children: [
|
|
101
|
-
/* @__PURE__ */
|
|
102
|
+
/* @__PURE__ */ _("div", {
|
|
102
103
|
className: "flex items-start gap-2 flex-wrap",
|
|
103
|
-
children: [/* @__PURE__ */
|
|
104
|
+
children: [/* @__PURE__ */ g("h4", {
|
|
104
105
|
className: "font-medium text-text-primary break-words max-w-[100ch]",
|
|
105
106
|
children: n.displayName
|
|
106
|
-
}),
|
|
107
|
+
}), r && /* @__PURE__ */ _("span", {
|
|
107
108
|
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",
|
|
108
|
-
children: [/* @__PURE__ */
|
|
109
|
+
children: [/* @__PURE__ */ g(l, { className: "w-3 h-3" }), " installed"]
|
|
109
110
|
})]
|
|
110
111
|
}),
|
|
111
|
-
/* @__PURE__ */
|
|
112
|
+
/* @__PURE__ */ g("p", {
|
|
112
113
|
className: "text-sm text-text-secondary mt-0.5",
|
|
113
114
|
children: n.description
|
|
114
115
|
}),
|
|
115
|
-
/* @__PURE__ */
|
|
116
|
+
/* @__PURE__ */ g("p", {
|
|
116
117
|
className: "text-[10px] font-mono text-text-placeholder mt-1 break-all",
|
|
117
118
|
title: e,
|
|
118
119
|
children: e
|
|
@@ -120,41 +121,41 @@ function _({ packages: _, agentId: v, title: y, description: b, onChange: x }) {
|
|
|
120
121
|
]
|
|
121
122
|
})]
|
|
122
123
|
}),
|
|
123
|
-
/* @__PURE__ */
|
|
124
|
+
/* @__PURE__ */ g("div", {
|
|
124
125
|
className: "flex items-center gap-2",
|
|
125
|
-
children:
|
|
126
|
+
children: r && c ? /* @__PURE__ */ _("button", {
|
|
126
127
|
type: "button",
|
|
127
128
|
disabled: !0,
|
|
128
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",
|
|
129
130
|
"data-testid": `plugin-installed-${e}`,
|
|
130
131
|
"aria-live": "polite",
|
|
131
|
-
children: [/* @__PURE__ */
|
|
132
|
-
}) :
|
|
132
|
+
children: [/* @__PURE__ */ g(u, { className: "w-3.5 h-3.5" }), "Installed"]
|
|
133
|
+
}) : r ? /* @__PURE__ */ _("button", {
|
|
133
134
|
type: "button",
|
|
134
|
-
disabled: s || !
|
|
135
|
-
onClick: () =>
|
|
135
|
+
disabled: s || !y,
|
|
136
|
+
onClick: () => z(e),
|
|
136
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",
|
|
137
138
|
"data-testid": `plugin-remove-${e}`,
|
|
138
|
-
children: [o ? /* @__PURE__ */
|
|
139
|
-
}) : /* @__PURE__ */
|
|
139
|
+
children: [o ? /* @__PURE__ */ g(f, { className: "w-3.5 h-3.5 animate-spin" }) : /* @__PURE__ */ g(m, { className: "w-3.5 h-3.5" }), "Remove"]
|
|
140
|
+
}) : /* @__PURE__ */ g("button", {
|
|
140
141
|
type: "button",
|
|
141
|
-
disabled: s || !
|
|
142
|
-
onClick: () =>
|
|
142
|
+
disabled: s || !y,
|
|
143
|
+
onClick: () => R(e),
|
|
143
144
|
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",
|
|
144
145
|
"data-testid": `plugin-install-${e}`,
|
|
145
146
|
"aria-busy": a,
|
|
146
|
-
children: a ? /* @__PURE__ */
|
|
147
|
+
children: a ? /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g(f, { className: "w-3.5 h-3.5 animate-spin" }), "Installing…"] }) : /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g(d, { className: "w-3.5 h-3.5" }), "Install"] })
|
|
147
148
|
})
|
|
148
149
|
}),
|
|
149
|
-
|
|
150
|
+
v && /* @__PURE__ */ g("p", {
|
|
150
151
|
className: "text-xs text-status-error-text",
|
|
151
|
-
children:
|
|
152
|
+
children: v
|
|
152
153
|
})
|
|
153
154
|
]
|
|
154
155
|
}, e);
|
|
155
156
|
})
|
|
156
157
|
}),
|
|
157
|
-
!
|
|
158
|
+
!y && /* @__PURE__ */ g("p", {
|
|
158
159
|
className: "text-xs text-text-muted",
|
|
159
160
|
children: "Select an agent to manage plugins."
|
|
160
161
|
})
|
|
@@ -162,6 +163,6 @@ function _({ packages: _, agentId: v, title: y, description: b, onChange: x }) {
|
|
|
162
163
|
});
|
|
163
164
|
}
|
|
164
165
|
//#endregion
|
|
165
|
-
export {
|
|
166
|
+
export { v as PluginHarnessPicker };
|
|
166
167
|
|
|
167
168
|
//# sourceMappingURL=PluginHarnessPicker.js.map
|
|
@@ -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 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 w-9 h-9 rounded-md bg-bg-sunken flex items-center justify-center\">\n <Puzzle className=\"w-4 h-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=\"w-3 h-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=\"w-3.5 h-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=\"w-3.5 h-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"w-3.5 h-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=\"w-3.5 h-3.5 animate-spin\" />\n Installing…\n </>\n ) : (\n <>\n <Download className=\"w-3.5 h-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 } from 'react';\nimport { Check, CheckCircle2, Download, Loader2, Puzzle, Trash2 } from 'lucide-react';\nimport { useRemoveAgentPluginMutation } from '@/generated/wspace-operations';\nimport { usePluginAvailability } from '@/hooks/usePluginAvailability';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\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 const { active: activeProfile } = useActiveAgentProfile(agentId);\n const { installed, installing, install, refetch, loading, error } = usePluginAvailability(\n packages,\n agentId,\n activeProfile\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 w-9 h-9 rounded-md bg-bg-sunken flex items-center justify-center\">\n <Puzzle className=\"w-4 h-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=\"w-3 h-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=\"w-3.5 h-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=\"w-3.5 h-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"w-3.5 h-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=\"w-3.5 h-3.5 animate-spin\" />\n Installing…\n </>\n ) : (\n <>\n <Download className=\"w-3.5 h-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,12 +1,13 @@
|
|
|
1
1
|
import { useVibeNavigate as e } from "../../providers/VibeControlsProvider.js";
|
|
2
2
|
import { QuotaExhaustedDialog as t } from "./QuotaExhaustedDialog.js";
|
|
3
3
|
import { usePluginAvailability as n } from "../../hooks/usePluginAvailability.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { useActiveAgentProfile as r } from "../../hooks/useActiveAgentProfile.js";
|
|
5
|
+
import { getPluginMeta as i } from "../../constants/pluginCatalog.js";
|
|
6
|
+
import { useCallback as a, useMemo as o } from "react";
|
|
7
|
+
import { AlertCircle as s, Clock as c, Download as l, Loader2 as u, Puzzle as d, RefreshCw as f, Server as p } from "lucide-react";
|
|
8
|
+
import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
8
9
|
//#region src/components/shared/PluginGate.tsx
|
|
9
|
-
function
|
|
10
|
+
function _(e) {
|
|
10
11
|
return e.requirements && e.requirements.length > 0 ? e.requirements.map((e) => ({
|
|
11
12
|
packages: e.packages,
|
|
12
13
|
mode: e.mode ?? "all",
|
|
@@ -21,50 +22,50 @@ function g(e) {
|
|
|
21
22
|
description: e.description
|
|
22
23
|
}] : [];
|
|
23
24
|
}
|
|
24
|
-
function
|
|
25
|
-
let { agentId:
|
|
25
|
+
function v(v) {
|
|
26
|
+
let { agentId: y, compact: b = !1, onInstalled: x, children: S } = v, { active: C } = r(y), w = o(() => _(v), [v]), { loading: T, initialLoading: E, installed: D, installing: O, install: k, error: A, errorIsAgentState: j, quotaExhausted: M, clearQuotaExhausted: N, refetch: P } = n(o(() => {
|
|
26
27
|
let e = /* @__PURE__ */ new Set();
|
|
27
|
-
for (let t of
|
|
28
|
+
for (let t of w) for (let n of t.packages) e.add(n);
|
|
28
29
|
return [...e];
|
|
29
|
-
}, [
|
|
30
|
-
await
|
|
31
|
-
}, [
|
|
32
|
-
return
|
|
33
|
-
className: `rounded-lg border border-border-default bg-bg-surface ${
|
|
30
|
+
}, [w]), y, C), F = e(), I = o(() => w.map((e) => e.mode === "any" ? e.packages.some((e) => !!D[e]) : e.packages.every((e) => !!D[e])), [w, D]), L = w.length > 0 && I.every(Boolean), R = a(async (e) => {
|
|
31
|
+
await k(e) && x?.();
|
|
32
|
+
}, [k, x]);
|
|
33
|
+
return y ? L ? /* @__PURE__ */ h(m, { children: S }) : E ? /* @__PURE__ */ h("div", {
|
|
34
|
+
className: `rounded-lg border border-border-default bg-bg-surface ${b ? "p-4" : "p-6"}`,
|
|
34
35
|
"data-testid": "plugin-gate-initial-loading",
|
|
35
|
-
children: /* @__PURE__ */
|
|
36
|
+
children: /* @__PURE__ */ g("div", {
|
|
36
37
|
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
37
|
-
children: [/* @__PURE__ */
|
|
38
|
+
children: [/* @__PURE__ */ h(u, { className: "h-4 w-4 animate-spin" }), "Checking plugin availability on the agent…"]
|
|
38
39
|
})
|
|
39
|
-
}) :
|
|
40
|
-
className: `rounded-lg border border-border-default bg-bg-surface ${
|
|
41
|
-
children: /* @__PURE__ */
|
|
40
|
+
}) : j ? /* @__PURE__ */ h("div", {
|
|
41
|
+
className: `rounded-lg border border-border-default bg-bg-surface ${b ? "p-4" : "p-6"}`,
|
|
42
|
+
children: /* @__PURE__ */ g("div", {
|
|
42
43
|
className: "flex items-start gap-3",
|
|
43
|
-
children: [/* @__PURE__ */
|
|
44
|
+
children: [/* @__PURE__ */ h(c, { className: "h-5 w-5 text-status-warning-text shrink-0 mt-0.5" }), /* @__PURE__ */ g("div", {
|
|
44
45
|
className: "flex-1 min-w-0 space-y-2",
|
|
45
46
|
children: [
|
|
46
|
-
/* @__PURE__ */
|
|
47
|
+
/* @__PURE__ */ h("p", {
|
|
47
48
|
className: "text-sm font-semibold text-text-primary",
|
|
48
49
|
children: "Agent is not ready yet"
|
|
49
50
|
}),
|
|
50
|
-
/* @__PURE__ */
|
|
51
|
+
/* @__PURE__ */ h("p", {
|
|
51
52
|
className: "text-sm text-text-secondary",
|
|
52
53
|
children: "Your agent is still starting up or waiting for gateway credentials. Plugin install is only available once the agent reaches the ready state."
|
|
53
54
|
}),
|
|
54
|
-
/* @__PURE__ */
|
|
55
|
+
/* @__PURE__ */ h("p", {
|
|
55
56
|
className: "text-xs font-mono text-text-placeholder break-all",
|
|
56
|
-
children:
|
|
57
|
+
children: A
|
|
57
58
|
}),
|
|
58
|
-
/* @__PURE__ */
|
|
59
|
+
/* @__PURE__ */ g("div", {
|
|
59
60
|
className: "flex flex-wrap items-center gap-2 pt-1",
|
|
60
|
-
children: [/* @__PURE__ */
|
|
61
|
-
onClick: () => void
|
|
62
|
-
disabled:
|
|
61
|
+
children: [/* @__PURE__ */ g("button", {
|
|
62
|
+
onClick: () => void P(),
|
|
63
|
+
disabled: T,
|
|
63
64
|
className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50",
|
|
64
65
|
"data-testid": "plugin-gate-retry",
|
|
65
|
-
children: [/* @__PURE__ */
|
|
66
|
-
}), /* @__PURE__ */
|
|
67
|
-
onClick: () =>
|
|
66
|
+
children: [/* @__PURE__ */ h(f, { className: `h-3.5 w-3.5 ${T ? "animate-spin" : ""}` }), "Retry"]
|
|
67
|
+
}), /* @__PURE__ */ h("button", {
|
|
68
|
+
onClick: () => F("/agents"),
|
|
68
69
|
className: "rounded-md border border-border-default px-3 py-1.5 text-xs font-medium text-text-secondary hover:text-text-primary hover:border-border-strong transition-colors",
|
|
69
70
|
children: "Manage agents"
|
|
70
71
|
})]
|
|
@@ -72,102 +73,102 @@ function _(_) {
|
|
|
72
73
|
]
|
|
73
74
|
})]
|
|
74
75
|
})
|
|
75
|
-
}) : /* @__PURE__ */
|
|
76
|
+
}) : /* @__PURE__ */ g("div", {
|
|
76
77
|
className: "space-y-3",
|
|
77
|
-
children: [
|
|
78
|
-
if (
|
|
79
|
-
let n = (e.mode, e.packages.filter((e) => !
|
|
80
|
-
return /* @__PURE__ */
|
|
81
|
-
className: `rounded-lg border border-border-default bg-bg-surface ${
|
|
82
|
-
children: /* @__PURE__ */
|
|
78
|
+
children: [w.map((e, t) => {
|
|
79
|
+
if (I[t]) return null;
|
|
80
|
+
let n = (e.mode, e.packages.filter((e) => !D[e])), r = e.mode === "any" ? "Install a plugin to continue" : e.packages.length === 1 ? "Plugin required" : "Plugins required", a = e.mode === "any" ? "Pick any one of the plugins below. Once installed, this surface will render automatically." : e.packages.length === 1 ? "This surface needs the following agent plugin installed." : "This surface needs the following agent plugins installed.";
|
|
81
|
+
return /* @__PURE__ */ h("div", {
|
|
82
|
+
className: `rounded-lg border border-border-default bg-bg-surface ${b ? "p-4" : "p-6"}`,
|
|
83
|
+
children: /* @__PURE__ */ g("div", {
|
|
83
84
|
className: "flex items-start gap-3",
|
|
84
|
-
children: [/* @__PURE__ */
|
|
85
|
+
children: [/* @__PURE__ */ h(d, { className: "h-5 w-5 text-action-primary-bg shrink-0 mt-0.5" }), /* @__PURE__ */ g("div", {
|
|
85
86
|
className: "flex-1 min-w-0",
|
|
86
87
|
children: [
|
|
87
|
-
e.eyebrow && /* @__PURE__ */
|
|
88
|
+
e.eyebrow && /* @__PURE__ */ h("p", {
|
|
88
89
|
className: "text-xs uppercase tracking-wide text-text-secondary mb-1",
|
|
89
90
|
children: e.eyebrow
|
|
90
91
|
}),
|
|
91
|
-
/* @__PURE__ */
|
|
92
|
+
/* @__PURE__ */ h("p", {
|
|
92
93
|
className: "text-sm font-semibold text-text-primary",
|
|
93
|
-
children: e.title ??
|
|
94
|
+
children: e.title ?? r
|
|
94
95
|
}),
|
|
95
|
-
/* @__PURE__ */
|
|
96
|
+
/* @__PURE__ */ h("p", {
|
|
96
97
|
className: "mt-1 text-sm text-text-secondary",
|
|
97
98
|
children: e.description ?? a
|
|
98
99
|
}),
|
|
99
|
-
|
|
100
|
+
A && /* @__PURE__ */ g("div", {
|
|
100
101
|
role: "alert",
|
|
101
102
|
className: "mt-3 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text",
|
|
102
|
-
children: [/* @__PURE__ */
|
|
103
|
+
children: [/* @__PURE__ */ h(s, { className: "h-3.5 w-3.5 mt-0.5 shrink-0" }), /* @__PURE__ */ h("span", { children: A })]
|
|
103
104
|
}),
|
|
104
|
-
/* @__PURE__ */
|
|
105
|
+
/* @__PURE__ */ h("ul", {
|
|
105
106
|
className: "mt-4 space-y-2",
|
|
106
107
|
children: n.map((e) => {
|
|
107
|
-
let t =
|
|
108
|
-
return /* @__PURE__ */
|
|
108
|
+
let t = i(e), n = O === e, r = O !== null;
|
|
109
|
+
return /* @__PURE__ */ g("li", {
|
|
109
110
|
className: "flex items-center justify-between gap-3 rounded-md border border-border-default bg-bg-sunken px-3 py-2",
|
|
110
|
-
children: [/* @__PURE__ */
|
|
111
|
+
children: [/* @__PURE__ */ g("div", {
|
|
111
112
|
className: "min-w-0",
|
|
112
113
|
children: [
|
|
113
|
-
/* @__PURE__ */
|
|
114
|
+
/* @__PURE__ */ h("p", {
|
|
114
115
|
className: "text-sm font-medium text-text-primary truncate",
|
|
115
116
|
children: t.displayName
|
|
116
117
|
}),
|
|
117
|
-
/* @__PURE__ */
|
|
118
|
+
/* @__PURE__ */ h("p", {
|
|
118
119
|
className: "text-xs text-text-secondary truncate",
|
|
119
120
|
children: t.description
|
|
120
121
|
}),
|
|
121
|
-
/* @__PURE__ */
|
|
122
|
+
/* @__PURE__ */ h("p", {
|
|
122
123
|
className: "text-[10px] font-mono text-text-placeholder truncate",
|
|
123
124
|
title: e,
|
|
124
125
|
children: e
|
|
125
126
|
})
|
|
126
127
|
]
|
|
127
|
-
}), /* @__PURE__ */
|
|
128
|
-
onClick: () => void
|
|
129
|
-
disabled:
|
|
128
|
+
}), /* @__PURE__ */ h("button", {
|
|
129
|
+
onClick: () => void R(e),
|
|
130
|
+
disabled: r || T,
|
|
130
131
|
className: "inline-flex items-center gap-1.5 whitespace-nowrap rounded-md bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
131
132
|
"data-testid": `plugin-install-${e}`,
|
|
132
|
-
children: n ? /* @__PURE__ */
|
|
133
|
+
children: n ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(u, { className: "h-3.5 w-3.5 animate-spin" }), "Installing…"] }) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(l, { className: "h-3.5 w-3.5" }), "Install"] })
|
|
133
134
|
})]
|
|
134
135
|
}, e);
|
|
135
136
|
})
|
|
136
137
|
}),
|
|
137
|
-
|
|
138
|
+
T && n.length === 0 && /* @__PURE__ */ g("div", {
|
|
138
139
|
className: "mt-4 flex items-center gap-2 text-xs text-text-secondary",
|
|
139
|
-
children: [/* @__PURE__ */
|
|
140
|
+
children: [/* @__PURE__ */ h(u, { className: "h-3.5 w-3.5 animate-spin" }), "Checking plugins…"]
|
|
140
141
|
})
|
|
141
142
|
]
|
|
142
143
|
})]
|
|
143
144
|
})
|
|
144
145
|
}, t);
|
|
145
|
-
}), /* @__PURE__ */
|
|
146
|
-
isOpen:
|
|
147
|
-
onClose:
|
|
146
|
+
}), /* @__PURE__ */ h(t, {
|
|
147
|
+
isOpen: M,
|
|
148
|
+
onClose: N,
|
|
148
149
|
onConfirm: () => {
|
|
149
|
-
|
|
150
|
+
N(), F("/billing");
|
|
150
151
|
},
|
|
151
152
|
title: "Plugin quota reached",
|
|
152
153
|
message: "You've used all plugin install quota on your current plan. Upgrade to install more plugins."
|
|
153
154
|
})]
|
|
154
|
-
}) : /* @__PURE__ */
|
|
155
|
-
className: `rounded-lg border border-border-default bg-bg-surface ${
|
|
156
|
-
children: /* @__PURE__ */
|
|
155
|
+
}) : /* @__PURE__ */ h("div", {
|
|
156
|
+
className: `rounded-lg border border-border-default bg-bg-surface ${b ? "p-4" : "p-6"}`,
|
|
157
|
+
children: /* @__PURE__ */ g("div", {
|
|
157
158
|
className: "flex items-start gap-3",
|
|
158
|
-
children: [/* @__PURE__ */
|
|
159
|
+
children: [/* @__PURE__ */ h(p, { className: "h-5 w-5 text-text-secondary shrink-0 mt-0.5" }), /* @__PURE__ */ g("div", {
|
|
159
160
|
className: "flex-1 space-y-2",
|
|
160
161
|
children: [
|
|
161
|
-
/* @__PURE__ */
|
|
162
|
+
/* @__PURE__ */ h("p", {
|
|
162
163
|
className: "text-sm font-semibold text-text-primary",
|
|
163
164
|
children: "No active agent"
|
|
164
165
|
}),
|
|
165
|
-
/* @__PURE__ */
|
|
166
|
+
/* @__PURE__ */ h("p", {
|
|
166
167
|
className: "text-sm text-text-secondary",
|
|
167
168
|
children: "This surface needs an active vibecontrols agent with a tunnel URL and API key."
|
|
168
169
|
}),
|
|
169
|
-
/* @__PURE__ */
|
|
170
|
-
onClick: () =>
|
|
170
|
+
/* @__PURE__ */ h("button", {
|
|
171
|
+
onClick: () => F("/agents"),
|
|
171
172
|
className: "px-3 py-1.5 text-xs font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors",
|
|
172
173
|
children: "Go to Agents"
|
|
173
174
|
})
|
|
@@ -177,6 +178,6 @@ function _(_) {
|
|
|
177
178
|
});
|
|
178
179
|
}
|
|
179
180
|
//#endregion
|
|
180
|
-
export {
|
|
181
|
+
export { v as PluginGate };
|
|
181
182
|
|
|
182
183
|
//# sourceMappingURL=PluginGate.js.map
|