@burdenoff/microfe-vibecontrols 2026.526.1 → 2026.526.2

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