@burdenoff/microfe-vibecontrols 2026.905.2 → 2026.905.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plugin.js CHANGED
@@ -1,102 +1,84 @@
1
- import { AIAssistantPanel as e } from "./components/assistant/AIAssistantPanel.js";
2
- import "./components/assistant/index.js";
3
- import { Bot as t, BotMessageSquare as n, Terminal as r, Zap as i } from "lucide-react";
4
- import { usePanelManagerStore as a } from "@burdenoff/fe-libs/shared/plugins/panels/usePanelManagerStore";
1
+ import { Bot as e, BotMessageSquare as t, Terminal as n, Zap as r } from "lucide-react";
5
2
  //#region src/plugin.tsx
6
- var o = "vibecontrols.assistant", s = 420, c = {
3
+ var i = {
7
4
  manifest: {
8
5
  id: "vibecontrols",
9
6
  name: "VibeControls",
10
7
  version: "2026.328.1",
11
8
  description: "Development environment management — agents, sessions, tunnels, vibes",
12
9
  activationEvents: [{ type: "eager" }],
13
- contributes: {
14
- panels: [{
15
- id: o,
10
+ contributes: { commands: [
11
+ {
12
+ id: "vc:go-to-agents",
16
13
  pluginId: "vibecontrols",
17
- title: "AI Assistant",
14
+ label: "Go to Agents",
15
+ description: "View all connected agents",
16
+ icon: e,
17
+ keywords: [
18
+ "agents",
19
+ "vibecontrols",
20
+ "dev"
21
+ ],
22
+ shortcut: "g a",
23
+ section: "VibeControls",
24
+ execute: () => {}
25
+ },
26
+ {
27
+ id: "vc:go-to-vibes",
28
+ pluginId: "vibecontrols",
29
+ label: "Go to Vibes",
30
+ description: "View all vibes (dev environments)",
31
+ icon: r,
32
+ keywords: [
33
+ "vibes",
34
+ "environments",
35
+ "dev"
36
+ ],
37
+ shortcut: "g v",
38
+ section: "VibeControls",
39
+ execute: () => {}
40
+ },
41
+ {
42
+ id: "vc:go-to-sessions",
43
+ pluginId: "vibecontrols",
44
+ label: "Go to Sessions",
45
+ description: "View terminal sessions",
18
46
  icon: n,
19
- position: "right",
20
- component: e,
21
- defaultSize: s,
22
- minSize: 320,
23
- maxSize: 720,
24
- defaultVisible: !1
25
- }],
26
- commands: [
27
- {
28
- id: "vc:go-to-agents",
29
- pluginId: "vibecontrols",
30
- label: "Go to Agents",
31
- description: "View all connected agents",
32
- icon: t,
33
- keywords: [
34
- "agents",
35
- "vibecontrols",
36
- "dev"
37
- ],
38
- shortcut: "g a",
39
- section: "VibeControls",
40
- execute: () => {}
41
- },
42
- {
43
- id: "vc:go-to-vibes",
44
- pluginId: "vibecontrols",
45
- label: "Go to Vibes",
46
- description: "View all vibes (dev environments)",
47
- icon: i,
48
- keywords: [
49
- "vibes",
50
- "environments",
51
- "dev"
52
- ],
53
- shortcut: "g v",
54
- section: "VibeControls",
55
- execute: () => {}
56
- },
57
- {
58
- id: "vc:go-to-sessions",
59
- pluginId: "vibecontrols",
60
- label: "Go to Sessions",
61
- description: "View terminal sessions",
62
- icon: r,
63
- keywords: [
64
- "sessions",
65
- "terminal",
66
- "shell"
67
- ],
68
- section: "VibeControls",
69
- execute: () => {}
70
- },
71
- {
72
- id: "vc:toggle-ai-assistant",
73
- pluginId: "vibecontrols",
74
- label: "Toggle AI Assistant",
75
- description: "Open or close the AI assistant panel",
76
- icon: n,
77
- keywords: [
78
- "ai",
79
- "assistant",
80
- "chat",
81
- "help"
82
- ],
83
- shortcut: "Ctrl+Shift+A",
84
- section: "VibeControls",
85
- execute: () => {}
86
- }
87
- ]
88
- }
47
+ keywords: [
48
+ "sessions",
49
+ "terminal",
50
+ "shell"
51
+ ],
52
+ section: "VibeControls",
53
+ execute: () => {}
54
+ },
55
+ {
56
+ id: "vc:toggle-ai-assistant",
57
+ pluginId: "vibecontrols",
58
+ label: "Toggle AI Assistant",
59
+ description: "Open or close the AI assistant panel",
60
+ icon: t,
61
+ keywords: [
62
+ "ai",
63
+ "assistant",
64
+ "chat",
65
+ "help"
66
+ ],
67
+ shortcut: "Ctrl+Shift+A",
68
+ section: "VibeControls",
69
+ execute: () => {}
70
+ }
71
+ ] }
89
72
  },
90
73
  activate(e) {
91
74
  let t = e.commands.getByPlugin("vibecontrols");
92
75
  for (let n of t) n.id === "vc:go-to-agents" ? n.execute = () => e.shell.navigate("/vibecontrols/agents") : n.id === "vc:go-to-vibes" ? n.execute = () => e.shell.navigate("/vibecontrols/vibes") : n.id === "vc:go-to-sessions" ? n.execute = () => e.shell.navigate("/vibecontrols/sessions") : n.id === "vc:toggle-ai-assistant" && (n.execute = () => {
93
- let e = a.getState();
94
- e.panels[o] || e.initPanel(o, "right", s), a.getState().togglePanel(o);
76
+ window.dispatchEvent(new CustomEvent("vc:toggle-ai-assistant"));
95
77
  });
96
78
  e.events.on("context:workspace-changed", () => {});
97
79
  }
98
80
  };
99
81
  //#endregion
100
- export { c as vibeControlsPlugin };
82
+ export { i as vibeControlsPlugin };
101
83
 
102
84
  //# sourceMappingURL=plugin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","names":[],"sources":["../src/plugin.tsx"],"sourcesContent":["/**\n * VibeControls UI Plugin.\n *\n * Exports the UIPlugin for the VibeControls microfrontend,\n * declaring its contributions to the app shell (navigation, commands,\n * settings, status bar indicators).\n *\n * This is the plugin counterpart to the VibeControlsRoot component.\n * Both can coexist — the Root handles rendering while the plugin\n * handles shell integration.\n *\n * @module @burdenoff/microfe-vibecontrols/plugin\n *\n * @example\n * ```tsx\n * // In vibecontrols-app/src/plugins/index.ts\n * import { vibeControlsPlugin } from '@burdenoff/microfe-vibecontrols/plugin';\n *\n * export const PLUGINS = [vibeControlsPlugin];\n * ```\n */\n\nimport type { UIPlugin } from '@burdenoff/fe-libs/shared/plugins';\nimport { Terminal, Bot, BotMessageSquare, Zap } from 'lucide-react';\n// ★★ Import the store from its OWN module, not from `chrome/PanelContainer`.\n//\n// PanelContainer's source re-exports it as `panelManagerStore` and its comment\n// tells consumers to import it from there — but that re-export does NOT survive\n// the published build. fe-libs is bundled with rolldown, which tree-shakes leaf\n// re-exports that nothing inside the package references, so the shipped\n// `dist/chrome/PanelContainer.js` exports `PanelContainer` and nothing else.\n// Importing from there typechecks against source and then fails the app build\n// with MISSING_EXPORT. (The MFE's own transport carries the same warning about\n// tree-shaken leaf re-exports, so this is a known shape of this toolchain.)\n//\n// ★ The warning attached to that re-export — that another path yields a second\n// Zustand instance — does not apply here: `PanelContainer` imports this same\n// module internally, and both specifiers resolve to the one file inside\n// `node_modules/@burdenoff/fe-libs`, so the app's bundle has a single instance.\n// `tsconfig.json` already maps this exact specifier, which is the repo's own\n// signal that it is the sanctioned path.\nimport { usePanelManagerStore } from '@burdenoff/fe-libs/shared/plugins/panels/usePanelManagerStore';\nimport { AIAssistantPanel } from './components/assistant';\n\n/**\n * The assistant's panel id, shared by the contribution and the toggle command.\n *\n * ★ One constant, not two string literals: they must match exactly or the\n * command toggles a panel that was never registered — which fails silently,\n * because every mutator in the panel-manager store returns early on an unknown\n * id rather than throwing.\n */\nconst ASSISTANT_PANEL_ID = 'vibecontrols.assistant';\n\n/** Matches the contribution's `defaultSize`; see the toggle command below. */\nconst ASSISTANT_PANEL_SIZE = 420;\n\nexport const vibeControlsPlugin: UIPlugin = {\n manifest: {\n id: 'vibecontrols',\n name: 'VibeControls',\n version: '2026.328.1',\n description: 'Development environment management — agents, sessions, tunnels, vibes',\n activationEvents: [{ type: 'eager' }],\n\n contributes: {\n // Nav items are registered by the app shell (productNav.tsx) and\n // VibeControlsRoot; do not duplicate them here.\n\n /**\n * The AI assistant, mounted into the shell's right panel.\n *\n * ★★ `component` is VibeControls' OWN wrapper, not fe-libs'\n * `AIAssistantPanel` directly. The shared panel is deliberately exported\n * WITHOUT an `AssistantProductProvider` around it — a provider baked in\n * there could only carry one product's modes and page vocabulary, which is\n * exactly the coupling the shared assistant exists to avoid. Our wrapper\n * supplies the provider, the six seam fields and `writeAccess`. Passing\n * the shared panel here would typecheck (its extra props are optional) and\n * then render with no modes and English copy.\n *\n * ★ `pluginId` is required by the type but IGNORED — the runtime\n * overwrites it with the id it registered the plugin under.\n *\n * ★ `icon` is a component REFERENCE, not an element: `BotMessageSquare`,\n * never `<BotMessageSquare />`. It differs from `NavItem.icon`, which is a\n * ReactNode — a genuine inconsistency in the plugin API, and an easy\n * five-minute mistake.\n *\n * ★ `defaultVisible: false` — the assistant opens on demand. Open state is\n * never persisted (the store's `partialize` strips it), so panels always\n * start closed on reload regardless; this just declines the auto-open.\n */\n panels: [\n {\n id: ASSISTANT_PANEL_ID,\n pluginId: 'vibecontrols',\n title: 'AI Assistant',\n icon: BotMessageSquare,\n position: 'right',\n component: AIAssistantPanel,\n defaultSize: ASSISTANT_PANEL_SIZE,\n minSize: 320,\n maxSize: 720,\n defaultVisible: false,\n },\n ],\n\n // Commands for the command palette (Cmd+K)\n commands: [\n {\n id: 'vc:go-to-agents',\n pluginId: 'vibecontrols',\n label: 'Go to Agents',\n description: 'View all connected agents',\n icon: Bot,\n keywords: ['agents', 'vibecontrols', 'dev'],\n shortcut: 'g a',\n section: 'VibeControls',\n execute: () => {\n // Will be wired by shell navigate\n },\n },\n {\n id: 'vc:go-to-vibes',\n pluginId: 'vibecontrols',\n label: 'Go to Vibes',\n description: 'View all vibes (dev environments)',\n icon: Zap,\n keywords: ['vibes', 'environments', 'dev'],\n shortcut: 'g v',\n section: 'VibeControls',\n execute: () => {},\n },\n {\n id: 'vc:go-to-sessions',\n pluginId: 'vibecontrols',\n label: 'Go to Sessions',\n description: 'View terminal sessions',\n icon: Terminal,\n keywords: ['sessions', 'terminal', 'shell'],\n section: 'VibeControls',\n execute: () => {},\n },\n {\n id: 'vc:toggle-ai-assistant',\n pluginId: 'vibecontrols',\n label: 'Toggle AI Assistant',\n description: 'Open or close the AI assistant panel',\n icon: BotMessageSquare,\n keywords: ['ai', 'assistant', 'chat', 'help'],\n shortcut: 'Ctrl+Shift+A',\n section: 'VibeControls',\n execute: () => {},\n },\n ],\n },\n },\n\n activate(ctx) {\n // Wire command navigation to use shell.navigate\n const commands = ctx.commands.getByPlugin('vibecontrols');\n for (const cmd of commands) {\n if (cmd.id === 'vc:go-to-agents') {\n cmd.execute = () => ctx.shell.navigate('/vibecontrols/agents');\n } else if (cmd.id === 'vc:go-to-vibes') {\n cmd.execute = () => ctx.shell.navigate('/vibecontrols/vibes');\n } else if (cmd.id === 'vc:go-to-sessions') {\n cmd.execute = () => ctx.shell.navigate('/vibecontrols/sessions');\n } else if (cmd.id === 'vc:toggle-ai-assistant') {\n /**\n * ★★ This command was DEAD before the assistant became a panel.\n *\n * It dispatched a `vc:toggle-ai-assistant` DOM event for\n * `AssistantPanelContext` to catch — a file that no longer exists in\n * vibecontrols-app, removed when the legacy overlay was deleted as a\n * duplicate of the widget. Nothing listened, so the command and its\n * `Ctrl+Shift+A` shortcut did nothing at all, and said nothing about it.\n *\n * `togglePanel` returns early on an unknown id rather than throwing,\n * and the only caller of `initPanel` is `PanelContainer`'s mount effect.\n * A shortcut pressed before that effect has run would therefore be\n * another silent no-op so initialise first if we have to. Harmless\n * when the container already did it: `initPanel` is keyed by id.\n */\n cmd.execute = () => {\n const store = usePanelManagerStore.getState();\n if (!store.panels[ASSISTANT_PANEL_ID]) {\n store.initPanel(ASSISTANT_PANEL_ID, 'right', ASSISTANT_PANEL_SIZE);\n }\n // Re-read: initPanel replaced the state we captured above.\n usePanelManagerStore.getState().togglePanel(ASSISTANT_PANEL_ID);\n };\n }\n }\n\n // Listen for workspace changes to refresh data\n ctx.events.on('context:workspace-changed', () => {\n // Plugin can refresh agent list, session state, etc.\n });\n },\n};\n"],"mappings":";;;;;AAoDA,IAAM,IAAqB,0BAGrB,IAAuB,KAEhB,IAA+B;CAC1C,UAAU;EACR,IAAI;EACJ,MAAM;EACN,SAAS;EACT,aAAa;EACb,kBAAkB,CAAC,EAAE,MAAM,SAAS,CAAC;EAErC,aAAa;GA4BX,QAAQ,CACN;IACE,IAAI;IACJ,UAAU;IACV,OAAO;IACP,MAAM;IACN,UAAU;IACV,WAAW;IACX,aAAa;IACb,SAAS;IACT,SAAS;IACT,gBAAgB;IACjB,CACF;GAGD,UAAU;IACR;KACE,IAAI;KACJ,UAAU;KACV,OAAO;KACP,aAAa;KACb,MAAM;KACN,UAAU;MAAC;MAAU;MAAgB;MAAM;KAC3C,UAAU;KACV,SAAS;KACT,eAAe;KAGhB;IACD;KACE,IAAI;KACJ,UAAU;KACV,OAAO;KACP,aAAa;KACb,MAAM;KACN,UAAU;MAAC;MAAS;MAAgB;MAAM;KAC1C,UAAU;KACV,SAAS;KACT,eAAe;KAChB;IACD;KACE,IAAI;KACJ,UAAU;KACV,OAAO;KACP,aAAa;KACb,MAAM;KACN,UAAU;MAAC;MAAY;MAAY;MAAQ;KAC3C,SAAS;KACT,eAAe;KAChB;IACD;KACE,IAAI;KACJ,UAAU;KACV,OAAO;KACP,aAAa;KACb,MAAM;KACN,UAAU;MAAC;MAAM;MAAa;MAAQ;MAAO;KAC7C,UAAU;KACV,SAAS;KACT,eAAe;KAChB;IACF;GACF;EACF;CAED,SAAS,GAAK;EAEZ,IAAM,IAAW,EAAI,SAAS,YAAY,eAAe;AACzD,OAAK,IAAM,KAAO,EAChB,CAAI,EAAI,OAAO,oBACb,EAAI,gBAAgB,EAAI,MAAM,SAAS,uBAAuB,GACrD,EAAI,OAAO,mBACpB,EAAI,gBAAgB,EAAI,MAAM,SAAS,sBAAsB,GACpD,EAAI,OAAO,sBACpB,EAAI,gBAAgB,EAAI,MAAM,SAAS,yBAAyB,GACvD,EAAI,OAAO,6BAgBpB,EAAI,gBAAgB;GAClB,IAAM,IAAQ,EAAqB,UAAU;AAK7C,GAJK,EAAM,OAAO,MAChB,EAAM,UAAU,GAAoB,SAAS,EAAqB,EAGpE,EAAqB,UAAU,CAAC,YAAY,EAAmB;;AAMrE,IAAI,OAAO,GAAG,mCAAmC,GAE/C;;CAEL"}
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../src/plugin.tsx"],"sourcesContent":["/**\n * VibeControls UI Plugin.\n *\n * Exports the UIPlugin for the VibeControls microfrontend,\n * declaring its contributions to the app shell (navigation, commands,\n * settings, status bar indicators).\n *\n * This is the plugin counterpart to the VibeControlsRoot component.\n * Both can coexist — the Root handles rendering while the plugin\n * handles shell integration.\n *\n * @module @burdenoff/microfe-vibecontrols/plugin\n *\n * @example\n * ```tsx\n * // In vibecontrols-app/src/plugins/index.ts\n * import { vibeControlsPlugin } from '@burdenoff/microfe-vibecontrols/plugin';\n *\n * export const PLUGINS = [vibeControlsPlugin];\n * ```\n */\n\nimport type { UIPlugin } from '@burdenoff/fe-libs/shared/plugins';\nimport { Terminal, Bot, BotMessageSquare, Zap } from 'lucide-react';\nexport const vibeControlsPlugin: UIPlugin = {\n manifest: {\n id: 'vibecontrols',\n name: 'VibeControls',\n version: '2026.328.1',\n description: 'Development environment management — agents, sessions, tunnels, vibes',\n activationEvents: [{ type: 'eager' }],\n\n contributes: {\n // Nav items are registered by the app shell (productNav.tsx) and\n // VibeControlsRoot; do not duplicate them here.\n\n // Commands for the command palette (Cmd+K)\n commands: [\n {\n id: 'vc:go-to-agents',\n pluginId: 'vibecontrols',\n label: 'Go to Agents',\n description: 'View all connected agents',\n icon: Bot,\n keywords: ['agents', 'vibecontrols', 'dev'],\n shortcut: 'g a',\n section: 'VibeControls',\n execute: () => {\n // Will be wired by shell navigate\n },\n },\n {\n id: 'vc:go-to-vibes',\n pluginId: 'vibecontrols',\n label: 'Go to Vibes',\n description: 'View all vibes (dev environments)',\n icon: Zap,\n keywords: ['vibes', 'environments', 'dev'],\n shortcut: 'g v',\n section: 'VibeControls',\n execute: () => {},\n },\n {\n id: 'vc:go-to-sessions',\n pluginId: 'vibecontrols',\n label: 'Go to Sessions',\n description: 'View terminal sessions',\n icon: Terminal,\n keywords: ['sessions', 'terminal', 'shell'],\n section: 'VibeControls',\n execute: () => {},\n },\n {\n id: 'vc:toggle-ai-assistant',\n pluginId: 'vibecontrols',\n label: 'Toggle AI Assistant',\n description: 'Open or close the AI assistant panel',\n icon: BotMessageSquare,\n keywords: ['ai', 'assistant', 'chat', 'help'],\n shortcut: 'Ctrl+Shift+A',\n section: 'VibeControls',\n execute: () => {},\n },\n ],\n },\n },\n\n activate(ctx) {\n // Wire command navigation to use shell.navigate\n const commands = ctx.commands.getByPlugin('vibecontrols');\n for (const cmd of commands) {\n if (cmd.id === 'vc:go-to-agents') {\n cmd.execute = () => ctx.shell.navigate('/vibecontrols/agents');\n } else if (cmd.id === 'vc:go-to-vibes') {\n cmd.execute = () => ctx.shell.navigate('/vibecontrols/vibes');\n } else if (cmd.id === 'vc:go-to-sessions') {\n cmd.execute = () => ctx.shell.navigate('/vibecontrols/sessions');\n } else if (cmd.id === 'vc:toggle-ai-assistant') {\n /**\n * ★★ Dispatches to `AIAssistantOverlay` in vibecontrols-app.\n *\n * This command was DEAD for a long time: it fired this same event for\n * `AssistantPanelContext`, a shell component deleted when the legacy\n * overlay was removed. Nothing listened, so the command and its\n * Ctrl+Shift+A shortcut did nothing and said nothing about it.\n *\n * The listener now exists again. An event rather than a direct call\n * because the assistant is an overlay owned by the app shell the MFE\n * has no handle on it, and should not need one.\n */\n cmd.execute = () => {\n window.dispatchEvent(new CustomEvent('vc:toggle-ai-assistant'));\n };\n }\n }\n\n // Listen for workspace changes to refresh data\n ctx.events.on('context:workspace-changed', () => {\n // Plugin can refresh agent list, session state, etc.\n });\n },\n};\n"],"mappings":";;AAwBA,IAAa,IAA+B;CAC1C,UAAU;EACR,IAAI;EACJ,MAAM;EACN,SAAS;EACT,aAAa;EACb,kBAAkB,CAAC,EAAE,MAAM,SAAS,CAAC;EAErC,aAAa,EAKX,UAAU;GACR;IACE,IAAI;IACJ,UAAU;IACV,OAAO;IACP,aAAa;IACb,MAAM;IACN,UAAU;KAAC;KAAU;KAAgB;KAAM;IAC3C,UAAU;IACV,SAAS;IACT,eAAe;IAGhB;GACD;IACE,IAAI;IACJ,UAAU;IACV,OAAO;IACP,aAAa;IACb,MAAM;IACN,UAAU;KAAC;KAAS;KAAgB;KAAM;IAC1C,UAAU;IACV,SAAS;IACT,eAAe;IAChB;GACD;IACE,IAAI;IACJ,UAAU;IACV,OAAO;IACP,aAAa;IACb,MAAM;IACN,UAAU;KAAC;KAAY;KAAY;KAAQ;IAC3C,SAAS;IACT,eAAe;IAChB;GACD;IACE,IAAI;IACJ,UAAU;IACV,OAAO;IACP,aAAa;IACb,MAAM;IACN,UAAU;KAAC;KAAM;KAAa;KAAQ;KAAO;IAC7C,UAAU;IACV,SAAS;IACT,eAAe;IAChB;GACF,EACF;EACF;CAED,SAAS,GAAK;EAEZ,IAAM,IAAW,EAAI,SAAS,YAAY,eAAe;AACzD,OAAK,IAAM,KAAO,EAChB,CAAI,EAAI,OAAO,oBACb,EAAI,gBAAgB,EAAI,MAAM,SAAS,uBAAuB,GACrD,EAAI,OAAO,mBACpB,EAAI,gBAAgB,EAAI,MAAM,SAAS,sBAAsB,GACpD,EAAI,OAAO,sBACpB,EAAI,gBAAgB,EAAI,MAAM,SAAS,yBAAyB,GACvD,EAAI,OAAO,6BAapB,EAAI,gBAAgB;AAClB,UAAO,cAAc,IAAI,YAAY,yBAAyB,CAAC;;AAMrE,IAAI,OAAO,GAAG,mCAAmC,GAE/C;;CAEL"}
@@ -2,24 +2,25 @@ import { buildVibeControlsCache as e } from "../lib/apolloCache.js";
2
2
  import { getMainDefinition as t } from "../node_modules/@apollo/client/utilities/internal/getMainDefinition.js";
3
3
  import { CombinedGraphQLErrors as n } from "../node_modules/@apollo/client/errors/CombinedGraphQLErrors.js";
4
4
  import { BatchHttpLink as r } from "../node_modules/@apollo/client/link/batch-http/batchHttpLink.js";
5
- import i from "./AgentEventsProvider.js";
6
- import { QUOTA_EXHAUSTED_EVENT as a, inferQuotaKindFromOperation as o } from "../utils/quotaUtils.js";
5
+ import { configureSpeechEngine as i } from "../services/speech/index.js";
6
+ import a from "./AgentEventsProvider.js";
7
+ import { QUOTA_EXHAUSTED_EVENT as o, inferQuotaKindFromOperation as ee } from "../utils/quotaUtils.js";
7
8
  import { QuotaExhaustedProvider as s } from "./QuotaExhaustedProvider.js";
8
9
  import c from "../hooks/useInitialDataLoader.js";
9
- import { createContext as l, use as u, useCallback as d, useEffect as f, useMemo as ee, useRef as p, useState as m } from "react";
10
- import { MemoryRouter as h, useNavigate as g } from "react-router";
11
- import { ApolloClient as te, ApolloLink as _, Observable as v, from as y } from "@apollo/client";
12
- import { onError as b } from "@apollo/client/link/error";
13
- import { setContext as x } from "@apollo/client/link/context";
14
- import { ApolloProvider as S } from "@apollo/client/react";
15
- import { MutationCache as C, QueryClient as w, QueryClientProvider as T } from "@tanstack/react-query";
16
- import { getStoredAuthToken as E, getStoredWorkspaceToken as ne, isWorkspaceTokenValidForContext as re } from "@burdenoff/fe-libs/shared/graphql";
17
- import { getStoredWorkspaceId as ie } from "@burdenoff/fe-libs/shared/graphql/fetch";
18
- import { PermissionProvider as ae } from "@burdenoff/fe-libs/shared/providers/shell";
19
- import { isAuthNetworkError as oe } from "@burdenoff/fe-libs/shared/utils";
20
- import { Fragment as D, jsx as O } from "react/jsx-runtime";
10
+ import { createContext as l, use as u, useCallback as d, useEffect as f, useMemo as p, useRef as m, useState as h } from "react";
11
+ import { MemoryRouter as te, useNavigate as g } from "react-router";
12
+ import { ApolloClient as _, ApolloLink as v, Observable as y, from as b } from "@apollo/client";
13
+ import { onError as ne } from "@apollo/client/link/error";
14
+ import { setContext as re } from "@apollo/client/link/context";
15
+ import { ApolloProvider as x } from "@apollo/client/react";
16
+ import { MutationCache as S, QueryClient as C, QueryClientProvider as w } from "@tanstack/react-query";
17
+ import { getStoredAuthToken as ie, getStoredWorkspaceToken as ae, isWorkspaceTokenValidForContext as oe } from "@burdenoff/fe-libs/shared/graphql";
18
+ import { getStoredWorkspaceId as se } from "@burdenoff/fe-libs/shared/graphql/fetch";
19
+ import { PermissionProvider as ce } from "@burdenoff/fe-libs/shared/providers/shell";
20
+ import { isAuthNetworkError as le } from "@burdenoff/fe-libs/shared/utils";
21
+ import { Fragment as T, jsx as E } from "react/jsx-runtime";
21
22
  //#region src/providers/VibeControlsProvider.tsx
22
- function se() {
23
+ function D() {
23
24
  try {
24
25
  let e = sessionStorage.getItem("burdenoff-active-context-project");
25
26
  if (e) return e;
@@ -31,27 +32,27 @@ function se() {
31
32
  return null;
32
33
  }
33
34
  }
34
- function k(e = []) {
35
+ function O(e = []) {
35
36
  return e.some((e) => {
36
37
  let t = typeof e.extensions?.code == "string" ? e.extensions.code : "", n = typeof e.extensions?.errorCode == "string" ? e.extensions.errorCode : "", r = (e.message ?? "").toLowerCase();
37
38
  return t === "UNAUTHENTICATED" || n === "TOKEN_EXPIRED" || r.includes("token_expired") || r.includes("token expired") || r.includes("session expired") || r.includes("unauthorized") || r.includes("unauthenticated") || r.includes("authentication required") || r.includes("workspace context missing");
38
39
  });
39
40
  }
40
- function A(e) {
41
+ function k(e) {
41
42
  let n = t(e);
42
43
  return n.kind === "OperationDefinition" && n.operation === "mutation";
43
44
  }
44
- function j(e) {
45
+ function A(e) {
45
46
  let t = e.errors;
46
47
  return Array.isArray(t) && t.length > 0;
47
48
  }
48
- function M(e) {
49
- return new _((t, n) => {
50
- let r = A(t.query) && t.getContext().autoRefreshAfterMutation !== !1 && t.getContext().skipMutationAutoRefresh !== !0;
51
- return new v((i) => {
49
+ function j(e) {
50
+ return new v((t, n) => {
51
+ let r = k(t.query) && t.getContext().autoRefreshAfterMutation !== !1 && t.getContext().skipMutationAutoRefresh !== !0;
52
+ return new y((i) => {
52
53
  let a = n(t).subscribe({
53
54
  next: (t) => {
54
- i.next(t), !(!r || j(t)) && e()?.refetchQueries({ include: "active" });
55
+ i.next(t), !(!r || A(t)) && e()?.refetchQueries({ include: "active" });
55
56
  },
56
57
  error: (e) => {
57
58
  i.error(e);
@@ -66,18 +67,18 @@ function M(e) {
66
67
  });
67
68
  });
68
69
  }
69
- function N(e = []) {
70
+ function M(e = []) {
70
71
  return e.find((e) => {
71
72
  let t = typeof e.extensions?.code == "string" ? e.extensions.code : "", n = typeof e.extensions?.errorCode == "string" ? e.extensions.errorCode : "", r = (e.message ?? "").toLowerCase();
72
73
  return t === "QUOTA_EXHAUSTED" || n === "QUOTA_EXHAUSTED" || r.includes("quota_exhausted") || r.includes("quota exhausted");
73
74
  });
74
75
  }
75
- var P = ({ workspaceId: e, children: t }) => (c({ workspaceId: e }), /* @__PURE__ */ O(D, { children: t })), F = l(null), I = () => {
76
- let e = u(F);
76
+ var N = ({ workspaceId: e, children: t }) => (c({ workspaceId: e }), /* @__PURE__ */ E(T, { children: t })), P = l(null), F = () => {
77
+ let e = u(P);
77
78
  if (!e) throw Error("useVibeControls must be used within VibeControlsProvider");
78
79
  return e;
79
- }, L = () => {
80
- let { navigate: e, basePath: t } = I(), n = g();
80
+ }, I = () => {
81
+ let { navigate: e, basePath: t } = F(), n = g();
81
82
  return d((r) => {
82
83
  let i = t && t !== "/" ? `${t}${r}` : r;
83
84
  e ? e(i) : n(i);
@@ -86,10 +87,10 @@ var P = ({ workspaceId: e, children: t }) => (c({ workspaceId: e }), /* @__PURE_
86
87
  t,
87
88
  n
88
89
  ]);
89
- }, R = ({ children: t, basePath: c, navigate: l, currentUser: u, workspaceId: d, organizationId: g, projectId: _, tenantId: v, apiGatewayUrl: D, authToken: A, workspaceToken: j, onAgentSelect: I, onSessionStart: L, onVibeActivate: R, defaultView: z, registerNavItems: B, registerFooterItems: V, onAuthError: H }) => {
90
- let [U] = m(() => {
91
- let e = new w({
92
- mutationCache: new C({ onSuccess: () => {
90
+ }, L = ({ children: t, basePath: c, navigate: l, currentUser: u, workspaceId: g, organizationId: v, projectId: y, tenantId: T, apiGatewayUrl: k, authToken: A, workspaceToken: F, onAgentSelect: I, onSessionStart: L, onVibeActivate: R, defaultView: z, registerNavItems: B, registerFooterItems: V, onAuthError: H, ttsEndpoint: U }) => {
91
+ let [ue] = h(() => {
92
+ let e = new C({
93
+ mutationCache: new S({ onSuccess: () => {
93
94
  e.invalidateQueries();
94
95
  } }),
95
96
  defaultOptions: { queries: {
@@ -98,43 +99,46 @@ var P = ({ workspaceId: e, children: t }) => (c({ workspaceId: e }), /* @__PURE_
98
99
  } }
99
100
  });
100
101
  return e;
101
- }), W = p(A), G = p(j), K = p(d), q = p(g), J = p(v), Y = p(_), X = p(H);
102
+ }), W = m(A), G = m(F), K = m(g), q = m(v), J = m(T), Y = m(y), X = m(H);
102
103
  f(() => {
103
104
  W.current = A;
104
- }, [A]), f(() => {
105
- G.current = j;
106
- }, [j]), f(() => {
107
- K.current = d;
108
- }, [d]), f(() => {
109
- q.current = g;
105
+ }, [A]), i({
106
+ ttsEndpoint: U,
107
+ getHeaders: d(() => W.current ? { authorization: `Bearer ${W.current}` } : {}, [])
108
+ }), f(() => {
109
+ G.current = F;
110
+ }, [F]), f(() => {
111
+ K.current = g;
110
112
  }, [g]), f(() => {
111
- J.current = v;
113
+ q.current = v;
112
114
  }, [v]), f(() => {
113
- Y.current = _;
114
- }, [_]), f(() => {
115
+ J.current = T;
116
+ }, [T]), f(() => {
117
+ Y.current = y;
118
+ }, [y]), f(() => {
115
119
  X.current = H;
116
120
  }, [H]);
117
- let ce = ee(() => {
118
- let t = b(({ error: e, operation: t }) => {
121
+ let de = p(() => {
122
+ let t = ne(({ error: e, operation: t }) => {
119
123
  if (n.is(e)) {
120
- if (k(e.errors)) {
124
+ if (O(e.errors)) {
121
125
  console.warn("[VibeControls] Auth error detected — notifying host shell"), X.current?.();
122
126
  return;
123
127
  }
124
- let n = N(e.errors);
128
+ let n = M(e.errors);
125
129
  if (n) {
126
130
  let e = {
127
- kind: o(t?.operationName),
131
+ kind: ee(t?.operationName),
128
132
  message: n.message,
129
133
  operationName: t?.operationName ?? null
130
134
  };
131
- window.dispatchEvent(new CustomEvent(a, { detail: e }));
135
+ window.dispatchEvent(new CustomEvent(o, { detail: e }));
132
136
  return;
133
137
  }
134
138
  }
135
- e instanceof Error && oe(e) && (console.warn("[VibeControls] Auth network error — notifying host shell"), X.current?.());
136
- }), i = x((e, { headers: t }) => {
137
- let n = W.current ?? E(), r = K.current || ie(), i = Y.current || se(), a = G.current ?? ne(), o = re(a, {
139
+ e instanceof Error && le(e) && (console.warn("[VibeControls] Auth network error — notifying host shell"), X.current?.());
140
+ }), i = re((e, { headers: t }) => {
141
+ let n = W.current ?? ie(), r = K.current || se(), i = Y.current || D(), a = G.current ?? ae(), o = oe(a, {
138
142
  workspaceId: r,
139
143
  organizationId: q.current,
140
144
  tenantId: J.current,
@@ -147,17 +151,17 @@ var P = ({ workspaceId: e, children: t }) => (c({ workspaceId: e }), /* @__PURE_
147
151
  ...r ? { "x-workspace-id": r } : {},
148
152
  ...i ? { "x-project-id": i } : {}
149
153
  } };
150
- }), s = new r({
151
- uri: D ? `${D}/workspaces/graphql` : "/workspaces/graphql",
154
+ }), a = new r({
155
+ uri: k ? `${k}/workspaces/graphql` : "/workspaces/graphql",
152
156
  batchMax: 10,
153
157
  batchInterval: 10
154
- }), c = null;
155
- return c = new te({
156
- link: y([
158
+ }), s = null;
159
+ return s = new _({
160
+ link: b([
157
161
  t,
158
- M(() => c),
162
+ j(() => s),
159
163
  i,
160
- s
164
+ a
161
165
  ]),
162
166
  cache: e(),
163
167
  defaultOptions: {
@@ -168,18 +172,18 @@ var P = ({ workspaceId: e, children: t }) => (c({ workspaceId: e }), /* @__PURE_
168
172
  query: { errorPolicy: "all" },
169
173
  mutate: { errorPolicy: "all" }
170
174
  }
171
- }), c;
172
- }, [D]), Z = {
175
+ }), s;
176
+ }, [k]), Z = {
173
177
  basePath: c,
174
178
  navigate: l,
175
179
  currentUser: u,
176
- workspaceId: d,
177
- organizationId: g,
178
- projectId: _,
179
- tenantId: v,
180
- apiGatewayUrl: D,
180
+ workspaceId: g,
181
+ organizationId: v,
182
+ projectId: y,
183
+ tenantId: T,
184
+ apiGatewayUrl: k,
181
185
  authToken: A,
182
- workspaceToken: j,
186
+ workspaceToken: F,
183
187
  onAgentSelect: I,
184
188
  onSessionStart: L,
185
189
  onVibeActivate: R,
@@ -187,34 +191,34 @@ var P = ({ workspaceId: e, children: t }) => (c({ workspaceId: e }), /* @__PURE_
187
191
  registerNavItems: B,
188
192
  registerFooterItems: V,
189
193
  onAuthError: H
190
- }, Q = /* @__PURE__ */ O(s, { children: typeof window < "u" && /^\/vibecontrols\/(share|deck)\//.test(window.location.pathname) ? t : /* @__PURE__ */ O(P, {
191
- workspaceId: d,
192
- children: /* @__PURE__ */ O(ae, {
193
- scopeId: d ?? null,
194
+ }, Q = /* @__PURE__ */ E(s, { children: typeof window < "u" && /^\/vibecontrols\/(share|deck)\//.test(window.location.pathname) ? t : /* @__PURE__ */ E(N, {
195
+ workspaceId: g,
196
+ children: /* @__PURE__ */ E(ce, {
197
+ scopeId: g ?? null,
194
198
  gateway: "workspace",
195
- children: /* @__PURE__ */ O(i, {
199
+ children: /* @__PURE__ */ E(a, {
196
200
  toastPosition: "bottom-right",
197
201
  autoDismissDelay: 5e3,
198
202
  maxVisibleToasts: 3,
199
203
  children: t
200
204
  })
201
205
  })
202
- }) }), $ = /* @__PURE__ */ O(T, {
203
- client: U,
204
- children: c ? /* @__PURE__ */ O(F.Provider, {
206
+ }) }), $ = /* @__PURE__ */ E(w, {
207
+ client: ue,
208
+ children: c ? /* @__PURE__ */ E(P.Provider, {
205
209
  value: Z,
206
210
  children: Q
207
- }) : /* @__PURE__ */ O(S, {
208
- client: ce,
209
- children: /* @__PURE__ */ O(F.Provider, {
211
+ }) : /* @__PURE__ */ E(x, {
212
+ client: de,
213
+ children: /* @__PURE__ */ E(P.Provider, {
210
214
  value: Z,
211
215
  children: Q
212
216
  })
213
217
  })
214
218
  });
215
- return c ? $ : /* @__PURE__ */ O(h, { children: $ });
219
+ return c ? $ : /* @__PURE__ */ E(te, { children: $ });
216
220
  };
217
221
  //#endregion
218
- export { R as VibeControlsProvider, I as useVibeControls, L as useVibeNavigate };
222
+ export { L as VibeControlsProvider, F as useVibeControls, I as useVibeNavigate };
219
223
 
220
224
  //# sourceMappingURL=VibeControlsProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"VibeControlsProvider.js","names":[],"sources":["../../src/providers/VibeControlsProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, use, useMemo, useCallback, useRef, useEffect, useState } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router';\nimport { ApolloClient, ApolloLink, type FetchResult, Observable, from } from '@apollo/client';\nimport { buildVibeControlsCache } from '@/lib/apolloCache';\nimport { BatchHttpLink } from '@apollo/client/link/batch-http';\nimport { onError } from '@apollo/client/link/error';\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { setContext } from '@apollo/client/link/context';\nimport { ApolloProvider } from '@apollo/client/react';\nimport { getMainDefinition } from '@apollo/client/utilities';\nimport { MutationCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport {\n getStoredAuthToken,\n getStoredWorkspaceToken,\n isWorkspaceTokenValidForContext,\n} from '@burdenoff/fe-libs/shared/graphql';\nimport { getStoredWorkspaceId } from '@burdenoff/fe-libs/shared/graphql/fetch';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { isAuthNetworkError } from '@burdenoff/fe-libs/shared/utils';\nimport type { VibeControlsRootProps } from '../types';\nimport { AgentEventsProvider } from './AgentEventsProvider';\nimport { QuotaExhaustedProvider } from './QuotaExhaustedProvider';\nimport { useInitialDataLoader } from '../hooks/useInitialDataLoader';\nimport {\n QUOTA_EXHAUSTED_EVENT,\n inferQuotaKindFromOperation,\n type QuotaExhaustedEventDetail,\n} from '../utils/quotaUtils';\n\ntype GraphQLErrorLike = { message?: string; extensions?: Record<string, unknown> };\n\nfunction getStoredProjectId(): string | null {\n try {\n const fromSession = sessionStorage.getItem('burdenoff-active-context-project');\n if (fromSession) return fromSession;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n const profileContext = activeProfileId\n ? localStorage.getItem(`bf-p-${activeProfileId}-context`)\n : null;\n if (!profileContext) return null;\n\n const parsed = JSON.parse(profileContext) as { projectId?: unknown };\n return typeof parsed.projectId === 'string' && parsed.projectId.length > 0\n ? parsed.projectId\n : null;\n } catch {\n return null;\n }\n}\n\nfunction hasSessionAuthErrors(errors: ReadonlyArray<GraphQLErrorLike> = []): boolean {\n return errors.some((error) => {\n const code = typeof error.extensions?.code === 'string' ? error.extensions.code : '';\n const errorCode =\n typeof error.extensions?.errorCode === 'string' ? error.extensions.errorCode : '';\n const message = (error.message ?? '').toLowerCase();\n\n return (\n code === 'UNAUTHENTICATED' ||\n errorCode === 'TOKEN_EXPIRED' ||\n message.includes('token_expired') ||\n message.includes('token expired') ||\n message.includes('session expired') ||\n message.includes('unauthorized') ||\n message.includes('unauthenticated') ||\n message.includes('authentication required') ||\n message.includes('workspace context missing')\n );\n });\n}\n\nfunction isMutationOperation(query: Parameters<typeof getMainDefinition>[0]): boolean {\n const definition = getMainDefinition(query);\n return definition.kind === 'OperationDefinition' && definition.operation === 'mutation';\n}\n\nfunction hasGraphQLResultErrors(result: FetchResult<Record<string, unknown>>): boolean {\n const errors = result.errors;\n return Array.isArray(errors) && errors.length > 0;\n}\n\nfunction createMutationAutoRefreshLink(\n getClient: () => InstanceType<typeof ApolloClient> | null\n): ApolloLink {\n return new ApolloLink((operation, forward) => {\n const shouldRefreshAfterMutation =\n isMutationOperation(operation.query) &&\n operation.getContext().autoRefreshAfterMutation !== false &&\n operation.getContext().skipMutationAutoRefresh !== true;\n\n return new Observable<FetchResult<Record<string, unknown>>>((observer) => {\n const subscription = forward(operation).subscribe({\n next: (result) => {\n observer.next(result as FetchResult<Record<string, unknown>>);\n if (!shouldRefreshAfterMutation || hasGraphQLResultErrors(result)) return;\n\n void getClient()?.refetchQueries({ include: 'active' });\n },\n error: (error: unknown) => {\n observer.error(error);\n },\n complete: () => {\n observer.complete();\n },\n });\n\n return () => {\n subscription.unsubscribe();\n };\n });\n });\n}\n\nfunction findQuotaExhaustedError(\n errors: ReadonlyArray<GraphQLErrorLike> = []\n): GraphQLErrorLike | undefined {\n return errors.find((error) => {\n const code = typeof error.extensions?.code === 'string' ? error.extensions.code : '';\n const errorCode =\n typeof error.extensions?.errorCode === 'string' ? error.extensions.errorCode : '';\n // Match both the constant form and the human-readable phrase, case\n // insensitively — mirrors what `isQuotaExhaustedError` accepts at the\n // per-page level so the link doesn't miss any quota error the rest\n // of the code base already recognises.\n const message = (error.message ?? '').toLowerCase();\n return (\n code === 'QUOTA_EXHAUSTED' ||\n errorCode === 'QUOTA_EXHAUSTED' ||\n message.includes('quota_exhausted') ||\n message.includes('quota exhausted')\n );\n });\n}\n\n/**\n * Runs the initial data query inside the ApolloProvider tree so the result\n * lands in the MFE's own Apollo cache (not the shell's).\n */\nconst InitialDataPrefetcher: FC<PropsWithChildren<{ workspaceId?: string | null }>> = ({\n workspaceId,\n children,\n}) => {\n useInitialDataLoader({ workspaceId });\n return <>{children}</>;\n};\n\n/**\n * Context value for VibeControls microfrontend\n */\ntype VibeControlsContextValue = VibeControlsRootProps;\n\nconst VibeControlsContext = createContext<VibeControlsContextValue | null>(null);\n\n/**\n * Hook to access VibeControls context\n */\nexport const useVibeControls = () => {\n const context = use(VibeControlsContext);\n if (!context) {\n throw new Error('useVibeControls must be used within VibeControlsProvider');\n }\n return context;\n};\n\n/**\n * Hook for navigation within the VibeControls microfrontend.\n * Uses the shell's navigate function if available, falling back to\n * React Router's useNavigate. Automatically prepends basePath.\n *\n * Usage:\n * const navigate = useVibeNavigate();\n * navigate('/agents/123'); // navigates to /vibecontrols/agents/123\n */\nexport const useVibeNavigate = () => {\n const { navigate: shellNavigate, basePath } = useVibeControls();\n const routerNavigate = useRouterNavigate();\n\n return useCallback(\n (path: string) => {\n const fullPath = basePath && basePath !== '/' ? `${basePath}${path}` : path;\n if (shellNavigate) {\n shellNavigate(fullPath);\n } else {\n routerNavigate(fullPath);\n }\n },\n [shellNavigate, basePath, routerNavigate]\n );\n};\n\n/**\n * Provider component for VibeControls microfrontend\n * Sets up routing, Apollo Client with auth error detection, and context.\n *\n * The Apollo Client is configured with:\n * - An onError link that detects UNAUTHENTICATED / FORBIDDEN responses and\n * invokes the `onAuthError` prop so the host shell can trigger logout.\n * - A dynamic auth link that reads the token per-request (not baked in at\n * client creation time), so token refreshes take effect immediately.\n */\nexport const VibeControlsProvider: FC<PropsWithChildren<VibeControlsRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n projectId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n onAgentSelect,\n onSessionStart,\n onVibeActivate,\n defaultView,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n}) => {\n // Per-instance QueryClient so multiple MFE mounts don't share cache\n const [queryClient] = useState(() => {\n const client = new QueryClient({\n mutationCache: new MutationCache({\n onSuccess: () => {\n void client.invalidateQueries();\n },\n }),\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000, // 5 minutes\n retry: 2,\n },\n },\n });\n return client;\n });\n\n // Store tokens and callbacks in refs so the Apollo Client (and its cache)\n // survives token refreshes. The setContext auth link reads from refs\n // per-request, so refreshed tokens take effect immediately without\n // destroying the InMemoryCache.\n const authTokenRef = useRef(authToken);\n const workspaceTokenRef = useRef(workspaceToken);\n const workspaceIdRef = useRef(workspaceId);\n const organizationIdRef = useRef(organizationId);\n const tenantIdRef = useRef(tenantId);\n const projectIdRef = useRef(projectId);\n const onAuthErrorRef = useRef(onAuthError);\n useEffect(() => {\n authTokenRef.current = authToken;\n }, [authToken]);\n useEffect(() => {\n workspaceTokenRef.current = workspaceToken;\n }, [workspaceToken]);\n useEffect(() => {\n workspaceIdRef.current = workspaceId;\n }, [workspaceId]);\n useEffect(() => {\n organizationIdRef.current = organizationId;\n }, [organizationId]);\n useEffect(() => {\n tenantIdRef.current = tenantId;\n }, [tenantId]);\n useEffect(() => {\n projectIdRef.current = projectId;\n }, [projectId]);\n useEffect(() => {\n onAuthErrorRef.current = onAuthError;\n }, [onAuthError]);\n\n // Build Apollo Client with error link + dynamic auth link.\n // Only recreated when apiGatewayUrl changes (different endpoint = different client).\n //\n // SCOPE NOTE (codex #128 review):\n // This client — and the `errorLink` below — is ONLY used when the microfe\n // mounts in standalone mode (no `basePath`, e.g. `bun run dev` against\n // this package directly). When integrated into an app shell (the\n // production path) `basePath` is set, the local ApolloProvider is\n // skipped further down, and mutations go through the shell's\n // `MultiGatewayProvider` Apollo client. In that integrated mode the\n // global error-dispatch path here does NOT fire — quota-handling\n // relies on the per-page `isQuotaExhaustedError(result.error)`\n // checks (e.g. TargetsPage.handleCreate) which still render the\n // local QuotaExhaustedDialog. Tracked as a follow-up: lift this\n // link into fe-libs `MultiGatewayProvider` so the global event\n // dispatches in shell mode too.\n const apolloClient = useMemo(() => {\n // Error link (Apollo v4 pattern): detect authentication failures + quota.\n //\n // Quota: we use `errorPolicy: 'all'` for mutations, so GraphQL errors land\n // on `result.error` instead of rejecting the mutation promise. Most\n // callsites only have a `catch` block, so quota errors silently disappear\n // (BOFF-2625). Dispatching a CustomEvent here lets a single provider\n // (QuotaExhaustedProvider) render the dialog for every mutation without\n // touching individual handlers. Per-page handlers that already react to\n // `isQuotaExhaustedError(result.error)` still work — both paths just open\n // the same modal state.\n const errorLink = onError(({ error, operation }) => {\n if (CombinedGraphQLErrors.is(error)) {\n // Auth first: a response can carry BOTH an UNAUTHENTICATED and a\n // QUOTA_EXHAUSTED error (token expired mid-request that also\n // triggered a quota check). If we returned after dispatching the\n // quota event, the host shell would never get the auth signal and\n // the session would stay broken behind the modal. The quota\n // dialog is moot for an expired session anyway.\n if (hasSessionAuthErrors(error.errors)) {\n console.warn('[VibeControls] Auth error detected — notifying host shell');\n onAuthErrorRef.current?.();\n return;\n }\n\n const quotaError = findQuotaExhaustedError(error.errors);\n if (quotaError) {\n const detail: QuotaExhaustedEventDetail = {\n kind: inferQuotaKindFromOperation(operation?.operationName),\n message: quotaError.message,\n operationName: operation?.operationName ?? null,\n };\n window.dispatchEvent(\n new CustomEvent<QuotaExhaustedEventDetail>(QUOTA_EXHAUSTED_EVENT, { detail })\n );\n return;\n }\n }\n\n if (error instanceof Error && isAuthNetworkError(error)) {\n console.warn('[VibeControls] Auth network error — notifying host shell');\n onAuthErrorRef.current?.();\n }\n });\n\n // Auth link: dynamically attach dual-token auth and workspace context per-request.\n // Reads from refs so token refreshes don't recreate the client.\n const authLink = setContext((_, { headers }) => {\n const resolvedAuthToken = authTokenRef.current ?? getStoredAuthToken();\n // Fall back to storage when workspaceId prop is empty (e.g. during initial render\n // before ActiveContextProvider has propagated the value through workspaceBaseProps)\n const resolvedWorkspaceId = workspaceIdRef.current || getStoredWorkspaceId();\n const resolvedProjectId = projectIdRef.current || getStoredProjectId();\n const rawWorkspaceToken = workspaceTokenRef.current ?? getStoredWorkspaceToken();\n const resolvedWorkspaceToken = isWorkspaceTokenValidForContext(rawWorkspaceToken, {\n workspaceId: resolvedWorkspaceId,\n organizationId: organizationIdRef.current,\n tenantId: tenantIdRef.current,\n projectId: resolvedProjectId,\n })\n ? rawWorkspaceToken\n : null;\n\n return {\n headers: {\n ...headers,\n ...(resolvedAuthToken ? { Authorization: `Bearer ${resolvedAuthToken}` } : {}),\n ...(resolvedWorkspaceToken\n ? { 'X-Workspace-Authorization': `Bearer ${resolvedWorkspaceToken}` }\n : {}),\n ...(resolvedWorkspaceId ? { 'x-workspace-id': resolvedWorkspaceId } : {}),\n ...(resolvedProjectId ? { 'x-project-id': resolvedProjectId } : {}),\n },\n };\n });\n\n const httpLink = new BatchHttpLink({\n uri: apiGatewayUrl ? `${apiGatewayUrl}/workspaces/graphql` : '/workspaces/graphql',\n batchMax: 10,\n batchInterval: 10, // ms — tight window so latency isn't hurt\n });\n\n let client: InstanceType<typeof ApolloClient> | null = null;\n const mutationAutoRefreshLink = createMutationAutoRefreshLink(() => client);\n\n client = new ApolloClient({\n link: from([errorLink, mutationAutoRefreshLink, authLink, httpLink]),\n cache: buildVibeControlsCache(),\n defaultOptions: {\n watchQuery: { fetchPolicy: 'cache-first', errorPolicy: 'all' },\n query: { errorPolicy: 'all' },\n mutate: { errorPolicy: 'all' },\n },\n });\n return client;\n }, [apiGatewayUrl]);\n\n const contextValue: VibeControlsContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n projectId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n onAgentSelect,\n onSessionStart,\n onVibeActivate,\n defaultView,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n };\n\n const isPublicLinkRoute =\n typeof window !== 'undefined' &&\n /^\\/vibecontrols\\/(share|deck)\\//.test(window.location.pathname);\n\n // Core providers that wrap the children\n // PermissionProvider requires AuthProvider which is provided by the shell.\n // It fetches permissions from wspace-rbac-svc via the workspace gateway.\n // AgentEventsProvider manages WebSocket connections to agents for real-time notifications.\n //\n // QuotaExhaustedProvider wraps both public and authenticated trees because\n // public deck routes (`/vibecontrols/deck/:token`) still run mutations like\n // `ExecuteSharedVibeDeckButton` that can hit command-execution quota\n // (#128 review codex P2). The provider is cheap and route-agnostic — it\n // only listens for a window event.\n const authedTree = (\n <InitialDataPrefetcher workspaceId={workspaceId}>\n <PermissionProvider scopeId={workspaceId ?? null} gateway=\"workspace\">\n <AgentEventsProvider\n toastPosition=\"bottom-right\"\n autoDismissDelay={5000}\n maxVisibleToasts={3}\n >\n {children}\n </AgentEventsProvider>\n </PermissionProvider>\n </InitialDataPrefetcher>\n );\n\n const routedChildren = (\n <QuotaExhaustedProvider>{isPublicLinkRoute ? children : authedTree}</QuotaExhaustedProvider>\n );\n\n const coreProviders = (\n <QueryClientProvider client={queryClient}>\n {basePath ? (\n <VibeControlsContext.Provider value={contextValue}>\n {routedChildren}\n </VibeControlsContext.Provider>\n ) : (\n <ApolloProvider client={apolloClient}>\n <VibeControlsContext.Provider value={contextValue}>\n {routedChildren}\n </VibeControlsContext.Provider>\n </ApolloProvider>\n )}\n </QueryClientProvider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (basePath) {\n // Integrated mode: shell already has BrowserRouter, don't create nested router\n return coreProviders;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{coreProviders}</MemoryRouter>;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgCA,SAAS,KAAoC;AAC3C,KAAI;EACF,IAAM,IAAc,eAAe,QAAQ,mCAAmC;AAC9E,MAAI,EAAa,QAAO;EAExB,IAAM,IAAkB,eAAe,QAAQ,oBAAoB,EAC7D,IAAiB,IACnB,aAAa,QAAQ,QAAQ,EAAgB,UAAU,GACvD;AACJ,MAAI,CAAC,EAAgB,QAAO;EAE5B,IAAM,IAAS,KAAK,MAAM,EAAe;AACzC,SAAO,OAAO,EAAO,aAAc,YAAY,EAAO,UAAU,SAAS,IACrE,EAAO,YACP;SACE;AACN,SAAO;;;AAIX,SAAS,EAAqB,IAA0C,EAAE,EAAW;AACnF,QAAO,EAAO,MAAM,MAAU;EAC5B,IAAM,IAAO,OAAO,EAAM,YAAY,QAAS,WAAW,EAAM,WAAW,OAAO,IAC5E,IACJ,OAAO,EAAM,YAAY,aAAc,WAAW,EAAM,WAAW,YAAY,IAC3E,KAAW,EAAM,WAAW,IAAI,aAAa;AAEnD,SACE,MAAS,qBACT,MAAc,mBACd,EAAQ,SAAS,gBAAgB,IACjC,EAAQ,SAAS,gBAAgB,IACjC,EAAQ,SAAS,kBAAkB,IACnC,EAAQ,SAAS,eAAe,IAChC,EAAQ,SAAS,kBAAkB,IACnC,EAAQ,SAAS,0BAA0B,IAC3C,EAAQ,SAAS,4BAA4B;GAE/C;;AAGJ,SAAS,EAAoB,GAAyD;CACpF,IAAM,IAAa,EAAkB,EAAM;AAC3C,QAAO,EAAW,SAAS,yBAAyB,EAAW,cAAc;;AAG/E,SAAS,EAAuB,GAAuD;CACrF,IAAM,IAAS,EAAO;AACtB,QAAO,MAAM,QAAQ,EAAO,IAAI,EAAO,SAAS;;AAGlD,SAAS,EACP,GACY;AACZ,QAAO,IAAI,GAAY,GAAW,MAAY;EAC5C,IAAM,IACJ,EAAoB,EAAU,MAAM,IACpC,EAAU,YAAY,CAAC,6BAA6B,MACpD,EAAU,YAAY,CAAC,4BAA4B;AAErD,SAAO,IAAI,GAAkD,MAAa;GACxE,IAAM,IAAe,EAAQ,EAAU,CAAC,UAAU;IAChD,OAAO,MAAW;AAChB,OAAS,KAAK,EAA+C,EACzD,GAAC,KAA8B,EAAuB,EAAO,KAE5D,GAAW,EAAE,eAAe,EAAE,SAAS,UAAU,CAAC;;IAEzD,QAAQ,MAAmB;AACzB,OAAS,MAAM,EAAM;;IAEvB,gBAAgB;AACd,OAAS,UAAU;;IAEtB,CAAC;AAEF,gBAAa;AACX,MAAa,aAAa;;IAE5B;GACF;;AAGJ,SAAS,EACP,IAA0C,EAAE,EACd;AAC9B,QAAO,EAAO,MAAM,MAAU;EAC5B,IAAM,IAAO,OAAO,EAAM,YAAY,QAAS,WAAW,EAAM,WAAW,OAAO,IAC5E,IACJ,OAAO,EAAM,YAAY,aAAc,WAAW,EAAM,WAAW,YAAY,IAK3E,KAAW,EAAM,WAAW,IAAI,aAAa;AACnD,SACE,MAAS,qBACT,MAAc,qBACd,EAAQ,SAAS,kBAAkB,IACnC,EAAQ,SAAS,kBAAkB;GAErC;;AAOJ,IAAM,KAAiF,EACrF,gBACA,mBAEA,EAAqB,EAAE,gBAAa,CAAC,EAC9B,kBAAA,GAAA,EAAG,aAAY,CAAA,GAQlB,IAAsB,EAA+C,KAAK,EAKnE,UAAwB;CACnC,IAAM,IAAU,EAAI,EAAoB;AACxC,KAAI,CAAC,EACH,OAAU,MAAM,2DAA2D;AAE7E,QAAO;GAYI,UAAwB;CACnC,IAAM,EAAE,UAAU,GAAe,gBAAa,GAAiB,EACzD,IAAiB,GAAmB;AAE1C,QAAO,GACJ,MAAiB;EAChB,IAAM,IAAW,KAAY,MAAa,MAAM,GAAG,IAAW,MAAS;AACvE,EAAI,IACF,EAAc,EAAS,GAEvB,EAAe,EAAS;IAG5B;EAAC;EAAe;EAAU;EAAe,CAC1C;GAaU,KAAsE,EACjF,aACA,aACA,aACA,gBACA,gBACA,mBACA,cACA,aACA,kBACA,cACA,mBACA,kBACA,mBACA,mBACA,gBACA,qBACA,wBACA,qBACI;CAEJ,IAAM,CAAC,KAAe,QAAe;EACnC,IAAM,IAAS,IAAI,EAAY;GAC7B,eAAe,IAAI,EAAc,EAC/B,iBAAiB;AACV,MAAO,mBAAmB;MAElC,CAAC;GACF,gBAAgB,EACd,SAAS;IACP,WAAW,MAAS;IACpB,OAAO;IACR,EACF;GACF,CAAC;AACF,SAAO;GACP,EAMI,IAAe,EAAO,EAAU,EAChC,IAAoB,EAAO,EAAe,EAC1C,IAAiB,EAAO,EAAY,EACpC,IAAoB,EAAO,EAAe,EAC1C,IAAc,EAAO,EAAS,EAC9B,IAAe,EAAO,EAAU,EAChC,IAAiB,EAAO,EAAY;AAmB1C,CAlBA,QAAgB;AACd,IAAa,UAAU;IACtB,CAAC,EAAU,CAAC,EACf,QAAgB;AACd,IAAkB,UAAU;IAC3B,CAAC,EAAe,CAAC,EACpB,QAAgB;AACd,IAAe,UAAU;IACxB,CAAC,EAAY,CAAC,EACjB,QAAgB;AACd,IAAkB,UAAU;IAC3B,CAAC,EAAe,CAAC,EACpB,QAAgB;AACd,IAAY,UAAU;IACrB,CAAC,EAAS,CAAC,EACd,QAAgB;AACd,IAAa,UAAU;IACtB,CAAC,EAAU,CAAC,EACf,QAAgB;AACd,IAAe,UAAU;IACxB,CAAC,EAAY,CAAC;CAkBjB,IAAM,KAAe,SAAc;EAWjC,IAAM,IAAY,GAAS,EAAE,UAAO,mBAAgB;AAClD,OAAI,EAAsB,GAAG,EAAM,EAAE;AAOnC,QAAI,EAAqB,EAAM,OAAO,EAAE;AAEtC,KADA,QAAQ,KAAK,4DAA4D,EACzE,EAAe,WAAW;AAC1B;;IAGF,IAAM,IAAa,EAAwB,EAAM,OAAO;AACxD,QAAI,GAAY;KACd,IAAM,IAAoC;MACxC,MAAM,EAA4B,GAAW,cAAc;MAC3D,SAAS,EAAW;MACpB,eAAe,GAAW,iBAAiB;MAC5C;AACD,YAAO,cACL,IAAI,YAAuC,GAAuB,EAAE,WAAQ,CAAC,CAC9E;AACD;;;AAIJ,GAAI,aAAiB,SAAS,GAAmB,EAAM,KACrD,QAAQ,KAAK,2DAA2D,EACxE,EAAe,WAAW;IAE5B,EAII,IAAW,GAAY,GAAG,EAAE,iBAAc;GAC9C,IAAM,IAAoB,EAAa,WAAW,GAAoB,EAGhE,IAAsB,EAAe,WAAW,IAAsB,EACtE,IAAoB,EAAa,WAAW,IAAoB,EAChE,IAAoB,EAAkB,WAAW,IAAyB,EAC1E,IAAyB,GAAgC,GAAmB;IAChF,aAAa;IACb,gBAAgB,EAAkB;IAClC,UAAU,EAAY;IACtB,WAAW;IACZ,CAAC,GACE,IACA;AAEJ,UAAO,EACL,SAAS;IACP,GAAG;IACH,GAAI,IAAoB,EAAE,eAAe,UAAU,KAAqB,GAAG,EAAE;IAC7E,GAAI,IACA,EAAE,6BAA6B,UAAU,KAA0B,GACnE,EAAE;IACN,GAAI,IAAsB,EAAE,kBAAkB,GAAqB,GAAG,EAAE;IACxE,GAAI,IAAoB,EAAE,gBAAgB,GAAmB,GAAG,EAAE;IACnE,EACF;IACD,EAEI,IAAW,IAAI,EAAc;GACjC,KAAK,IAAgB,GAAG,EAAc,uBAAuB;GAC7D,UAAU;GACV,eAAe;GAChB,CAAC,EAEE,IAAmD;AAYvD,SATA,IAAS,IAAI,GAAa;GACxB,MAAM,EAAK;IAAC;IAHkB,QAAoC,EAAO;IAGzB;IAAU;IAAS,CAAC;GACpE,OAAO,GAAwB;GAC/B,gBAAgB;IACd,YAAY;KAAE,aAAa;KAAe,aAAa;KAAO;IAC9D,OAAO,EAAE,aAAa,OAAO;IAC7B,QAAQ,EAAE,aAAa,OAAO;IAC/B;GACF,CAAC,EACK;IACN,CAAC,EAAc,CAAC,EAEb,IAAyC;EAC7C;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,EA8BK,IACJ,kBAAC,GAAD,EAAA,UA5BA,OAAO,SAAW,OAClB,kCAAkC,KAAK,OAAO,SAAS,SAAS,GA2BnB,IAd7C,kBAAC,GAAD;EAAoC;YAClC,kBAAC,IAAD;GAAoB,SAAS,KAAe;GAAM,SAAQ;aACxD,kBAAC,GAAD;IACE,eAAc;IACd,kBAAkB;IAClB,kBAAkB;IAEjB;IACmB,CAAA;GACH,CAAA;EACC,CAAA,EAIoE,CAAA,EAGxF,IACJ,kBAAC,GAAD;EAAqB,QAAQ;YAC1B,IACC,kBAAC,EAAoB,UAArB;GAA8B,OAAO;aAClC;GAC4B,CAAA,GAE/B,kBAAC,GAAD;GAAgB,QAAQ;aACtB,kBAAC,EAAoB,UAArB;IAA8B,OAAO;cAClC;IAC4B,CAAA;GAChB,CAAA;EAEC,CAAA;AAWxB,QANI,IAEK,IAIF,kBAAC,GAAD,EAAA,UAAe,GAA6B,CAAA"}
1
+ {"version":3,"file":"VibeControlsProvider.js","names":[],"sources":["../../src/providers/VibeControlsProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, use, useMemo, useCallback, useRef, useEffect, useState } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router';\nimport { ApolloClient, ApolloLink, type FetchResult, Observable, from } from '@apollo/client';\nimport { buildVibeControlsCache } from '@/lib/apolloCache';\nimport { BatchHttpLink } from '@apollo/client/link/batch-http';\nimport { onError } from '@apollo/client/link/error';\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { setContext } from '@apollo/client/link/context';\nimport { ApolloProvider } from '@apollo/client/react';\nimport { getMainDefinition } from '@apollo/client/utilities';\nimport { MutationCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport {\n getStoredAuthToken,\n getStoredWorkspaceToken,\n isWorkspaceTokenValidForContext,\n} from '@burdenoff/fe-libs/shared/graphql';\nimport { getStoredWorkspaceId } from '@burdenoff/fe-libs/shared/graphql/fetch';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { isAuthNetworkError } from '@burdenoff/fe-libs/shared/utils';\nimport type { VibeControlsRootProps } from '../types';\nimport { configureSpeechEngine } from '../services/speech';\nimport { AgentEventsProvider } from './AgentEventsProvider';\nimport { QuotaExhaustedProvider } from './QuotaExhaustedProvider';\nimport { useInitialDataLoader } from '../hooks/useInitialDataLoader';\nimport {\n QUOTA_EXHAUSTED_EVENT,\n inferQuotaKindFromOperation,\n type QuotaExhaustedEventDetail,\n} from '../utils/quotaUtils';\n\ntype GraphQLErrorLike = { message?: string; extensions?: Record<string, unknown> };\n\nfunction getStoredProjectId(): string | null {\n try {\n const fromSession = sessionStorage.getItem('burdenoff-active-context-project');\n if (fromSession) return fromSession;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n const profileContext = activeProfileId\n ? localStorage.getItem(`bf-p-${activeProfileId}-context`)\n : null;\n if (!profileContext) return null;\n\n const parsed = JSON.parse(profileContext) as { projectId?: unknown };\n return typeof parsed.projectId === 'string' && parsed.projectId.length > 0\n ? parsed.projectId\n : null;\n } catch {\n return null;\n }\n}\n\nfunction hasSessionAuthErrors(errors: ReadonlyArray<GraphQLErrorLike> = []): boolean {\n return errors.some((error) => {\n const code = typeof error.extensions?.code === 'string' ? error.extensions.code : '';\n const errorCode =\n typeof error.extensions?.errorCode === 'string' ? error.extensions.errorCode : '';\n const message = (error.message ?? '').toLowerCase();\n\n return (\n code === 'UNAUTHENTICATED' ||\n errorCode === 'TOKEN_EXPIRED' ||\n message.includes('token_expired') ||\n message.includes('token expired') ||\n message.includes('session expired') ||\n message.includes('unauthorized') ||\n message.includes('unauthenticated') ||\n message.includes('authentication required') ||\n message.includes('workspace context missing')\n );\n });\n}\n\nfunction isMutationOperation(query: Parameters<typeof getMainDefinition>[0]): boolean {\n const definition = getMainDefinition(query);\n return definition.kind === 'OperationDefinition' && definition.operation === 'mutation';\n}\n\nfunction hasGraphQLResultErrors(result: FetchResult<Record<string, unknown>>): boolean {\n const errors = result.errors;\n return Array.isArray(errors) && errors.length > 0;\n}\n\nfunction createMutationAutoRefreshLink(\n getClient: () => InstanceType<typeof ApolloClient> | null\n): ApolloLink {\n return new ApolloLink((operation, forward) => {\n const shouldRefreshAfterMutation =\n isMutationOperation(operation.query) &&\n operation.getContext().autoRefreshAfterMutation !== false &&\n operation.getContext().skipMutationAutoRefresh !== true;\n\n return new Observable<FetchResult<Record<string, unknown>>>((observer) => {\n const subscription = forward(operation).subscribe({\n next: (result) => {\n observer.next(result as FetchResult<Record<string, unknown>>);\n if (!shouldRefreshAfterMutation || hasGraphQLResultErrors(result)) return;\n\n void getClient()?.refetchQueries({ include: 'active' });\n },\n error: (error: unknown) => {\n observer.error(error);\n },\n complete: () => {\n observer.complete();\n },\n });\n\n return () => {\n subscription.unsubscribe();\n };\n });\n });\n}\n\nfunction findQuotaExhaustedError(\n errors: ReadonlyArray<GraphQLErrorLike> = []\n): GraphQLErrorLike | undefined {\n return errors.find((error) => {\n const code = typeof error.extensions?.code === 'string' ? error.extensions.code : '';\n const errorCode =\n typeof error.extensions?.errorCode === 'string' ? error.extensions.errorCode : '';\n // Match both the constant form and the human-readable phrase, case\n // insensitively — mirrors what `isQuotaExhaustedError` accepts at the\n // per-page level so the link doesn't miss any quota error the rest\n // of the code base already recognises.\n const message = (error.message ?? '').toLowerCase();\n return (\n code === 'QUOTA_EXHAUSTED' ||\n errorCode === 'QUOTA_EXHAUSTED' ||\n message.includes('quota_exhausted') ||\n message.includes('quota exhausted')\n );\n });\n}\n\n/**\n * Runs the initial data query inside the ApolloProvider tree so the result\n * lands in the MFE's own Apollo cache (not the shell's).\n */\nconst InitialDataPrefetcher: FC<PropsWithChildren<{ workspaceId?: string | null }>> = ({\n workspaceId,\n children,\n}) => {\n useInitialDataLoader({ workspaceId });\n return <>{children}</>;\n};\n\n/**\n * Context value for VibeControls microfrontend\n */\ntype VibeControlsContextValue = VibeControlsRootProps;\n\nconst VibeControlsContext = createContext<VibeControlsContextValue | null>(null);\n\n/**\n * Hook to access VibeControls context\n */\nexport const useVibeControls = () => {\n const context = use(VibeControlsContext);\n if (!context) {\n throw new Error('useVibeControls must be used within VibeControlsProvider');\n }\n return context;\n};\n\n/**\n * Hook for navigation within the VibeControls microfrontend.\n * Uses the shell's navigate function if available, falling back to\n * React Router's useNavigate. Automatically prepends basePath.\n *\n * Usage:\n * const navigate = useVibeNavigate();\n * navigate('/agents/123'); // navigates to /vibecontrols/agents/123\n */\nexport const useVibeNavigate = () => {\n const { navigate: shellNavigate, basePath } = useVibeControls();\n const routerNavigate = useRouterNavigate();\n\n return useCallback(\n (path: string) => {\n const fullPath = basePath && basePath !== '/' ? `${basePath}${path}` : path;\n if (shellNavigate) {\n shellNavigate(fullPath);\n } else {\n routerNavigate(fullPath);\n }\n },\n [shellNavigate, basePath, routerNavigate]\n );\n};\n\n/**\n * Provider component for VibeControls microfrontend\n * Sets up routing, Apollo Client with auth error detection, and context.\n *\n * The Apollo Client is configured with:\n * - An onError link that detects UNAUTHENTICATED / FORBIDDEN responses and\n * invokes the `onAuthError` prop so the host shell can trigger logout.\n * - A dynamic auth link that reads the token per-request (not baked in at\n * client creation time), so token refreshes take effect immediately.\n */\nexport const VibeControlsProvider: FC<PropsWithChildren<VibeControlsRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n projectId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n onAgentSelect,\n onSessionStart,\n onVibeActivate,\n defaultView,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n ttsEndpoint,\n}) => {\n // Per-instance QueryClient so multiple MFE mounts don't share cache\n const [queryClient] = useState(() => {\n const client = new QueryClient({\n mutationCache: new MutationCache({\n onSuccess: () => {\n void client.invalidateQueries();\n },\n }),\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000, // 5 minutes\n retry: 2,\n },\n },\n });\n return client;\n });\n\n // Store tokens and callbacks in refs so the Apollo Client (and its cache)\n // survives token refreshes. The setContext auth link reads from refs\n // per-request, so refreshed tokens take effect immediately without\n // destroying the InMemoryCache.\n const authTokenRef = useRef(authToken);\n const workspaceTokenRef = useRef(workspaceToken);\n const workspaceIdRef = useRef(workspaceId);\n const organizationIdRef = useRef(organizationId);\n const tenantIdRef = useRef(tenantId);\n const projectIdRef = useRef(projectId);\n const onAuthErrorRef = useRef(onAuthError);\n useEffect(() => {\n authTokenRef.current = authToken;\n }, [authToken]);\n\n /**\n * Point the assistant's speech stack at the hosted TTS proxy.\n *\n * ★★ Without this the assistant speaks in DEVICE voices only. `getSpeechEngine`\n * falls back to `browserSpeechEngine` whenever no endpoint is configured, so\n * the feature degrades silently — you get a robotic system voice and no voice\n * picker, because the selector hides itself when fewer than two voices are\n * available for the locale. Nothing errors, nothing logs.\n *\n * ★ Wired HERE rather than exported for the host to call. Nothing outside this\n * module graph would import `configureSpeechEngine`, and fe-libs is bundled\n * with rolldown — an unreferenced export is tree-shaken out of `dist`, so the\n * function would not exist at runtime to be called. HealthyBowl learned this\n * the same way.\n *\n * ★★ The token is read through a REF, not captured. `configureSpeechEngine` is\n * deliberately idempotent — resetting the memoised engine every render would\n * cancel playback mid-reply — so it keeps the FIRST `getHeaders` it is given.\n * A captured token would work until it refreshed and then fail mid-sentence,\n * which is the hardest place to notice. The proxy bills per character, so an\n * endpoint reachable without headers is a metered API open to anyone who reads\n * the bundle.\n */\n const getTtsHeaders = useCallback(\n (): Record<string, string> =>\n authTokenRef.current ? { authorization: `Bearer ${authTokenRef.current}` } : {},\n []\n );\n configureSpeechEngine({ ttsEndpoint, getHeaders: getTtsHeaders });\n useEffect(() => {\n workspaceTokenRef.current = workspaceToken;\n }, [workspaceToken]);\n useEffect(() => {\n workspaceIdRef.current = workspaceId;\n }, [workspaceId]);\n useEffect(() => {\n organizationIdRef.current = organizationId;\n }, [organizationId]);\n useEffect(() => {\n tenantIdRef.current = tenantId;\n }, [tenantId]);\n useEffect(() => {\n projectIdRef.current = projectId;\n }, [projectId]);\n useEffect(() => {\n onAuthErrorRef.current = onAuthError;\n }, [onAuthError]);\n\n // Build Apollo Client with error link + dynamic auth link.\n // Only recreated when apiGatewayUrl changes (different endpoint = different client).\n //\n // SCOPE NOTE (codex #128 review):\n // This client — and the `errorLink` below — is ONLY used when the microfe\n // mounts in standalone mode (no `basePath`, e.g. `bun run dev` against\n // this package directly). When integrated into an app shell (the\n // production path) `basePath` is set, the local ApolloProvider is\n // skipped further down, and mutations go through the shell's\n // `MultiGatewayProvider` Apollo client. In that integrated mode the\n // global error-dispatch path here does NOT fire — quota-handling\n // relies on the per-page `isQuotaExhaustedError(result.error)`\n // checks (e.g. TargetsPage.handleCreate) which still render the\n // local QuotaExhaustedDialog. Tracked as a follow-up: lift this\n // link into fe-libs `MultiGatewayProvider` so the global event\n // dispatches in shell mode too.\n const apolloClient = useMemo(() => {\n // Error link (Apollo v4 pattern): detect authentication failures + quota.\n //\n // Quota: we use `errorPolicy: 'all'` for mutations, so GraphQL errors land\n // on `result.error` instead of rejecting the mutation promise. Most\n // callsites only have a `catch` block, so quota errors silently disappear\n // (BOFF-2625). Dispatching a CustomEvent here lets a single provider\n // (QuotaExhaustedProvider) render the dialog for every mutation without\n // touching individual handlers. Per-page handlers that already react to\n // `isQuotaExhaustedError(result.error)` still work — both paths just open\n // the same modal state.\n const errorLink = onError(({ error, operation }) => {\n if (CombinedGraphQLErrors.is(error)) {\n // Auth first: a response can carry BOTH an UNAUTHENTICATED and a\n // QUOTA_EXHAUSTED error (token expired mid-request that also\n // triggered a quota check). If we returned after dispatching the\n // quota event, the host shell would never get the auth signal and\n // the session would stay broken behind the modal. The quota\n // dialog is moot for an expired session anyway.\n if (hasSessionAuthErrors(error.errors)) {\n console.warn('[VibeControls] Auth error detected — notifying host shell');\n onAuthErrorRef.current?.();\n return;\n }\n\n const quotaError = findQuotaExhaustedError(error.errors);\n if (quotaError) {\n const detail: QuotaExhaustedEventDetail = {\n kind: inferQuotaKindFromOperation(operation?.operationName),\n message: quotaError.message,\n operationName: operation?.operationName ?? null,\n };\n window.dispatchEvent(\n new CustomEvent<QuotaExhaustedEventDetail>(QUOTA_EXHAUSTED_EVENT, { detail })\n );\n return;\n }\n }\n\n if (error instanceof Error && isAuthNetworkError(error)) {\n console.warn('[VibeControls] Auth network error — notifying host shell');\n onAuthErrorRef.current?.();\n }\n });\n\n // Auth link: dynamically attach dual-token auth and workspace context per-request.\n // Reads from refs so token refreshes don't recreate the client.\n const authLink = setContext((_, { headers }) => {\n const resolvedAuthToken = authTokenRef.current ?? getStoredAuthToken();\n // Fall back to storage when workspaceId prop is empty (e.g. during initial render\n // before ActiveContextProvider has propagated the value through workspaceBaseProps)\n const resolvedWorkspaceId = workspaceIdRef.current || getStoredWorkspaceId();\n const resolvedProjectId = projectIdRef.current || getStoredProjectId();\n const rawWorkspaceToken = workspaceTokenRef.current ?? getStoredWorkspaceToken();\n const resolvedWorkspaceToken = isWorkspaceTokenValidForContext(rawWorkspaceToken, {\n workspaceId: resolvedWorkspaceId,\n organizationId: organizationIdRef.current,\n tenantId: tenantIdRef.current,\n projectId: resolvedProjectId,\n })\n ? rawWorkspaceToken\n : null;\n\n return {\n headers: {\n ...headers,\n ...(resolvedAuthToken ? { Authorization: `Bearer ${resolvedAuthToken}` } : {}),\n ...(resolvedWorkspaceToken\n ? { 'X-Workspace-Authorization': `Bearer ${resolvedWorkspaceToken}` }\n : {}),\n ...(resolvedWorkspaceId ? { 'x-workspace-id': resolvedWorkspaceId } : {}),\n ...(resolvedProjectId ? { 'x-project-id': resolvedProjectId } : {}),\n },\n };\n });\n\n const httpLink = new BatchHttpLink({\n uri: apiGatewayUrl ? `${apiGatewayUrl}/workspaces/graphql` : '/workspaces/graphql',\n batchMax: 10,\n batchInterval: 10, // ms — tight window so latency isn't hurt\n });\n\n let client: InstanceType<typeof ApolloClient> | null = null;\n const mutationAutoRefreshLink = createMutationAutoRefreshLink(() => client);\n\n client = new ApolloClient({\n link: from([errorLink, mutationAutoRefreshLink, authLink, httpLink]),\n cache: buildVibeControlsCache(),\n defaultOptions: {\n watchQuery: { fetchPolicy: 'cache-first', errorPolicy: 'all' },\n query: { errorPolicy: 'all' },\n mutate: { errorPolicy: 'all' },\n },\n });\n return client;\n }, [apiGatewayUrl]);\n\n const contextValue: VibeControlsContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n projectId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n onAgentSelect,\n onSessionStart,\n onVibeActivate,\n defaultView,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n };\n\n const isPublicLinkRoute =\n typeof window !== 'undefined' &&\n /^\\/vibecontrols\\/(share|deck)\\//.test(window.location.pathname);\n\n // Core providers that wrap the children\n // PermissionProvider requires AuthProvider which is provided by the shell.\n // It fetches permissions from wspace-rbac-svc via the workspace gateway.\n // AgentEventsProvider manages WebSocket connections to agents for real-time notifications.\n //\n // QuotaExhaustedProvider wraps both public and authenticated trees because\n // public deck routes (`/vibecontrols/deck/:token`) still run mutations like\n // `ExecuteSharedVibeDeckButton` that can hit command-execution quota\n // (#128 review codex P2). The provider is cheap and route-agnostic — it\n // only listens for a window event.\n const authedTree = (\n <InitialDataPrefetcher workspaceId={workspaceId}>\n <PermissionProvider scopeId={workspaceId ?? null} gateway=\"workspace\">\n <AgentEventsProvider\n toastPosition=\"bottom-right\"\n autoDismissDelay={5000}\n maxVisibleToasts={3}\n >\n {children}\n </AgentEventsProvider>\n </PermissionProvider>\n </InitialDataPrefetcher>\n );\n\n const routedChildren = (\n <QuotaExhaustedProvider>{isPublicLinkRoute ? children : authedTree}</QuotaExhaustedProvider>\n );\n\n const coreProviders = (\n <QueryClientProvider client={queryClient}>\n {basePath ? (\n <VibeControlsContext.Provider value={contextValue}>\n {routedChildren}\n </VibeControlsContext.Provider>\n ) : (\n <ApolloProvider client={apolloClient}>\n <VibeControlsContext.Provider value={contextValue}>\n {routedChildren}\n </VibeControlsContext.Provider>\n </ApolloProvider>\n )}\n </QueryClientProvider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (basePath) {\n // Integrated mode: shell already has BrowserRouter, don't create nested router\n return coreProviders;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{coreProviders}</MemoryRouter>;\n};\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { configureSpeechEngine as e } from "@burdenoff/fe-libs/shared/assistant/speech/createSpeechEngine";
2
+ export { e as configureSpeechEngine };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-vibecontrols",
3
- "version": "2026.905.2",
3
+ "version": "2026.905.4",
4
4
  "description": "VibeControls microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [
@@ -62,7 +62,7 @@
62
62
  "license": "PROPRIETARY",
63
63
  "dependencies": {
64
64
  "@apollo/client": "4.1.9",
65
- "@burdenoff/fe-libs": "2026.905.5",
65
+ "@burdenoff/fe-libs": "2026.905.7",
66
66
  "@tanstack/react-query": "^5.90.16",
67
67
  "@xyflow/react": "^12.10.2",
68
68
  "clsx": "^2.1.1",