@burdenoff/microfe-vibecontrols 2026.521.3 → 2026.522.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/VibeControlsRoot.js +86 -78
  2. package/dist/VibeControlsRoot.js.map +1 -1
  3. package/dist/VibeControlsRoutes.js +168 -125
  4. package/dist/VibeControlsRoutes.js.map +1 -1
  5. package/dist/components/agent-manager/AgentManagerBookmarksDrawer.js +174 -0
  6. package/dist/components/agent-manager/AgentManagerBookmarksDrawer.js.map +1 -0
  7. package/dist/components/agent-manager/AgentManagerChatPane.js +149 -94
  8. package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
  9. package/dist/components/agent-manager/AgentManagerMessageItem.js +52 -41
  10. package/dist/components/agent-manager/AgentManagerMessageItem.js.map +1 -1
  11. package/dist/components/agent-manager/AgentManagerMessageList.js +22 -15
  12. package/dist/components/agent-manager/AgentManagerMessageList.js.map +1 -1
  13. package/dist/components/agents/OpenInEditorMenu.js +241 -0
  14. package/dist/components/agents/OpenInEditorMenu.js.map +1 -0
  15. package/dist/components/agents/VibeOpenInEditorMenu.js +20 -0
  16. package/dist/components/agents/VibeOpenInEditorMenu.js.map +1 -0
  17. package/dist/components/agents/idePresets.js +148 -0
  18. package/dist/components/agents/idePresets.js.map +1 -0
  19. package/dist/components/agents/index.js +3 -0
  20. package/dist/components/ai/AIPlanPanel.js +332 -0
  21. package/dist/components/ai/AIPlanPanel.js.map +1 -0
  22. package/dist/components/analytics/index.js +2 -2
  23. package/dist/components/assistant/AssistantMessageItem.js +55 -47
  24. package/dist/components/assistant/AssistantMessageItem.js.map +1 -1
  25. package/dist/components/audit/AuditExportButton.js +87 -41
  26. package/dist/components/audit/AuditExportButton.js.map +1 -1
  27. package/dist/components/gitops/CIDurationChart.js +11 -11
  28. package/dist/components/security/SecurityEmptyState.js +33 -0
  29. package/dist/components/security/SecurityEmptyState.js.map +1 -0
  30. package/dist/components/security/SecurityEvidenceList.js +42 -0
  31. package/dist/components/security/SecurityEvidenceList.js.map +1 -0
  32. package/dist/components/security/SecurityFindingsTable.js +201 -0
  33. package/dist/components/security/SecurityFindingsTable.js.map +1 -0
  34. package/dist/components/security/SecurityHeaderStrip.js +67 -0
  35. package/dist/components/security/SecurityHeaderStrip.js.map +1 -0
  36. package/dist/components/security/SecurityLifecycleStepper.js +66 -0
  37. package/dist/components/security/SecurityLifecycleStepper.js.map +1 -0
  38. package/dist/components/security/SecurityProviderDrawer.js +112 -0
  39. package/dist/components/security/SecurityProviderDrawer.js.map +1 -0
  40. package/dist/components/security/SecurityTabPanel.js +105 -0
  41. package/dist/components/security/SecurityTabPanel.js.map +1 -0
  42. package/dist/components/security/gql.js +128 -0
  43. package/dist/components/security/gql.js.map +1 -0
  44. package/dist/components/security/index.js +8 -0
  45. package/dist/components/security/types.js +80 -0
  46. package/dist/components/security/types.js.map +1 -0
  47. package/dist/components/settings/IdePreferencesSection.js +66 -0
  48. package/dist/components/settings/IdePreferencesSection.js.map +1 -0
  49. package/dist/components/shared/PageSkeleton.js +88 -0
  50. package/dist/components/shared/PageSkeleton.js.map +1 -0
  51. package/dist/constants/pluginCatalog.js +20 -8
  52. package/dist/constants/pluginCatalog.js.map +1 -1
  53. package/dist/generated/wspace-operations.js +1350 -1262
  54. package/dist/generated/wspace-operations.js.map +1 -1
  55. package/dist/generated/wspace-types.js.map +1 -1
  56. package/dist/hooks/useToggleAgentBookmark.js +32 -0
  57. package/dist/hooks/useToggleAgentBookmark.js.map +1 -0
  58. package/dist/index.js +10 -10
  59. package/dist/pages/AgentDetailsPage.js +25 -25
  60. package/dist/pages/AgentDetailsPage.js.map +1 -1
  61. package/dist/pages/AgentsPage.js +326 -327
  62. package/dist/pages/AgentsPage.js.map +1 -1
  63. package/dist/pages/AnalyticsPage.js +425 -420
  64. package/dist/pages/AnalyticsPage.js.map +1 -1
  65. package/dist/pages/CatalogDetailPage.js +258 -249
  66. package/dist/pages/CatalogDetailPage.js.map +1 -1
  67. package/dist/pages/CatalogPage.js +22 -22
  68. package/dist/pages/CatalogPage.js.map +1 -1
  69. package/dist/pages/ComponentDetailPage.js +30 -30
  70. package/dist/pages/ComponentDetailPage.js.map +1 -1
  71. package/dist/pages/ComponentsPage.js +23 -23
  72. package/dist/pages/ComponentsPage.js.map +1 -1
  73. package/dist/pages/LogsPage.js +107 -0
  74. package/dist/pages/LogsPage.js.map +1 -0
  75. package/dist/pages/NoteDetailsPage.js +12 -12
  76. package/dist/pages/NoteDetailsPage.js.map +1 -1
  77. package/dist/pages/SessionDetailsPage.js +12 -12
  78. package/dist/pages/SessionDetailsPage.js.map +1 -1
  79. package/dist/pages/SessionsPage.js +158 -136
  80. package/dist/pages/SessionsPage.js.map +1 -1
  81. package/dist/pages/SettingsPage.js +60 -52
  82. package/dist/pages/SettingsPage.js.map +1 -1
  83. package/dist/pages/TargetDetailsPage.js +18 -18
  84. package/dist/pages/TargetDetailsPage.js.map +1 -1
  85. package/dist/pages/TargetsPage.js +328 -331
  86. package/dist/pages/TargetsPage.js.map +1 -1
  87. package/dist/pages/TemplateDetailPage.js +17 -17
  88. package/dist/pages/TemplateDetailPage.js.map +1 -1
  89. package/dist/pages/TemplatesPage.js +17 -17
  90. package/dist/pages/TemplatesPage.js.map +1 -1
  91. package/dist/pages/VibeDetailsPage.js +552 -535
  92. package/dist/pages/VibeDetailsPage.js.map +1 -1
  93. package/dist/pages/VibesPage.js +321 -296
  94. package/dist/pages/VibesPage.js.map +1 -1
  95. package/dist/pages/ai/AIBookmarksPage.js +184 -0
  96. package/dist/pages/ai/AIBookmarksPage.js.map +1 -0
  97. package/dist/pages/ai/AIPage.js +138 -109
  98. package/dist/pages/ai/AIPage.js.map +1 -1
  99. package/dist/services/planApi.js +18 -0
  100. package/dist/services/planApi.js.map +1 -0
  101. package/dist/store/vibeControlsStore.js +20 -2
  102. package/dist/store/vibeControlsStore.js.map +1 -1
  103. package/dist/translations/en.js +30 -1
  104. package/dist/translations/en.js.map +1 -1
  105. package/package.json +4 -4
@@ -2,61 +2,72 @@ import { useTr as e } from "../../shared/hooks/useTr.js";
2
2
  import { AssistantMarkdownRenderer as t } from "../assistant/AssistantMarkdownRenderer.js";
3
3
  import { relativeTime as n } from "../../utils/relativeTime.js";
4
4
  import { useState as r } from "react";
5
- import { Bot as i, Check as a, Copy as o, User as s } from "lucide-react";
6
- import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
5
+ import { Bookmark as i, BookmarkCheck as a, Bot as o, Check as s, Copy as c, User as l } from "lucide-react";
6
+ import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
7
7
  //#region src/components/agent-manager/AgentManagerMessageItem.tsx
8
- var d = RegExp("\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])", "g");
9
- function f(e) {
10
- return e.replace(d, "");
8
+ var p = RegExp("\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])", "g");
9
+ function m(e) {
10
+ return e.replace(p, "");
11
11
  }
12
- function p(e, t) {
12
+ function h(e, t) {
13
13
  if (!t) return e;
14
14
  let n = e.toLowerCase(), r = t.toLowerCase(), i = [], a = 0, o = n.indexOf(r, a);
15
- for (; o !== -1;) o > a && i.push(e.slice(a, o)), i.push(/* @__PURE__ */ l("mark", {
15
+ for (; o !== -1;) o > a && i.push(e.slice(a, o)), i.push(/* @__PURE__ */ d("mark", {
16
16
  className: "bg-status-warning-bg text-status-warning-text rounded-sm px-0.5",
17
17
  children: e.slice(o, o + t.length)
18
18
  }, o)), a = o + t.length, o = n.indexOf(r, a);
19
- return a < e.length && i.push(e.slice(a)), i.length > 0 ? /* @__PURE__ */ l(c, { children: i }) : e;
19
+ return a < e.length && i.push(e.slice(a)), i.length > 0 ? /* @__PURE__ */ d(u, { children: i }) : e;
20
20
  }
21
- var m = ({ message: c, searchQuery: d = "" }) => {
22
- let [m, h] = r(!1), [g, _] = r(!1), v = c.role === "user", y = e(), b = f(c.content), x = () => {
23
- navigator.clipboard.writeText(b), h(!0), setTimeout(() => h(!1), 2e3);
24
- }, S = c.tokens ? c.tokens.input + c.tokens.output : null;
25
- return /* @__PURE__ */ u("div", {
26
- className: `flex gap-2.5 ${v ? "flex-row-reverse" : "flex-row"}`,
27
- onMouseEnter: () => _(!0),
28
- onMouseLeave: () => _(!1),
29
- children: [/* @__PURE__ */ l("div", {
30
- className: `flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center mt-0.5 ${v ? "bg-action-primary-bg" : "bg-bg-elevated border border-border-default"}`,
31
- children: v ? /* @__PURE__ */ l(s, { className: "w-3.5 h-3.5 text-action-primary-text" }) : /* @__PURE__ */ l(i, { className: "w-3.5 h-3.5 text-text-secondary" })
32
- }), /* @__PURE__ */ u("div", {
33
- className: `relative max-w-[85%] rounded-lg px-3 py-2 ${v ? "bg-action-primary-bg text-action-primary-text" : "bg-bg-elevated border border-border-subtle"}`,
34
- children: [v ? /* @__PURE__ */ l("p", {
21
+ var g = ({ message: u, searchQuery: p = "", isBookmarked: g = !1, onToggleBookmark: _, flashMessageId: v }) => {
22
+ let [y, b] = r(!1), [x, S] = r(!1), C = u.role === "user", w = e(), T = m(u.content), E = v === u.id, D = () => {
23
+ navigator.clipboard.writeText(T), b(!0), setTimeout(() => b(!1), 2e3);
24
+ }, O = () => {
25
+ _ && _(u.id);
26
+ }, k = u.tokens ? u.tokens.input + u.tokens.output : null;
27
+ return /* @__PURE__ */ f("div", {
28
+ "data-message-id": u.id,
29
+ className: `flex gap-2.5 scroll-mt-16 transition-shadow duration-300 ${C ? "flex-row-reverse" : "flex-row"} ${E ? "ring-2 ring-status-warning-bg/60 rounded-lg" : ""}`,
30
+ onMouseEnter: () => S(!0),
31
+ onMouseLeave: () => S(!1),
32
+ children: [/* @__PURE__ */ d("div", {
33
+ className: `flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center mt-0.5 ${C ? "bg-action-primary-bg" : "bg-bg-elevated border border-border-default"}`,
34
+ children: C ? /* @__PURE__ */ d(l, { className: "w-3.5 h-3.5 text-action-primary-text" }) : /* @__PURE__ */ d(o, { className: "w-3.5 h-3.5 text-text-secondary" })
35
+ }), /* @__PURE__ */ f("div", {
36
+ className: `relative max-w-[85%] rounded-lg px-3 py-2 ${C ? "bg-action-primary-bg text-action-primary-text" : "bg-bg-elevated border border-border-subtle"}`,
37
+ children: [C ? /* @__PURE__ */ d("p", {
35
38
  className: "text-xs whitespace-pre-wrap leading-relaxed break-words",
36
- children: d ? p(b, d) : b
37
- }) : d ? /* @__PURE__ */ l("p", {
39
+ children: p ? h(T, p) : T
40
+ }) : p ? /* @__PURE__ */ d("p", {
38
41
  className: "text-xs whitespace-pre-wrap leading-relaxed break-words text-text-primary",
39
- children: p(b, d)
40
- }) : /* @__PURE__ */ l(t, { content: b }), /* @__PURE__ */ u("div", {
41
- className: `flex items-center gap-2 text-[10px] mt-1 ${v ? "text-action-primary-text/60" : "text-text-muted"}`,
42
+ children: h(T, p)
43
+ }) : /* @__PURE__ */ d(t, { content: T }), /* @__PURE__ */ f("div", {
44
+ className: `flex items-center gap-2 text-[10px] mt-1 ${C ? "text-action-primary-text/60" : "text-text-muted"}`,
42
45
  children: [
43
- /* @__PURE__ */ l("span", { children: n(c.timestamp) }),
44
- S !== null && !v && /* @__PURE__ */ u("span", { children: [
45
- S,
46
+ /* @__PURE__ */ d("span", { children: n(u.timestamp) }),
47
+ k !== null && !C && /* @__PURE__ */ f("span", { children: [
48
+ k,
46
49
  " ",
47
- y("vibecontrols.agentManager.messageItem.tokens", "tokens")
50
+ w("vibecontrols.agentManager.messageItem.tokens", "tokens")
48
51
  ] }),
49
- c.sdk && /* @__PURE__ */ l("span", {
52
+ u.sdk && /* @__PURE__ */ d("span", {
50
53
  className: "px-1 py-0.5 rounded bg-bg-elevated text-text-muted uppercase font-mono text-[9px]",
51
- children: c.sdk
54
+ children: u.sdk
52
55
  }),
53
- c.model && !v && /* @__PURE__ */ l("span", { children: c.model }),
54
- g && /* @__PURE__ */ l("button", {
55
- onClick: x,
56
- className: `ml-auto p-0.5 rounded transition-colors ${v ? "hover:bg-action-primary-bg-hover" : "hover:bg-bg-sunken"}`,
57
- "aria-label": y("vibecontrols.agentManager.messageItem.copyMessage", "Copy message"),
58
- title: y("vibecontrols.agentManager.messageItem.copyMessage", "Copy message"),
59
- children: m ? /* @__PURE__ */ l(a, { className: "w-3 h-3 text-status-success-text" }) : /* @__PURE__ */ l(o, { className: "w-3 h-3" })
56
+ u.model && !C && /* @__PURE__ */ d("span", { children: u.model }),
57
+ (x || g) && _ && /* @__PURE__ */ d("button", {
58
+ onClick: O,
59
+ className: `ml-auto p-0.5 rounded transition-colors ${C ? "hover:bg-action-primary-bg-hover" : "hover:bg-bg-sunken"}`,
60
+ "aria-label": g ? w("vibecontrols.agentManager.messageItem.removeBookmark", "Remove bookmark") : w("vibecontrols.agentManager.messageItem.bookmarkMessage", "Bookmark message"),
61
+ title: g ? w("vibecontrols.agentManager.messageItem.removeBookmark", "Remove bookmark") : w("vibecontrols.agentManager.messageItem.bookmarkMessage", "Bookmark message"),
62
+ "data-testid": "agent-manager-bookmark-toggle",
63
+ children: g ? /* @__PURE__ */ d(a, { className: "w-3 h-3 text-status-warning-text" }) : /* @__PURE__ */ d(i, { className: "w-3 h-3" })
64
+ }),
65
+ x && /* @__PURE__ */ d("button", {
66
+ onClick: D,
67
+ className: `${_ ? "" : "ml-auto"} p-0.5 rounded transition-colors ${C ? "hover:bg-action-primary-bg-hover" : "hover:bg-bg-sunken"}`,
68
+ "aria-label": w("vibecontrols.agentManager.messageItem.copyMessage", "Copy message"),
69
+ title: w("vibecontrols.agentManager.messageItem.copyMessage", "Copy message"),
70
+ children: y ? /* @__PURE__ */ d(s, { className: "w-3 h-3 text-status-success-text" }) : /* @__PURE__ */ d(c, { className: "w-3 h-3" })
60
71
  })
61
72
  ]
62
73
  })]
@@ -64,6 +75,6 @@ var m = ({ message: c, searchQuery: d = "" }) => {
64
75
  });
65
76
  };
66
77
  //#endregion
67
- export { m as AgentManagerMessageItem };
78
+ export { g as AgentManagerMessageItem };
68
79
 
69
80
  //# sourceMappingURL=AgentManagerMessageItem.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AgentManagerMessageItem.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerMessageItem.tsx"],"sourcesContent":["/**\n * Single message bubble for the Agent Manager chat.\n *\n * User messages are right-aligned; assistant messages are left-aligned.\n * Uses AssistantMarkdownRenderer for rich content in assistant messages.\n * Supports optional search query highlighting.\n */\n\nimport { useState, type FC, type ReactNode } from 'react';\nimport { Bot, Copy, Check, User } from 'lucide-react';\nimport { AssistantMarkdownRenderer } from '@/components/assistant/AssistantMarkdownRenderer';\nimport { relativeTime } from '@/utils/relativeTime';\nimport type { AgentManagerMessage } from '@/types/agentManager';\nimport { useTr } from '../../shared/hooks/useTr';\n\nconst ANSI_ESCAPE_RE = new RegExp(\n `${String.fromCharCode(27)}(?:[@-Z\\\\-_]|\\\\[[0-?]*[ -/]*[@-~])`,\n 'g'\n);\n\nfunction stripAnsi(value: string): string {\n return value.replace(ANSI_ESCAPE_RE, '');\n}\n\n/** Highlight occurrences of `query` within `text`. */\nfunction highlightText(text: string, query: string): ReactNode {\n if (!query) return text;\n\n const lowerText = text.toLowerCase();\n const lowerQuery = query.toLowerCase();\n const parts: ReactNode[] = [];\n let lastIndex = 0;\n let matchIndex = lowerText.indexOf(lowerQuery, lastIndex);\n\n while (matchIndex !== -1) {\n if (matchIndex > lastIndex) {\n parts.push(text.slice(lastIndex, matchIndex));\n }\n parts.push(\n <mark\n key={matchIndex}\n className=\"bg-status-warning-bg text-status-warning-text rounded-sm px-0.5\"\n >\n {text.slice(matchIndex, matchIndex + query.length)}\n </mark>\n );\n lastIndex = matchIndex + query.length;\n matchIndex = lowerText.indexOf(lowerQuery, lastIndex);\n }\n\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts.length > 0 ? <>{parts}</> : text;\n}\n\ninterface AgentManagerMessageItemProps {\n message: AgentManagerMessage;\n searchQuery?: string;\n}\n\nexport const AgentManagerMessageItem: FC<AgentManagerMessageItemProps> = ({\n message,\n searchQuery = '',\n}) => {\n const [copied, setCopied] = useState(false);\n const [hovered, setHovered] = useState(false);\n const isUser = message.role === 'user';\n const tr = useTr();\n const displayContent = stripAnsi(message.content);\n\n const handleCopy = () => {\n navigator.clipboard.writeText(displayContent);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n const totalTokens = message.tokens ? message.tokens.input + message.tokens.output : null;\n\n return (\n <div\n className={`flex gap-2.5 ${isUser ? 'flex-row-reverse' : 'flex-row'}`}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n >\n {/* Avatar */}\n <div\n className={`flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center mt-0.5 ${\n isUser ? 'bg-action-primary-bg' : 'bg-bg-elevated border border-border-default'\n }`}\n >\n {isUser ? (\n <User className=\"w-3.5 h-3.5 text-action-primary-text\" />\n ) : (\n <Bot className=\"w-3.5 h-3.5 text-text-secondary\" />\n )}\n </div>\n\n {/* Message bubble */}\n <div\n className={`relative max-w-[85%] rounded-lg px-3 py-2 ${\n isUser\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-elevated border border-border-subtle'\n }`}\n >\n {/* Content */}\n {isUser ? (\n <p className=\"text-xs whitespace-pre-wrap leading-relaxed break-words\">\n {searchQuery ? highlightText(displayContent, searchQuery) : displayContent}\n </p>\n ) : searchQuery ? (\n <p className=\"text-xs whitespace-pre-wrap leading-relaxed break-words text-text-primary\">\n {highlightText(displayContent, searchQuery)}\n </p>\n ) : (\n <AssistantMarkdownRenderer content={displayContent} />\n )}\n\n {/* Footer: timestamp, tokens, copy */}\n <div\n className={`flex items-center gap-2 text-[10px] mt-1 ${\n isUser ? 'text-action-primary-text/60' : 'text-text-muted'\n }`}\n >\n <span>{relativeTime(message.timestamp)}</span>\n\n {totalTokens !== null && !isUser && (\n <span>\n {totalTokens} {tr('vibecontrols.agentManager.messageItem.tokens', 'tokens')}\n </span>\n )}\n\n {message.sdk && (\n <span className=\"px-1 py-0.5 rounded bg-bg-elevated text-text-muted uppercase font-mono text-[9px]\">\n {message.sdk}\n </span>\n )}\n\n {message.model && !isUser && <span>{message.model}</span>}\n\n {/* Copy button on hover */}\n {hovered && (\n <button\n onClick={handleCopy}\n className={`ml-auto p-0.5 rounded transition-colors ${\n isUser ? 'hover:bg-action-primary-bg-hover' : 'hover:bg-bg-sunken'\n }`}\n aria-label={tr('vibecontrols.agentManager.messageItem.copyMessage', 'Copy message')}\n title={tr('vibecontrols.agentManager.messageItem.copyMessage', 'Copy message')}\n >\n {copied ? (\n <Check className=\"w-3 h-3 text-status-success-text\" />\n ) : (\n <Copy className=\"w-3 h-3\" />\n )}\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;AAeA,IAAM,IAAqB,OACzB,0CACA,IACD;AAED,SAAS,EAAU,GAAuB;AACxC,QAAO,EAAM,QAAQ,GAAgB,GAAG;;AAI1C,SAAS,EAAc,GAAc,GAA0B;AAC7D,KAAI,CAAC,EAAO,QAAO;CAEnB,IAAM,IAAY,EAAK,aAAa,EAC9B,IAAa,EAAM,aAAa,EAChC,IAAqB,EAAE,EACzB,IAAY,GACZ,IAAa,EAAU,QAAQ,GAAY,EAAU;AAEzD,QAAO,MAAe,IAapB,CAZI,IAAa,KACf,EAAM,KAAK,EAAK,MAAM,GAAW,EAAW,CAAC,EAE/C,EAAM,KACJ,kBAAC,QAAD;EAEE,WAAU;YAET,EAAK,MAAM,GAAY,IAAa,EAAM,OAAO;EAC7C,EAJA,EAIA,CACR,EACD,IAAY,IAAa,EAAM,QAC/B,IAAa,EAAU,QAAQ,GAAY,EAAU;AAOvD,QAJI,IAAY,EAAK,UACnB,EAAM,KAAK,EAAK,MAAM,EAAU,CAAC,EAG5B,EAAM,SAAS,IAAI,kBAAA,GAAA,EAAA,UAAG,GAAS,CAAA,GAAG;;AAQ3C,IAAa,KAA6D,EACxE,YACA,iBAAc,SACV;CACJ,IAAM,CAAC,GAAQ,KAAa,EAAS,GAAM,EACrC,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,IAAS,EAAQ,SAAS,QAC1B,IAAK,GAAO,EACZ,IAAiB,EAAU,EAAQ,QAAQ,EAE3C,UAAmB;AAGvB,EAFA,UAAU,UAAU,UAAU,EAAe,EAC7C,EAAU,GAAK,EACf,iBAAiB,EAAU,GAAM,EAAE,IAAK;IAGpC,IAAc,EAAQ,SAAS,EAAQ,OAAO,QAAQ,EAAQ,OAAO,SAAS;AAEpF,QACE,kBAAC,OAAD;EACE,WAAW,gBAAgB,IAAS,qBAAqB;EACzD,oBAAoB,EAAW,GAAK;EACpC,oBAAoB,EAAW,GAAM;YAHvC,CAME,kBAAC,OAAD;GACE,WAAW,8EACT,IAAS,yBAAyB;aAGnC,IACC,kBAAC,GAAD,EAAM,WAAU,wCAAyC,CAAA,GAEzD,kBAAC,GAAD,EAAK,WAAU,mCAAoC,CAAA;GAEjD,CAAA,EAGN,kBAAC,OAAD;GACE,WAAW,6CACT,IACI,kDACA;aAJR,CAQG,IACC,kBAAC,KAAD;IAAG,WAAU;cACV,IAAc,EAAc,GAAgB,EAAY,GAAG;IAC1D,CAAA,GACF,IACF,kBAAC,KAAD;IAAG,WAAU;cACV,EAAc,GAAgB,EAAY;IACzC,CAAA,GAEJ,kBAAC,GAAD,EAA2B,SAAS,GAAkB,CAAA,EAIxD,kBAAC,OAAD;IACE,WAAW,4CACT,IAAS,gCAAgC;cAF7C;KAKE,kBAAC,QAAD,EAAA,UAAO,EAAa,EAAQ,UAAU,EAAQ,CAAA;KAE7C,MAAgB,QAAQ,CAAC,KACxB,kBAAC,QAAD,EAAA,UAAA;MACG;MAAY;MAAE,EAAG,gDAAgD,SAAS;MACtE,EAAA,CAAA;KAGR,EAAQ,OACP,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAQ;MACJ,CAAA;KAGR,EAAQ,SAAS,CAAC,KAAU,kBAAC,QAAD,EAAA,UAAO,EAAQ,OAAa,CAAA;KAGxD,KACC,kBAAC,UAAD;MACE,SAAS;MACT,WAAW,2CACT,IAAS,qCAAqC;MAEhD,cAAY,EAAG,qDAAqD,eAAe;MACnF,OAAO,EAAG,qDAAqD,eAAe;gBAE7E,IACC,kBAAC,GAAD,EAAO,WAAU,oCAAqC,CAAA,GAEtD,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;MAEvB,CAAA;KAEP;MACF;KACF"}
1
+ {"version":3,"file":"AgentManagerMessageItem.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerMessageItem.tsx"],"sourcesContent":["/**\n * Single message bubble for the Agent Manager chat.\n *\n * User messages are right-aligned; assistant messages are left-aligned.\n * Uses AssistantMarkdownRenderer for rich content in assistant messages.\n * Supports optional search query highlighting.\n */\n\nimport { useState, type FC, type ReactNode } from 'react';\nimport { Bot, Bookmark, BookmarkCheck, Copy, Check, User } from 'lucide-react';\nimport { AssistantMarkdownRenderer } from '@/components/assistant/AssistantMarkdownRenderer';\nimport { relativeTime } from '@/utils/relativeTime';\nimport type { AgentManagerMessage } from '@/types/agentManager';\nimport { useTr } from '../../shared/hooks/useTr';\n\nconst ANSI_ESCAPE_RE = new RegExp(\n `${String.fromCharCode(27)}(?:[@-Z\\\\-_]|\\\\[[0-?]*[ -/]*[@-~])`,\n 'g'\n);\n\nfunction stripAnsi(value: string): string {\n return value.replace(ANSI_ESCAPE_RE, '');\n}\n\n/** Highlight occurrences of `query` within `text`. */\nfunction highlightText(text: string, query: string): ReactNode {\n if (!query) return text;\n\n const lowerText = text.toLowerCase();\n const lowerQuery = query.toLowerCase();\n const parts: ReactNode[] = [];\n let lastIndex = 0;\n let matchIndex = lowerText.indexOf(lowerQuery, lastIndex);\n\n while (matchIndex !== -1) {\n if (matchIndex > lastIndex) {\n parts.push(text.slice(lastIndex, matchIndex));\n }\n parts.push(\n <mark\n key={matchIndex}\n className=\"bg-status-warning-bg text-status-warning-text rounded-sm px-0.5\"\n >\n {text.slice(matchIndex, matchIndex + query.length)}\n </mark>\n );\n lastIndex = matchIndex + query.length;\n matchIndex = lowerText.indexOf(lowerQuery, lastIndex);\n }\n\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts.length > 0 ? <>{parts}</> : text;\n}\n\ninterface AgentManagerMessageItemProps {\n message: AgentManagerMessage;\n searchQuery?: string;\n isBookmarked?: boolean;\n onToggleBookmark?: (messageId: string) => void;\n flashMessageId?: string | null;\n}\n\nexport const AgentManagerMessageItem: FC<AgentManagerMessageItemProps> = ({\n message,\n searchQuery = '',\n isBookmarked = false,\n onToggleBookmark,\n flashMessageId,\n}) => {\n const [copied, setCopied] = useState(false);\n const [hovered, setHovered] = useState(false);\n const isUser = message.role === 'user';\n const tr = useTr();\n const displayContent = stripAnsi(message.content);\n const isFlashing = flashMessageId === message.id;\n\n const handleCopy = () => {\n navigator.clipboard.writeText(displayContent);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n const handleToggleBookmark = () => {\n if (onToggleBookmark) onToggleBookmark(message.id);\n };\n\n const totalTokens = message.tokens ? message.tokens.input + message.tokens.output : null;\n\n return (\n <div\n data-message-id={message.id}\n className={`flex gap-2.5 scroll-mt-16 transition-shadow duration-300 ${\n isUser ? 'flex-row-reverse' : 'flex-row'\n } ${isFlashing ? 'ring-2 ring-status-warning-bg/60 rounded-lg' : ''}`}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n >\n {/* Avatar */}\n <div\n className={`flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center mt-0.5 ${\n isUser ? 'bg-action-primary-bg' : 'bg-bg-elevated border border-border-default'\n }`}\n >\n {isUser ? (\n <User className=\"w-3.5 h-3.5 text-action-primary-text\" />\n ) : (\n <Bot className=\"w-3.5 h-3.5 text-text-secondary\" />\n )}\n </div>\n\n {/* Message bubble */}\n <div\n className={`relative max-w-[85%] rounded-lg px-3 py-2 ${\n isUser\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-elevated border border-border-subtle'\n }`}\n >\n {/* Content */}\n {isUser ? (\n <p className=\"text-xs whitespace-pre-wrap leading-relaxed break-words\">\n {searchQuery ? highlightText(displayContent, searchQuery) : displayContent}\n </p>\n ) : searchQuery ? (\n <p className=\"text-xs whitespace-pre-wrap leading-relaxed break-words text-text-primary\">\n {highlightText(displayContent, searchQuery)}\n </p>\n ) : (\n <AssistantMarkdownRenderer content={displayContent} />\n )}\n\n {/* Footer: timestamp, tokens, bookmark, copy */}\n <div\n className={`flex items-center gap-2 text-[10px] mt-1 ${\n isUser ? 'text-action-primary-text/60' : 'text-text-muted'\n }`}\n >\n <span>{relativeTime(message.timestamp)}</span>\n\n {totalTokens !== null && !isUser && (\n <span>\n {totalTokens} {tr('vibecontrols.agentManager.messageItem.tokens', 'tokens')}\n </span>\n )}\n\n {message.sdk && (\n <span className=\"px-1 py-0.5 rounded bg-bg-elevated text-text-muted uppercase font-mono text-[9px]\">\n {message.sdk}\n </span>\n )}\n\n {message.model && !isUser && <span>{message.model}</span>}\n\n {/* Bookmark + Copy buttons on hover (or when bookmarked) */}\n {(hovered || isBookmarked) && onToggleBookmark && (\n <button\n onClick={handleToggleBookmark}\n className={`ml-auto p-0.5 rounded transition-colors ${\n isUser ? 'hover:bg-action-primary-bg-hover' : 'hover:bg-bg-sunken'\n }`}\n aria-label={\n isBookmarked\n ? tr('vibecontrols.agentManager.messageItem.removeBookmark', 'Remove bookmark')\n : tr('vibecontrols.agentManager.messageItem.bookmarkMessage', 'Bookmark message')\n }\n title={\n isBookmarked\n ? tr('vibecontrols.agentManager.messageItem.removeBookmark', 'Remove bookmark')\n : tr('vibecontrols.agentManager.messageItem.bookmarkMessage', 'Bookmark message')\n }\n data-testid=\"agent-manager-bookmark-toggle\"\n >\n {isBookmarked ? (\n <BookmarkCheck className=\"w-3 h-3 text-status-warning-text\" />\n ) : (\n <Bookmark className=\"w-3 h-3\" />\n )}\n </button>\n )}\n\n {hovered && (\n <button\n onClick={handleCopy}\n className={`${onToggleBookmark ? '' : 'ml-auto'} p-0.5 rounded transition-colors ${\n isUser ? 'hover:bg-action-primary-bg-hover' : 'hover:bg-bg-sunken'\n }`}\n aria-label={tr('vibecontrols.agentManager.messageItem.copyMessage', 'Copy message')}\n title={tr('vibecontrols.agentManager.messageItem.copyMessage', 'Copy message')}\n >\n {copied ? (\n <Check className=\"w-3 h-3 text-status-success-text\" />\n ) : (\n <Copy className=\"w-3 h-3\" />\n )}\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -5,13 +5,17 @@ import { useEffect as r, useMemo as i, useRef as a } from "react";
5
5
  import { MessageSquare as o } from "lucide-react";
6
6
  import { jsx as s, jsxs as c } from "react/jsx-runtime";
7
7
  //#region src/components/agent-manager/AgentManagerMessageList.tsx
8
- var l = ({ messages: l, isStreaming: u, onCancelStream: d, searchQuery: f }) => {
9
- let p = a(null), m = a(null), h = e(), g = f?.trim().toLowerCase() ?? "", _ = i(() => g ? l.filter((e) => e.content.toLowerCase().includes(g)) : l, [l, g]);
8
+ var l = ({ messages: l, isStreaming: u, onCancelStream: d, searchQuery: f, bookmarkedIds: p, onToggleBookmark: m, flashMessageId: h }) => {
9
+ let g = a(null), _ = a(null), v = e(), y = f?.trim().toLowerCase() ?? "", b = i(() => y ? l.filter((e) => e.content.toLowerCase().includes(y)) : l, [l, y]);
10
10
  return r(() => {
11
- let e = m.current;
12
- e && e.scrollHeight - e.scrollTop - e.clientHeight < 100 && (e.scrollTop = e.scrollHeight);
13
- }, [l, u]), r(() => {
14
- let e = m.current;
11
+ let e = _.current;
12
+ e && (h || e.scrollHeight - e.scrollTop - e.clientHeight < 100 && (e.scrollTop = e.scrollHeight));
13
+ }, [
14
+ l,
15
+ u,
16
+ h
17
+ ]), r(() => {
18
+ let e = _.current;
15
19
  e && (e.scrollTop = e.scrollHeight);
16
20
  }, []), l.length === 0 && !u ? /* @__PURE__ */ c("div", {
17
21
  className: "flex-1 flex flex-col items-center justify-center px-4 text-center",
@@ -22,33 +26,36 @@ var l = ({ messages: l, isStreaming: u, onCancelStream: d, searchQuery: f }) =>
22
26
  }),
23
27
  /* @__PURE__ */ s("p", {
24
28
  className: "text-sm font-medium text-text-primary",
25
- children: h("vibecontrols.agentManager.messageList.startConversation", "Start a conversation")
29
+ children: v("vibecontrols.agentManager.messageList.startConversation", "Start a conversation")
26
30
  }),
27
31
  /* @__PURE__ */ s("p", {
28
32
  className: "text-xs text-text-secondary mt-1 max-w-xs",
29
- children: h("vibecontrols.agentManager.messageList.typeMessage", "Type a message below to begin interacting with the AI agent.")
33
+ children: v("vibecontrols.agentManager.messageList.typeMessage", "Type a message below to begin interacting with the AI agent.")
30
34
  })
31
35
  ]
32
36
  }) : /* @__PURE__ */ c("div", {
33
- ref: m,
37
+ ref: _,
34
38
  className: "flex-1 overflow-y-auto px-4 py-4 space-y-3 min-h-0",
35
39
  role: "log",
36
40
  "aria-live": "polite",
37
- "aria-label": h("vibecontrols.agentManager.messageList.agentConversation", "Agent conversation"),
41
+ "aria-label": v("vibecontrols.agentManager.messageList.agentConversation", "Agent conversation"),
38
42
  children: [
39
- g && _.length === 0 && /* @__PURE__ */ s("div", {
43
+ y && b.length === 0 && /* @__PURE__ */ s("div", {
40
44
  className: "flex items-center justify-center py-8",
41
45
  children: /* @__PURE__ */ s("p", {
42
46
  className: "text-sm text-text-tertiary",
43
- children: h("vibecontrols.agentManager.messageList.noResults", "No messages match your search")
47
+ children: v("vibecontrols.agentManager.messageList.noResults", "No messages match your search")
44
48
  })
45
49
  }),
46
- _.map((e) => /* @__PURE__ */ s(t, {
50
+ b.map((e) => /* @__PURE__ */ s(t, {
47
51
  message: e,
48
- searchQuery: g
52
+ searchQuery: y,
53
+ isBookmarked: p?.has(e.id) ?? !1,
54
+ onToggleBookmark: m,
55
+ flashMessageId: h
49
56
  }, e.id)),
50
57
  u && d && /* @__PURE__ */ s(n, { onCancel: d }),
51
- /* @__PURE__ */ s("div", { ref: p })
58
+ /* @__PURE__ */ s("div", { ref: g })
52
59
  ]
53
60
  });
54
61
  };
@@ -1 +1 @@
1
- {"version":3,"file":"AgentManagerMessageList.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerMessageList.tsx"],"sourcesContent":["/**\n * Scrollable message list for the Agent Manager chat.\n *\n * Renders AgentManagerMessageItem for each message with auto-scroll\n * behavior, a streaming indicator, and optional search highlighting.\n */\n\nimport { useEffect, useRef, useMemo, type FC } from 'react';\nimport { MessageSquare } from 'lucide-react';\nimport { AgentManagerMessageItem } from './AgentManagerMessageItem';\nimport { AgentManagerStreamingIndicator } from './AgentManagerStreamingIndicator';\nimport type { AgentManagerMessage } from '@/types/agentManager';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AgentManagerMessageListProps {\n messages: AgentManagerMessage[];\n isStreaming: boolean;\n onCancelStream?: () => void;\n searchQuery?: string;\n}\n\nexport const AgentManagerMessageList: FC<AgentManagerMessageListProps> = ({\n messages,\n isStreaming,\n onCancelStream,\n searchQuery,\n}) => {\n const bottomRef = useRef<HTMLDivElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const tr = useTr();\n\n const normalizedQuery = searchQuery?.trim().toLowerCase() ?? '';\n\n // Filter messages when a search query is active\n const displayMessages = useMemo(() => {\n if (!normalizedQuery) return messages;\n return messages.filter((m) => m.content.toLowerCase().includes(normalizedQuery));\n }, [messages, normalizedQuery]);\n\n // Auto-scroll when new messages arrive or streaming updates\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const isNearBottom =\n container.scrollHeight - container.scrollTop - container.clientHeight < 100;\n\n if (isNearBottom) {\n container.scrollTop = container.scrollHeight;\n }\n }, [messages, isStreaming]);\n\n // Scroll to bottom on initial render\n useEffect(() => {\n const container = containerRef.current;\n if (container) {\n container.scrollTop = container.scrollHeight;\n }\n }, []);\n\n if (messages.length === 0 && !isStreaming) {\n return (\n <div className=\"flex-1 flex flex-col items-center justify-center px-4 text-center\">\n <div className=\"w-12 h-12 rounded-full bg-bg-sunken flex items-center justify-center mb-3\">\n <MessageSquare className=\"w-6 h-6 text-text-muted\" />\n </div>\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.agentManager.messageList.startConversation', 'Start a conversation')}\n </p>\n <p className=\"text-xs text-text-secondary mt-1 max-w-xs\">\n {tr(\n 'vibecontrols.agentManager.messageList.typeMessage',\n 'Type a message below to begin interacting with the AI agent.'\n )}\n </p>\n </div>\n );\n }\n\n return (\n <div\n ref={containerRef}\n className=\"flex-1 overflow-y-auto px-4 py-4 space-y-3 min-h-0\"\n role=\"log\"\n aria-live=\"polite\"\n aria-label={tr(\n 'vibecontrols.agentManager.messageList.agentConversation',\n 'Agent conversation'\n )}\n >\n {normalizedQuery && displayMessages.length === 0 && (\n <div className=\"flex items-center justify-center py-8\">\n <p className=\"text-sm text-text-tertiary\">\n {tr('vibecontrols.agentManager.messageList.noResults', 'No messages match your search')}\n </p>\n </div>\n )}\n\n {displayMessages.map((message) => (\n <AgentManagerMessageItem key={message.id} message={message} searchQuery={normalizedQuery} />\n ))}\n\n {isStreaming && onCancelStream && (\n <AgentManagerStreamingIndicator onCancel={onCancelStream} />\n )}\n\n <div ref={bottomRef} />\n </div>\n );\n};\n"],"mappings":";;;;;;;AAqBA,IAAa,KAA6D,EACxE,aACA,gBACA,mBACA,qBACI;CACJ,IAAM,IAAY,EAAuB,KAAK,EACxC,IAAe,EAAuB,KAAK,EAC3C,IAAK,GAAO,EAEZ,IAAkB,GAAa,MAAM,CAAC,aAAa,IAAI,IAGvD,IAAkB,QACjB,IACE,EAAS,QAAQ,MAAM,EAAE,QAAQ,aAAa,CAAC,SAAS,EAAgB,CAAC,GADnD,GAE5B,CAAC,GAAU,EAAgB,CAAC;AA0C/B,QAvCA,QAAgB;EACd,IAAM,IAAY,EAAa;AAC1B,OAGH,EAAU,eAAe,EAAU,YAAY,EAAU,eAAe,QAGxE,EAAU,YAAY,EAAU;IAEjC,CAAC,GAAU,EAAY,CAAC,EAG3B,QAAgB;EACd,IAAM,IAAY,EAAa;AAC/B,EAAI,MACF,EAAU,YAAY,EAAU;IAEjC,EAAE,CAAC,EAEF,EAAS,WAAW,KAAK,CAAC,IAE1B,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAe,WAAU,2BAA4B,CAAA;IACjD,CAAA;GACN,kBAAC,KAAD;IAAG,WAAU;cACV,EAAG,2DAA2D,uBAAuB;IACpF,CAAA;GACJ,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,qDACA,+DACD;IACC,CAAA;GACA;MAKR,kBAAC,OAAD;EACE,KAAK;EACL,WAAU;EACV,MAAK;EACL,aAAU;EACV,cAAY,EACV,2DACA,qBACD;YARH;GAUG,KAAmB,EAAgB,WAAW,KAC7C,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,mDAAmD,gCAAgC;KACrF,CAAA;IACA,CAAA;GAGP,EAAgB,KAAK,MACpB,kBAAC,GAAD;IAAmD;IAAS,aAAa;IAAmB,EAA9D,EAAQ,GAAsD,CAC5F;GAED,KAAe,KACd,kBAAC,GAAD,EAAgC,UAAU,GAAkB,CAAA;GAG9D,kBAAC,OAAD,EAAK,KAAK,GAAa,CAAA;GACnB"}
1
+ {"version":3,"file":"AgentManagerMessageList.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerMessageList.tsx"],"sourcesContent":["/**\n * Scrollable message list for the Agent Manager chat.\n *\n * Renders AgentManagerMessageItem for each message with auto-scroll\n * behavior, a streaming indicator, and optional search highlighting.\n */\n\nimport { useEffect, useRef, useMemo, type FC } from 'react';\nimport { MessageSquare } from 'lucide-react';\nimport { AgentManagerMessageItem } from './AgentManagerMessageItem';\nimport { AgentManagerStreamingIndicator } from './AgentManagerStreamingIndicator';\nimport type { AgentManagerMessage } from '@/types/agentManager';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AgentManagerMessageListProps {\n messages: AgentManagerMessage[];\n isStreaming: boolean;\n onCancelStream?: () => void;\n searchQuery?: string;\n bookmarkedIds?: Set<string>;\n onToggleBookmark?: (messageId: string) => void;\n flashMessageId?: string | null;\n}\n\nexport const AgentManagerMessageList: FC<AgentManagerMessageListProps> = ({\n messages,\n isStreaming,\n onCancelStream,\n searchQuery,\n bookmarkedIds,\n onToggleBookmark,\n flashMessageId,\n}) => {\n const bottomRef = useRef<HTMLDivElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const tr = useTr();\n\n const normalizedQuery = searchQuery?.trim().toLowerCase() ?? '';\n\n // Filter messages when a search query is active\n const displayMessages = useMemo(() => {\n if (!normalizedQuery) return messages;\n return messages.filter((m) => m.content.toLowerCase().includes(normalizedQuery));\n }, [messages, normalizedQuery]);\n\n // Auto-scroll when new messages arrive or streaming updates.\n // Suppressed while a flash-target is set so jump-to-bookmark isn't yanked away.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n if (flashMessageId) return;\n\n const isNearBottom =\n container.scrollHeight - container.scrollTop - container.clientHeight < 100;\n\n if (isNearBottom) {\n container.scrollTop = container.scrollHeight;\n }\n }, [messages, isStreaming, flashMessageId]);\n\n // Scroll to bottom on initial render\n useEffect(() => {\n const container = containerRef.current;\n if (container) {\n container.scrollTop = container.scrollHeight;\n }\n }, []);\n\n if (messages.length === 0 && !isStreaming) {\n return (\n <div className=\"flex-1 flex flex-col items-center justify-center px-4 text-center\">\n <div className=\"w-12 h-12 rounded-full bg-bg-sunken flex items-center justify-center mb-3\">\n <MessageSquare className=\"w-6 h-6 text-text-muted\" />\n </div>\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.agentManager.messageList.startConversation', 'Start a conversation')}\n </p>\n <p className=\"text-xs text-text-secondary mt-1 max-w-xs\">\n {tr(\n 'vibecontrols.agentManager.messageList.typeMessage',\n 'Type a message below to begin interacting with the AI agent.'\n )}\n </p>\n </div>\n );\n }\n\n return (\n <div\n ref={containerRef}\n className=\"flex-1 overflow-y-auto px-4 py-4 space-y-3 min-h-0\"\n role=\"log\"\n aria-live=\"polite\"\n aria-label={tr(\n 'vibecontrols.agentManager.messageList.agentConversation',\n 'Agent conversation'\n )}\n >\n {normalizedQuery && displayMessages.length === 0 && (\n <div className=\"flex items-center justify-center py-8\">\n <p className=\"text-sm text-text-tertiary\">\n {tr('vibecontrols.agentManager.messageList.noResults', 'No messages match your search')}\n </p>\n </div>\n )}\n\n {displayMessages.map((message) => (\n <AgentManagerMessageItem\n key={message.id}\n message={message}\n searchQuery={normalizedQuery}\n isBookmarked={bookmarkedIds?.has(message.id) ?? false}\n onToggleBookmark={onToggleBookmark}\n flashMessageId={flashMessageId}\n />\n ))}\n\n {isStreaming && onCancelStream && (\n <AgentManagerStreamingIndicator onCancel={onCancelStream} />\n )}\n\n <div ref={bottomRef} />\n </div>\n );\n};\n"],"mappings":";;;;;;;AAwBA,IAAa,KAA6D,EACxE,aACA,gBACA,mBACA,gBACA,kBACA,qBACA,wBACI;CACJ,IAAM,IAAY,EAAuB,KAAK,EACxC,IAAe,EAAuB,KAAK,EAC3C,IAAK,GAAO,EAEZ,IAAkB,GAAa,MAAM,CAAC,aAAa,IAAI,IAGvD,IAAkB,QACjB,IACE,EAAS,QAAQ,MAAM,EAAE,QAAQ,aAAa,CAAC,SAAS,EAAgB,CAAC,GADnD,GAE5B,CAAC,GAAU,EAAgB,CAAC;AA4C/B,QAxCA,QAAgB;EACd,IAAM,IAAY,EAAa;AAC1B,QACD,KAGF,EAAU,eAAe,EAAU,YAAY,EAAU,eAAe,QAGxE,EAAU,YAAY,EAAU;IAEjC;EAAC;EAAU;EAAa;EAAe,CAAC,EAG3C,QAAgB;EACd,IAAM,IAAY,EAAa;AAC/B,EAAI,MACF,EAAU,YAAY,EAAU;IAEjC,EAAE,CAAC,EAEF,EAAS,WAAW,KAAK,CAAC,IAE1B,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAe,WAAU,2BAA4B,CAAA;IACjD,CAAA;GACN,kBAAC,KAAD;IAAG,WAAU;cACV,EAAG,2DAA2D,uBAAuB;IACpF,CAAA;GACJ,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,qDACA,+DACD;IACC,CAAA;GACA;MAKR,kBAAC,OAAD;EACE,KAAK;EACL,WAAU;EACV,MAAK;EACL,aAAU;EACV,cAAY,EACV,2DACA,qBACD;YARH;GAUG,KAAmB,EAAgB,WAAW,KAC7C,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,mDAAmD,gCAAgC;KACrF,CAAA;IACA,CAAA;GAGP,EAAgB,KAAK,MACpB,kBAAC,GAAD;IAEW;IACT,aAAa;IACb,cAAc,GAAe,IAAI,EAAQ,GAAG,IAAI;IAC9B;IACF;IAChB,EANK,EAAQ,GAMb,CACF;GAED,KAAe,KACd,kBAAC,GAAD,EAAgC,UAAU,GAAkB,CAAA;GAG9D,kBAAC,OAAD,EAAK,KAAK,GAAa,CAAA;GACnB"}
@@ -0,0 +1,241 @@
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { agentApiPath as t } from "../../utils/agentApiPath.js";
3
+ import { OpenInBrowserEditorButton as n } from "./OpenInBrowserEditorButton.js";
4
+ import { IDE_PRESETS as r, buildProtocolUrl as i, getIdePreset as a, resolveEffectiveIdeCommand as o } from "./idePresets.js";
5
+ import { useCallback as s, useMemo as c, useState as l } from "react";
6
+ import { AlertCircle as u, Check as d, ChevronDown as f, Code as p, ExternalLink as m, Globe as h, Loader2 as g } from "lucide-react";
7
+ import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
8
+ //#region src/components/agents/OpenInEditorMenu.tsx
9
+ function b({ vibeId: m, workspacePath: b, preferredIdeCommand: S, vibeIdeOverrides: C, tunnelUrl: w, agentApiKey: T, profile: E, browserEditorEnabled: D = !1, onSetDefaultForVibe: O, className: k }) {
10
+ let A = e(), [j, M] = l(!1), [N, P] = l({ kind: "idle" }), F = c(() => o(m, S, C ?? null), [
11
+ m,
12
+ S,
13
+ C
14
+ ]), I = c(() => a(F), [F]), L = !!b, R = !!w && !!T, z = s((e) => {
15
+ if (!b) return !1;
16
+ let t = i(e, b);
17
+ return t ? (window.location.href = t, !0) : !1;
18
+ }, [b]), B = s(async (e) => {
19
+ if (!b) {
20
+ P({
21
+ kind: "error",
22
+ message: A("vibecontrols.editor.noPath", "Vibe has no path")
23
+ });
24
+ return;
25
+ }
26
+ if (!R) {
27
+ P({
28
+ kind: "error",
29
+ message: A("vibecontrols.editor.agentOffline", "Agent is offline — install/start an agent for this Vibe to launch local editors.")
30
+ });
31
+ return;
32
+ }
33
+ P({
34
+ kind: "launching",
35
+ via: "agent"
36
+ });
37
+ try {
38
+ let n = w.replace(/\/$/, ""), r = await fetch(`${n}${t(E, "/editor/open")}`, {
39
+ method: "POST",
40
+ headers: {
41
+ "Content-Type": "application/json",
42
+ "x-agent-api-key": T
43
+ },
44
+ body: JSON.stringify({
45
+ command: e,
46
+ path: b
47
+ })
48
+ }), i = await r.json().catch(() => ({}));
49
+ if (!r.ok || i.ok === !1) throw Error(i.error || `HTTP ${r.status}`);
50
+ P({
51
+ kind: "ok",
52
+ message: A("vibecontrols.editor.launched", `Launched ${e}`)
53
+ }), setTimeout(() => P({ kind: "idle" }), 2500);
54
+ } catch (e) {
55
+ P({
56
+ kind: "error",
57
+ message: e instanceof Error ? e.message : String(e)
58
+ });
59
+ }
60
+ }, [
61
+ b,
62
+ R,
63
+ w,
64
+ E,
65
+ T,
66
+ A
67
+ ]), V = s(() => {
68
+ if (!I) {
69
+ P({
70
+ kind: "error",
71
+ message: A("vibecontrols.editor.unknownIde", `Unknown IDE: ${F}`)
72
+ });
73
+ return;
74
+ }
75
+ if (!b) {
76
+ P({
77
+ kind: "error",
78
+ message: A("vibecontrols.editor.noPath", "Vibe has no path")
79
+ });
80
+ return;
81
+ }
82
+ if (I.protocol) {
83
+ P({
84
+ kind: "launching",
85
+ via: "protocol"
86
+ }), z(I) ? (setTimeout(() => P({
87
+ kind: "ok",
88
+ message: A("vibecontrols.editor.opened", "Opened in your IDE")
89
+ }), 500), setTimeout(() => P({ kind: "idle" }), 2500)) : P({ kind: "idle" });
90
+ return;
91
+ }
92
+ B(I.command);
93
+ }, [
94
+ I,
95
+ b,
96
+ z,
97
+ B,
98
+ A,
99
+ F
100
+ ]), H = N.kind === "launching" ? A("vibecontrols.editor.launching", "Launching…") : N.kind === "ok" ? N.message : I ? A("vibecontrols.editor.openIn", `Open in ${I.label}`) : A("vibecontrols.editor.open", "Open in editor"), U = N.kind === "launching" ? g : N.kind === "ok" ? d : N.kind === "error" ? u : p;
101
+ return /* @__PURE__ */ y("div", {
102
+ className: `relative inline-flex ${k ?? ""}`,
103
+ children: [
104
+ /* @__PURE__ */ y("div", {
105
+ className: "inline-flex items-stretch",
106
+ children: [/* @__PURE__ */ y("button", {
107
+ type: "button",
108
+ onClick: V,
109
+ disabled: !L || N.kind === "launching",
110
+ title: L ? H : A("vibecontrols.editor.noPath", "Vibe has no path"),
111
+ className: "flex items-center gap-1.5 px-2.5 py-1.5 sm:px-3 sm:py-2 text-xs sm:text-sm bg-bg-sunken text-text-primary rounded-l-md hover:bg-bg-sunken/80 transition-colors disabled:opacity-50",
112
+ children: [/* @__PURE__ */ v(U, { className: `w-3.5 h-3.5 sm:w-4 sm:h-4 ${N.kind === "launching" ? "animate-spin" : ""}` }), /* @__PURE__ */ v("span", {
113
+ className: "hidden sm:inline",
114
+ children: H
115
+ })]
116
+ }), /* @__PURE__ */ v("button", {
117
+ type: "button",
118
+ onClick: () => M((e) => !e),
119
+ "aria-label": A("vibecontrols.editor.moreOptions", "More editor options"),
120
+ className: "flex items-center px-1.5 py-1.5 sm:px-2 sm:py-2 bg-bg-sunken text-text-primary rounded-r-md border-l border-border-default hover:bg-bg-sunken/80 transition-colors",
121
+ children: /* @__PURE__ */ v(f, { className: "w-3.5 h-3.5 sm:w-4 sm:h-4" })
122
+ })]
123
+ }),
124
+ N.kind === "error" && /* @__PURE__ */ v("div", {
125
+ className: "absolute top-full left-0 mt-1 z-50 bg-bg-surface border border-border-default rounded-md shadow-lg p-2 text-xs text-status-error-text max-w-[280px]",
126
+ children: N.message
127
+ }),
128
+ j && /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("div", {
129
+ className: "fixed inset-0 z-40",
130
+ onClick: () => M(!1),
131
+ "aria-hidden": !0
132
+ }), /* @__PURE__ */ y("div", {
133
+ role: "menu",
134
+ className: "absolute right-0 top-full mt-1 z-50 w-72 max-h-[420px] overflow-auto bg-bg-surface border border-border-default rounded-md shadow-lg py-1",
135
+ children: [D && R && /* @__PURE__ */ y("div", {
136
+ className: "px-2 py-1 border-b border-border-default",
137
+ children: [/* @__PURE__ */ v("div", {
138
+ className: "text-[10px] uppercase tracking-wide text-text-secondary px-2 py-1",
139
+ children: A("vibecontrols.editor.browser", "Browser")
140
+ }), /* @__PURE__ */ v(n, {
141
+ tunnelUrl: w,
142
+ agentApiKey: T,
143
+ profile: E,
144
+ workspacePath: b || void 0,
145
+ className: "w-full justify-start"
146
+ })]
147
+ }), /* @__PURE__ */ y("div", {
148
+ className: "px-2 py-1",
149
+ children: [/* @__PURE__ */ y("div", {
150
+ className: "text-[10px] uppercase tracking-wide text-text-secondary px-2 py-1 flex items-center gap-1",
151
+ children: [
152
+ /* @__PURE__ */ v(h, { className: "w-3 h-3" }),
153
+ " ",
154
+ A("vibecontrols.editor.localIde", "Local IDE")
155
+ ]
156
+ }), r.map((e) => /* @__PURE__ */ v(x, {
157
+ preset: e,
158
+ isDefault: e.command === F,
159
+ isOverride: C?.[m] === e.command,
160
+ hasPath: L,
161
+ agentReachable: R,
162
+ onProtocol: () => {
163
+ M(!1), z(e);
164
+ },
165
+ onAgent: () => {
166
+ M(!1), B(e.command);
167
+ },
168
+ onSetDefault: O ? async () => {
169
+ await O(e.command);
170
+ } : void 0
171
+ }, e.command))]
172
+ })]
173
+ })] })
174
+ ]
175
+ });
176
+ }
177
+ function x({ preset: t, isDefault: n, isOverride: r, hasPath: i, agentReachable: a, onProtocol: o, onAgent: s, onSetDefault: c }) {
178
+ let l = e(), u = !!t.protocol && i, f = i && a, h = t.family === "terminal";
179
+ return /* @__PURE__ */ y("div", {
180
+ className: "group px-2 py-1.5 rounded-md hover:bg-bg-sunken",
181
+ children: [
182
+ /* @__PURE__ */ y("div", {
183
+ className: "flex items-center justify-between gap-2",
184
+ children: [/* @__PURE__ */ y("div", {
185
+ className: "flex items-center gap-2 min-w-0",
186
+ children: [
187
+ /* @__PURE__ */ v("span", {
188
+ className: "text-sm text-text-primary truncate",
189
+ children: t.label
190
+ }),
191
+ r && /* @__PURE__ */ v("span", {
192
+ className: "text-[9px] uppercase tracking-wider px-1 py-0.5 rounded bg-status-info-bg text-status-info-text",
193
+ children: l("vibecontrols.editor.override", "override")
194
+ }),
195
+ !r && n && /* @__PURE__ */ v("span", {
196
+ className: "text-[9px] uppercase tracking-wider px-1 py-0.5 rounded bg-bg-sunken text-text-secondary",
197
+ children: l("vibecontrols.editor.default", "default")
198
+ })
199
+ ]
200
+ }), /* @__PURE__ */ y("div", {
201
+ className: "flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity",
202
+ children: [
203
+ u && /* @__PURE__ */ v("button", {
204
+ type: "button",
205
+ onClick: o,
206
+ title: l("vibecontrols.editor.openProtocol", "Open via OS deep-link"),
207
+ className: "p-1 rounded hover:bg-bg-surface",
208
+ children: /* @__PURE__ */ v(m, { className: "w-3.5 h-3.5" })
209
+ }),
210
+ f && /* @__PURE__ */ v("button", {
211
+ type: "button",
212
+ onClick: s,
213
+ title: h ? l("vibecontrols.editor.openAgentTerminal", "Spawn in agent host") : l("vibecontrols.editor.openAgent", "Spawn via agent"),
214
+ className: "p-1 rounded hover:bg-bg-surface",
215
+ children: /* @__PURE__ */ v(p, { className: "w-3.5 h-3.5" })
216
+ }),
217
+ c && /* @__PURE__ */ v("button", {
218
+ type: "button",
219
+ onClick: () => void c(),
220
+ title: l("vibecontrols.editor.setDefault", "Set as default for this Vibe"),
221
+ className: "p-1 rounded hover:bg-bg-surface",
222
+ children: /* @__PURE__ */ v(d, { className: "w-3.5 h-3.5" })
223
+ })
224
+ ]
225
+ })]
226
+ }),
227
+ t.hint && /* @__PURE__ */ v("div", {
228
+ className: "text-[10px] text-text-secondary mt-0.5",
229
+ children: t.hint
230
+ }),
231
+ !u && !f && /* @__PURE__ */ v("div", {
232
+ className: "text-[10px] text-status-warning-text mt-0.5",
233
+ children: i ? l("vibecontrols.editor.needAgent", "Requires an online agent") : l("vibecontrols.editor.noPath", "Vibe has no path")
234
+ })
235
+ ]
236
+ });
237
+ }
238
+ //#endregion
239
+ export { b as OpenInEditorMenu };
240
+
241
+ //# sourceMappingURL=OpenInEditorMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OpenInEditorMenu.js","names":[],"sources":["../../../src/components/agents/OpenInEditorMenu.tsx"],"sourcesContent":["/**\n * OpenInEditorMenu\n *\n * One-button picker that opens a Vibe in either:\n * - the browser-hosted code-server (uses OpenInBrowserEditorButton plumbing)\n * - the user's local IDE via OS deep-link (vscode://, idea://, ...)\n * - the agent's host environment via POST /api/editor/open (terminal /\n * no-protocol IDEs like vim, subl, fleet, neovide)\n *\n * Resolves the effective IDE command from the user's saved preferences:\n * vibeIdeOverrides[vibeId] → preferredIdeCommand → 'code'\n */\n\nimport { useCallback, useMemo, useState } from 'react';\nimport { Code, ChevronDown, ExternalLink, Loader2, Globe, Check, AlertCircle } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { agentApiPath } from '@/utils/agentApiPath';\nimport {\n IDE_PRESETS,\n buildProtocolUrl,\n getIdePreset,\n resolveEffectiveIdeCommand,\n type IdePreset,\n} from './idePresets';\nimport { OpenInBrowserEditorButton } from './OpenInBrowserEditorButton';\n\nexport interface OpenInEditorMenuProps {\n vibeId: string;\n workspacePath: string | null | undefined;\n preferredIdeCommand: string | null | undefined;\n vibeIdeOverrides: Record<string, string> | null | undefined;\n tunnelUrl: string | null;\n agentApiKey: string | null;\n profile: string;\n browserEditorEnabled?: boolean;\n onSetDefaultForVibe?: (command: string) => Promise<void> | void;\n className?: string;\n}\n\ntype LaunchState =\n | { kind: 'idle' }\n | { kind: 'launching'; via: 'protocol' | 'agent' | 'browser' }\n | { kind: 'ok'; message: string }\n | { kind: 'error'; message: string };\n\nexport function OpenInEditorMenu({\n vibeId,\n workspacePath,\n preferredIdeCommand,\n vibeIdeOverrides,\n tunnelUrl,\n agentApiKey,\n profile,\n browserEditorEnabled = false,\n onSetDefaultForVibe,\n className,\n}: OpenInEditorMenuProps) {\n const tr = useTr();\n const [open, setOpen] = useState(false);\n const [launchState, setLaunchState] = useState<LaunchState>({ kind: 'idle' });\n\n const effectiveCommand = useMemo(\n () => resolveEffectiveIdeCommand(vibeId, preferredIdeCommand, vibeIdeOverrides ?? null),\n [vibeId, preferredIdeCommand, vibeIdeOverrides]\n );\n const effectivePreset = useMemo(() => getIdePreset(effectiveCommand), [effectiveCommand]);\n const hasPath = !!workspacePath;\n const agentReachable = !!tunnelUrl && !!agentApiKey;\n\n const launchViaProtocol = useCallback(\n (preset: IdePreset): boolean => {\n if (!workspacePath) return false;\n const url = buildProtocolUrl(preset, workspacePath);\n if (!url) return false;\n window.location.href = url;\n return true;\n },\n [workspacePath]\n );\n\n const launchViaAgent = useCallback(\n async (command: string) => {\n if (!workspacePath) {\n setLaunchState({\n kind: 'error',\n message: tr('vibecontrols.editor.noPath', 'Vibe has no path'),\n });\n return;\n }\n if (!agentReachable) {\n setLaunchState({\n kind: 'error',\n message: tr(\n 'vibecontrols.editor.agentOffline',\n 'Agent is offline — install/start an agent for this Vibe to launch local editors.'\n ),\n });\n return;\n }\n setLaunchState({ kind: 'launching', via: 'agent' });\n try {\n const base = tunnelUrl!.replace(/\\/$/, '');\n const res = await fetch(`${base}${agentApiPath(profile, '/editor/open')}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-agent-api-key': agentApiKey!,\n },\n body: JSON.stringify({ command, path: workspacePath }),\n });\n const data = (await res.json().catch(() => ({}))) as { ok?: boolean; error?: string };\n if (!res.ok || data.ok === false) {\n throw new Error(data.error || `HTTP ${res.status}`);\n }\n setLaunchState({\n kind: 'ok',\n message: tr('vibecontrols.editor.launched', `Launched ${command}`),\n });\n setTimeout(() => setLaunchState({ kind: 'idle' }), 2500);\n } catch (err) {\n setLaunchState({\n kind: 'error',\n message: err instanceof Error ? err.message : String(err),\n });\n }\n },\n [workspacePath, agentReachable, tunnelUrl, profile, agentApiKey, tr]\n );\n\n const launchPrimary = useCallback(() => {\n if (!effectivePreset) {\n setLaunchState({\n kind: 'error',\n message: tr('vibecontrols.editor.unknownIde', `Unknown IDE: ${effectiveCommand}`),\n });\n return;\n }\n if (!workspacePath) {\n setLaunchState({\n kind: 'error',\n message: tr('vibecontrols.editor.noPath', 'Vibe has no path'),\n });\n return;\n }\n if (effectivePreset.protocol) {\n setLaunchState({ kind: 'launching', via: 'protocol' });\n const ok = launchViaProtocol(effectivePreset);\n if (ok) {\n setTimeout(\n () =>\n setLaunchState({\n kind: 'ok',\n message: tr('vibecontrols.editor.opened', 'Opened in your IDE'),\n }),\n 500\n );\n setTimeout(() => setLaunchState({ kind: 'idle' }), 2500);\n } else {\n setLaunchState({ kind: 'idle' });\n }\n return;\n }\n void launchViaAgent(effectivePreset.command);\n }, [effectivePreset, workspacePath, launchViaProtocol, launchViaAgent, tr, effectiveCommand]);\n\n const primaryLabel =\n launchState.kind === 'launching'\n ? tr('vibecontrols.editor.launching', 'Launching…')\n : launchState.kind === 'ok'\n ? launchState.message\n : effectivePreset\n ? tr('vibecontrols.editor.openIn', `Open in ${effectivePreset.label}`)\n : tr('vibecontrols.editor.open', 'Open in editor');\n\n const PrimaryIcon =\n launchState.kind === 'launching'\n ? Loader2\n : launchState.kind === 'ok'\n ? Check\n : launchState.kind === 'error'\n ? AlertCircle\n : Code;\n\n return (\n <div className={`relative inline-flex ${className ?? ''}`}>\n <div className=\"inline-flex items-stretch\">\n <button\n type=\"button\"\n onClick={launchPrimary}\n disabled={!hasPath || launchState.kind === 'launching'}\n title={!hasPath ? tr('vibecontrols.editor.noPath', 'Vibe has no path') : primaryLabel}\n className=\"flex items-center gap-1.5 px-2.5 py-1.5 sm:px-3 sm:py-2 text-xs sm:text-sm bg-bg-sunken text-text-primary rounded-l-md hover:bg-bg-sunken/80 transition-colors disabled:opacity-50\"\n >\n <PrimaryIcon\n className={`w-3.5 h-3.5 sm:w-4 sm:h-4 ${launchState.kind === 'launching' ? 'animate-spin' : ''}`}\n />\n <span className=\"hidden sm:inline\">{primaryLabel}</span>\n </button>\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n aria-label={tr('vibecontrols.editor.moreOptions', 'More editor options')}\n className=\"flex items-center px-1.5 py-1.5 sm:px-2 sm:py-2 bg-bg-sunken text-text-primary rounded-r-md border-l border-border-default hover:bg-bg-sunken/80 transition-colors\"\n >\n <ChevronDown className=\"w-3.5 h-3.5 sm:w-4 sm:h-4\" />\n </button>\n </div>\n\n {launchState.kind === 'error' && (\n <div className=\"absolute top-full left-0 mt-1 z-50 bg-bg-surface border border-border-default rounded-md shadow-lg p-2 text-xs text-status-error-text max-w-[280px]\">\n {launchState.message}\n </div>\n )}\n\n {open && (\n <>\n <div className=\"fixed inset-0 z-40\" onClick={() => setOpen(false)} aria-hidden />\n <div\n role=\"menu\"\n className=\"absolute right-0 top-full mt-1 z-50 w-72 max-h-[420px] overflow-auto bg-bg-surface border border-border-default rounded-md shadow-lg py-1\"\n >\n {browserEditorEnabled && agentReachable && (\n <div className=\"px-2 py-1 border-b border-border-default\">\n <div className=\"text-[10px] uppercase tracking-wide text-text-secondary px-2 py-1\">\n {tr('vibecontrols.editor.browser', 'Browser')}\n </div>\n <OpenInBrowserEditorButton\n tunnelUrl={tunnelUrl}\n agentApiKey={agentApiKey}\n profile={profile}\n workspacePath={workspacePath || undefined}\n className=\"w-full justify-start\"\n />\n </div>\n )}\n\n <div className=\"px-2 py-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-text-secondary px-2 py-1 flex items-center gap-1\">\n <Globe className=\"w-3 h-3\" /> {tr('vibecontrols.editor.localIde', 'Local IDE')}\n </div>\n {IDE_PRESETS.map((preset) => (\n <IdeMenuRow\n key={preset.command}\n preset={preset}\n isDefault={preset.command === effectiveCommand}\n isOverride={vibeIdeOverrides?.[vibeId] === preset.command}\n hasPath={hasPath}\n agentReachable={agentReachable}\n onProtocol={() => {\n setOpen(false);\n launchViaProtocol(preset);\n }}\n onAgent={() => {\n setOpen(false);\n void launchViaAgent(preset.command);\n }}\n onSetDefault={\n onSetDefaultForVibe\n ? async () => {\n await onSetDefaultForVibe(preset.command);\n }\n : undefined\n }\n />\n ))}\n </div>\n </div>\n </>\n )}\n </div>\n );\n}\n\ninterface IdeMenuRowProps {\n preset: IdePreset;\n isDefault: boolean;\n isOverride: boolean;\n hasPath: boolean;\n agentReachable: boolean;\n onProtocol: () => void;\n onAgent: () => void;\n onSetDefault?: () => Promise<void>;\n}\n\nfunction IdeMenuRow({\n preset,\n isDefault,\n isOverride,\n hasPath,\n agentReachable,\n onProtocol,\n onAgent,\n onSetDefault,\n}: IdeMenuRowProps) {\n const tr = useTr();\n const canProtocol = !!preset.protocol && hasPath;\n const canAgent = hasPath && agentReachable;\n const isTerminal = preset.family === 'terminal';\n\n return (\n <div className=\"group px-2 py-1.5 rounded-md hover:bg-bg-sunken\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <span className=\"text-sm text-text-primary truncate\">{preset.label}</span>\n {isOverride && (\n <span className=\"text-[9px] uppercase tracking-wider px-1 py-0.5 rounded bg-status-info-bg text-status-info-text\">\n {tr('vibecontrols.editor.override', 'override')}\n </span>\n )}\n {!isOverride && isDefault && (\n <span className=\"text-[9px] uppercase tracking-wider px-1 py-0.5 rounded bg-bg-sunken text-text-secondary\">\n {tr('vibecontrols.editor.default', 'default')}\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity\">\n {canProtocol && (\n <button\n type=\"button\"\n onClick={onProtocol}\n title={tr('vibecontrols.editor.openProtocol', 'Open via OS deep-link')}\n className=\"p-1 rounded hover:bg-bg-surface\"\n >\n <ExternalLink className=\"w-3.5 h-3.5\" />\n </button>\n )}\n {canAgent && (\n <button\n type=\"button\"\n onClick={onAgent}\n title={\n isTerminal\n ? tr('vibecontrols.editor.openAgentTerminal', 'Spawn in agent host')\n : tr('vibecontrols.editor.openAgent', 'Spawn via agent')\n }\n className=\"p-1 rounded hover:bg-bg-surface\"\n >\n <Code className=\"w-3.5 h-3.5\" />\n </button>\n )}\n {onSetDefault && (\n <button\n type=\"button\"\n onClick={() => void onSetDefault()}\n title={tr('vibecontrols.editor.setDefault', 'Set as default for this Vibe')}\n className=\"p-1 rounded hover:bg-bg-surface\"\n >\n <Check className=\"w-3.5 h-3.5\" />\n </button>\n )}\n </div>\n </div>\n {preset.hint && <div className=\"text-[10px] text-text-secondary mt-0.5\">{preset.hint}</div>}\n {!canProtocol && !canAgent && (\n <div className=\"text-[10px] text-status-warning-text mt-0.5\">\n {!hasPath\n ? tr('vibecontrols.editor.noPath', 'Vibe has no path')\n : tr('vibecontrols.editor.needAgent', 'Requires an online agent')}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { useVibeControlsStore as e } from "../../store/vibeControlsStore.js";
2
+ import { OpenInEditorMenu as t } from "./OpenInEditorMenu.js";
3
+ import { useCallback as n } from "react";
4
+ import { jsx as r } from "react/jsx-runtime";
5
+ //#region src/components/agents/VibeOpenInEditorMenu.tsx
6
+ function i(i) {
7
+ let a = e((e) => e.preferredIdeCommand), o = e((e) => e.vibeIdeOverrides), s = e((e) => e.setVibeIdeOverride), c = n(async (e) => {
8
+ s(i.vibeId, e);
9
+ }, [s, i.vibeId]);
10
+ return /* @__PURE__ */ r(t, {
11
+ ...i,
12
+ preferredIdeCommand: a,
13
+ vibeIdeOverrides: o,
14
+ onSetDefaultForVibe: c
15
+ });
16
+ }
17
+ //#endregion
18
+ export { i as VibeOpenInEditorMenu };
19
+
20
+ //# sourceMappingURL=VibeOpenInEditorMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VibeOpenInEditorMenu.js","names":[],"sources":["../../../src/components/agents/VibeOpenInEditorMenu.tsx"],"sourcesContent":["/**\n * VibeOpenInEditorMenu\n *\n * Stateful wrapper around `OpenInEditorMenu` — reads/writes the per-user\n * IDE preferences from zustand `vibeControlsStore`. Hosts (VibeDetailsPage,\n * VibesPage card actions) just pass per-Vibe launch context; this\n * component handles preference fetch + persistence.\n */\n\nimport { useCallback } from 'react';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\nimport { OpenInEditorMenu, type OpenInEditorMenuProps } from './OpenInEditorMenu';\n\ntype WrapperProps = Omit<\n OpenInEditorMenuProps,\n 'preferredIdeCommand' | 'vibeIdeOverrides' | 'onSetDefaultForVibe'\n>;\n\nexport function VibeOpenInEditorMenu(props: WrapperProps) {\n const preferredIdeCommand = useVibeControlsStore((s) => s.preferredIdeCommand);\n const vibeIdeOverrides = useVibeControlsStore((s) => s.vibeIdeOverrides);\n const setVibeIdeOverride = useVibeControlsStore((s) => s.setVibeIdeOverride);\n\n const onSetDefaultForVibe = useCallback(\n async (command: string) => {\n setVibeIdeOverride(props.vibeId, command);\n },\n [setVibeIdeOverride, props.vibeId]\n );\n\n return (\n <OpenInEditorMenu\n {...props}\n preferredIdeCommand={preferredIdeCommand}\n vibeIdeOverrides={vibeIdeOverrides}\n onSetDefaultForVibe={onSetDefaultForVibe}\n />\n );\n}\n"],"mappings":";;;;;AAkBA,SAAgB,EAAqB,GAAqB;CACxD,IAAM,IAAsB,GAAsB,MAAM,EAAE,oBAAoB,EACxE,IAAmB,GAAsB,MAAM,EAAE,iBAAiB,EAClE,IAAqB,GAAsB,MAAM,EAAE,mBAAmB,EAEtE,IAAsB,EAC1B,OAAO,MAAoB;AACzB,IAAmB,EAAM,QAAQ,EAAQ;IAE3C,CAAC,GAAoB,EAAM,OAAO,CACnC;AAED,QACE,kBAAC,GAAD;EACE,GAAI;EACiB;EACH;EACG;EACrB,CAAA"}