@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.
@@ -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 { 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";
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 _({ 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;
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 I = i(async (e) => {
19
- if (await w(e)) {
20
- P((t) => ({
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 = F.current.get(e);
25
+ let t = L.current.get(e);
25
26
  t && clearTimeout(t);
26
27
  let n = setTimeout(() => {
27
- P((t) => {
28
+ I((t) => {
28
29
  if (!t[e]) return t;
29
30
  let n = { ...t };
30
31
  return delete n[e], n;
31
- }), F.current.delete(e);
32
+ }), L.current.delete(e);
32
33
  }, 1500);
33
- F.current.set(e, n), x?.();
34
+ L.current.set(e, n), S?.();
34
35
  }
35
- }, [w, x]), L = i(async (e) => {
36
- if (v) {
37
- A(e), M((t) => ({
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 O({ variables: {
43
- agentId: v,
43
+ let { data: t } = await A({ variables: {
44
+ agentId: y,
44
45
  packageName: e
45
46
  } });
46
47
  if (!t?.removeAgentPlugin.success) {
47
- M((n) => ({
48
+ P((n) => ({
48
49
  ...n,
49
50
  [e]: t?.removeAgentPlugin.error || "Remove failed"
50
51
  }));
51
52
  return;
52
53
  }
53
- await T(), x?.();
54
+ await D(), S?.();
54
55
  } catch (t) {
55
- M((n) => ({
56
+ P((n) => ({
56
57
  ...n,
57
58
  [e]: t instanceof Error ? t.message : String(t)
58
59
  }));
59
60
  } finally {
60
- A(null);
61
+ M(null);
61
62
  }
62
63
  }
63
64
  }, [
64
- v,
65
- O,
66
- T,
67
- x
65
+ y,
66
+ A,
67
+ D,
68
+ S
68
69
  ]);
69
- return _.length === 0 ? null : /* @__PURE__ */ g("section", {
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
- (y || b) && /* @__PURE__ */ g("div", { children: [y && /* @__PURE__ */ h("h3", {
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
- D && /* @__PURE__ */ h("div", {
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: D
84
+ children: k
84
85
  }),
85
- /* @__PURE__ */ h("div", {
86
+ /* @__PURE__ */ g("div", {
86
87
  className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3",
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, {
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__ */ g("div", {
94
+ /* @__PURE__ */ _("div", {
94
95
  className: "flex items-start gap-3",
95
- children: [/* @__PURE__ */ h("div", {
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__ */ h(f, { className: "w-4 h-4 text-text-secondary" })
98
- }), /* @__PURE__ */ g("div", {
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__ */ g("div", {
102
+ /* @__PURE__ */ _("div", {
102
103
  className: "flex items-start gap-2 flex-wrap",
103
- children: [/* @__PURE__ */ h("h4", {
104
+ children: [/* @__PURE__ */ g("h4", {
104
105
  className: "font-medium text-text-primary break-words max-w-[100ch]",
105
106
  children: n.displayName
106
- }), i && /* @__PURE__ */ g("span", {
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__ */ h(c, { className: "w-3 h-3" }), " installed"]
109
+ children: [/* @__PURE__ */ g(l, { className: "w-3 h-3" }), " installed"]
109
110
  })]
110
111
  }),
111
- /* @__PURE__ */ h("p", {
112
+ /* @__PURE__ */ g("p", {
112
113
  className: "text-sm text-text-secondary mt-0.5",
113
114
  children: n.description
114
115
  }),
115
- /* @__PURE__ */ h("p", {
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__ */ h("div", {
124
+ /* @__PURE__ */ g("div", {
124
125
  className: "flex items-center gap-2",
125
- children: i && _ ? /* @__PURE__ */ g("button", {
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__ */ h(l, { className: "w-3.5 h-3.5" }), "Installed"]
132
- }) : i ? /* @__PURE__ */ g("button", {
132
+ children: [/* @__PURE__ */ g(u, { className: "w-3.5 h-3.5" }), "Installed"]
133
+ }) : r ? /* @__PURE__ */ _("button", {
133
134
  type: "button",
134
- disabled: s || !v,
135
- onClick: () => L(e),
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__ */ 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", {
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 || !v,
142
- onClick: () => I(e),
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__ */ 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"] })
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
- y && /* @__PURE__ */ h("p", {
150
+ v && /* @__PURE__ */ g("p", {
150
151
  className: "text-xs text-status-error-text",
151
- children: y
152
+ children: v
152
153
  })
153
154
  ]
154
155
  }, e);
155
156
  })
156
157
  }),
157
- !v && /* @__PURE__ */ h("p", {
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 { _ as PluginHarnessPicker };
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 { 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";
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 g(e) {
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: 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(() => {
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 S) for (let n of t.packages) e.add(n);
28
+ for (let t of w) for (let n of t.packages) e.add(n);
28
29
  return [...e];
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"}`,
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__ */ h("div", {
36
+ children: /* @__PURE__ */ g("div", {
36
37
  className: "flex items-center gap-2 text-sm text-text-secondary",
37
- children: [/* @__PURE__ */ m(l, { className: "h-4 w-4 animate-spin" }), "Checking plugin availability on the agent…"]
38
+ children: [/* @__PURE__ */ h(u, { className: "h-4 w-4 animate-spin" }), "Checking plugin availability on the agent…"]
38
39
  })
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", {
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__ */ m(s, { className: "h-5 w-5 text-status-warning-text shrink-0 mt-0.5" }), /* @__PURE__ */ h("div", {
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__ */ m("p", {
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__ */ m("p", {
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__ */ m("p", {
55
+ /* @__PURE__ */ h("p", {
55
56
  className: "text-xs font-mono text-text-placeholder break-all",
56
- children: O
57
+ children: A
57
58
  }),
58
- /* @__PURE__ */ h("div", {
59
+ /* @__PURE__ */ g("div", {
59
60
  className: "flex flex-wrap items-center gap-2 pt-1",
60
- children: [/* @__PURE__ */ h("button", {
61
- onClick: () => void M(),
62
- disabled: C,
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__ */ m(d, { className: `h-3.5 w-3.5 ${C ? "animate-spin" : ""}` }), "Retry"]
66
- }), /* @__PURE__ */ m("button", {
67
- onClick: () => N("/agents"),
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__ */ h("div", {
76
+ }) : /* @__PURE__ */ g("div", {
76
77
  className: "space-y-3",
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", {
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__ */ m(u, { className: "h-5 w-5 text-action-primary-bg shrink-0 mt-0.5" }), /* @__PURE__ */ h("div", {
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__ */ m("p", {
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__ */ m("p", {
92
+ /* @__PURE__ */ h("p", {
92
93
  className: "text-sm font-semibold text-text-primary",
93
- children: e.title ?? i
94
+ children: e.title ?? r
94
95
  }),
95
- /* @__PURE__ */ m("p", {
96
+ /* @__PURE__ */ h("p", {
96
97
  className: "mt-1 text-sm text-text-secondary",
97
98
  children: e.description ?? a
98
99
  }),
99
- O && /* @__PURE__ */ h("div", {
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__ */ m(o, { className: "h-3.5 w-3.5 mt-0.5 shrink-0" }), /* @__PURE__ */ m("span", { children: O })]
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__ */ m("ul", {
105
+ /* @__PURE__ */ h("ul", {
105
106
  className: "mt-4 space-y-2",
106
107
  children: n.map((e) => {
107
- let t = r(e), n = E === e, i = E !== null;
108
- return /* @__PURE__ */ h("li", {
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__ */ h("div", {
111
+ children: [/* @__PURE__ */ g("div", {
111
112
  className: "min-w-0",
112
113
  children: [
113
- /* @__PURE__ */ m("p", {
114
+ /* @__PURE__ */ h("p", {
114
115
  className: "text-sm font-medium text-text-primary truncate",
115
116
  children: t.displayName
116
117
  }),
117
- /* @__PURE__ */ m("p", {
118
+ /* @__PURE__ */ h("p", {
118
119
  className: "text-xs text-text-secondary truncate",
119
120
  children: t.description
120
121
  }),
121
- /* @__PURE__ */ m("p", {
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__ */ m("button", {
128
- onClick: () => void I(e),
129
- disabled: i || C,
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__ */ 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"] })
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
- C && n.length === 0 && /* @__PURE__ */ h("div", {
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__ */ m(l, { className: "h-3.5 w-3.5 animate-spin" }), "Checking plugins…"]
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__ */ m(t, {
146
- isOpen: A,
147
- onClose: j,
146
+ }), /* @__PURE__ */ h(t, {
147
+ isOpen: M,
148
+ onClose: N,
148
149
  onConfirm: () => {
149
- j(), N("/billing");
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__ */ m("div", {
155
- className: `rounded-lg border border-border-default bg-bg-surface ${y ? "p-4" : "p-6"}`,
156
- children: /* @__PURE__ */ h("div", {
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__ */ m(f, { className: "h-5 w-5 text-text-secondary shrink-0 mt-0.5" }), /* @__PURE__ */ h("div", {
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__ */ m("p", {
162
+ /* @__PURE__ */ h("p", {
162
163
  className: "text-sm font-semibold text-text-primary",
163
164
  children: "No active agent"
164
165
  }),
165
- /* @__PURE__ */ m("p", {
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__ */ m("button", {
170
- onClick: () => N("/agents"),
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 { _ as PluginGate };
181
+ export { v as PluginGate };
181
182
 
182
183
  //# sourceMappingURL=PluginGate.js.map