@burdenoff/microfe-vibecontrols 2026.531.5 → 2026.531.7

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.
Files changed (39) hide show
  1. package/dist/components/agents/RemoteTaskPermissionsCard.js +111 -0
  2. package/dist/components/agents/RemoteTaskPermissionsCard.js.map +1 -0
  3. package/dist/components/agents/index.js +1 -0
  4. package/dist/components/assistant/AIAssistantPanel.js +37 -35
  5. package/dist/components/assistant/AIAssistantPanel.js.map +1 -1
  6. package/dist/components/assistant/AiCreditsMeter.js +21 -0
  7. package/dist/components/assistant/AiCreditsMeter.js.map +1 -0
  8. package/dist/components/assistant/AssistantChatArea.js +129 -108
  9. package/dist/components/assistant/AssistantChatArea.js.map +1 -1
  10. package/dist/components/notes/NotesTabPanel.js +6 -6
  11. package/dist/components/sessions/TerminalPanel.js +4 -4
  12. package/dist/components/sessions/TerminalPanel.js.map +1 -1
  13. package/dist/generated/global-operations.js +32 -0
  14. package/dist/generated/global-operations.js.map +1 -0
  15. package/dist/generated/wspace-operations.js +1366 -1342
  16. package/dist/generated/wspace-operations.js.map +1 -1
  17. package/dist/generated/wspace-types.js.map +1 -1
  18. package/dist/hooks/useAiCreditsQuota.js +47 -0
  19. package/dist/hooks/useAiCreditsQuota.js.map +1 -0
  20. package/dist/index.js +10 -10
  21. package/dist/pages/ActionsPage.js +1 -1
  22. package/dist/pages/AgentDetailsPage.js +346 -338
  23. package/dist/pages/AgentDetailsPage.js.map +1 -1
  24. package/dist/pages/AgentsPage.js +8 -8
  25. package/dist/pages/SessionDetailsPage.js +72 -71
  26. package/dist/pages/SessionDetailsPage.js.map +1 -1
  27. package/dist/pages/SessionsPage.js +8 -8
  28. package/dist/pages/SettingsPage.js +7 -7
  29. package/dist/pages/VibeCalendarPage.js +1 -1
  30. package/dist/pages/VibeDeckPage.js +1 -1
  31. package/dist/pages/VibeDetailsPage.js +11 -11
  32. package/dist/pages/VibesPage.js +7 -7
  33. package/dist/services/assistantApi.js +87 -62
  34. package/dist/services/assistantApi.js.map +1 -1
  35. package/dist/utils/aiCredits.js +15 -0
  36. package/dist/utils/aiCredits.js.map +1 -0
  37. package/dist/utils/quotaUtils.js +5 -0
  38. package/dist/utils/quotaUtils.js.map +1 -1
  39. package/package.json +1 -1
@@ -0,0 +1,111 @@
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { deleteAgentConfigValue as t, getAgentConfigValue as n, setAgentConfigValue as r } from "../../services/aiApi.js";
3
+ import { useCallback as i, useEffect as a, useState as o } from "react";
4
+ import { Loader2 as s, ShieldAlert as c } from "lucide-react";
5
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
6
+ import { Card as d, Switch as f } from "@burdenoff/fe-libs/ui";
7
+ //#region src/components/agents/RemoteTaskPermissionsCard.tsx
8
+ function p(e) {
9
+ return [{
10
+ key: "security:vibecontrols_allow_remote_shell_task",
11
+ title: e("agentDetails.remoteShellTask.title", "Allow remote shell + script tasks"),
12
+ description: e("agentDetails.remoteShellTask.description", "Enable VibeDeck buttons, scheduled jobs, and the UI to run shell commands and scripts on this agent over its tunnel. Required for any UI-triggered \"execute action\" that runs a command."),
13
+ warning: e("agentDetails.remoteShellTask.warning", "Anyone with workspace access can execute commands on the host machine. Audit log records every invocation; reviewers can revert this flag at any time.")
14
+ }, {
15
+ key: "security:vibecontrols_allow_remote_file_task",
16
+ title: e("agentDetails.remoteFileTask.title", "Allow remote file writes + deletes"),
17
+ description: e("agentDetails.remoteFileTask.description", "Enable UI-triggered file_operation write/delete tasks on this agent. Reads are always allowed; this gate covers mutations only."),
18
+ warning: e("agentDetails.remoteFileTask.warning", "Writes and deletes are confined to the cwd / homedir / tmpdir allowlist via resolveSafePath + isSensitivePath, but a malicious caller could still corrupt files in those scopes.")
19
+ }];
20
+ }
21
+ function m(e) {
22
+ if (!e) return !1;
23
+ let t = e.trim().toLowerCase();
24
+ return t === "1" || t === "true" || t === "yes";
25
+ }
26
+ function h({ agent: h }) {
27
+ let g = e(), _ = p(g), [v, y] = o(!0), [b, x] = o(null), [S, C] = o({}), [w, T] = o(null), E = i(async () => {
28
+ y(!0), T(null);
29
+ try {
30
+ let e = await Promise.all(_.map(async (e) => [e.key, m(await n(h, e.key))]));
31
+ C(Object.fromEntries(e));
32
+ } catch (e) {
33
+ T(e instanceof Error ? e.message : g("agentDetails.remoteTaskPermissions.loadFailed", "Failed to load permissions"));
34
+ } finally {
35
+ y(!1);
36
+ }
37
+ }, [h, g]);
38
+ a(() => {
39
+ E();
40
+ }, [E]);
41
+ let D = i(async (e, n) => {
42
+ x(e), T(null);
43
+ try {
44
+ n ? await r(h, e, "1") : await t(h, e), C((t) => ({
45
+ ...t,
46
+ [e]: n
47
+ }));
48
+ } catch (e) {
49
+ T(e instanceof Error ? e.message : g("agentDetails.remoteTaskPermissions.saveFailed", "Failed to save permission"));
50
+ } finally {
51
+ x(null);
52
+ }
53
+ }, [h, g]);
54
+ return /* @__PURE__ */ u(d, { children: [/* @__PURE__ */ u("div", {
55
+ className: "p-4 border-b border-border-default",
56
+ children: [/* @__PURE__ */ u("h3", {
57
+ className: "font-semibold text-text-primary flex items-center gap-2",
58
+ children: [/* @__PURE__ */ l(c, { className: "size-5" }), g("agentDetails.remoteTaskPermissions.title", "Remote Task Permissions")]
59
+ }), /* @__PURE__ */ l("p", {
60
+ className: "mt-1 text-xs text-text-secondary",
61
+ children: g("agentDetails.remoteTaskPermissions.subtitle", "These gates default to deny so an agent on a public tunnel can’t be coerced into shell execution. Toggle them on per-agent if you want VibeDeck buttons / scheduled jobs to run shell or file-mutation tasks here.")
62
+ })]
63
+ }), /* @__PURE__ */ u("div", {
64
+ className: "p-4 space-y-4",
65
+ children: [w ? /* @__PURE__ */ l("div", {
66
+ className: "rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text",
67
+ children: w
68
+ }) : null, v ? /* @__PURE__ */ u("div", {
69
+ className: "flex items-center gap-2 text-sm text-text-secondary",
70
+ children: [/* @__PURE__ */ l(s, { className: "size-4 animate-spin" }), g("agentDetails.remoteTaskPermissions.loading", "Loading…")]
71
+ }) : _.map((e) => {
72
+ let t = S[e.key] ?? !1, n = b === e.key;
73
+ return /* @__PURE__ */ u("div", {
74
+ className: "flex items-start gap-3 rounded-lg border border-border-default bg-bg-primary p-3",
75
+ children: [/* @__PURE__ */ u("div", {
76
+ className: "flex-1 min-w-0 space-y-1",
77
+ children: [
78
+ /* @__PURE__ */ l("p", {
79
+ className: "text-sm font-medium text-text-primary",
80
+ children: e.title
81
+ }),
82
+ /* @__PURE__ */ l("p", {
83
+ className: "text-xs text-text-secondary",
84
+ children: e.description
85
+ }),
86
+ e.warning ? /* @__PURE__ */ l("p", {
87
+ className: "text-xs text-status-warning-text",
88
+ children: e.warning
89
+ }) : null,
90
+ /* @__PURE__ */ l("p", {
91
+ className: "text-[11px] font-mono text-text-tertiary",
92
+ children: e.key
93
+ })
94
+ ]
95
+ }), /* @__PURE__ */ u("div", {
96
+ className: "flex items-center gap-2 flex-shrink-0",
97
+ children: [n ? /* @__PURE__ */ l(s, { className: "size-4 animate-spin text-text-secondary" }) : null, /* @__PURE__ */ l(f, {
98
+ checked: t,
99
+ onCheckedChange: (t) => void D(e.key, t),
100
+ disabled: n,
101
+ "aria-label": e.title
102
+ })]
103
+ })]
104
+ }, e.key);
105
+ })]
106
+ })] });
107
+ }
108
+ //#endregion
109
+ export { h as RemoteTaskPermissionsCard };
110
+
111
+ //# sourceMappingURL=RemoteTaskPermissionsCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RemoteTaskPermissionsCard.js","names":[],"sources":["../../../src/components/agents/RemoteTaskPermissionsCard.tsx"],"sourcesContent":["/**\n * RemoteTaskPermissionsCard\n *\n * Toggles the per-agent \"allow remote shell tasks\" and \"allow remote\n * file-mutation tasks\" gates that the agent enforces in\n * `core/request-security.ts:denyNonLocalMutation`. The agent's task\n * plugin (POST /api/profiles/<n>/tasks/) refuses non-loopback callers by\n * default because remote shell execution + file writes are high-blast\n * radius; this card lets operators opt in per-agent without having to\n * SSH in and restart the daemon with the matching env var\n * (VIBECONTROLS_ALLOW_REMOTE_SHELL_TASK / _FILE_TASK).\n *\n * Storage: writes to the agent's encrypted config bag under\n * `security:vibecontrols_allow_remote_shell_task` and\n * `security:vibecontrols_allow_remote_file_task`. The same keys are\n * read by the agent's gate via `db.getConfig(...)`. Persisted across\n * agent restarts.\n */\nimport { useCallback, useEffect, useState } from 'react';\nimport { ShieldAlert, Loader2 } from 'lucide-react';\n\nimport { Card } from '@burdenoff/fe-libs/ui';\nimport { Switch } from '@burdenoff/fe-libs/ui';\n\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n deleteAgentConfigValue,\n getAgentConfigValue,\n setAgentConfigValue,\n type AgentRef,\n} from '@/services/aiApi';\n\ninterface RemoteTaskPermissionsCardProps {\n agent: AgentRef;\n}\n\ninterface GateDef {\n key: string;\n title: string;\n description: string;\n warning?: string;\n}\n\nfunction buildGates(tr: ReturnType<typeof useTr>): GateDef[] {\n return [\n {\n key: 'security:vibecontrols_allow_remote_shell_task',\n title: tr(\n 'agentDetails.remoteShellTask.title',\n 'Allow remote shell + script tasks'\n ),\n description: tr(\n 'agentDetails.remoteShellTask.description',\n 'Enable VibeDeck buttons, scheduled jobs, and the UI to run shell commands and scripts on this agent over its tunnel. Required for any UI-triggered \"execute action\" that runs a command.'\n ),\n warning: tr(\n 'agentDetails.remoteShellTask.warning',\n 'Anyone with workspace access can execute commands on the host machine. Audit log records every invocation; reviewers can revert this flag at any time.'\n ),\n },\n {\n key: 'security:vibecontrols_allow_remote_file_task',\n title: tr(\n 'agentDetails.remoteFileTask.title',\n 'Allow remote file writes + deletes'\n ),\n description: tr(\n 'agentDetails.remoteFileTask.description',\n 'Enable UI-triggered file_operation write/delete tasks on this agent. Reads are always allowed; this gate covers mutations only.'\n ),\n warning: tr(\n 'agentDetails.remoteFileTask.warning',\n 'Writes and deletes are confined to the cwd / homedir / tmpdir allowlist via resolveSafePath + isSensitivePath, but a malicious caller could still corrupt files in those scopes.'\n ),\n },\n ];\n}\n\nfunction isTruthy(raw: string | null | undefined): boolean {\n if (!raw) return false;\n const v = raw.trim().toLowerCase();\n return v === '1' || v === 'true' || v === 'yes';\n}\n\nexport function RemoteTaskPermissionsCard({ agent }: RemoteTaskPermissionsCardProps) {\n const tr = useTr();\n const gates = buildGates(tr);\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState<string | null>(null);\n const [values, setValues] = useState<Record<string, boolean>>({});\n const [error, setError] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const entries = await Promise.all(\n gates.map(async (g) => [g.key, isTruthy(await getAgentConfigValue(agent, g.key))] as const)\n );\n setValues(Object.fromEntries(entries));\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('agentDetails.remoteTaskPermissions.loadFailed', 'Failed to load permissions')\n );\n } finally {\n setLoading(false);\n }\n // gates is stable across renders (derived from tr); intentionally not in deps.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [agent, tr]);\n\n useEffect(() => {\n void load();\n }, [load]);\n\n const toggle = useCallback(\n async (key: string, next: boolean) => {\n setSaving(key);\n setError(null);\n try {\n if (next) {\n await setAgentConfigValue(agent, key, '1');\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n setValues((prev) => ({ ...prev, [key]: next }));\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\n 'agentDetails.remoteTaskPermissions.saveFailed',\n 'Failed to save permission'\n )\n );\n } finally {\n setSaving(null);\n }\n },\n [agent, tr]\n );\n\n return (\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <ShieldAlert className=\"size-5\" />\n {tr(\n 'agentDetails.remoteTaskPermissions.title',\n 'Remote Task Permissions'\n )}\n </h3>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {tr(\n 'agentDetails.remoteTaskPermissions.subtitle',\n 'These gates default to deny so an agent on a public tunnel can’t be coerced into shell execution. Toggle them on per-agent if you want VibeDeck buttons / scheduled jobs to run shell or file-mutation tasks here.'\n )}\n </p>\n </div>\n <div className=\"p-4 space-y-4\">\n {error ? (\n <div className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"size-4 animate-spin\" />\n {tr('agentDetails.remoteTaskPermissions.loading', 'Loading…')}\n </div>\n ) : (\n gates.map((g) => {\n const current = values[g.key] ?? false;\n const busy = saving === g.key;\n return (\n <div\n key={g.key}\n className=\"flex items-start gap-3 rounded-lg border border-border-default bg-bg-primary p-3\"\n >\n <div className=\"flex-1 min-w-0 space-y-1\">\n <p className=\"text-sm font-medium text-text-primary\">{g.title}</p>\n <p className=\"text-xs text-text-secondary\">{g.description}</p>\n {g.warning ? (\n <p className=\"text-xs text-status-warning-text\">{g.warning}</p>\n ) : null}\n <p className=\"text-[11px] font-mono text-text-tertiary\">{g.key}</p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n {busy ? (\n <Loader2 className=\"size-4 animate-spin text-text-secondary\" />\n ) : null}\n <Switch\n checked={current}\n onCheckedChange={(checked) => void toggle(g.key, checked)}\n disabled={busy}\n aria-label={g.title}\n />\n </div>\n </div>\n );\n })\n )}\n </div>\n </Card>\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import "./AgentStatusBadge.js";
2
2
  import "./AgentCard.js";
3
+ import "./RemoteTaskPermissionsCard.js";
3
4
  import "./AgentList.js";
4
5
  import "./AgentForm.js";
5
6
  import "./PluginUIPanel.js";
@@ -3,69 +3,71 @@ import { useAssistantStore as t } from "../../store/assistantStore.js";
3
3
  import { useAssistantKeyboardShortcuts as n } from "../../hooks/useAssistantKeyboardShortcuts.js";
4
4
  import { useAssistantHeartbeat as r } from "../../hooks/useAssistantHeartbeat.js";
5
5
  import { AssistantChatArea as i } from "./AssistantChatArea.js";
6
- import { AssistantConversationList as a } from "./AssistantConversationList.js";
7
- import { AssistantExportMenu as o } from "./AssistantExportMenu.js";
8
- import { useRef as s } from "react";
9
- import { Eraser as c, MessageSquare as l, X as u } from "lucide-react";
10
- import { jsx as d, jsxs as f } from "react/jsx-runtime";
6
+ import { AiCreditsMeter as a } from "./AiCreditsMeter.js";
7
+ import { AssistantConversationList as o } from "./AssistantConversationList.js";
8
+ import { AssistantExportMenu as s } from "./AssistantExportMenu.js";
9
+ import { useRef as c } from "react";
10
+ import { Eraser as l, MessageSquare as u, X as d } from "lucide-react";
11
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
11
12
  //#region src/components/assistant/AIAssistantPanel.tsx
12
- var p = ({ onClose: p, visible: m }) => {
13
- let h = e(), g = s(null), { conversations: _, activeConversationId: v, conversationListOpen: y, toggleConversationList: b, clearConversation: x } = t(), S = _.find((e) => e.id === v);
14
- r(S?.backendSessionId ?? null, S?.mode ?? null, m ?? !0);
15
- let C = (S?.messages.length ?? 0) > 0;
13
+ var m = ({ onClose: m, visible: h }) => {
14
+ let g = e(), _ = c(null), { conversations: v, activeConversationId: y, conversationListOpen: b, toggleConversationList: x, clearConversation: S } = t(), C = v.find((e) => e.id === y);
15
+ r(C?.backendSessionId ?? null, C?.mode ?? null, h ?? !0);
16
+ let w = (C?.messages.length ?? 0) > 0;
16
17
  return n({
17
- panelRef: g,
18
- onClose: p
19
- }), /* @__PURE__ */ f("div", {
20
- ref: g,
18
+ panelRef: _,
19
+ onClose: m
20
+ }), /* @__PURE__ */ p("div", {
21
+ ref: _,
21
22
  className: "flex flex-col h-full w-full bg-bg-surface relative",
22
23
  role: "complementary",
23
- "aria-label": h("vibecontrols.assistant.panel.ariaLabel", "AI Assistant"),
24
+ "aria-label": g("vibecontrols.assistant.panel.ariaLabel", "AI Assistant"),
24
25
  "data-testid": "ai-assistant-panel",
25
26
  tabIndex: -1,
26
27
  children: [
27
- /* @__PURE__ */ f("div", {
28
+ /* @__PURE__ */ p("div", {
28
29
  className: "flex items-center justify-between px-3 py-2 border-b border-border-default bg-bg-elevated flex-shrink-0",
29
- children: [/* @__PURE__ */ f("div", {
30
+ children: [/* @__PURE__ */ p("div", {
30
31
  className: "flex items-center gap-2 min-w-0",
31
- children: [/* @__PURE__ */ d("button", {
32
+ children: [/* @__PURE__ */ f("button", {
32
33
  type: "button",
33
- onClick: b,
34
+ onClick: x,
34
35
  className: "p-1 hover:bg-bg-sunken rounded transition-colors",
35
- "aria-label": h("vibecontrols.assistant.panel.toggleList", "Toggle conversation list"),
36
+ "aria-label": g("vibecontrols.assistant.panel.toggleList", "Toggle conversation list"),
36
37
  "data-testid": "toggle-conversation-list",
37
- children: /* @__PURE__ */ d(l, { className: "size-3.5 text-text-secondary" })
38
- }), /* @__PURE__ */ d("span", {
38
+ children: /* @__PURE__ */ f(u, { className: "size-3.5 text-text-secondary" })
39
+ }), /* @__PURE__ */ f("span", {
39
40
  className: "text-xs font-semibold text-text-primary truncate",
40
- children: S?.title ?? h("vibecontrols.assistant.panel.title", "AI Assistant")
41
+ children: C?.title ?? g("vibecontrols.assistant.panel.title", "AI Assistant")
41
42
  })]
42
- }), /* @__PURE__ */ f("div", {
43
+ }), /* @__PURE__ */ p("div", {
43
44
  className: "flex items-center gap-0.5 flex-shrink-0",
44
45
  children: [
45
- C && /* @__PURE__ */ d("button", {
46
+ w && /* @__PURE__ */ f("button", {
46
47
  type: "button",
47
- onClick: () => v && x(v),
48
+ onClick: () => y && S(y),
48
49
  className: "p-1 hover:bg-bg-sunken rounded transition-colors",
49
- "aria-label": h("vibecontrols.assistant.panel.clearConversation", "Clear conversation"),
50
- children: /* @__PURE__ */ d(c, { className: "size-3.5 text-text-secondary" })
50
+ "aria-label": g("vibecontrols.assistant.panel.clearConversation", "Clear conversation"),
51
+ children: /* @__PURE__ */ f(l, { className: "size-3.5 text-text-secondary" })
51
52
  }),
52
- /* @__PURE__ */ d(o, {}),
53
- /* @__PURE__ */ d("button", {
53
+ /* @__PURE__ */ f(s, {}),
54
+ /* @__PURE__ */ f("button", {
54
55
  type: "button",
55
- onClick: p,
56
+ onClick: m,
56
57
  className: "p-1 hover:bg-bg-sunken rounded transition-colors md:hidden",
57
- "aria-label": h("vibecontrols.assistant.panel.close", "Close assistant"),
58
- children: /* @__PURE__ */ d(u, { className: "size-3.5 text-text-secondary" })
58
+ "aria-label": g("vibecontrols.assistant.panel.close", "Close assistant"),
59
+ children: /* @__PURE__ */ f(d, { className: "size-3.5 text-text-secondary" })
59
60
  })
60
61
  ]
61
62
  })]
62
63
  }),
63
- y && /* @__PURE__ */ d(a, {}),
64
- /* @__PURE__ */ d(i, {})
64
+ /* @__PURE__ */ f(a, {}),
65
+ b && /* @__PURE__ */ f(o, {}),
66
+ /* @__PURE__ */ f(i, {})
65
67
  ]
66
68
  });
67
69
  };
68
70
  //#endregion
69
- export { p as AIAssistantPanel };
71
+ export { m as AIAssistantPanel };
70
72
 
71
73
  //# sourceMappingURL=AIAssistantPanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AIAssistantPanel.js","names":[],"sources":["../../../src/components/assistant/AIAssistantPanel.tsx"],"sourcesContent":["/**\n * AI Assistant Panel — top-level component for the right-pane assistant.\n *\n * Receives PanelProps from the plugin panel system. Orchestrates the\n * conversation list, chat area, and export menu.\n *\n * Responsive behavior:\n * - Desktop (>= 768px): Renders inline within PanelContainer (right panel)\n * - Mobile (< 768px): Panel container handles sizing; panel fills available space\n */\n\nimport { useRef, type FC } from 'react';\nimport { MessageSquare, Eraser, X } from 'lucide-react';\nimport type { PanelProps } from '@burdenoff/fe-libs/shared/plugins';\nimport { useAssistantStore } from '@/store/assistantStore';\nimport { useAssistantKeyboardShortcuts } from '@/hooks/useAssistantKeyboardShortcuts';\nimport { useAssistantHeartbeat } from '@/hooks/useAssistantHeartbeat';\nimport { AssistantChatArea } from './AssistantChatArea';\nimport { AssistantConversationList } from './AssistantConversationList';\nimport { AssistantExportMenu } from './AssistantExportMenu';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport const AIAssistantPanel: FC<PanelProps> = ({ onClose, visible }) => {\n const tr = useTr();\n const panelRef = useRef<HTMLDivElement>(null);\n\n const {\n conversations,\n activeConversationId,\n conversationListOpen,\n toggleConversationList,\n clearConversation,\n } = useAssistantStore();\n\n const activeConversation = conversations.find((c) => c.id === activeConversationId);\n const activeSessionId = activeConversation?.backendSessionId ?? null;\n\n // Keep sandbox alive while panel is open\n useAssistantHeartbeat(activeSessionId, activeConversation?.mode ?? null, visible ?? true);\n\n const hasMessages = (activeConversation?.messages.length ?? 0) > 0;\n\n useAssistantKeyboardShortcuts({\n panelRef,\n onClose,\n });\n\n return (\n <div\n ref={panelRef}\n className=\"flex flex-col h-full w-full bg-bg-surface relative\"\n role=\"complementary\"\n aria-label={tr('vibecontrols.assistant.panel.ariaLabel', 'AI Assistant')}\n data-testid=\"ai-assistant-panel\"\n tabIndex={-1}\n >\n {/* Panel header */}\n <div className=\"flex items-center justify-between px-3 py-2 border-b border-border-default bg-bg-elevated flex-shrink-0\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <button\n type=\"button\"\n onClick={toggleConversationList}\n className=\"p-1 hover:bg-bg-sunken rounded transition-colors\"\n aria-label={tr('vibecontrols.assistant.panel.toggleList', 'Toggle conversation list')}\n data-testid=\"toggle-conversation-list\"\n >\n <MessageSquare className=\"size-3.5 text-text-secondary\" />\n </button>\n <span className=\"text-xs font-semibold text-text-primary truncate\">\n {activeConversation?.title ?? tr('vibecontrols.assistant.panel.title', 'AI Assistant')}\n </span>\n </div>\n\n <div className=\"flex items-center gap-0.5 flex-shrink-0\">\n {hasMessages && (\n <button\n type=\"button\"\n onClick={() => activeConversationId && clearConversation(activeConversationId)}\n className=\"p-1 hover:bg-bg-sunken rounded transition-colors\"\n aria-label={tr(\n 'vibecontrols.assistant.panel.clearConversation',\n 'Clear conversation'\n )}\n >\n <Eraser className=\"size-3.5 text-text-secondary\" />\n </button>\n )}\n <AssistantExportMenu />\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 hover:bg-bg-sunken rounded transition-colors md:hidden\"\n aria-label={tr('vibecontrols.assistant.panel.close', 'Close assistant')}\n >\n <X className=\"size-3.5 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n {/* Conversation list overlay */}\n {conversationListOpen && <AssistantConversationList />}\n\n {/* Chat area */}\n <AssistantChatArea />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;AAsBA,IAAa,KAAoC,EAAE,YAAS,iBAAc;CACxE,IAAM,IAAK,GAAO,EACZ,IAAW,EAAuB,KAAK,EAEvC,EACJ,kBACA,yBACA,yBACA,2BACA,yBACE,GAAmB,EAEjB,IAAqB,EAAc,MAAM,MAAM,EAAE,OAAO,EAAqB;AAInF,GAHwB,GAAoB,oBAAoB,MAGzB,GAAoB,QAAQ,MAAM,KAAW,GAAK;CAEzF,IAAM,KAAe,GAAoB,SAAS,UAAU,KAAK;AAOjE,QALA,EAA8B;EAC5B;EACA;EACD,CAAC,EAGA,kBAAC,OAAD;EACE,KAAK;EACL,WAAU;EACV,MAAK;EACL,cAAY,EAAG,0CAA0C,eAAe;EACxE,eAAY;EACZ,UAAU;YANZ;GASE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAY,EAAG,2CAA2C,2BAA2B;MACrF,eAAY;gBAEZ,kBAAC,GAAD,EAAe,WAAU,gCAAiC,CAAA;MACnD,CAAA,EACT,kBAAC,QAAD;MAAM,WAAU;gBACb,GAAoB,SAAS,EAAG,sCAAsC,eAAe;MACjF,CAAA,CACH;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG,KACC,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,KAAwB,EAAkB,EAAqB;OAC9E,WAAU;OACV,cAAY,EACV,kDACA,qBACD;iBAED,kBAAC,GAAD,EAAQ,WAAU,gCAAiC,CAAA;OAC5C,CAAA;MAEX,kBAAC,GAAD,EAAuB,CAAA;MACvB,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,WAAU;OACV,cAAY,EAAG,sCAAsC,kBAAkB;iBAEvE,kBAAC,GAAD,EAAG,WAAU,gCAAiC,CAAA;OACvC,CAAA;MACL;OACF;;GAGL,KAAwB,kBAAC,GAAD,EAA6B,CAAA;GAGtD,kBAAC,GAAD,EAAqB,CAAA;GACjB"}
1
+ {"version":3,"file":"AIAssistantPanel.js","names":[],"sources":["../../../src/components/assistant/AIAssistantPanel.tsx"],"sourcesContent":["/**\n * AI Assistant Panel — top-level component for the right-pane assistant.\n *\n * Receives PanelProps from the plugin panel system. Orchestrates the\n * conversation list, chat area, and export menu.\n *\n * Responsive behavior:\n * - Desktop (>= 768px): Renders inline within PanelContainer (right panel)\n * - Mobile (< 768px): Panel container handles sizing; panel fills available space\n */\n\nimport { useRef, type FC } from 'react';\nimport { MessageSquare, Eraser, X } from 'lucide-react';\nimport type { PanelProps } from '@burdenoff/fe-libs/shared/plugins';\nimport { useAssistantStore } from '@/store/assistantStore';\nimport { useAssistantKeyboardShortcuts } from '@/hooks/useAssistantKeyboardShortcuts';\nimport { useAssistantHeartbeat } from '@/hooks/useAssistantHeartbeat';\nimport { AssistantChatArea } from './AssistantChatArea';\nimport { AiCreditsMeter } from './AiCreditsMeter';\nimport { AssistantConversationList } from './AssistantConversationList';\nimport { AssistantExportMenu } from './AssistantExportMenu';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport const AIAssistantPanel: FC<PanelProps> = ({ onClose, visible }) => {\n const tr = useTr();\n const panelRef = useRef<HTMLDivElement>(null);\n\n const {\n conversations,\n activeConversationId,\n conversationListOpen,\n toggleConversationList,\n clearConversation,\n } = useAssistantStore();\n\n const activeConversation = conversations.find((c) => c.id === activeConversationId);\n const activeSessionId = activeConversation?.backendSessionId ?? null;\n\n // Keep sandbox alive while panel is open\n useAssistantHeartbeat(activeSessionId, activeConversation?.mode ?? null, visible ?? true);\n\n const hasMessages = (activeConversation?.messages.length ?? 0) > 0;\n\n useAssistantKeyboardShortcuts({\n panelRef,\n onClose,\n });\n\n return (\n <div\n ref={panelRef}\n className=\"flex flex-col h-full w-full bg-bg-surface relative\"\n role=\"complementary\"\n aria-label={tr('vibecontrols.assistant.panel.ariaLabel', 'AI Assistant')}\n data-testid=\"ai-assistant-panel\"\n tabIndex={-1}\n >\n {/* Panel header */}\n <div className=\"flex items-center justify-between px-3 py-2 border-b border-border-default bg-bg-elevated flex-shrink-0\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <button\n type=\"button\"\n onClick={toggleConversationList}\n className=\"p-1 hover:bg-bg-sunken rounded transition-colors\"\n aria-label={tr('vibecontrols.assistant.panel.toggleList', 'Toggle conversation list')}\n data-testid=\"toggle-conversation-list\"\n >\n <MessageSquare className=\"size-3.5 text-text-secondary\" />\n </button>\n <span className=\"text-xs font-semibold text-text-primary truncate\">\n {activeConversation?.title ?? tr('vibecontrols.assistant.panel.title', 'AI Assistant')}\n </span>\n </div>\n\n <div className=\"flex items-center gap-0.5 flex-shrink-0\">\n {hasMessages && (\n <button\n type=\"button\"\n onClick={() => activeConversationId && clearConversation(activeConversationId)}\n className=\"p-1 hover:bg-bg-sunken rounded transition-colors\"\n aria-label={tr(\n 'vibecontrols.assistant.panel.clearConversation',\n 'Clear conversation'\n )}\n >\n <Eraser className=\"size-3.5 text-text-secondary\" />\n </button>\n )}\n <AssistantExportMenu />\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 hover:bg-bg-sunken rounded transition-colors md:hidden\"\n aria-label={tr('vibecontrols.assistant.panel.close', 'Close assistant')}\n >\n <X className=\"size-3.5 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n {/* Live AI-credits usage meter (hidden when there's no quota to show) */}\n <AiCreditsMeter />\n\n {/* Conversation list overlay */}\n {conversationListOpen && <AssistantConversationList />}\n\n {/* Chat area */}\n <AssistantChatArea />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;AAuBA,IAAa,KAAoC,EAAE,YAAS,iBAAc;CACxE,IAAM,IAAK,GAAO,EACZ,IAAW,EAAuB,KAAK,EAEvC,EACJ,kBACA,yBACA,yBACA,2BACA,yBACE,GAAmB,EAEjB,IAAqB,EAAc,MAAM,MAAM,EAAE,OAAO,EAAqB;AAInF,GAHwB,GAAoB,oBAAoB,MAGzB,GAAoB,QAAQ,MAAM,KAAW,GAAK;CAEzF,IAAM,KAAe,GAAoB,SAAS,UAAU,KAAK;AAOjE,QALA,EAA8B;EAC5B;EACA;EACD,CAAC,EAGA,kBAAC,OAAD;EACE,KAAK;EACL,WAAU;EACV,MAAK;EACL,cAAY,EAAG,0CAA0C,eAAe;EACxE,eAAY;EACZ,UAAU;YANZ;GASE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAY,EAAG,2CAA2C,2BAA2B;MACrF,eAAY;gBAEZ,kBAAC,GAAD,EAAe,WAAU,gCAAiC,CAAA;MACnD,CAAA,EACT,kBAAC,QAAD;MAAM,WAAU;gBACb,GAAoB,SAAS,EAAG,sCAAsC,eAAe;MACjF,CAAA,CACH;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG,KACC,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,KAAwB,EAAkB,EAAqB;OAC9E,WAAU;OACV,cAAY,EACV,kDACA,qBACD;iBAED,kBAAC,GAAD,EAAQ,WAAU,gCAAiC,CAAA;OAC5C,CAAA;MAEX,kBAAC,GAAD,EAAuB,CAAA;MACvB,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,WAAU;OACV,cAAY,EAAG,sCAAsC,kBAAkB;iBAEvE,kBAAC,GAAD,EAAG,WAAU,gCAAiC,CAAA;OACvC,CAAA;MACL;OACF;;GAGN,kBAAC,GAAD,EAAkB,CAAA;GAGjB,KAAwB,kBAAC,GAAD,EAA6B,CAAA;GAGtD,kBAAC,GAAD,EAAqB,CAAA;GACjB"}
@@ -0,0 +1,21 @@
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { useAiCreditsQuota as t } from "../../hooks/useAiCreditsQuota.js";
3
+ import { jsx as n } from "react/jsx-runtime";
4
+ import { QuotaMeter as r } from "@burdenoff/fe-libs/shared/components";
5
+ //#region src/components/assistant/AiCreditsMeter.tsx
6
+ var i = () => {
7
+ let i = e(), { data: a } = t();
8
+ return !a || a.noLimit ? null : /* @__PURE__ */ n("div", {
9
+ className: "px-3 py-1.5 border-b border-border-default bg-bg-surface flex-shrink-0",
10
+ children: /* @__PURE__ */ n(r, {
11
+ label: i("vibecontrols.assistant.credits.label", "AI credits"),
12
+ used: a.used,
13
+ limit: a.limit,
14
+ unit: i("vibecontrols.assistant.credits.unit", "tokens")
15
+ })
16
+ });
17
+ };
18
+ //#endregion
19
+ export { i as AiCreditsMeter };
20
+
21
+ //# sourceMappingURL=AiCreditsMeter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiCreditsMeter.js","names":[],"sources":["../../../src/components/assistant/AiCreditsMeter.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { QuotaMeter } from '@burdenoff/fe-libs/shared/components';\nimport { useAiCreditsQuota } from '@/hooks/useAiCreditsQuota';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Thin AI-credits usage strip shown under the assistant panel header. Hidden\n * entirely when there's no quota to show (standalone preview, no billing\n * account, or unlimited plan with nothing meaningful to meter).\n */\nexport const AiCreditsMeter: FC = () => {\n const tr = useTr();\n const { data } = useAiCreditsQuota();\n\n // Nothing to meter — hide rather than render an empty/Unlimited strip.\n if (!data || data.noLimit) return null;\n\n return (\n <div className=\"px-3 py-1.5 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <QuotaMeter\n label={tr('vibecontrols.assistant.credits.label', 'AI credits')}\n used={data.used}\n limit={data.limit}\n unit={tr('vibecontrols.assistant.credits.unit', 'tokens')}\n />\n </div>\n );\n};\n"],"mappings":";;;;;AAUA,IAAa,UAA2B;CACtC,IAAM,IAAK,GAAO,EACZ,EAAE,YAAS,GAAmB;AAKpC,QAFI,CAAC,KAAQ,EAAK,UAAgB,OAGhC,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,OAAO,EAAG,wCAAwC,aAAa;GAC/D,MAAM,EAAK;GACX,OAAO,EAAK;GACZ,MAAM,EAAG,uCAAuC,SAAS;GACzD,CAAA;EACE,CAAA"}