@burdenoff/microfe-vibecontrols 2026.522.5 → 2026.522.6

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.
@@ -12,23 +12,24 @@ import { PageSkeleton as u } from "../../components/shared/PageSkeleton.js";
12
12
  import { PluginHarnessPicker as d } from "../../components/ai/PluginHarnessPicker.js";
13
13
  import { AITemplatesTab as f } from "../../components/ai/AITemplatesTab.js";
14
14
  import { AIContextsTab as p } from "../../components/ai/AIContextsTab.js";
15
- import { AIPromptsTab as ee } from "../../components/ai/AIPromptsTab.js";
16
- import { AITasksTab as m } from "../../components/ai/AITasksTab.js";
17
- import { AIStatsTab as h } from "../../components/ai/AIStatsTab.js";
18
- import { AIPlaygroundTab as g } from "../../components/ai/AIPlaygroundTab.js";
19
- import { BridleConfigPanel as _ } from "../../components/agent-manager/BridleConfigPanel.js";
20
- import { AIOverviewTab as v } from "../../components/ai/AIOverviewTab.js";
21
- import { AISessionsTab as y } from "../../components/ai/AISessionsTab.js";
22
- import { ClaudeCredentialsCard as b } from "../../components/ai/ClaudeCredentialsCard.js";
23
- import { AIPlanPanel as x } from "../../components/ai/AIPlanPanel.js";
24
- import { useCallback as S, useMemo as C, useState as w } from "react";
15
+ import { AIPromptsTab as m } from "../../components/ai/AIPromptsTab.js";
16
+ import { AITasksTab as h } from "../../components/ai/AITasksTab.js";
17
+ import { AIStatsTab as g } from "../../components/ai/AIStatsTab.js";
18
+ import { AIPlaygroundTab as _ } from "../../components/ai/AIPlaygroundTab.js";
19
+ import { BridleConfigPanel as v } from "../../components/agent-manager/BridleConfigPanel.js";
20
+ import { AIOverviewTab as y } from "../../components/ai/AIOverviewTab.js";
21
+ import { AISessionsTab as b } from "../../components/ai/AISessionsTab.js";
22
+ import { ClaudeCredentialsCard as x } from "../../components/ai/ClaudeCredentialsCard.js";
23
+ import { AIPlanPanel as S } from "../../components/ai/AIPlanPanel.js";
24
+ import { AIModeToggle as C } from "../../components/ai/AIModeToggle.js";
25
+ import { useCallback as w, useMemo as T, useState as ee } from "react";
25
26
  import { useI18n as te } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
26
- import { BarChart3 as T, Brain as E, FileCode2 as D, FileText as O, LayoutDashboard as k, ListTodo as A, MapIcon as j, MessageSquare as M, MessagesSquare as N, Play as P } from "lucide-react";
27
- import { useMatch as F, useParams as I } from "react-router-dom";
28
- import { jsx as L, jsxs as R } from "react/jsx-runtime";
29
- import { MotionPage as z } from "@burdenoff/fe-libs/shared/motion";
27
+ import { BarChart3 as E, FileCode2 as D, FileText as O, LayoutDashboard as k, ListTodo as A, MessageSquare as j, MessagesSquare as M, Play as N } from "lucide-react";
28
+ import { useMatch as P, useParams as F } from "react-router-dom";
29
+ import { jsx as I, jsxs as L } from "react/jsx-runtime";
30
+ import { MotionPage as R } from "@burdenoff/fe-libs/shared/motion";
30
31
  //#region src/pages/ai/AIPage.tsx
31
- var B = [
32
+ var z = [
32
33
  {
33
34
  id: "overview",
34
35
  icon: k
@@ -43,11 +44,11 @@ var B = [
43
44
  },
44
45
  {
45
46
  id: "prompts",
46
- icon: M
47
+ icon: j
47
48
  },
48
49
  {
49
50
  id: "sessions",
50
- icon: N
51
+ icon: M
51
52
  },
52
53
  {
53
54
  id: "tasks",
@@ -55,160 +56,146 @@ var B = [
55
56
  },
56
57
  {
57
58
  id: "stats",
58
- icon: T
59
+ icon: E
59
60
  },
60
61
  {
61
62
  id: "playground",
62
- icon: P
63
+ icon: N
63
64
  },
64
65
  {
65
66
  id: "config",
66
67
  icon: k
67
68
  }
68
- ], ne = new Set(B.map((e) => e.id));
69
+ ], B = new Set(z.map((e) => e.id));
69
70
  function V() {
70
- let { t: T } = te(), { tab: D } = I(), O = F("vibecontrols/ai-tools/plan") ? "plan" : "ai", k = D && ne.has(D) ? D : "overview", A = e(), M = S((e, t) => {
71
- let n = T(e);
71
+ let { t: E } = te(), { tab: D } = F(), O = P("vibecontrols/ai-tools/plan") ? "plan" : "ai", k = D && B.has(D) ? D : "overview", A = e(), j = w((e, t) => {
72
+ let n = E(e);
72
73
  return n === e ? t : n;
73
- }, [T]), P = S((e) => {
74
+ }, [E]), N = w((e) => {
74
75
  switch (e) {
75
- case "overview": return M("ai.overview", "Overview");
76
- case "templates": return M("ai.templates", "Templates");
77
- case "contexts": return M("ai.contexts", "Contexts");
78
- case "prompts": return M("ai.prompts", "Prompts");
79
- case "sessions": return M("nav.sessions", "Sessions");
80
- case "tasks": return M("ai.tasks", "Tasks");
81
- case "stats": return M("ai.stats", "Stats");
82
- case "playground": return M("ai.playground", "Playground");
83
- case "config": return M("ai.config", "Config");
76
+ case "overview": return j("ai.overview", "Overview");
77
+ case "templates": return j("ai.templates", "Templates");
78
+ case "contexts": return j("ai.contexts", "Contexts");
79
+ case "prompts": return j("ai.prompts", "Prompts");
80
+ case "sessions": return j("nav.sessions", "Sessions");
81
+ case "tasks": return j("ai.tasks", "Tasks");
82
+ case "stats": return j("ai.stats", "Stats");
83
+ case "playground": return j("ai.playground", "Playground");
84
+ case "config": return j("ai.config", "Config");
84
85
  default: return e;
85
86
  }
86
- }, [M]), { agents: V, loading: H, error: U, refetch: W } = t();
87
+ }, [j]), { agents: V, loading: H, error: U, refetch: W } = t();
87
88
  U && console.error("[ai] failed to load initial workspace data", U);
88
- let G = C(() => V.find((e) => e.isActive && e.tunnelUrl && e.agentApiKey) ?? null, [V]), K = G?.id ?? "", { active: q } = l(G?.id ?? null), J = C(() => ({
89
+ let G = T(() => V.find((e) => e.isActive && e.tunnelUrl && e.agentApiKey) ?? null, [V]), K = G?.id ?? "", { active: q } = l(G?.id ?? null), J = T(() => ({
89
90
  id: K,
90
91
  profile: q
91
- }), [K, q]), Y = S((e) => {
92
+ }), [K, q]), Y = w((e) => {
92
93
  A(e === "overview" ? "/ai-tools/ai" : `/ai-tools/ai/${e}`);
93
- }, [A]), X = S((e) => {
94
- A(e === "plan" ? "/ai-tools/plan" : `/ai-tools/ai/${k}`);
95
- }, [A, k]), Z = S((e) => {
94
+ }, [A]), X = w((e) => {
96
95
  A(`/ai-tools/sessions/${e}`);
97
- }, [A]), re = !!K, [Q, $] = w(null), ie = S(() => {
98
- $(M("ai.pluginInstalled", "Plugin installed. Refreshing…")), console.info("[ai] orchestrator install completed");
99
- }, [M]), ae = S(() => {
100
- $(M("ai.harnessUpdated", "Harness updated.")), console.info("[ai] harness picker changed");
101
- }, [M]);
102
- return /* @__PURE__ */ R(z, {
96
+ }, [A]), Z = !!K, [Q, $] = ee(null), ne = w(() => {
97
+ $(j("ai.pluginInstalled", "Plugin installed. Refreshing…")), console.info("[ai] orchestrator install completed");
98
+ }, [j]), re = w(() => {
99
+ $(j("ai.harnessUpdated", "Harness updated.")), console.info("[ai] harness picker changed");
100
+ }, [j]);
101
+ return /* @__PURE__ */ L(R, {
103
102
  className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen",
104
103
  children: [
105
- /* @__PURE__ */ L(n, {
106
- title: M("nav.ai", "AI"),
107
- description: M("ai.description", "Manage AI sessions, prompts, templates, and contexts for your vibecoding workflow")
104
+ /* @__PURE__ */ I(n, {
105
+ title: j("nav.ai", "AI"),
106
+ description: j("ai.description", "Manage AI sessions, prompts, templates, and contexts for your vibecoding workflow")
108
107
  }),
109
- /* @__PURE__ */ L("div", {
110
- role: "tablist",
111
- "aria-label": M("ai.modeToggleLabel", "AI workspace mode"),
112
- className: "inline-flex rounded-lg border border-border-default bg-bg-sunken p-0.5",
113
- children: ["plan", "ai"].map((e) => {
114
- let t = O === e, n = e === "plan" ? j : E;
115
- return /* @__PURE__ */ R("button", {
116
- role: "tab",
117
- "aria-selected": t,
118
- "data-testid": `ai-mode-toggle-${e}`,
119
- onClick: () => X(e),
120
- className: `flex items-center gap-2 px-4 py-1.5 text-sm font-medium rounded-md transition-colors ${t ? "bg-bg-surface text-action-primary-bg shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
121
- children: [/* @__PURE__ */ L(n, { className: "h-4 w-4" }), e === "plan" ? M("ai.mode.plan", "Plan") : M("ai.mode.ai", "AI")]
122
- }, e);
123
- })
108
+ /* @__PURE__ */ I(C, {
109
+ aiHref: `/ai-tools/ai/${k}`,
110
+ activeMode: O
124
111
  }),
125
- O === "ai" && /* @__PURE__ */ L("div", {
112
+ O === "ai" && /* @__PURE__ */ I("div", {
126
113
  className: "border-b border-border-default -mx-4 sm:-mx-6 px-4 sm:px-6",
127
- children: /* @__PURE__ */ L("nav", {
114
+ children: /* @__PURE__ */ I("nav", {
128
115
  className: "flex gap-1 overflow-x-auto pb-1",
129
116
  role: "tablist",
130
- children: B.map((e) => {
117
+ children: z.map((e) => {
131
118
  let t = e.icon, n = k === e.id;
132
- return /* @__PURE__ */ R("button", {
119
+ return /* @__PURE__ */ L("button", {
133
120
  role: "tab",
134
121
  "aria-selected": n,
135
122
  onClick: () => Y(e.id),
136
123
  className: `flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-t-md border-b-2 px-2.5 py-2 text-xs font-medium transition-colors sm:px-3 sm:py-2.5 sm:text-sm ${n ? "border-action-primary-bg text-action-primary-bg" : "border-transparent text-text-secondary hover:text-text-primary hover:border-border-default"}`,
137
- children: [/* @__PURE__ */ L(t, { className: "h-3.5 w-3.5 sm:h-4 sm:w-4" }), P(e.id)]
124
+ children: [/* @__PURE__ */ I(t, { className: "h-3.5 w-3.5 sm:h-4 sm:w-4" }), N(e.id)]
138
125
  }, e.id);
139
126
  })
140
127
  })
141
128
  }),
142
- H && V.length === 0 ? /* @__PURE__ */ L(u, {
129
+ H && V.length === 0 ? /* @__PURE__ */ I(u, {
143
130
  variant: "rows",
144
131
  count: 6
145
- }) : U ? /* @__PURE__ */ L(r, {
146
- title: M("ai.loadFailed.title", "Could not load the AI workspace"),
147
- message: M("ai.loadFailed.message", "We couldn't load the AI workspace. Please try again in a moment."),
132
+ }) : U ? /* @__PURE__ */ I(r, {
133
+ title: j("ai.loadFailed.title", "Could not load the AI workspace"),
134
+ message: j("ai.loadFailed.message", "We couldn't load the AI workspace. Please try again in a moment."),
148
135
  onRetry: W
149
- }) : re ? O === "plan" ? /* @__PURE__ */ L(c, {
136
+ }) : Z ? O === "plan" ? /* @__PURE__ */ I(c, {
150
137
  agentId: K,
151
- children: /* @__PURE__ */ L(x, {
138
+ children: /* @__PURE__ */ I(S, {
152
139
  agent: J,
153
140
  agentApiKey: G?.agentApiKey ?? null,
154
141
  tunnelUrl: G?.tunnelUrl ?? null
155
142
  })
156
- }) : /* @__PURE__ */ L(c, {
143
+ }) : /* @__PURE__ */ I(c, {
157
144
  agentId: K,
158
- children: /* @__PURE__ */ L(s, {
145
+ children: /* @__PURE__ */ I(s, {
159
146
  agentId: K,
160
147
  required: [a],
161
148
  mode: "all",
162
- eyebrow: M("ai.pluginRequired", "AI plugin required"),
163
- title: M("ai.installOrchestrator", "Install the AI orchestrator"),
164
- description: M("ai.installOrchestratorDesc", "The AI page routes prompts and sessions through the AI orchestrator plugin on your agent. Install it to continue."),
165
- onInstalled: ie,
166
- children: /* @__PURE__ */ R("div", {
149
+ eyebrow: j("ai.pluginRequired", "AI plugin required"),
150
+ title: j("ai.installOrchestrator", "Install the AI orchestrator"),
151
+ description: j("ai.installOrchestratorDesc", "The AI page routes prompts and sessions through the AI orchestrator plugin on your agent. Install it to continue."),
152
+ onInstalled: ne,
153
+ children: /* @__PURE__ */ L("div", {
167
154
  className: "space-y-6",
168
155
  children: [
169
- Q && /* @__PURE__ */ L("div", {
156
+ Q && /* @__PURE__ */ I("div", {
170
157
  role: "status",
171
158
  className: "rounded-md border border-status-success-border bg-status-success-bg px-3 py-2 text-xs text-status-success-text",
172
159
  children: Q
173
160
  }),
174
- /* @__PURE__ */ L(d, {
161
+ /* @__PURE__ */ I(d, {
175
162
  agentId: K,
176
163
  packages: o,
177
- title: M("ai.harnessesTitle", "AI harnesses"),
178
- description: M("ai.harnessesDesc", "Install and manage the coding-agent harnesses available on this agent. You can install multiple and switch between them per-session."),
179
- onChange: ae
164
+ title: j("ai.harnessesTitle", "AI harnesses"),
165
+ description: j("ai.harnessesDesc", "Install and manage the coding-agent harnesses available on this agent. You can install multiple and switch between them per-session."),
166
+ onChange: re
180
167
  }),
181
- /* @__PURE__ */ R("div", { children: [
182
- k === "overview" && /* @__PURE__ */ L(v, {
168
+ /* @__PURE__ */ L("div", { children: [
169
+ k === "overview" && /* @__PURE__ */ I(y, {
183
170
  agent: J,
184
- onNavigateToSession: Z
171
+ onNavigateToSession: X
185
172
  }),
186
- k === "templates" && /* @__PURE__ */ L(f, { agentId: K }),
187
- k === "contexts" && /* @__PURE__ */ L(p, { agent: J }),
188
- k === "prompts" && /* @__PURE__ */ L(ee, { agent: J }),
189
- k === "sessions" && /* @__PURE__ */ L(y, {
173
+ k === "templates" && /* @__PURE__ */ I(f, { agentId: K }),
174
+ k === "contexts" && /* @__PURE__ */ I(p, { agent: J }),
175
+ k === "prompts" && /* @__PURE__ */ I(m, { agent: J }),
176
+ k === "sessions" && /* @__PURE__ */ I(b, {
190
177
  agent: J,
191
- onNavigateToSession: Z
178
+ onNavigateToSession: X
192
179
  }),
193
- k === "tasks" && /* @__PURE__ */ L(m, {}),
194
- k === "stats" && /* @__PURE__ */ L(h, { agent: J }),
195
- k === "playground" && /* @__PURE__ */ L(g, { agent: J }),
196
- k === "config" && /* @__PURE__ */ R("div", {
180
+ k === "tasks" && /* @__PURE__ */ I(h, {}),
181
+ k === "stats" && /* @__PURE__ */ I(g, { agent: J }),
182
+ k === "playground" && /* @__PURE__ */ I(_, { agent: J }),
183
+ k === "config" && /* @__PURE__ */ L("div", {
197
184
  className: "space-y-6",
198
- children: [/* @__PURE__ */ L(b, { agent: J }), /* @__PURE__ */ L(_, { agent: J })]
185
+ children: [/* @__PURE__ */ I(x, { agent: J }), /* @__PURE__ */ I(v, { agent: J })]
199
186
  })
200
187
  ] })
201
188
  ]
202
189
  })
203
190
  })
204
- }) : /* @__PURE__ */ L(i, {
205
- icon: /* @__PURE__ */ L(N, { className: "w-8 h-8 text-text-secondary" }),
206
- title: M("ai.noActiveAgent", "No active agent available"),
207
- description: M("ai.noActiveAgentDescription", "AI features require an active agent with a configured tunnel URL and API key. Add an agent or start an existing one."),
208
- action: /* @__PURE__ */ L("button", {
191
+ }) : /* @__PURE__ */ I(i, {
192
+ icon: /* @__PURE__ */ I(M, { className: "w-8 h-8 text-text-secondary" }),
193
+ title: j("ai.noActiveAgent", "No active agent available"),
194
+ description: j("ai.noActiveAgentDescription", "AI features require an active agent with a configured tunnel URL and API key. Add an agent or start an existing one."),
195
+ action: /* @__PURE__ */ I("button", {
209
196
  onClick: () => A("/agents"),
210
197
  className: "px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors text-sm",
211
- children: M("ai.goToAgents", "Go to Agents")
198
+ children: j("ai.goToAgents", "Go to Agents")
212
199
  })
213
200
  })
214
201
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"AIPage.js","names":[],"sources":["../../../src/pages/ai/AIPage.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport { useMatch, useParams } from 'react-router-dom';\nimport {\n LayoutDashboard,\n FileCode2,\n FileText,\n MessageSquare,\n MessagesSquare,\n ListTodo,\n BarChart3,\n Play,\n Brain,\n MapIcon,\n} from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport {\n PageHeader,\n EmptyState,\n ErrorState,\n PluginGate,\n RequireAgentReachable,\n} from '@/components/shared';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { AI_CORE_PACKAGE, AI_PROVIDER_PACKAGES } from '@/constants/pluginCatalog';\nimport { PluginHarnessPicker } from '@/components/ai/PluginHarnessPicker';\n\nimport { AIOverviewTab } from '@/components/ai/AIOverviewTab';\nimport { AITemplatesTab } from '@/components/ai/AITemplatesTab';\nimport { AIContextsTab } from '@/components/ai/AIContextsTab';\nimport { AIPromptsTab } from '@/components/ai/AIPromptsTab';\nimport { AISessionsTab } from '@/components/ai/AISessionsTab';\nimport { AITasksTab } from '@/components/ai/AITasksTab';\nimport { AIStatsTab } from '@/components/ai/AIStatsTab';\nimport { AIPlaygroundTab } from '@/components/ai/AIPlaygroundTab';\nimport { ClaudeCredentialsCard } from '@/components/ai/ClaudeCredentialsCard';\nimport { BridleConfigPanel } from '@/components/agent-manager/BridleConfigPanel';\nimport { AIPlanPanel } from '@/components/ai/AIPlanPanel';\n\ntype AIMode = 'plan' | 'ai';\n\ntype AITab =\n | 'overview'\n | 'templates'\n | 'contexts'\n | 'prompts'\n | 'sessions'\n | 'tasks'\n | 'stats'\n | 'playground'\n | 'config';\n\nconst tabs: { id: AITab; icon: typeof LayoutDashboard }[] = [\n { id: 'overview', icon: LayoutDashboard },\n { id: 'templates', icon: FileCode2 },\n { id: 'contexts', icon: FileText },\n { id: 'prompts', icon: MessageSquare },\n { id: 'sessions', icon: MessagesSquare },\n { id: 'tasks', icon: ListTodo },\n { id: 'stats', icon: BarChart3 },\n { id: 'playground', icon: Play },\n { id: 'config', icon: LayoutDashboard },\n];\n\nconst validTabs = new Set<string>(tabs.map((t) => t.id));\n\n/**\n * AI page — tab-based layout for AI / vibecoding features.\n *\n * URL shape:\n * /ai-tools → routed to /ai-tools/ai/overview (handled in VibeControlsRoutes).\n * /ai-tools/plan → Plan mode (full-bleed plannotator iframe panel).\n * /ai-tools/ai/:tab → AI mode (the existing 9 sub-tabs).\n *\n * The mode toggle below `PageHeader` switches between the two without a\n * full route reload — the AIPlanPanel inside Plan mode reuses the same\n * agent context that AI mode already loaded.\n */\nexport function AIPage() {\n const { t } = useI18n();\n const { tab: tabParam } = useParams<{ tab?: string }>();\n const planRouteMatch = useMatch('vibecontrols/ai-tools/plan');\n const activeMode: AIMode = planRouteMatch ? 'plan' : 'ai';\n const activeTab: AITab = tabParam && validTabs.has(tabParam) ? (tabParam as AITab) : 'overview';\n const handleNavigate = useVibeNavigate();\n\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const tabLabel = useCallback(\n (tabId: AITab): string => {\n switch (tabId) {\n case 'overview':\n return tr('ai.overview', 'Overview');\n case 'templates':\n return tr('ai.templates', 'Templates');\n case 'contexts':\n return tr('ai.contexts', 'Contexts');\n case 'prompts':\n return tr('ai.prompts', 'Prompts');\n case 'sessions':\n return tr('nav.sessions', 'Sessions');\n case 'tasks':\n return tr('ai.tasks', 'Tasks');\n case 'stats':\n return tr('ai.stats', 'Stats');\n case 'playground':\n return tr('ai.playground', 'Playground');\n case 'config':\n return tr('ai.config', 'Config');\n default:\n return tabId;\n }\n },\n [tr]\n );\n\n const { agents, loading: agentsLoading, error: agentsError, refetch } = useCachedInitialData();\n\n // If the initial data query throws (e.g. gateway query-cost limit, network\n // error, transient federation issue), surface a friendly fallback rather\n // than the raw GraphQL error. The technical details still go to the console\n // for debuggability — without PII, since the error message itself never\n // contains secrets.\n if (agentsError) {\n // eslint-disable-next-line no-console -- intentional debug breadcrumb for failed AI workspace load\n console.error('[ai] failed to load initial workspace data', agentsError);\n }\n const activeAgent = useMemo(\n () => agents.find((agent) => agent.isActive && agent.tunnelUrl && agent.agentApiKey) ?? null,\n [agents]\n );\n const agentId = activeAgent?.id ?? '';\n // AgentRef threads the canonical agent → profile → plugins/data\n // hierarchy through every aiApi call. Components receive the ref\n // and construct paths via `agentApiPath(profile, ...)`.\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n\n const switchTab = useCallback(\n (tab: AITab) => {\n handleNavigate(tab === 'overview' ? '/ai-tools/ai' : `/ai-tools/ai/${tab}`);\n },\n [handleNavigate]\n );\n\n const switchMode = useCallback(\n (mode: AIMode) => {\n handleNavigate(mode === 'plan' ? '/ai-tools/plan' : `/ai-tools/ai/${activeTab}`);\n },\n [handleNavigate, activeTab]\n );\n\n const navigateToSession = useCallback(\n (sessionId: string) => {\n handleNavigate(`/ai-tools/sessions/${sessionId}`);\n },\n [handleNavigate]\n );\n\n const hasAgent = !!agentId;\n\n // Surface install lifecycle for debuggability. PluginGate / PluginHarnessPicker\n // already render inline error chips, but a console breadcrumb + a top-level\n // banner helps when the install actually succeeds on the agent but a stale\n // detection cache hides the success — without this the install button looked\n // like it silently swallowed errors.\n const [lastInstallNote, setLastInstallNote] = useState<string | null>(null);\n const handleInstalled = useCallback(() => {\n setLastInstallNote(tr('ai.pluginInstalled', 'Plugin installed. Refreshing…'));\n // eslint-disable-next-line no-console -- intentional install breadcrumb\n console.info('[ai] orchestrator install completed');\n }, [tr]);\n const handleHarnessChange = useCallback(() => {\n setLastInstallNote(tr('ai.harnessUpdated', 'Harness updated.'));\n // eslint-disable-next-line no-console -- intentional install breadcrumb\n console.info('[ai] harness picker changed');\n }, [tr]);\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <PageHeader\n title={tr('nav.ai', 'AI')}\n description={tr(\n 'ai.description',\n 'Manage AI sessions, prompts, templates, and contexts for your vibecoding workflow'\n )}\n />\n\n {/* Mode pivot — Plan / AI. Sits below PageHeader, above the\n AI-mode sub-tab strip. Switching to Plan hides the 9 sub-tabs\n and renders the AIPlanPanel in their place. */}\n <div\n role=\"tablist\"\n aria-label={tr('ai.modeToggleLabel', 'AI workspace mode')}\n className=\"inline-flex rounded-lg border border-border-default bg-bg-sunken p-0.5\"\n >\n {(['plan', 'ai'] as const).map((m) => {\n const isActive = activeMode === m;\n const Icon = m === 'plan' ? MapIcon : Brain;\n return (\n <button\n key={m}\n role=\"tab\"\n aria-selected={isActive}\n data-testid={`ai-mode-toggle-${m}`}\n onClick={() => switchMode(m)}\n className={`flex items-center gap-2 px-4 py-1.5 text-sm font-medium rounded-md transition-colors ${\n isActive\n ? 'bg-bg-surface text-action-primary-bg shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n <Icon className=\"h-4 w-4\" />\n {m === 'plan' ? tr('ai.mode.plan', 'Plan') : tr('ai.mode.ai', 'AI')}\n </button>\n );\n })}\n </div>\n\n {/* AI sub-tab navigation — only rendered in AI mode. */}\n {activeMode === 'ai' && (\n <div className=\"border-b border-border-default -mx-4 sm:-mx-6 px-4 sm:px-6\">\n <nav className=\"flex gap-1 overflow-x-auto pb-1\" role=\"tablist\">\n {tabs.map((tab) => {\n const Icon = tab.icon;\n const isActive = activeTab === tab.id;\n return (\n <button\n key={tab.id}\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => switchTab(tab.id)}\n className={`flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-t-md border-b-2 px-2.5 py-2 text-xs font-medium transition-colors sm:px-3 sm:py-2.5 sm:text-sm ${\n isActive\n ? 'border-action-primary-bg text-action-primary-bg'\n : 'border-transparent text-text-secondary hover:text-text-primary hover:border-border-default'\n }`}\n >\n <Icon className=\"h-3.5 w-3.5 sm:h-4 sm:w-4\" />\n {tabLabel(tab.id)}\n </button>\n );\n })}\n </nav>\n </div>\n )}\n\n {/* Content */}\n {agentsLoading && agents.length === 0 ? (\n <PageSkeleton variant=\"rows\" count={6} />\n ) : agentsError ? (\n <ErrorState\n title={tr('ai.loadFailed.title', 'Could not load the AI workspace')}\n message={tr(\n 'ai.loadFailed.message',\n \"We couldn't load the AI workspace. Please try again in a moment.\"\n )}\n onRetry={refetch}\n />\n ) : !hasAgent ? (\n <EmptyState\n icon={<MessagesSquare className=\"w-8 h-8 text-text-secondary\" />}\n title={tr('ai.noActiveAgent', 'No active agent available')}\n description={tr(\n 'ai.noActiveAgentDescription',\n 'AI features require an active agent with a configured tunnel URL and API key. Add an agent or start an existing one.'\n )}\n action={\n <button\n onClick={() => handleNavigate('/agents')}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors text-sm\"\n >\n {tr('ai.goToAgents', 'Go to Agents')}\n </button>\n }\n />\n ) : activeMode === 'plan' ? (\n <RequireAgentReachable agentId={agentId}>\n <AIPlanPanel\n agent={agentRef}\n agentApiKey={activeAgent?.agentApiKey ?? null}\n tunnelUrl={activeAgent?.tunnelUrl ?? null}\n />\n </RequireAgentReachable>\n ) : (\n <RequireAgentReachable agentId={agentId}>\n <PluginGate\n agentId={agentId}\n required={[AI_CORE_PACKAGE]}\n mode=\"all\"\n eyebrow={tr('ai.pluginRequired', 'AI plugin required')}\n title={tr('ai.installOrchestrator', 'Install the AI orchestrator')}\n description={tr(\n 'ai.installOrchestratorDesc',\n 'The AI page routes prompts and sessions through the AI orchestrator plugin on your agent. Install it to continue.'\n )}\n onInstalled={handleInstalled}\n >\n <div className=\"space-y-6\">\n {lastInstallNote && (\n <div\n role=\"status\"\n className=\"rounded-md border border-status-success-border bg-status-success-bg px-3 py-2 text-xs text-status-success-text\"\n >\n {lastInstallNote}\n </div>\n )}\n <PluginHarnessPicker\n agentId={agentId}\n packages={AI_PROVIDER_PACKAGES}\n title={tr('ai.harnessesTitle', 'AI harnesses')}\n description={tr(\n 'ai.harnessesDesc',\n 'Install and manage the coding-agent harnesses available on this agent. You can install multiple and switch between them per-session.'\n )}\n onChange={handleHarnessChange}\n />\n <div>\n {activeTab === 'overview' && (\n <AIOverviewTab agent={agentRef} onNavigateToSession={navigateToSession} />\n )}\n {activeTab === 'templates' && <AITemplatesTab agentId={agentId} />}\n {activeTab === 'contexts' && <AIContextsTab agent={agentRef} />}\n {activeTab === 'prompts' && <AIPromptsTab agent={agentRef} />}\n {activeTab === 'sessions' && (\n <AISessionsTab agent={agentRef} onNavigateToSession={navigateToSession} />\n )}\n {activeTab === 'tasks' && <AITasksTab />}\n {activeTab === 'stats' && <AIStatsTab agent={agentRef} />}\n {activeTab === 'playground' && <AIPlaygroundTab agent={agentRef} />}\n {activeTab === 'config' && (\n <div className=\"space-y-6\">\n <ClaudeCredentialsCard agent={agentRef} />\n <BridleConfigPanel agent={agentRef} />\n </div>\n )}\n </div>\n </div>\n </PluginGate>\n </RequireAgentReachable>\n )}\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AIPage.js","names":[],"sources":["../../../src/pages/ai/AIPage.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport { useMatch, useParams } from 'react-router-dom';\nimport {\n LayoutDashboard,\n FileCode2,\n FileText,\n MessageSquare,\n MessagesSquare,\n ListTodo,\n BarChart3,\n Play,\n} from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport {\n PageHeader,\n EmptyState,\n ErrorState,\n PluginGate,\n RequireAgentReachable,\n} from '@/components/shared';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { AI_CORE_PACKAGE, AI_PROVIDER_PACKAGES } from '@/constants/pluginCatalog';\nimport { PluginHarnessPicker } from '@/components/ai/PluginHarnessPicker';\n\nimport { AIOverviewTab } from '@/components/ai/AIOverviewTab';\nimport { AITemplatesTab } from '@/components/ai/AITemplatesTab';\nimport { AIContextsTab } from '@/components/ai/AIContextsTab';\nimport { AIPromptsTab } from '@/components/ai/AIPromptsTab';\nimport { AISessionsTab } from '@/components/ai/AISessionsTab';\nimport { AITasksTab } from '@/components/ai/AITasksTab';\nimport { AIStatsTab } from '@/components/ai/AIStatsTab';\nimport { AIPlaygroundTab } from '@/components/ai/AIPlaygroundTab';\nimport { ClaudeCredentialsCard } from '@/components/ai/ClaudeCredentialsCard';\nimport { BridleConfigPanel } from '@/components/agent-manager/BridleConfigPanel';\nimport { AIPlanPanel } from '@/components/ai/AIPlanPanel';\nimport { AIModeToggle } from '@/components/ai/AIModeToggle';\n\ntype AIMode = 'plan' | 'ai';\n\ntype AITab =\n | 'overview'\n | 'templates'\n | 'contexts'\n | 'prompts'\n | 'sessions'\n | 'tasks'\n | 'stats'\n | 'playground'\n | 'config';\n\nconst tabs: { id: AITab; icon: typeof LayoutDashboard }[] = [\n { id: 'overview', icon: LayoutDashboard },\n { id: 'templates', icon: FileCode2 },\n { id: 'contexts', icon: FileText },\n { id: 'prompts', icon: MessageSquare },\n { id: 'sessions', icon: MessagesSquare },\n { id: 'tasks', icon: ListTodo },\n { id: 'stats', icon: BarChart3 },\n { id: 'playground', icon: Play },\n { id: 'config', icon: LayoutDashboard },\n];\n\nconst validTabs = new Set<string>(tabs.map((t) => t.id));\n\n/**\n * AI page — tab-based layout for AI / vibecoding features.\n *\n * URL shape:\n * /ai-tools → routed to /ai-tools/ai/overview (handled in VibeControlsRoutes).\n * /ai-tools/plan → Plan mode (full-bleed plannotator iframe panel).\n * /ai-tools/ai/:tab → AI mode (the existing 9 sub-tabs).\n *\n * The mode toggle below `PageHeader` switches between the two without a\n * full route reload — the AIPlanPanel inside Plan mode reuses the same\n * agent context that AI mode already loaded.\n */\nexport function AIPage() {\n const { t } = useI18n();\n const { tab: tabParam } = useParams<{ tab?: string }>();\n const planRouteMatch = useMatch('vibecontrols/ai-tools/plan');\n const activeMode: AIMode = planRouteMatch ? 'plan' : 'ai';\n const activeTab: AITab = tabParam && validTabs.has(tabParam) ? (tabParam as AITab) : 'overview';\n const handleNavigate = useVibeNavigate();\n\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const tabLabel = useCallback(\n (tabId: AITab): string => {\n switch (tabId) {\n case 'overview':\n return tr('ai.overview', 'Overview');\n case 'templates':\n return tr('ai.templates', 'Templates');\n case 'contexts':\n return tr('ai.contexts', 'Contexts');\n case 'prompts':\n return tr('ai.prompts', 'Prompts');\n case 'sessions':\n return tr('nav.sessions', 'Sessions');\n case 'tasks':\n return tr('ai.tasks', 'Tasks');\n case 'stats':\n return tr('ai.stats', 'Stats');\n case 'playground':\n return tr('ai.playground', 'Playground');\n case 'config':\n return tr('ai.config', 'Config');\n default:\n return tabId;\n }\n },\n [tr]\n );\n\n const { agents, loading: agentsLoading, error: agentsError, refetch } = useCachedInitialData();\n\n // If the initial data query throws (e.g. gateway query-cost limit, network\n // error, transient federation issue), surface a friendly fallback rather\n // than the raw GraphQL error. The technical details still go to the console\n // for debuggability — without PII, since the error message itself never\n // contains secrets.\n if (agentsError) {\n // eslint-disable-next-line no-console -- intentional debug breadcrumb for failed AI workspace load\n console.error('[ai] failed to load initial workspace data', agentsError);\n }\n const activeAgent = useMemo(\n () => agents.find((agent) => agent.isActive && agent.tunnelUrl && agent.agentApiKey) ?? null,\n [agents]\n );\n const agentId = activeAgent?.id ?? '';\n // AgentRef threads the canonical agent → profile → plugins/data\n // hierarchy through every aiApi call. Components receive the ref\n // and construct paths via `agentApiPath(profile, ...)`.\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n\n const switchTab = useCallback(\n (tab: AITab) => {\n handleNavigate(tab === 'overview' ? '/ai-tools/ai' : `/ai-tools/ai/${tab}`);\n },\n [handleNavigate]\n );\n\n const navigateToSession = useCallback(\n (sessionId: string) => {\n handleNavigate(`/ai-tools/sessions/${sessionId}`);\n },\n [handleNavigate]\n );\n\n const hasAgent = !!agentId;\n\n // Surface install lifecycle for debuggability. PluginGate / PluginHarnessPicker\n // already render inline error chips, but a console breadcrumb + a top-level\n // banner helps when the install actually succeeds on the agent but a stale\n // detection cache hides the success — without this the install button looked\n // like it silently swallowed errors.\n const [lastInstallNote, setLastInstallNote] = useState<string | null>(null);\n const handleInstalled = useCallback(() => {\n setLastInstallNote(tr('ai.pluginInstalled', 'Plugin installed. Refreshing…'));\n // eslint-disable-next-line no-console -- intentional install breadcrumb\n console.info('[ai] orchestrator install completed');\n }, [tr]);\n const handleHarnessChange = useCallback(() => {\n setLastInstallNote(tr('ai.harnessUpdated', 'Harness updated.'));\n // eslint-disable-next-line no-console -- intentional install breadcrumb\n console.info('[ai] harness picker changed');\n }, [tr]);\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <PageHeader\n title={tr('nav.ai', 'AI')}\n description={tr(\n 'ai.description',\n 'Manage AI sessions, prompts, templates, and contexts for your vibecoding workflow'\n )}\n />\n\n {/* Mode pivot — Plan / AI. Sits below PageHeader, above the\n AI-mode sub-tab strip. Switching to Plan hides the 9 sub-tabs\n and renders the AIPlanPanel in their place. Stays inside\n /ai-tools so the active sub-tab survives the toggle. */}\n <AIModeToggle aiHref={`/ai-tools/ai/${activeTab}`} activeMode={activeMode} />\n\n {/* AI sub-tab navigation — only rendered in AI mode. */}\n {activeMode === 'ai' && (\n <div className=\"border-b border-border-default -mx-4 sm:-mx-6 px-4 sm:px-6\">\n <nav className=\"flex gap-1 overflow-x-auto pb-1\" role=\"tablist\">\n {tabs.map((tab) => {\n const Icon = tab.icon;\n const isActive = activeTab === tab.id;\n return (\n <button\n key={tab.id}\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => switchTab(tab.id)}\n className={`flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-t-md border-b-2 px-2.5 py-2 text-xs font-medium transition-colors sm:px-3 sm:py-2.5 sm:text-sm ${\n isActive\n ? 'border-action-primary-bg text-action-primary-bg'\n : 'border-transparent text-text-secondary hover:text-text-primary hover:border-border-default'\n }`}\n >\n <Icon className=\"h-3.5 w-3.5 sm:h-4 sm:w-4\" />\n {tabLabel(tab.id)}\n </button>\n );\n })}\n </nav>\n </div>\n )}\n\n {/* Content */}\n {agentsLoading && agents.length === 0 ? (\n <PageSkeleton variant=\"rows\" count={6} />\n ) : agentsError ? (\n <ErrorState\n title={tr('ai.loadFailed.title', 'Could not load the AI workspace')}\n message={tr(\n 'ai.loadFailed.message',\n \"We couldn't load the AI workspace. Please try again in a moment.\"\n )}\n onRetry={refetch}\n />\n ) : !hasAgent ? (\n <EmptyState\n icon={<MessagesSquare className=\"w-8 h-8 text-text-secondary\" />}\n title={tr('ai.noActiveAgent', 'No active agent available')}\n description={tr(\n 'ai.noActiveAgentDescription',\n 'AI features require an active agent with a configured tunnel URL and API key. Add an agent or start an existing one.'\n )}\n action={\n <button\n onClick={() => handleNavigate('/agents')}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors text-sm\"\n >\n {tr('ai.goToAgents', 'Go to Agents')}\n </button>\n }\n />\n ) : activeMode === 'plan' ? (\n <RequireAgentReachable agentId={agentId}>\n <AIPlanPanel\n agent={agentRef}\n agentApiKey={activeAgent?.agentApiKey ?? null}\n tunnelUrl={activeAgent?.tunnelUrl ?? null}\n />\n </RequireAgentReachable>\n ) : (\n <RequireAgentReachable agentId={agentId}>\n <PluginGate\n agentId={agentId}\n required={[AI_CORE_PACKAGE]}\n mode=\"all\"\n eyebrow={tr('ai.pluginRequired', 'AI plugin required')}\n title={tr('ai.installOrchestrator', 'Install the AI orchestrator')}\n description={tr(\n 'ai.installOrchestratorDesc',\n 'The AI page routes prompts and sessions through the AI orchestrator plugin on your agent. Install it to continue.'\n )}\n onInstalled={handleInstalled}\n >\n <div className=\"space-y-6\">\n {lastInstallNote && (\n <div\n role=\"status\"\n className=\"rounded-md border border-status-success-border bg-status-success-bg px-3 py-2 text-xs text-status-success-text\"\n >\n {lastInstallNote}\n </div>\n )}\n <PluginHarnessPicker\n agentId={agentId}\n packages={AI_PROVIDER_PACKAGES}\n title={tr('ai.harnessesTitle', 'AI harnesses')}\n description={tr(\n 'ai.harnessesDesc',\n 'Install and manage the coding-agent harnesses available on this agent. You can install multiple and switch between them per-session.'\n )}\n onChange={handleHarnessChange}\n />\n <div>\n {activeTab === 'overview' && (\n <AIOverviewTab agent={agentRef} onNavigateToSession={navigateToSession} />\n )}\n {activeTab === 'templates' && <AITemplatesTab agentId={agentId} />}\n {activeTab === 'contexts' && <AIContextsTab agent={agentRef} />}\n {activeTab === 'prompts' && <AIPromptsTab agent={agentRef} />}\n {activeTab === 'sessions' && (\n <AISessionsTab agent={agentRef} onNavigateToSession={navigateToSession} />\n )}\n {activeTab === 'tasks' && <AITasksTab />}\n {activeTab === 'stats' && <AIStatsTab agent={agentRef} />}\n {activeTab === 'playground' && <AIPlaygroundTab agent={agentRef} />}\n {activeTab === 'config' && (\n <div className=\"space-y-6\">\n <ClaudeCredentialsCard agent={agentRef} />\n <BridleConfigPanel agent={agentRef} />\n </div>\n )}\n </div>\n </div>\n </PluginGate>\n </RequireAgentReachable>\n )}\n </MotionPage>\n );\n}\n"],"mappings":"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"}