@burdenoff/microfe-vibecontrols 2026.523.1 → 2026.523.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.
@@ -108,7 +108,7 @@ function f(f, p) {
108
108
  if (t?.installAgentPlugin?.success) return S((t) => ({
109
109
  ...t,
110
110
  [e]: !0
111
- })), await A(), !0;
111
+ })), setTimeout(() => void A(), 3e3), !0;
112
112
  throw Error(t?.installAgentPlugin?.error || "Install failed");
113
113
  } catch (e) {
114
114
  if (r(e)) return O(!0), !1;
@@ -1 +1 @@
1
- {"version":3,"file":"usePluginAvailability.js","names":[],"sources":["../../src/hooks/usePluginAvailability.ts"],"sourcesContent":["/**\n * usePluginAvailability — query + install helpers for vibecontrols-agent plugins.\n *\n * Wraps the existing backend-proxied GraphQL ops (AgentPlugins query +\n * InstallAgentPlugin mutation) and exposes a small state surface that\n * gating components can render against.\n *\n * Live updates: subscribes to `vibecontrolsAgentPluginStream` so the\n * installed map reflects out-of-band installs (CLI, other tabs,\n * agent-side changes) without per-tab polling. The initial AgentPlugins\n * query is still used for first paint so the gate can render before\n * the WebSocket completes its handshake.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport {\n AgentPluginsDocument,\n useInstallAgentPluginMutation,\n useVibecontrolsAgentPluginStreamSubscription,\n type AgentPluginsQuery,\n} from '@/generated/wspace-operations';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\n\n/**\n * Classifies transient \"the agent itself isn't ready\" errors so the UI can\n * render a dedicated waiting state instead of an install CTA. These errors\n * are not the user's to fix via an install click — they resolve on their\n * own (or require Agents-page intervention).\n */\nfunction classifyAgentStateError(message: string | null | undefined): boolean {\n if (!message) return false;\n const m = message.toLowerCase();\n return (\n m.includes('agent not yet configured') ||\n m.includes('awaiting-config') ||\n m.includes('initializing') ||\n m.includes('agent unreachable') ||\n m.includes('agent returned 503') ||\n m.includes('http 503') ||\n m.includes('plugin routes are unavailable') ||\n m.includes('econnrefused') ||\n m.includes('rate_limited') ||\n m.includes('rate limited') ||\n m.includes('operation timeout') ||\n m.includes('operation_timeout') ||\n // Bun's fetch surfaces these human-friendly strings when the agent\n // socket isn't accepting connections. Without this branch, the raw\n // text would leak into the install card as the error chip.\n m.includes('typo in the url') ||\n m.includes('unable to connect') ||\n m.includes('failed to open socket') ||\n m.includes('failedtoopensocket') ||\n m.includes('fetch failed') ||\n m.includes('enotfound') ||\n m.includes('getaddrinfo')\n // Note: \"operation was aborted\" is deliberately NOT classified as an\n // agent-state error. Apollo aborts previous in-flight queries when\n // the same lazy query is re-fired (common in React Strict Mode\n // double-mount). Those aborts are transient and not about agent\n // state; the follow-up fetch succeeds normally.\n );\n}\n\n/**\n * Translate raw error strings (especially Bun fetch's human-friendly but\n * jargon-y errors) into a short, user-safe message. Used right before we\n * stash anything into the surfaced `error` field — guarantees we never\n * render \"Was there a typo in the url or port?\" or similar into the DOM.\n */\nfunction sanitizeAgentStateError(raw: string): string {\n const m = raw.toLowerCase();\n if (\n m.includes('typo in the url') ||\n m.includes('unable to connect') ||\n m.includes('failed to open socket') ||\n m.includes('failedtoopensocket') ||\n m.includes('econnrefused') ||\n m.includes('enotfound') ||\n m.includes('getaddrinfo') ||\n m.includes('fetch failed') ||\n m.includes('agent unreachable')\n ) {\n return 'Agent unreachable. Make sure the vibecontrols-agent is running locally.';\n }\n if (m.includes('agent not yet configured') || m.includes('awaiting-config')) {\n return 'Agent is waiting for configuration. Finish setup in the Agents page.';\n }\n if (m.includes('initializing')) {\n return 'Agent is starting up. Try again in a moment.';\n }\n if (m.includes('http 503') || m.includes('agent returned 503')) {\n return 'Agent service unavailable. It may be restarting.';\n }\n return raw.length > 200 ? `${raw.slice(0, 200)}…` : raw;\n}\n\nexport interface UsePluginAvailabilityResult {\n loading: boolean;\n /**\n * True while the very first fetch is in flight (or before it has begun).\n * Consumers should render a loading spinner instead of the install CTA in\n * this state so users don't see a flash of \"plugin required / Install\"\n * followed by a flash of the real content.\n */\n initialLoading: boolean;\n installed: Record<string, boolean>;\n missing: string[];\n anyInstalled: boolean;\n allInstalled: boolean;\n installing: string | null;\n install: (packageName: string) => Promise<boolean>;\n error: string | null;\n /** True when `error` points to the agent itself not being ready. */\n errorIsAgentState: boolean;\n quotaExhausted: boolean;\n clearQuotaExhausted: () => void;\n refetch: () => Promise<void>;\n}\n\nexport function usePluginAvailability(\n packages: readonly string[],\n agentId: string | null\n): UsePluginAvailabilityResult {\n const apollo = useApolloClient();\n const [installPluginMutation] = useInstallAgentPluginMutation();\n\n // Read cached data synchronously so the first render already has the\n // installed map populated when another consumer has primed the cache.\n const cachedInstalledSet = useMemo(() => {\n if (!agentId) return null;\n try {\n const cached = apollo.readQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n });\n const installedList = cached?.agentPlugins?.installed ?? null;\n if (!installedList) return null;\n return new Set(installedList.map((p: { packageName: string }) => p.packageName));\n } catch {\n return null;\n }\n }, [apollo, agentId]);\n\n const [loading, setLoading] = useState(false);\n const [hasFetchedOnce, setHasFetchedOnce] = useState(() => cachedInstalledSet !== null);\n const [installedMap, setInstalledMap] = useState<Record<string, boolean>>(() => {\n const next: Record<string, boolean> = {};\n if (cachedInstalledSet) {\n for (const pkg of packages) next[pkg] = cachedInstalledSet.has(pkg);\n }\n return next;\n });\n const [installing, setInstalling] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [quotaExhausted, setQuotaExhausted] = useState(false);\n const errorTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Stable key so we re-run only when the *set* of requested packages changes.\n const packagesKey = useMemo(() => packages.slice().sort().join('|'), [packages]);\n\n const refetch = useCallback(async () => {\n if (!agentId) {\n setInstalledMap({});\n return;\n }\n setLoading(true);\n try {\n // Use the Apollo client directly with `network-only` so out-of-band\n // installs (CLI, other tabs, agent-side changes) are always picked\n // up. `useAgentPluginsLazyQuery`'s lazy execute() doesn't accept\n // per-call fetchPolicy in Apollo v4; `apollo.query()` does.\n const { data } = await apollo.query<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n fetchPolicy: 'network-only',\n });\n const next: Record<string, boolean> = {};\n const installedList = data?.agentPlugins?.installed ?? [];\n const pluginError = data?.agentPlugins?.error ?? null;\n const installedSet = new Set(\n installedList.map((p: { packageName: string }) => p.packageName)\n );\n for (const pkg of packages) next[pkg] = installedSet.has(pkg);\n if (pluginError) {\n if (!classifyAgentStateError(pluginError)) {\n setInstalledMap(next);\n }\n // Log the raw error for debugging, but only surface a sanitized\n // version to the UI. Raw Bun fetch errors (\"Was there a typo in\n // the url or port?\") would otherwise leak into the install card.\n // eslint-disable-next-line no-console -- intentional: keep technical detail for debugging\n console.error('[ai] agentPlugins returned error', pluginError);\n setError(sanitizeAgentStateError(pluginError));\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n if (!classifyAgentStateError(pluginError)) {\n errorTimerRef.current = setTimeout(() => setError(null), 5000);\n }\n return;\n }\n setInstalledMap(next);\n // Clear any lingering error from a previously-aborted fetch so the\n // gate doesn't keep showing \"Agent not ready\" after a successful\n // subsequent request (e.g. React Strict Mode double-mount).\n setError(null);\n if (errorTimerRef.current) {\n clearTimeout(errorTimerRef.current);\n errorTimerRef.current = null;\n }\n } catch (err) {\n const msg =\n err instanceof Error ? err.message : 'Agent unreachable — unable to read plugin list';\n const isAgentStateError = classifyAgentStateError(msg);\n // Keep the last known installed state while the agent is restarting or\n // finalizing. Otherwise a transient 503 flashes an install CTA for an\n // already-installed plugin.\n if (!isAgentStateError) {\n setInstalledMap({});\n }\n // eslint-disable-next-line no-console -- intentional: keep technical detail for debugging\n console.error('[ai] agentPlugins fetch failed', err);\n setError(sanitizeAgentStateError(msg));\n // State-related errors stay visible until resolved; transient\n // network/GraphQL errors auto-dismiss so the UI stays tidy.\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n if (!isAgentStateError) {\n errorTimerRef.current = setTimeout(() => setError(null), 5000);\n }\n } finally {\n setLoading(false);\n setHasFetchedOnce(true);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [agentId, apollo, packagesKey]);\n\n useEffect(() => {\n void refetch();\n }, [refetch]);\n\n // Subscribe to the push stream. The svc emits the installed list\n // whenever it diverges from its previous snapshot, so we only update\n // local state on payloads — and we also write through to the Apollo\n // cache so other consumers reading `agentPlugins` directly see fresh\n // data without their own refetch.\n useVibecontrolsAgentPluginStreamSubscription({\n variables: { agentId: agentId ?? '' },\n skip: !agentId,\n onData: ({ data }) => {\n const installedList = data.data?.vibecontrolsAgentPluginStream;\n if (!installedList || !agentId) return;\n const installedSet = new Set(\n installedList.map((p: { packageName: string }) => p.packageName)\n );\n setInstalledMap((prev) => {\n const next: Record<string, boolean> = { ...prev };\n let changed = false;\n for (const pkg of packages) {\n const has = installedSet.has(pkg);\n if (next[pkg] !== has) {\n next[pkg] = has;\n changed = true;\n }\n }\n return changed ? next : prev;\n });\n setHasFetchedOnce(true);\n // Mirror into Apollo cache so consumers that read AgentPluginsQuery\n // directly (e.g. PluginHarnessPicker) see the same fresh list.\n try {\n const existing = apollo.readQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n });\n apollo.writeQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n data: {\n agentPlugins: {\n __typename: 'AgentPluginListResult',\n installed: installedList,\n available: existing?.agentPlugins?.available ?? [],\n error: null,\n },\n },\n });\n } catch {\n // Cache may not have been primed yet — the next refetch will\n // populate `available` and we'll write again on the next event.\n }\n },\n });\n\n // Also refetch when the tab regains focus so installs made elsewhere flow in.\n useEffect(() => {\n const onFocus = () => void refetch();\n window.addEventListener('focus', onFocus);\n return () => window.removeEventListener('focus', onFocus);\n }, [refetch]);\n\n useEffect(() => {\n return () => {\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n };\n }, []);\n\n const install = useCallback(\n async (packageName: string): Promise<boolean> => {\n if (!agentId) return false;\n setInstalling(packageName);\n setError(null);\n try {\n const { data } = await installPluginMutation({\n variables: { agentId, packageName },\n });\n if (data?.installAgentPlugin?.success) {\n setInstalledMap((prev) => ({ ...prev, [packageName]: true }));\n await refetch();\n return true;\n }\n throw new Error(data?.installAgentPlugin?.error || 'Install failed');\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setQuotaExhausted(true);\n return false;\n }\n const msg = err instanceof Error ? err.message : 'Failed to install plugin';\n // eslint-disable-next-line no-console -- intentional: keep technical detail for debugging\n console.error('[ai] plugin install failed', err);\n setError(sanitizeAgentStateError(msg));\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n if (!classifyAgentStateError(msg)) {\n errorTimerRef.current = setTimeout(() => setError(null), 5000);\n }\n return false;\n } finally {\n setInstalling(null);\n }\n },\n [agentId, installPluginMutation, refetch]\n );\n\n const clearQuotaExhausted = useCallback(() => setQuotaExhausted(false), []);\n\n const missing = useMemo(\n () => packages.filter((pkg) => !installedMap[pkg]),\n [packages, installedMap]\n );\n const anyInstalled = useMemo(\n () => packages.some((pkg) => installedMap[pkg]),\n [packages, installedMap]\n );\n const allInstalled = missing.length === 0 && packages.length > 0;\n\n return {\n loading,\n initialLoading: !hasFetchedOnce,\n installed: installedMap,\n missing,\n anyInstalled,\n allInstalled,\n installing,\n install,\n error,\n errorIsAgentState: classifyAgentStateError(error),\n quotaExhausted,\n clearQuotaExhausted,\n refetch,\n };\n}\n"],"mappings":";;;;;AA8BA,SAAS,EAAwB,GAA6C;AAC5E,KAAI,CAAC,EAAS,QAAO;CACrB,IAAM,IAAI,EAAQ,aAAa;AAC/B,QACE,EAAE,SAAS,2BAA2B,IACtC,EAAE,SAAS,kBAAkB,IAC7B,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,oBAAoB,IAC/B,EAAE,SAAS,qBAAqB,IAChC,EAAE,SAAS,WAAW,IACtB,EAAE,SAAS,gCAAgC,IAC3C,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,oBAAoB,IAC/B,EAAE,SAAS,oBAAoB,IAI/B,EAAE,SAAS,kBAAkB,IAC7B,EAAE,SAAS,oBAAoB,IAC/B,EAAE,SAAS,wBAAwB,IACnC,EAAE,SAAS,qBAAqB,IAChC,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,YAAY,IACvB,EAAE,SAAS,cAAc;;AAe7B,SAAS,EAAwB,GAAqB;CACpD,IAAM,IAAI,EAAI,aAAa;AAuB3B,QArBE,EAAE,SAAS,kBAAkB,IAC7B,EAAE,SAAS,oBAAoB,IAC/B,EAAE,SAAS,wBAAwB,IACnC,EAAE,SAAS,qBAAqB,IAChC,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,YAAY,IACvB,EAAE,SAAS,cAAc,IACzB,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,oBAAoB,GAExB,4EAEL,EAAE,SAAS,2BAA2B,IAAI,EAAE,SAAS,kBAAkB,GAClE,yEAEL,EAAE,SAAS,eAAe,GACrB,iDAEL,EAAE,SAAS,WAAW,IAAI,EAAE,SAAS,qBAAqB,GACrD,qDAEF,EAAI,SAAS,MAAM,GAAG,EAAI,MAAM,GAAG,IAAI,CAAC,KAAK;;AA0BtD,SAAgB,EACd,GACA,GAC6B;CAC7B,IAAM,IAAS,GAAiB,EAC1B,CAAC,KAAyB,GAA+B,EAIzD,IAAqB,QAAc;AACvC,MAAI,CAAC,EAAS,QAAO;AACrB,MAAI;GAKF,IAAM,IAJS,EAAO,UAA6B;IACjD,OAAO;IACP,WAAW,EAAE,YAAS;IACvB,CAAC,EAC4B,cAAc,aAAa;AAEzD,UADK,IACE,IAAI,IAAI,EAAc,KAAK,MAA+B,EAAE,YAAY,CAAC,GADrD;UAErB;AACN,UAAO;;IAER,CAAC,GAAQ,EAAQ,CAAC,EAEf,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAgB,KAAqB,QAAe,MAAuB,KAAK,EACjF,CAAC,GAAc,KAAmB,QAAwC;EAC9E,IAAM,IAAgC,EAAE;AACxC,MAAI,EACF,MAAK,IAAM,KAAO,EAAU,GAAK,KAAO,EAAmB,IAAI,EAAI;AAErE,SAAO;GACP,EACI,CAAC,GAAY,KAAiB,EAAwB,KAAK,EAC3D,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAgB,KAAqB,EAAS,GAAM,EACrD,IAAgB,EAA6C,KAAK,EAKlE,IAAU,EAAY,YAAY;AACtC,MAAI,CAAC,GAAS;AACZ,KAAgB,EAAE,CAAC;AACnB;;AAEF,IAAW,GAAK;AAChB,MAAI;GAKF,IAAM,EAAE,YAAS,MAAM,EAAO,MAAyB;IACrD,OAAO;IACP,WAAW,EAAE,YAAS;IACtB,aAAa;IACd,CAAC,EACI,IAAgC,EAAE,EAClC,IAAgB,GAAM,cAAc,aAAa,EAAE,EACnD,IAAc,GAAM,cAAc,SAAS,MAC3C,IAAe,IAAI,IACvB,EAAc,KAAK,MAA+B,EAAE,YAAY,CACjE;AACD,QAAK,IAAM,KAAO,EAAU,GAAK,KAAO,EAAa,IAAI,EAAI;AAC7D,OAAI,GAAa;AAWf,IAVK,EAAwB,EAAY,IACvC,EAAgB,EAAK,EAMvB,QAAQ,MAAM,oCAAoC,EAAY,EAC9D,EAAS,EAAwB,EAAY,CAAC,EAC1C,EAAc,WAAS,aAAa,EAAc,QAAQ,EACzD,EAAwB,EAAY,KACvC,EAAc,UAAU,iBAAiB,EAAS,KAAK,EAAE,IAAK;AAEhE;;AAOF,GALA,EAAgB,EAAK,EAIrB,EAAS,KAAK,EACd,AAEE,EAAc,aADd,aAAa,EAAc,QAAQ,EACX;WAEnB,GAAK;GACZ,IAAM,IACJ,aAAe,QAAQ,EAAI,UAAU,kDACjC,IAAoB,EAAwB,EAAI;AAatD,GATK,KACH,EAAgB,EAAE,CAAC,EAGrB,QAAQ,MAAM,kCAAkC,EAAI,EACpD,EAAS,EAAwB,EAAI,CAAC,EAGlC,EAAc,WAAS,aAAa,EAAc,QAAQ,EACzD,MACH,EAAc,UAAU,iBAAiB,EAAS,KAAK,EAAE,IAAK;YAExD;AAER,GADA,EAAW,GAAM,EACjB,EAAkB,GAAK;;IAGxB;EAAC;EAAS;EA1EO,QAAc,EAAS,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,EAAS,CAAC;EA0E/C,CAAC;AAkElC,CAhEA,QAAgB;AACT,KAAS;IACb,CAAC,EAAQ,CAAC,EAOb,EAA6C;EAC3C,WAAW,EAAE,SAAS,KAAW,IAAI;EACrC,MAAM,CAAC;EACP,SAAS,EAAE,cAAW;GACpB,IAAM,IAAgB,EAAK,MAAM;AACjC,OAAI,CAAC,KAAiB,CAAC,EAAS;GAChC,IAAM,IAAe,IAAI,IACvB,EAAc,KAAK,MAA+B,EAAE,YAAY,CACjE;AAaD,GAZA,GAAiB,MAAS;IACxB,IAAM,IAAgC,EAAE,GAAG,GAAM,EAC7C,IAAU;AACd,SAAK,IAAM,KAAO,GAAU;KAC1B,IAAM,IAAM,EAAa,IAAI,EAAI;AACjC,KAAI,EAAK,OAAS,MAChB,EAAK,KAAO,GACZ,IAAU;;AAGd,WAAO,IAAU,IAAO;KACxB,EACF,EAAkB,GAAK;AAGvB,OAAI;IACF,IAAM,IAAW,EAAO,UAA6B;KACnD,OAAO;KACP,WAAW,EAAE,YAAS;KACvB,CAAC;AACF,MAAO,WAA8B;KACnC,OAAO;KACP,WAAW,EAAE,YAAS;KACtB,MAAM,EACJ,cAAc;MACZ,YAAY;MACZ,WAAW;MACX,WAAW,GAAU,cAAc,aAAa,EAAE;MAClD,OAAO;MACR,EACF;KACF,CAAC;WACI;;EAKX,CAAC,EAGF,QAAgB;EACd,IAAM,UAAgB,KAAK,GAAS;AAEpC,SADA,OAAO,iBAAiB,SAAS,EAAQ,QAC5B,OAAO,oBAAoB,SAAS,EAAQ;IACxD,CAAC,EAAQ,CAAC,EAEb,cACe;AACX,EAAI,EAAc,WAAS,aAAa,EAAc,QAAQ;IAE/D,EAAE,CAAC;CAEN,IAAM,IAAU,EACd,OAAO,MAA0C;AAC/C,MAAI,CAAC,EAAS,QAAO;AAErB,EADA,EAAc,EAAY,EAC1B,EAAS,KAAK;AACd,MAAI;GACF,IAAM,EAAE,YAAS,MAAM,EAAsB,EAC3C,WAAW;IAAE;IAAS;IAAa,EACpC,CAAC;AACF,OAAI,GAAM,oBAAoB,QAG5B,QAFA,GAAiB,OAAU;IAAE,GAAG;KAAO,IAAc;IAAM,EAAE,EAC7D,MAAM,GAAS,EACR;AAET,SAAU,MAAM,GAAM,oBAAoB,SAAS,iBAAiB;WAC7D,GAAK;AACZ,OAAI,EAAsB,EAAI,CAE5B,QADA,EAAkB,GAAK,EAChB;GAET,IAAM,IAAM,aAAe,QAAQ,EAAI,UAAU;AAQjD,UANA,QAAQ,MAAM,8BAA8B,EAAI,EAChD,EAAS,EAAwB,EAAI,CAAC,EAClC,EAAc,WAAS,aAAa,EAAc,QAAQ,EACzD,EAAwB,EAAI,KAC/B,EAAc,UAAU,iBAAiB,EAAS,KAAK,EAAE,IAAK,GAEzD;YACC;AACR,KAAc,KAAK;;IAGvB;EAAC;EAAS;EAAuB;EAAQ,CAC1C,EAEK,IAAsB,QAAkB,EAAkB,GAAM,EAAE,EAAE,CAAC,EAErE,IAAU,QACR,EAAS,QAAQ,MAAQ,CAAC,EAAa,GAAK,EAClD,CAAC,GAAU,EAAa,CACzB,EACK,IAAe,QACb,EAAS,MAAM,MAAQ,EAAa,GAAK,EAC/C,CAAC,GAAU,EAAa,CACzB,EACK,IAAe,EAAQ,WAAW,KAAK,EAAS,SAAS;AAE/D,QAAO;EACL;EACA,gBAAgB,CAAC;EACjB,WAAW;EACX;EACA;EACA;EACA;EACA;EACA;EACA,mBAAmB,EAAwB,EAAM;EACjD;EACA;EACA;EACD"}
1
+ {"version":3,"file":"usePluginAvailability.js","names":[],"sources":["../../src/hooks/usePluginAvailability.ts"],"sourcesContent":["/**\n * usePluginAvailability — query + install helpers for vibecontrols-agent plugins.\n *\n * Wraps the existing backend-proxied GraphQL ops (AgentPlugins query +\n * InstallAgentPlugin mutation) and exposes a small state surface that\n * gating components can render against.\n *\n * Live updates: subscribes to `vibecontrolsAgentPluginStream` so the\n * installed map reflects out-of-band installs (CLI, other tabs,\n * agent-side changes) without per-tab polling. The initial AgentPlugins\n * query is still used for first paint so the gate can render before\n * the WebSocket completes its handshake.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport {\n AgentPluginsDocument,\n useInstallAgentPluginMutation,\n useVibecontrolsAgentPluginStreamSubscription,\n type AgentPluginsQuery,\n} from '@/generated/wspace-operations';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\n\n/**\n * Classifies transient \"the agent itself isn't ready\" errors so the UI can\n * render a dedicated waiting state instead of an install CTA. These errors\n * are not the user's to fix via an install click — they resolve on their\n * own (or require Agents-page intervention).\n */\nfunction classifyAgentStateError(message: string | null | undefined): boolean {\n if (!message) return false;\n const m = message.toLowerCase();\n return (\n m.includes('agent not yet configured') ||\n m.includes('awaiting-config') ||\n m.includes('initializing') ||\n m.includes('agent unreachable') ||\n m.includes('agent returned 503') ||\n m.includes('http 503') ||\n m.includes('plugin routes are unavailable') ||\n m.includes('econnrefused') ||\n m.includes('rate_limited') ||\n m.includes('rate limited') ||\n m.includes('operation timeout') ||\n m.includes('operation_timeout') ||\n // Bun's fetch surfaces these human-friendly strings when the agent\n // socket isn't accepting connections. Without this branch, the raw\n // text would leak into the install card as the error chip.\n m.includes('typo in the url') ||\n m.includes('unable to connect') ||\n m.includes('failed to open socket') ||\n m.includes('failedtoopensocket') ||\n m.includes('fetch failed') ||\n m.includes('enotfound') ||\n m.includes('getaddrinfo')\n // Note: \"operation was aborted\" is deliberately NOT classified as an\n // agent-state error. Apollo aborts previous in-flight queries when\n // the same lazy query is re-fired (common in React Strict Mode\n // double-mount). Those aborts are transient and not about agent\n // state; the follow-up fetch succeeds normally.\n );\n}\n\n/**\n * Translate raw error strings (especially Bun fetch's human-friendly but\n * jargon-y errors) into a short, user-safe message. Used right before we\n * stash anything into the surfaced `error` field — guarantees we never\n * render \"Was there a typo in the url or port?\" or similar into the DOM.\n */\nfunction sanitizeAgentStateError(raw: string): string {\n const m = raw.toLowerCase();\n if (\n m.includes('typo in the url') ||\n m.includes('unable to connect') ||\n m.includes('failed to open socket') ||\n m.includes('failedtoopensocket') ||\n m.includes('econnrefused') ||\n m.includes('enotfound') ||\n m.includes('getaddrinfo') ||\n m.includes('fetch failed') ||\n m.includes('agent unreachable')\n ) {\n return 'Agent unreachable. Make sure the vibecontrols-agent is running locally.';\n }\n if (m.includes('agent not yet configured') || m.includes('awaiting-config')) {\n return 'Agent is waiting for configuration. Finish setup in the Agents page.';\n }\n if (m.includes('initializing')) {\n return 'Agent is starting up. Try again in a moment.';\n }\n if (m.includes('http 503') || m.includes('agent returned 503')) {\n return 'Agent service unavailable. It may be restarting.';\n }\n return raw.length > 200 ? `${raw.slice(0, 200)}…` : raw;\n}\n\nexport interface UsePluginAvailabilityResult {\n loading: boolean;\n /**\n * True while the very first fetch is in flight (or before it has begun).\n * Consumers should render a loading spinner instead of the install CTA in\n * this state so users don't see a flash of \"plugin required / Install\"\n * followed by a flash of the real content.\n */\n initialLoading: boolean;\n installed: Record<string, boolean>;\n missing: string[];\n anyInstalled: boolean;\n allInstalled: boolean;\n installing: string | null;\n install: (packageName: string) => Promise<boolean>;\n error: string | null;\n /** True when `error` points to the agent itself not being ready. */\n errorIsAgentState: boolean;\n quotaExhausted: boolean;\n clearQuotaExhausted: () => void;\n refetch: () => Promise<void>;\n}\n\nexport function usePluginAvailability(\n packages: readonly string[],\n agentId: string | null\n): UsePluginAvailabilityResult {\n const apollo = useApolloClient();\n const [installPluginMutation] = useInstallAgentPluginMutation();\n\n // Read cached data synchronously so the first render already has the\n // installed map populated when another consumer has primed the cache.\n const cachedInstalledSet = useMemo(() => {\n if (!agentId) return null;\n try {\n const cached = apollo.readQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n });\n const installedList = cached?.agentPlugins?.installed ?? null;\n if (!installedList) return null;\n return new Set(installedList.map((p: { packageName: string }) => p.packageName));\n } catch {\n return null;\n }\n }, [apollo, agentId]);\n\n const [loading, setLoading] = useState(false);\n const [hasFetchedOnce, setHasFetchedOnce] = useState(() => cachedInstalledSet !== null);\n const [installedMap, setInstalledMap] = useState<Record<string, boolean>>(() => {\n const next: Record<string, boolean> = {};\n if (cachedInstalledSet) {\n for (const pkg of packages) next[pkg] = cachedInstalledSet.has(pkg);\n }\n return next;\n });\n const [installing, setInstalling] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [quotaExhausted, setQuotaExhausted] = useState(false);\n const errorTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Stable key so we re-run only when the *set* of requested packages changes.\n const packagesKey = useMemo(() => packages.slice().sort().join('|'), [packages]);\n\n const refetch = useCallback(async () => {\n if (!agentId) {\n setInstalledMap({});\n return;\n }\n setLoading(true);\n try {\n // Use the Apollo client directly with `network-only` so out-of-band\n // installs (CLI, other tabs, agent-side changes) are always picked\n // up. `useAgentPluginsLazyQuery`'s lazy execute() doesn't accept\n // per-call fetchPolicy in Apollo v4; `apollo.query()` does.\n const { data } = await apollo.query<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n fetchPolicy: 'network-only',\n });\n const next: Record<string, boolean> = {};\n const installedList = data?.agentPlugins?.installed ?? [];\n const pluginError = data?.agentPlugins?.error ?? null;\n const installedSet = new Set(\n installedList.map((p: { packageName: string }) => p.packageName)\n );\n for (const pkg of packages) next[pkg] = installedSet.has(pkg);\n if (pluginError) {\n if (!classifyAgentStateError(pluginError)) {\n setInstalledMap(next);\n }\n // Log the raw error for debugging, but only surface a sanitized\n // version to the UI. Raw Bun fetch errors (\"Was there a typo in\n // the url or port?\") would otherwise leak into the install card.\n // eslint-disable-next-line no-console -- intentional: keep technical detail for debugging\n console.error('[ai] agentPlugins returned error', pluginError);\n setError(sanitizeAgentStateError(pluginError));\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n if (!classifyAgentStateError(pluginError)) {\n errorTimerRef.current = setTimeout(() => setError(null), 5000);\n }\n return;\n }\n setInstalledMap(next);\n // Clear any lingering error from a previously-aborted fetch so the\n // gate doesn't keep showing \"Agent not ready\" after a successful\n // subsequent request (e.g. React Strict Mode double-mount).\n setError(null);\n if (errorTimerRef.current) {\n clearTimeout(errorTimerRef.current);\n errorTimerRef.current = null;\n }\n } catch (err) {\n const msg =\n err instanceof Error ? err.message : 'Agent unreachable — unable to read plugin list';\n const isAgentStateError = classifyAgentStateError(msg);\n // Keep the last known installed state while the agent is restarting or\n // finalizing. Otherwise a transient 503 flashes an install CTA for an\n // already-installed plugin.\n if (!isAgentStateError) {\n setInstalledMap({});\n }\n // eslint-disable-next-line no-console -- intentional: keep technical detail for debugging\n console.error('[ai] agentPlugins fetch failed', err);\n setError(sanitizeAgentStateError(msg));\n // State-related errors stay visible until resolved; transient\n // network/GraphQL errors auto-dismiss so the UI stays tidy.\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n if (!isAgentStateError) {\n errorTimerRef.current = setTimeout(() => setError(null), 5000);\n }\n } finally {\n setLoading(false);\n setHasFetchedOnce(true);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [agentId, apollo, packagesKey]);\n\n useEffect(() => {\n void refetch();\n }, [refetch]);\n\n // Subscribe to the push stream. The svc emits the installed list\n // whenever it diverges from its previous snapshot, so we only update\n // local state on payloads — and we also write through to the Apollo\n // cache so other consumers reading `agentPlugins` directly see fresh\n // data without their own refetch.\n useVibecontrolsAgentPluginStreamSubscription({\n variables: { agentId: agentId ?? '' },\n skip: !agentId,\n onData: ({ data }) => {\n const installedList = data.data?.vibecontrolsAgentPluginStream;\n if (!installedList || !agentId) return;\n const installedSet = new Set(\n installedList.map((p: { packageName: string }) => p.packageName)\n );\n setInstalledMap((prev) => {\n const next: Record<string, boolean> = { ...prev };\n let changed = false;\n for (const pkg of packages) {\n const has = installedSet.has(pkg);\n if (next[pkg] !== has) {\n next[pkg] = has;\n changed = true;\n }\n }\n return changed ? next : prev;\n });\n setHasFetchedOnce(true);\n // Mirror into Apollo cache so consumers that read AgentPluginsQuery\n // directly (e.g. PluginHarnessPicker) see the same fresh list.\n try {\n const existing = apollo.readQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n });\n apollo.writeQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n data: {\n agentPlugins: {\n __typename: 'AgentPluginListResult',\n installed: installedList,\n available: existing?.agentPlugins?.available ?? [],\n error: null,\n },\n },\n });\n } catch {\n // Cache may not have been primed yet — the next refetch will\n // populate `available` and we'll write again on the next event.\n }\n },\n });\n\n // Also refetch when the tab regains focus so installs made elsewhere flow in.\n useEffect(() => {\n const onFocus = () => void refetch();\n window.addEventListener('focus', onFocus);\n return () => window.removeEventListener('focus', onFocus);\n }, [refetch]);\n\n useEffect(() => {\n return () => {\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n };\n }, []);\n\n const install = useCallback(\n async (packageName: string): Promise<boolean> => {\n if (!agentId) return false;\n setInstalling(packageName);\n setError(null);\n try {\n const { data } = await installPluginMutation({\n variables: { agentId, packageName },\n });\n if (data?.installAgentPlugin?.success) {\n setInstalledMap((prev) => ({ ...prev, [packageName]: true }));\n // Don't immediately refetch: the mutation returns success before the agent\n // finishes installing the npm package. An immediate refetch races the agent\n // and overwrites this optimistic update with a stale \"not installed\" result,\n // putting the install card back up. The subscription stream delivers the\n // confirmed update when the agent is done; a delayed refetch is a fallback\n // for environments where the WebSocket push is slow.\n setTimeout(() => void refetch(), 3000);\n return true;\n }\n throw new Error(data?.installAgentPlugin?.error || 'Install failed');\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setQuotaExhausted(true);\n return false;\n }\n const msg = err instanceof Error ? err.message : 'Failed to install plugin';\n // eslint-disable-next-line no-console -- intentional: keep technical detail for debugging\n console.error('[ai] plugin install failed', err);\n setError(sanitizeAgentStateError(msg));\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n if (!classifyAgentStateError(msg)) {\n errorTimerRef.current = setTimeout(() => setError(null), 5000);\n }\n return false;\n } finally {\n setInstalling(null);\n }\n },\n [agentId, installPluginMutation, refetch]\n );\n\n const clearQuotaExhausted = useCallback(() => setQuotaExhausted(false), []);\n\n const missing = useMemo(\n () => packages.filter((pkg) => !installedMap[pkg]),\n [packages, installedMap]\n );\n const anyInstalled = useMemo(\n () => packages.some((pkg) => installedMap[pkg]),\n [packages, installedMap]\n );\n const allInstalled = missing.length === 0 && packages.length > 0;\n\n return {\n loading,\n initialLoading: !hasFetchedOnce,\n installed: installedMap,\n missing,\n anyInstalled,\n allInstalled,\n installing,\n install,\n error,\n errorIsAgentState: classifyAgentStateError(error),\n quotaExhausted,\n clearQuotaExhausted,\n refetch,\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-vibecontrols",
3
- "version": "2026.523.1",
3
+ "version": "2026.523.2",
4
4
  "description": "VibeControls microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [