@burdenoff/microfe-vibecontrols 2026.602.3 → 2026.602.5

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.
@@ -1,157 +1,170 @@
1
1
  import { useTr as e } from "../../shared/hooks/useTr.js";
2
- import { SelectionCheckbox as t } from "../shared/SelectionCheckbox.js";
3
- import { ActionTypeBadge as n, SourceTypeBadge as r, TriggerTypeBadge as i } from "./ActionTypeBadge.js";
4
- import { EntityShareButton as a } from "../sharing/EntityShareButton.js";
2
+ import { VibeEntityType as t } from "../../generated/wspace-types.js";
3
+ import { EntityVibeLinkControl as n } from "../shared/EntityVibeLinkControl.js";
4
+ import { SelectionCheckbox as r } from "../shared/SelectionCheckbox.js";
5
+ import { ActionTypeBadge as i, SourceTypeBadge as a, TriggerTypeBadge as o } from "./ActionTypeBadge.js";
6
+ import { EntityShareButton as s } from "../sharing/EntityShareButton.js";
5
7
  import "../sharing/index.js";
6
- import { AlarmClock as o, CalendarClock as s, Clock as c, Copy as l, Hash as u, Pencil as d, Play as f, Trash2 as p } from "lucide-react";
7
- import { motion as m, useReducedMotion as h } from "framer-motion";
8
- import { jsx as g, jsxs as _ } from "react/jsx-runtime";
8
+ import { AlarmClock as c, CalendarClock as l, Clock as u, Copy as d, Hash as f, Pencil as p, Play as m, Trash2 as h } from "lucide-react";
9
+ import { motion as g, useReducedMotion as _ } from "framer-motion";
10
+ import { jsx as v, jsxs as y } from "react/jsx-runtime";
9
11
  //#region src/components/actions/ActionCard.tsx
10
- function v({ action: v, onExecute: y, onEdit: b, onDuplicate: x, onDelete: S, onSchedule: C, onAddToCalendar: w, canUpdate: T, canDelete: E, canExecute: D, isExecuting: O = !1, selected: k = !1, onToggleSelect: A }) {
11
- let j = h(), M = e();
12
- return /* @__PURE__ */ _(m.div, {
13
- className: `bg-bg-surface border rounded-lg p-4 hover:border-border-strong transition-colors group ${k ? "border-action-primary-bg ring-1 ring-action-primary-bg/25" : "border-border-default"}`,
14
- whileHover: j ? void 0 : {
12
+ function b({ action: b, onExecute: x, onEdit: S, onDuplicate: C, onDelete: w, onSchedule: T, onAddToCalendar: E, canUpdate: D, canDelete: O, canExecute: k, isExecuting: A = !1, selected: j = !1, onToggleSelect: M }) {
13
+ let N = _(), P = e();
14
+ return /* @__PURE__ */ y(g.div, {
15
+ className: `bg-bg-surface border rounded-lg p-4 hover:border-border-strong transition-colors group ${j ? "border-action-primary-bg ring-1 ring-action-primary-bg/25" : "border-border-default"}`,
16
+ whileHover: N ? void 0 : {
15
17
  y: -2,
16
18
  boxShadow: "0 4px 12px var(--color-border-default)"
17
19
  },
18
20
  transition: { duration: .15 },
19
21
  layout: !0,
20
22
  role: "group",
21
- "aria-label": `Action: ${v.displayName || v.name}`,
23
+ "aria-label": `Action: ${b.displayName || b.name}`,
22
24
  children: [
23
- /* @__PURE__ */ _("div", {
25
+ /* @__PURE__ */ y("div", {
24
26
  className: "flex items-start justify-between gap-3 mb-3",
25
- children: [/* @__PURE__ */ _("div", {
27
+ children: [/* @__PURE__ */ y("div", {
26
28
  className: "flex items-start gap-3 min-w-0 flex-1",
27
- children: [A && /* @__PURE__ */ g("div", {
29
+ children: [M && /* @__PURE__ */ v("div", {
28
30
  onClick: (e) => e.stopPropagation(),
29
- children: /* @__PURE__ */ g(t, {
30
- checked: k,
31
- onChange: A,
32
- ariaLabel: `Select action ${v.displayName || v.name}`
31
+ children: /* @__PURE__ */ v(r, {
32
+ checked: j,
33
+ onChange: M,
34
+ ariaLabel: `Select action ${b.displayName || b.name}`
33
35
  })
34
- }), /* @__PURE__ */ _("div", {
36
+ }), /* @__PURE__ */ y("div", {
35
37
  className: "min-w-0 flex-1",
36
- children: [/* @__PURE__ */ g("h3", {
38
+ children: [/* @__PURE__ */ v("h3", {
37
39
  className: "font-semibold text-text-primary truncate",
38
- children: v.displayName || v.name
39
- }), v.description && /* @__PURE__ */ g("p", {
40
+ children: b.displayName || b.name
41
+ }), b.description && /* @__PURE__ */ v("p", {
40
42
  className: "text-sm text-text-secondary mt-0.5 line-clamp-2",
41
- children: v.description
43
+ children: b.description
42
44
  })]
43
45
  })]
44
- }), /* @__PURE__ */ _("div", {
46
+ }), /* @__PURE__ */ y("div", {
45
47
  className: "flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0",
46
48
  children: [
47
- D && /* @__PURE__ */ g("button", {
49
+ k && /* @__PURE__ */ v("button", {
48
50
  type: "button",
49
51
  onClick: (e) => {
50
- e.stopPropagation(), y(v.id);
52
+ e.stopPropagation(), x(b.id);
51
53
  },
52
- disabled: O,
54
+ disabled: A,
53
55
  className: "p-1.5 rounded-md hover:bg-status-success-bg/20 text-status-success-text transition-colors disabled:opacity-50",
54
- title: M("vibecontrols.actions.card.execute", "Execute"),
55
- children: /* @__PURE__ */ g(f, { size: 14 })
56
+ title: P("vibecontrols.actions.card.execute", "Execute"),
57
+ children: /* @__PURE__ */ v(m, { size: 14 })
56
58
  }),
57
- T && /* @__PURE__ */ g("button", {
59
+ D && /* @__PURE__ */ v("button", {
58
60
  type: "button",
59
61
  onClick: (e) => {
60
- e.stopPropagation(), b(v.id);
62
+ e.stopPropagation(), S(b.id);
61
63
  },
62
64
  className: "p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors",
63
- title: M("vibecontrols.actions.card.edit", "Edit"),
64
- children: /* @__PURE__ */ g(d, { size: 14 })
65
+ title: P("vibecontrols.actions.card.edit", "Edit"),
66
+ children: /* @__PURE__ */ v(p, { size: 14 })
65
67
  }),
66
- C && /* @__PURE__ */ g("button", {
68
+ T && /* @__PURE__ */ v("button", {
67
69
  type: "button",
68
70
  onClick: (e) => {
69
- e.stopPropagation(), C(v.id);
71
+ e.stopPropagation(), T(b.id);
70
72
  },
71
73
  className: "p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors",
72
- title: M("vibecontrols.actions.card.schedule", "Schedule this action"),
73
- children: /* @__PURE__ */ g(o, { size: 14 })
74
+ title: P("vibecontrols.actions.card.schedule", "Schedule this action"),
75
+ children: /* @__PURE__ */ v(c, { size: 14 })
74
76
  }),
75
- w && /* @__PURE__ */ g("button", {
77
+ E && /* @__PURE__ */ v("button", {
76
78
  type: "button",
77
79
  onClick: (e) => {
78
- e.stopPropagation(), w(v.id);
80
+ e.stopPropagation(), E(b.id);
79
81
  },
80
82
  className: "p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors",
81
- title: M("vibecontrols.actions.card.addToCalendar", "Add to calendar"),
82
- children: /* @__PURE__ */ g(s, { size: 14 })
83
+ title: P("vibecontrols.actions.card.addToCalendar", "Add to calendar"),
84
+ children: /* @__PURE__ */ v(l, { size: 14 })
83
85
  }),
84
- /* @__PURE__ */ g("button", {
86
+ /* @__PURE__ */ v("button", {
85
87
  type: "button",
86
88
  onClick: (e) => {
87
- e.stopPropagation(), x(v.id);
89
+ e.stopPropagation(), C(b.id);
88
90
  },
89
91
  className: "p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors",
90
- title: M("vibecontrols.actions.card.duplicate", "Duplicate"),
91
- children: /* @__PURE__ */ g(l, { size: 14 })
92
+ title: P("vibecontrols.actions.card.duplicate", "Duplicate"),
93
+ children: /* @__PURE__ */ v(d, { size: 14 })
92
94
  }),
93
- /* @__PURE__ */ g(a, {
95
+ /* @__PURE__ */ v(s, {
94
96
  entity: "action",
95
97
  resource: {
96
- id: v.id,
97
- name: v.displayName || v.name,
98
- vibeId: v.vibeId ?? null
98
+ id: b.id,
99
+ name: b.displayName || b.name,
100
+ vibeId: b.vibeId ?? null
99
101
  },
100
- label: M("vibecontrols.actions.card.share", "Share")
102
+ label: P("vibecontrols.actions.card.share", "Share")
101
103
  }),
102
- E && /* @__PURE__ */ g("button", {
104
+ O && /* @__PURE__ */ v("button", {
103
105
  type: "button",
104
106
  onClick: (e) => {
105
- e.stopPropagation(), S(v.id);
107
+ e.stopPropagation(), w(b.id);
106
108
  },
107
109
  className: "p-1.5 rounded-md hover:bg-status-error-bg/20 text-status-error-text transition-colors",
108
- title: M("vibecontrols.actions.card.delete", "Delete"),
109
- children: /* @__PURE__ */ g(p, { size: 14 })
110
+ title: P("vibecontrols.actions.card.delete", "Delete"),
111
+ children: /* @__PURE__ */ v(h, { size: 14 })
110
112
  })
111
113
  ]
112
114
  })]
113
115
  }),
114
- /* @__PURE__ */ _("div", {
116
+ /* @__PURE__ */ y("div", {
115
117
  className: "flex flex-wrap items-center gap-1.5 mb-3",
116
118
  children: [
117
- /* @__PURE__ */ g(n, { type: v.actionType }),
118
- /* @__PURE__ */ g(i, { type: v.triggerType }),
119
- v.sourceType && /* @__PURE__ */ g(r, { type: v.sourceType })
119
+ /* @__PURE__ */ v(i, { type: b.actionType }),
120
+ /* @__PURE__ */ v(o, { type: b.triggerType }),
121
+ b.sourceType && /* @__PURE__ */ v(a, { type: b.sourceType })
120
122
  ]
121
123
  }),
122
- (v.category || v.tags && v.tags.length > 0) && /* @__PURE__ */ _("div", {
124
+ /* @__PURE__ */ v("div", {
125
+ className: "mb-3",
126
+ onClick: (e) => e.stopPropagation(),
127
+ children: /* @__PURE__ */ v(n, {
128
+ entityType: t.Action,
129
+ entityId: b.id,
130
+ currentVibeId: b.vibeId ?? null,
131
+ currentVisibility: b.visibility ?? null,
132
+ refetchQueries: ["ListActions"]
133
+ })
134
+ }),
135
+ (b.category || b.tags && b.tags.length > 0) && /* @__PURE__ */ y("div", {
123
136
  className: "flex flex-wrap items-center gap-1.5 mb-3",
124
- children: [v.category && /* @__PURE__ */ g("span", {
137
+ children: [b.category && /* @__PURE__ */ v("span", {
125
138
  className: "text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-secondary",
126
- children: v.category
127
- }), v.tags?.map((e) => /* @__PURE__ */ _("span", {
139
+ children: b.category
140
+ }), b.tags?.map((e) => /* @__PURE__ */ y("span", {
128
141
  className: "text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-placeholder",
129
142
  children: ["#", e]
130
143
  }, e))]
131
144
  }),
132
- /* @__PURE__ */ _("div", {
145
+ /* @__PURE__ */ y("div", {
133
146
  className: "flex items-center gap-4 text-xs text-text-placeholder",
134
- children: [/* @__PURE__ */ _("span", {
147
+ children: [/* @__PURE__ */ y("span", {
135
148
  className: "inline-flex items-center gap-1",
136
- children: [/* @__PURE__ */ g(u, { size: 12 }), M("vibecontrols.actions.card.executionRuns", "{{count}} runs", { count: v.executionCount ?? 0 })]
137
- }), /* @__PURE__ */ _("span", {
149
+ children: [/* @__PURE__ */ v(f, { size: 12 }), P("vibecontrols.actions.card.executionRuns", "{{count}} runs", { count: b.executionCount ?? 0 })]
150
+ }), /* @__PURE__ */ y("span", {
138
151
  className: "inline-flex items-center gap-1",
139
- children: [/* @__PURE__ */ g(c, { size: 12 }), ((e) => {
140
- if (!e) return M("vibecontrols.actions.card.never", "Never");
152
+ children: [/* @__PURE__ */ v(u, { size: 12 }), ((e) => {
153
+ if (!e) return P("vibecontrols.actions.card.never", "Never");
141
154
  let t = new Date(e), n = (/* @__PURE__ */ new Date()).getTime() - t.getTime(), r = Math.floor(n / 6e4);
142
- if (r < 1) return M("vibecontrols.actions.card.justNow", "Just now");
143
- if (r < 60) return M("vibecontrols.actions.card.minutesAgo", "{{count}}m ago", { count: r });
155
+ if (r < 1) return P("vibecontrols.actions.card.justNow", "Just now");
156
+ if (r < 60) return P("vibecontrols.actions.card.minutesAgo", "{{count}}m ago", { count: r });
144
157
  let i = Math.floor(r / 60);
145
- if (i < 24) return M("vibecontrols.actions.card.hoursAgo", "{{count}}h ago", { count: i });
158
+ if (i < 24) return P("vibecontrols.actions.card.hoursAgo", "{{count}}h ago", { count: i });
146
159
  let a = Math.floor(i / 24);
147
160
  return a < 7 ? `${a}d ago` : t.toLocaleDateString();
148
- })(v.lastExecutedAt)]
161
+ })(b.lastExecutedAt)]
149
162
  })]
150
163
  })
151
164
  ]
152
165
  });
153
166
  }
154
167
  //#endregion
155
- export { v as ActionCard };
168
+ export { b as ActionCard };
156
169
 
157
170
  //# sourceMappingURL=ActionCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ActionCard.js","names":[],"sources":["../../../src/components/actions/ActionCard.tsx"],"sourcesContent":["import { Play, Pencil, Copy, Trash2, Clock, Hash, CalendarClock, AlarmClock } from 'lucide-react';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport { ActionTypeBadge, TriggerTypeBadge, SourceTypeBadge } from './ActionTypeBadge';\nimport { SelectionCheckbox } from '../shared/SelectionCheckbox';\nimport { EntityShareButton } from '../sharing';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface ActionCardAction {\n id: string;\n name: string;\n displayName?: string | null;\n description?: string | null;\n actionType: string;\n sourceType?: string | null;\n triggerType: string;\n category?: string | null;\n tags?: string[] | null;\n executionCount?: number | null;\n lastExecutedAt?: string | null;\n confirmationRequired?: boolean;\n vibeId?: string | null;\n}\n\ninterface ActionCardProps {\n action: ActionCardAction;\n onExecute: (id: string) => void;\n onEdit: (id: string) => void;\n onDuplicate: (id: string) => void;\n onDelete: (id: string) => void;\n onSchedule?: (id: string) => void;\n onAddToCalendar?: (id: string) => void;\n canUpdate: boolean;\n canDelete: boolean;\n canExecute: boolean;\n isExecuting?: boolean;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n}\n\nexport function ActionCard({\n action,\n onExecute,\n onEdit,\n onDuplicate,\n onDelete,\n onSchedule,\n onAddToCalendar,\n canUpdate,\n canDelete,\n canExecute,\n isExecuting = false,\n selected = false,\n onToggleSelect,\n}: ActionCardProps) {\n const shouldReduceMotion = useReducedMotion();\n const tr = useTr();\n\n const formatDate = (dateStr: string | null | undefined) => {\n if (!dateStr) return tr('vibecontrols.actions.card.never', 'Never');\n const date = new Date(dateStr);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMin = Math.floor(diffMs / 60000);\n if (diffMin < 1) return tr('vibecontrols.actions.card.justNow', 'Just now');\n if (diffMin < 60)\n return tr('vibecontrols.actions.card.minutesAgo', '{{count}}m ago', { count: diffMin });\n const diffHr = Math.floor(diffMin / 60);\n if (diffHr < 24)\n return tr('vibecontrols.actions.card.hoursAgo', '{{count}}h ago', { count: diffHr });\n const diffDays = Math.floor(diffHr / 24);\n if (diffDays < 7) return `${diffDays}d ago`;\n return date.toLocaleDateString();\n };\n\n return (\n <motion.div\n className={`bg-bg-surface border rounded-lg p-4 hover:border-border-strong transition-colors group ${\n selected\n ? 'border-action-primary-bg ring-1 ring-action-primary-bg/25'\n : 'border-border-default'\n }`}\n whileHover={\n shouldReduceMotion\n ? undefined\n : { y: -2, boxShadow: '0 4px 12px var(--color-border-default)' }\n }\n transition={{ duration: 0.15 }}\n layout\n role=\"group\"\n aria-label={`Action: ${action.displayName || action.name}`}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between gap-3 mb-3\">\n <div className=\"flex items-start gap-3 min-w-0 flex-1\">\n {onToggleSelect && (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={`Select action ${action.displayName || action.name}`}\n />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"font-semibold text-text-primary truncate\">\n {action.displayName || action.name}\n </h3>\n {action.description && (\n <p className=\"text-sm text-text-secondary mt-0.5 line-clamp-2\">\n {action.description}\n </p>\n )}\n </div>\n </div>\n {/* Action buttons */}\n <div className=\"flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0\">\n {canExecute && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onExecute(action.id);\n }}\n disabled={isExecuting}\n className=\"p-1.5 rounded-md hover:bg-status-success-bg/20 text-status-success-text transition-colors disabled:opacity-50\"\n title={tr('vibecontrols.actions.card.execute', 'Execute')}\n >\n <Play size={14} />\n </button>\n )}\n {canUpdate && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onEdit(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.edit', 'Edit')}\n >\n <Pencil size={14} />\n </button>\n )}\n {onSchedule && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onSchedule(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.schedule', 'Schedule this action')}\n >\n <AlarmClock size={14} />\n </button>\n )}\n {onAddToCalendar && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onAddToCalendar(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.addToCalendar', 'Add to calendar')}\n >\n <CalendarClock size={14} />\n </button>\n )}\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onDuplicate(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.duplicate', 'Duplicate')}\n >\n <Copy size={14} />\n </button>\n <EntityShareButton\n entity=\"action\"\n resource={{\n id: action.id,\n name: action.displayName || action.name,\n vibeId: action.vibeId ?? null,\n }}\n label={tr('vibecontrols.actions.card.share', 'Share')}\n />\n {canDelete && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onDelete(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-status-error-bg/20 text-status-error-text transition-colors\"\n title={tr('vibecontrols.actions.card.delete', 'Delete')}\n >\n <Trash2 size={14} />\n </button>\n )}\n </div>\n </div>\n\n {/* Badges */}\n <div className=\"flex flex-wrap items-center gap-1.5 mb-3\">\n <ActionTypeBadge type={action.actionType} />\n <TriggerTypeBadge type={action.triggerType} />\n {action.sourceType && <SourceTypeBadge type={action.sourceType} />}\n </div>\n\n {/* Category and Tags */}\n {(action.category || (action.tags && action.tags.length > 0)) && (\n <div className=\"flex flex-wrap items-center gap-1.5 mb-3\">\n {action.category && (\n <span className=\"text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-secondary\">\n {action.category}\n </span>\n )}\n {action.tags?.map((tag) => (\n <span\n key={tag}\n className=\"text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-placeholder\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Stats */}\n <div className=\"flex items-center gap-4 text-xs text-text-placeholder\">\n <span className=\"inline-flex items-center gap-1\">\n <Hash size={12} />\n {tr('vibecontrols.actions.card.executionRuns', '{{count}} runs', {\n count: action.executionCount ?? 0,\n })}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Clock size={12} />\n {formatDate(action.lastExecutedAt)}\n </span>\n </div>\n </motion.div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ActionCard.js","names":[],"sources":["../../../src/components/actions/ActionCard.tsx"],"sourcesContent":["import { Play, Pencil, Copy, Trash2, Clock, Hash, CalendarClock, AlarmClock } from 'lucide-react';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport { ActionTypeBadge, TriggerTypeBadge, SourceTypeBadge } from './ActionTypeBadge';\nimport { SelectionCheckbox } from '../shared/SelectionCheckbox';\nimport { EntityVibeLinkControl } from '../shared/EntityVibeLinkControl';\nimport { EntityShareButton } from '../sharing';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { VibeEntityType, VibeVisibility } from '@/generated/wspace-types';\n\ninterface ActionCardAction {\n id: string;\n name: string;\n displayName?: string | null;\n description?: string | null;\n actionType: string;\n sourceType?: string | null;\n triggerType: string;\n category?: string | null;\n tags?: string[] | null;\n executionCount?: number | null;\n lastExecutedAt?: string | null;\n confirmationRequired?: boolean;\n vibeId?: string | null;\n visibility?: VibeVisibility | null;\n}\n\ninterface ActionCardProps {\n action: ActionCardAction;\n onExecute: (id: string) => void;\n onEdit: (id: string) => void;\n onDuplicate: (id: string) => void;\n onDelete: (id: string) => void;\n onSchedule?: (id: string) => void;\n onAddToCalendar?: (id: string) => void;\n canUpdate: boolean;\n canDelete: boolean;\n canExecute: boolean;\n isExecuting?: boolean;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n}\n\nexport function ActionCard({\n action,\n onExecute,\n onEdit,\n onDuplicate,\n onDelete,\n onSchedule,\n onAddToCalendar,\n canUpdate,\n canDelete,\n canExecute,\n isExecuting = false,\n selected = false,\n onToggleSelect,\n}: ActionCardProps) {\n const shouldReduceMotion = useReducedMotion();\n const tr = useTr();\n\n const formatDate = (dateStr: string | null | undefined) => {\n if (!dateStr) return tr('vibecontrols.actions.card.never', 'Never');\n const date = new Date(dateStr);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMin = Math.floor(diffMs / 60000);\n if (diffMin < 1) return tr('vibecontrols.actions.card.justNow', 'Just now');\n if (diffMin < 60)\n return tr('vibecontrols.actions.card.minutesAgo', '{{count}}m ago', { count: diffMin });\n const diffHr = Math.floor(diffMin / 60);\n if (diffHr < 24)\n return tr('vibecontrols.actions.card.hoursAgo', '{{count}}h ago', { count: diffHr });\n const diffDays = Math.floor(diffHr / 24);\n if (diffDays < 7) return `${diffDays}d ago`;\n return date.toLocaleDateString();\n };\n\n return (\n <motion.div\n className={`bg-bg-surface border rounded-lg p-4 hover:border-border-strong transition-colors group ${\n selected\n ? 'border-action-primary-bg ring-1 ring-action-primary-bg/25'\n : 'border-border-default'\n }`}\n whileHover={\n shouldReduceMotion\n ? undefined\n : { y: -2, boxShadow: '0 4px 12px var(--color-border-default)' }\n }\n transition={{ duration: 0.15 }}\n layout\n role=\"group\"\n aria-label={`Action: ${action.displayName || action.name}`}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between gap-3 mb-3\">\n <div className=\"flex items-start gap-3 min-w-0 flex-1\">\n {onToggleSelect && (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={`Select action ${action.displayName || action.name}`}\n />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"font-semibold text-text-primary truncate\">\n {action.displayName || action.name}\n </h3>\n {action.description && (\n <p className=\"text-sm text-text-secondary mt-0.5 line-clamp-2\">\n {action.description}\n </p>\n )}\n </div>\n </div>\n {/* Action buttons */}\n <div className=\"flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0\">\n {canExecute && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onExecute(action.id);\n }}\n disabled={isExecuting}\n className=\"p-1.5 rounded-md hover:bg-status-success-bg/20 text-status-success-text transition-colors disabled:opacity-50\"\n title={tr('vibecontrols.actions.card.execute', 'Execute')}\n >\n <Play size={14} />\n </button>\n )}\n {canUpdate && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onEdit(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.edit', 'Edit')}\n >\n <Pencil size={14} />\n </button>\n )}\n {onSchedule && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onSchedule(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.schedule', 'Schedule this action')}\n >\n <AlarmClock size={14} />\n </button>\n )}\n {onAddToCalendar && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onAddToCalendar(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.addToCalendar', 'Add to calendar')}\n >\n <CalendarClock size={14} />\n </button>\n )}\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onDuplicate(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.duplicate', 'Duplicate')}\n >\n <Copy size={14} />\n </button>\n <EntityShareButton\n entity=\"action\"\n resource={{\n id: action.id,\n name: action.displayName || action.name,\n vibeId: action.vibeId ?? null,\n }}\n label={tr('vibecontrols.actions.card.share', 'Share')}\n />\n {canDelete && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onDelete(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-status-error-bg/20 text-status-error-text transition-colors\"\n title={tr('vibecontrols.actions.card.delete', 'Delete')}\n >\n <Trash2 size={14} />\n </button>\n )}\n </div>\n </div>\n\n {/* Badges */}\n <div className=\"flex flex-wrap items-center gap-1.5 mb-3\">\n <ActionTypeBadge type={action.actionType} />\n <TriggerTypeBadge type={action.triggerType} />\n {action.sourceType && <SourceTypeBadge type={action.sourceType} />}\n </div>\n\n {/* Linked vibe — map / unmap this action to a vibe */}\n <div className=\"mb-3\" onClick={(event) => event.stopPropagation()}>\n <EntityVibeLinkControl\n entityType={VibeEntityType.Action}\n entityId={action.id}\n currentVibeId={action.vibeId ?? null}\n currentVisibility={action.visibility ?? null}\n refetchQueries={['ListActions']}\n />\n </div>\n\n {/* Category and Tags */}\n {(action.category || (action.tags && action.tags.length > 0)) && (\n <div className=\"flex flex-wrap items-center gap-1.5 mb-3\">\n {action.category && (\n <span className=\"text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-secondary\">\n {action.category}\n </span>\n )}\n {action.tags?.map((tag) => (\n <span\n key={tag}\n className=\"text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-placeholder\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Stats */}\n <div className=\"flex items-center gap-4 text-xs text-text-placeholder\">\n <span className=\"inline-flex items-center gap-1\">\n <Hash size={12} />\n {tr('vibecontrols.actions.card.executionRuns', '{{count}} runs', {\n count: action.executionCount ?? 0,\n })}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Clock size={12} />\n {formatDate(action.lastExecutedAt)}\n </span>\n </div>\n </motion.div>\n );\n}\n"],"mappings":";;;;;;;;;;;AA0CA,SAAgB,EAAW,EACzB,WACA,cACA,WACA,gBACA,aACA,eACA,oBACA,cACA,cACA,eACA,iBAAc,IACd,cAAW,IACX,qBACkB;CAClB,IAAM,IAAqB,GAAkB,EACvC,IAAK,GAAO;AAmBlB,QACE,kBAAC,EAAO,KAAR;EACE,WAAW,0FACT,IACI,8DACA;EAEN,YACE,IACI,KAAA,IACA;GAAE,GAAG;GAAI,WAAW;GAA0C;EAEpE,YAAY,EAAE,UAAU,KAAM;EAC9B,QAAA;EACA,MAAK;EACL,cAAY,WAAW,EAAO,eAAe,EAAO;YAdtD;GAiBE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACG,KACC,kBAAC,OAAD;MAAK,UAAU,MAAU,EAAM,iBAAiB;gBAC9C,kBAAC,GAAD;OACE,SAAS;OACT,UAAU;OACV,WAAW,iBAAiB,EAAO,eAAe,EAAO;OACzD,CAAA;MACE,CAAA,EAER,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAO,eAAe,EAAO;OAC3B,CAAA,EACJ,EAAO,eACN,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAO;OACN,CAAA,CAEF;QACF;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG,KACC,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAU;AAElB,QADA,EAAM,iBAAiB,EACvB,EAAU,EAAO,GAAG;;OAEtB,UAAU;OACV,WAAU;OACV,OAAO,EAAG,qCAAqC,UAAU;iBAEzD,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;OACX,CAAA;MAEV,KACC,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAU;AAElB,QADA,EAAM,iBAAiB,EACvB,EAAO,EAAO,GAAG;;OAEnB,WAAU;OACV,OAAO,EAAG,kCAAkC,OAAO;iBAEnD,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA;OACb,CAAA;MAEV,KACC,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAU;AAElB,QADA,EAAM,iBAAiB,EACvB,EAAW,EAAO,GAAG;;OAEvB,WAAU;OACV,OAAO,EAAG,sCAAsC,uBAAuB;iBAEvE,kBAAC,GAAD,EAAY,MAAM,IAAM,CAAA;OACjB,CAAA;MAEV,KACC,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAU;AAElB,QADA,EAAM,iBAAiB,EACvB,EAAgB,EAAO,GAAG;;OAE5B,WAAU;OACV,OAAO,EAAG,2CAA2C,kBAAkB;iBAEvE,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA;OACpB,CAAA;MAEX,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAU;AAElB,QADA,EAAM,iBAAiB,EACvB,EAAY,EAAO,GAAG;;OAExB,WAAU;OACV,OAAO,EAAG,uCAAuC,YAAY;iBAE7D,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;OACX,CAAA;MACT,kBAAC,GAAD;OACE,QAAO;OACP,UAAU;QACR,IAAI,EAAO;QACX,MAAM,EAAO,eAAe,EAAO;QACnC,QAAQ,EAAO,UAAU;QAC1B;OACD,OAAO,EAAG,mCAAmC,QAAQ;OACrD,CAAA;MACD,KACC,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAU;AAElB,QADA,EAAM,iBAAiB,EACvB,EAAS,EAAO,GAAG;;OAErB,WAAU;OACV,OAAO,EAAG,oCAAoC,SAAS;iBAEvD,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA;OACb,CAAA;MAEP;OACF;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAiB,MAAM,EAAO,YAAc,CAAA;KAC5C,kBAAC,GAAD,EAAkB,MAAM,EAAO,aAAe,CAAA;KAC7C,EAAO,cAAc,kBAAC,GAAD,EAAiB,MAAM,EAAO,YAAc,CAAA;KAC9D;;GAGN,kBAAC,OAAD;IAAK,WAAU;IAAO,UAAU,MAAU,EAAM,iBAAiB;cAC/D,kBAAC,GAAD;KACE,YAAY,EAAe;KAC3B,UAAU,EAAO;KACjB,eAAe,EAAO,UAAU;KAChC,mBAAmB,EAAO,cAAc;KACxC,gBAAgB,CAAC,cAAc;KAC/B,CAAA;IACE,CAAA;IAGJ,EAAO,YAAa,EAAO,QAAQ,EAAO,KAAK,SAAS,MACxD,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAO,YACN,kBAAC,QAAD;KAAM,WAAU;eACb,EAAO;KACH,CAAA,EAER,EAAO,MAAM,KAAK,MACjB,kBAAC,QAAD;KAEE,WAAU;eAFZ,CAGC,KACG,EACG;OAJA,EAIA,CACP,CACE;;GAIR,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EAAG,2CAA2C,kBAAkB,EAC/D,OAAO,EAAO,kBAAkB,GACjC,CAAC,CACG;QACP,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA,IAlMP,MAAuC;AACzD,UAAI,CAAC,EAAS,QAAO,EAAG,mCAAmC,QAAQ;MACnE,IAAM,IAAO,IAAI,KAAK,EAAQ,EAExB,qBADM,IAAI,MAAM,EACH,SAAS,GAAG,EAAK,SAAS,EACvC,IAAU,KAAK,MAAM,IAAS,IAAM;AAC1C,UAAI,IAAU,EAAG,QAAO,EAAG,qCAAqC,WAAW;AAC3E,UAAI,IAAU,GACZ,QAAO,EAAG,wCAAwC,kBAAkB,EAAE,OAAO,GAAS,CAAC;MACzF,IAAM,IAAS,KAAK,MAAM,IAAU,GAAG;AACvC,UAAI,IAAS,GACX,QAAO,EAAG,sCAAsC,kBAAkB,EAAE,OAAO,GAAQ,CAAC;MACtF,IAAM,IAAW,KAAK,MAAM,IAAS,GAAG;AAExC,aADI,IAAW,IAAU,GAAG,EAAS,SAC9B,EAAK,oBAAoB;QAqLd,EAAO,eAAe,CAC7B;OACH;;GACK"}
@@ -80,7 +80,7 @@ function h({ agentId: h, tunnelUrl: g, profile: _, workspacePath: v, mode: y = "
80
80
  ttlSeconds: 60
81
81
  } })).data?.mintAgentCapability?.token;
82
82
  if (!r) throw Error("Failed to mint editor capability");
83
- let i = `${g.replace(/\/$/, "")}/code-server/#vt=${encodeURIComponent(r)}`;
83
+ let i = `${g.replace(/\/$/, "")}/code-server/?vt=${encodeURIComponent(r)}`;
84
84
  y === "iframe" && b ? b(i) : window.open(i, "_blank"), setTimeout(() => w("idle"), 2e3);
85
85
  } catch (e) {
86
86
  E(e instanceof Error ? e.message : "Failed to open editor"), w("error");
@@ -1 +1 @@
1
- {"version":3,"file":"OpenInBrowserEditorButton.js","names":[],"sources":["../../../src/components/agents/OpenInBrowserEditorButton.tsx"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { Code, Loader2, AlertCircle, Check } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n useAgentCodeServerStatusLazyQuery,\n useAgentCodeServerStartMutation,\n useAgentCodeServerStopMutation,\n useMintAgentCapabilityMutation,\n} from '@/generated/wspace-operations';\nimport { AgentCapabilityScope } from '@/generated/wspace-types';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\ntype EditorState = 'idle' | 'checking' | 'installing' | 'starting' | 'ready' | 'error';\n\nexport interface OpenInBrowserEditorButtonProps {\n /**\n * Agent ID — F4 Wave B replaced the direct browser fetch (which relied\n * on `agentApiKey` headers) with svc-mediated `agentCodeServer*`\n * mutations. The agent's tunnel URL is still used for the iframe `src`,\n * but every control-plane call now routes through the gateway.\n */\n agentId: string | null;\n /** Agent tunnel URL (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /**\n * Canonical CLI profile name on the remote agent. Maps to the\n * `/api/profiles/<profile>/code-server/...` URL prefix.\n */\n profile: string;\n /** Directory to open as workspace root. Defaults to agent home if not set. */\n workspacePath?: string;\n /** How to open — new browser tab or call back with URL for iframe embedding */\n mode?: 'newtab' | 'iframe';\n /** Called with the ready URL when mode is 'iframe' */\n onIframeUrl?: (url: string) => void;\n /** Additional CSS class names */\n className?: string;\n}\n\ninterface CodeServerStatusPayload {\n installed?: boolean;\n installing?: boolean;\n running?: boolean;\n port?: number;\n error?: string;\n}\n\n// Code-server is a single named instance per profile; the proxy resolver\n// requires a `name`. Keep the legacy default — operators who installed\n// the plugin pre-rename still have an instance keyed under \"default\".\nconst CODE_SERVER_INSTANCE = 'default';\n\n// ── Component ──────────────────────────────────────────────────────────\n\nexport function OpenInBrowserEditorButton({\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode = 'newtab',\n onIframeUrl,\n className,\n}: OpenInBrowserEditorButtonProps) {\n const tr = useTr();\n const [state, setState] = useState<EditorState>('idle');\n const [errorMsg, setErrorMsg] = useState<string | null>(null);\n\n const [fetchStatus] = useAgentCodeServerStatusLazyQuery({ fetchPolicy: 'network-only' });\n const [startCodeServer] = useAgentCodeServerStartMutation();\n const [stopCodeServer] = useAgentCodeServerStopMutation();\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n\n const probe = useCallback(\n async (\n candidate: string\n ): Promise<{\n effective: string | null;\n status: CodeServerStatusPayload | null;\n missing: boolean;\n }> => {\n const res = await fetchStatus({\n variables: { agentId: agentId!, profile: candidate, name: CODE_SERVER_INSTANCE },\n });\n const proxy = res.data?.agentCodeServerStatus;\n if (!proxy) {\n return { effective: null, status: null, missing: false };\n }\n if (proxy.status === 404) {\n return { effective: null, status: null, missing: true };\n }\n if (!proxy.ok) {\n throw new Error(proxy.error || `Status check failed: HTTP ${proxy.status}`);\n }\n return {\n effective: candidate,\n status: (proxy.data as CodeServerStatusPayload | null) ?? {},\n missing: false,\n };\n },\n [fetchStatus, agentId]\n );\n\n const handleClick = useCallback(async () => {\n if (!agentId || !tunnelUrl) {\n setErrorMsg('Agent not selected');\n setState('error');\n return;\n }\n\n setErrorMsg(null);\n\n try {\n // Step 1: Probe the active profile, falling back to \"default\" if the\n // current profile reports 404 (code-server isn't a per-profile\n // service — its management API is profile-scoped though).\n setState('checking');\n const candidates = profile === 'default' ? ['default'] : [profile, 'default'];\n let effectiveProfile: string | null = null;\n let status: CodeServerStatusPayload | null = null;\n for (const candidate of candidates) {\n const { effective, status: probed, missing } = await probe(candidate);\n if (missing) continue;\n if (effective) {\n effectiveProfile = effective;\n status = probed;\n break;\n }\n }\n if (!effectiveProfile || !status) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.notInstalled',\n \"Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor.\"\n )\n );\n }\n\n // Step 2: code-server needs to be installed up-front via the\n // Plugins tab. The deprecated direct-browser install fetch isn't\n // proxied; surface a clear message instead of silently hanging.\n if (!status.installed) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.notInstalled',\n \"Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor.\"\n )\n );\n }\n\n // Step 3: Start if not running, or restart with a fresh workspace path.\n if (!status.running) {\n setState('starting');\n const startRes = await startCodeServer({\n variables: {\n agentId,\n profile: effectiveProfile,\n name: CODE_SERVER_INSTANCE,\n ...(workspacePath ? { path: workspacePath } : {}),\n },\n });\n const startProxy = startRes.data?.agentCodeServerStart;\n if (!startProxy?.ok) {\n throw new Error(startProxy?.error || `Start failed: HTTP ${startProxy?.status ?? 0}`);\n }\n await new Promise((r) => setTimeout(r, 1000));\n } else if (workspacePath) {\n setState('starting');\n // No dedicated restart proxy yet — stop, then start with the new path.\n await stopCodeServer({\n variables: { agentId, profile: effectiveProfile, name: CODE_SERVER_INSTANCE },\n });\n const restartRes = await startCodeServer({\n variables: {\n agentId,\n profile: effectiveProfile,\n name: CODE_SERVER_INSTANCE,\n path: workspacePath,\n },\n });\n const restartProxy = restartRes.data?.agentCodeServerStart;\n if (!restartProxy?.ok) {\n throw new Error(\n restartProxy?.error || `Restart failed: HTTP ${restartProxy?.status ?? 0}`\n );\n }\n await new Promise((r) => setTimeout(r, 1000));\n }\n\n // Step 4: Mint a short-lived IFRAME_PLUGIN capability so the\n // editor URL doesn't embed the long-lived `agentApiKey`. The\n // agent's `/code-server/` bootstrap exchanges the `#vt=` fragment\n // for a scoped HttpOnly cookie before proxying.\n setState('ready');\n const mintRes = await mintAgentCapability({\n variables: {\n agentId,\n profile: effectiveProfile,\n scope: AgentCapabilityScope.IframePlugin,\n resourceId: `code-server:${CODE_SERVER_INSTANCE}`,\n ttlSeconds: 60,\n },\n });\n const token = mintRes.data?.mintAgentCapability?.token;\n if (!token) {\n throw new Error('Failed to mint editor capability');\n }\n const base = tunnelUrl.replace(/\\/$/, '');\n const editorUrl = `${base}/code-server/#vt=${encodeURIComponent(token)}`;\n\n if (mode === 'iframe' && onIframeUrl) {\n onIframeUrl(editorUrl);\n } else {\n window.open(editorUrl, '_blank');\n }\n\n // Reset after a moment\n setTimeout(() => setState('idle'), 2000);\n } catch (err) {\n setErrorMsg(err instanceof Error ? err.message : 'Failed to open editor');\n setState('error');\n }\n }, [\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode,\n onIframeUrl,\n tr,\n probe,\n startCodeServer,\n stopCodeServer,\n mintAgentCapability,\n ]);\n\n const isDisabled =\n !agentId || !tunnelUrl || (state !== 'idle' && state !== 'error' && state !== 'ready');\n\n const label = (() => {\n switch (state) {\n case 'checking':\n return tr('vibecontrols.agents.editor.checking', 'Checking...');\n case 'installing':\n return tr('vibecontrols.agents.editor.installing', 'Installing...');\n case 'starting':\n return tr('vibecontrols.agents.editor.starting', 'Starting...');\n case 'ready':\n return tr('vibecontrols.agents.editor.opened', 'Opened');\n case 'error':\n return tr('vibecontrols.agents.editor.retry', 'Retry');\n default:\n return tr('vibecontrols.agents.editor.openEditor', 'Open Editor');\n }\n })();\n\n const Icon = (() => {\n switch (state) {\n case 'checking':\n case 'installing':\n case 'starting':\n return Loader2;\n case 'ready':\n return Check;\n case 'error':\n return AlertCircle;\n default:\n return Code;\n }\n })();\n\n const isSpinning = state === 'checking' || state === 'installing' || state === 'starting';\n\n return (\n <div className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={isDisabled}\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 rounded-md transition-colors disabled:opacity-50 ${\n state === 'error'\n ? 'bg-status-error-bg text-status-error-text hover:opacity-80'\n : state === 'ready'\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-sunken/80'\n } ${className || ''}`}\n title={errorMsg || 'Open VS Code in browser via code-server'}\n >\n <Icon className={`size-3.5 sm:w-4 sm:h-4 ${isSpinning ? 'animate-spin' : ''}`} />\n <span className=\"hidden sm:inline\">{label}</span>\n </button>\n\n {/* Error popover. Anchored to the right edge (the button sits at the\n header's right side) with a fixed width so the message wraps as\n normal prose instead of being squeezed into a narrow column by the\n inline-flex wrapper. */}\n {state === 'error' && errorMsg && (\n <div\n role=\"alert\"\n className=\"absolute top-full right-0 mt-1.5 z-50 w-72 rounded-md border border-status-error-text/30 bg-bg-surface p-3 text-xs leading-relaxed text-status-error-text whitespace-normal break-words shadow-lg\"\n >\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" />\n <span>{errorMsg}</span>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"OpenInBrowserEditorButton.js","names":[],"sources":["../../../src/components/agents/OpenInBrowserEditorButton.tsx"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { Code, Loader2, AlertCircle, Check } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n useAgentCodeServerStatusLazyQuery,\n useAgentCodeServerStartMutation,\n useAgentCodeServerStopMutation,\n useMintAgentCapabilityMutation,\n} from '@/generated/wspace-operations';\nimport { AgentCapabilityScope } from '@/generated/wspace-types';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\ntype EditorState = 'idle' | 'checking' | 'installing' | 'starting' | 'ready' | 'error';\n\nexport interface OpenInBrowserEditorButtonProps {\n /**\n * Agent ID — F4 Wave B replaced the direct browser fetch (which relied\n * on `agentApiKey` headers) with svc-mediated `agentCodeServer*`\n * mutations. The agent's tunnel URL is still used for the iframe `src`,\n * but every control-plane call now routes through the gateway.\n */\n agentId: string | null;\n /** Agent tunnel URL (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /**\n * Canonical CLI profile name on the remote agent. Maps to the\n * `/api/profiles/<profile>/code-server/...` URL prefix.\n */\n profile: string;\n /** Directory to open as workspace root. Defaults to agent home if not set. */\n workspacePath?: string;\n /** How to open — new browser tab or call back with URL for iframe embedding */\n mode?: 'newtab' | 'iframe';\n /** Called with the ready URL when mode is 'iframe' */\n onIframeUrl?: (url: string) => void;\n /** Additional CSS class names */\n className?: string;\n}\n\ninterface CodeServerStatusPayload {\n installed?: boolean;\n installing?: boolean;\n running?: boolean;\n port?: number;\n error?: string;\n}\n\n// Code-server is a single named instance per profile; the proxy resolver\n// requires a `name`. Keep the legacy default — operators who installed\n// the plugin pre-rename still have an instance keyed under \"default\".\nconst CODE_SERVER_INSTANCE = 'default';\n\n// ── Component ──────────────────────────────────────────────────────────\n\nexport function OpenInBrowserEditorButton({\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode = 'newtab',\n onIframeUrl,\n className,\n}: OpenInBrowserEditorButtonProps) {\n const tr = useTr();\n const [state, setState] = useState<EditorState>('idle');\n const [errorMsg, setErrorMsg] = useState<string | null>(null);\n\n const [fetchStatus] = useAgentCodeServerStatusLazyQuery({ fetchPolicy: 'network-only' });\n const [startCodeServer] = useAgentCodeServerStartMutation();\n const [stopCodeServer] = useAgentCodeServerStopMutation();\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n\n const probe = useCallback(\n async (\n candidate: string\n ): Promise<{\n effective: string | null;\n status: CodeServerStatusPayload | null;\n missing: boolean;\n }> => {\n const res = await fetchStatus({\n variables: { agentId: agentId!, profile: candidate, name: CODE_SERVER_INSTANCE },\n });\n const proxy = res.data?.agentCodeServerStatus;\n if (!proxy) {\n return { effective: null, status: null, missing: false };\n }\n if (proxy.status === 404) {\n return { effective: null, status: null, missing: true };\n }\n if (!proxy.ok) {\n throw new Error(proxy.error || `Status check failed: HTTP ${proxy.status}`);\n }\n return {\n effective: candidate,\n status: (proxy.data as CodeServerStatusPayload | null) ?? {},\n missing: false,\n };\n },\n [fetchStatus, agentId]\n );\n\n const handleClick = useCallback(async () => {\n if (!agentId || !tunnelUrl) {\n setErrorMsg('Agent not selected');\n setState('error');\n return;\n }\n\n setErrorMsg(null);\n\n try {\n // Step 1: Probe the active profile, falling back to \"default\" if the\n // current profile reports 404 (code-server isn't a per-profile\n // service — its management API is profile-scoped though).\n setState('checking');\n const candidates = profile === 'default' ? ['default'] : [profile, 'default'];\n let effectiveProfile: string | null = null;\n let status: CodeServerStatusPayload | null = null;\n for (const candidate of candidates) {\n const { effective, status: probed, missing } = await probe(candidate);\n if (missing) continue;\n if (effective) {\n effectiveProfile = effective;\n status = probed;\n break;\n }\n }\n if (!effectiveProfile || !status) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.notInstalled',\n \"Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor.\"\n )\n );\n }\n\n // Step 2: code-server needs to be installed up-front via the\n // Plugins tab. The deprecated direct-browser install fetch isn't\n // proxied; surface a clear message instead of silently hanging.\n if (!status.installed) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.notInstalled',\n \"Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor.\"\n )\n );\n }\n\n // Step 3: Start if not running, or restart with a fresh workspace path.\n if (!status.running) {\n setState('starting');\n const startRes = await startCodeServer({\n variables: {\n agentId,\n profile: effectiveProfile,\n name: CODE_SERVER_INSTANCE,\n ...(workspacePath ? { path: workspacePath } : {}),\n },\n });\n const startProxy = startRes.data?.agentCodeServerStart;\n if (!startProxy?.ok) {\n throw new Error(startProxy?.error || `Start failed: HTTP ${startProxy?.status ?? 0}`);\n }\n await new Promise((r) => setTimeout(r, 1000));\n } else if (workspacePath) {\n setState('starting');\n // No dedicated restart proxy yet — stop, then start with the new path.\n await stopCodeServer({\n variables: { agentId, profile: effectiveProfile, name: CODE_SERVER_INSTANCE },\n });\n const restartRes = await startCodeServer({\n variables: {\n agentId,\n profile: effectiveProfile,\n name: CODE_SERVER_INSTANCE,\n path: workspacePath,\n },\n });\n const restartProxy = restartRes.data?.agentCodeServerStart;\n if (!restartProxy?.ok) {\n throw new Error(\n restartProxy?.error || `Restart failed: HTTP ${restartProxy?.status ?? 0}`\n );\n }\n await new Promise((r) => setTimeout(r, 1000));\n }\n\n // Step 4: Mint a short-lived IFRAME_PLUGIN capability so the\n // editor URL doesn't embed the long-lived `agentApiKey`. The agent\n // consumes the `?vt=` query on the first `/code-server/` request and\n // mints a scoped proxy session cookie server-side before proxying.\n setState('ready');\n const mintRes = await mintAgentCapability({\n variables: {\n agentId,\n profile: effectiveProfile,\n scope: AgentCapabilityScope.IframePlugin,\n resourceId: `code-server:${CODE_SERVER_INSTANCE}`,\n ttlSeconds: 60,\n },\n });\n const token = mintRes.data?.mintAgentCapability?.token;\n if (!token) {\n throw new Error('Failed to mint editor capability');\n }\n const base = tunnelUrl.replace(/\\/$/, '');\n // code-server reads the capability from the `?vt=` QUERY (GET-only): the\n // agent consumes it server-side on the first `/code-server/` request to\n // mint the proxy session cookie. A `#vt=` fragment is never sent to the\n // server, so the editor would 401 before it could bootstrap (code-server\n // has no client-side fragment-exchange shim, unlike `/ui/*` / `/terminal`).\n const editorUrl = `${base}/code-server/?vt=${encodeURIComponent(token)}`;\n\n if (mode === 'iframe' && onIframeUrl) {\n onIframeUrl(editorUrl);\n } else {\n window.open(editorUrl, '_blank');\n }\n\n // Reset after a moment\n setTimeout(() => setState('idle'), 2000);\n } catch (err) {\n setErrorMsg(err instanceof Error ? err.message : 'Failed to open editor');\n setState('error');\n }\n }, [\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode,\n onIframeUrl,\n tr,\n probe,\n startCodeServer,\n stopCodeServer,\n mintAgentCapability,\n ]);\n\n const isDisabled =\n !agentId || !tunnelUrl || (state !== 'idle' && state !== 'error' && state !== 'ready');\n\n const label = (() => {\n switch (state) {\n case 'checking':\n return tr('vibecontrols.agents.editor.checking', 'Checking...');\n case 'installing':\n return tr('vibecontrols.agents.editor.installing', 'Installing...');\n case 'starting':\n return tr('vibecontrols.agents.editor.starting', 'Starting...');\n case 'ready':\n return tr('vibecontrols.agents.editor.opened', 'Opened');\n case 'error':\n return tr('vibecontrols.agents.editor.retry', 'Retry');\n default:\n return tr('vibecontrols.agents.editor.openEditor', 'Open Editor');\n }\n })();\n\n const Icon = (() => {\n switch (state) {\n case 'checking':\n case 'installing':\n case 'starting':\n return Loader2;\n case 'ready':\n return Check;\n case 'error':\n return AlertCircle;\n default:\n return Code;\n }\n })();\n\n const isSpinning = state === 'checking' || state === 'installing' || state === 'starting';\n\n return (\n <div className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={isDisabled}\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 rounded-md transition-colors disabled:opacity-50 ${\n state === 'error'\n ? 'bg-status-error-bg text-status-error-text hover:opacity-80'\n : state === 'ready'\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-sunken/80'\n } ${className || ''}`}\n title={errorMsg || 'Open VS Code in browser via code-server'}\n >\n <Icon className={`size-3.5 sm:w-4 sm:h-4 ${isSpinning ? 'animate-spin' : ''}`} />\n <span className=\"hidden sm:inline\">{label}</span>\n </button>\n\n {/* Error popover. Anchored to the right edge (the button sits at the\n header's right side) with a fixed width so the message wraps as\n normal prose instead of being squeezed into a narrow column by the\n inline-flex wrapper. */}\n {state === 'error' && errorMsg && (\n <div\n role=\"alert\"\n className=\"absolute top-full right-0 mt-1.5 z-50 w-72 rounded-md border border-status-error-text/30 bg-bg-surface p-3 text-xs leading-relaxed text-status-error-text whitespace-normal break-words shadow-lg\"\n >\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" />\n <span>{errorMsg}</span>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -26,7 +26,9 @@ function w({ availablePlugins: S, tunnelUrl: w, agentId: T, profile: E }) {
26
26
  resourceId: e,
27
27
  ttlSeconds: 60
28
28
  } })).data?.mintAgentCapability?.token;
29
- return n ? `${a}#vt=${encodeURIComponent(n)}` : a;
29
+ if (!n) return a;
30
+ let r = new URL(a);
31
+ return e === "code-server" || r.pathname.startsWith("/code-server") ? (r.searchParams.set("vt", n), r.toString()) : `${a}#vt=${encodeURIComponent(n)}`;
30
32
  } catch {
31
33
  return a;
32
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PluginUIPanel.js","names":[],"sources":["../../../src/components/agents/PluginUIPanel.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useState, useCallback, useRef, useEffect } from 'react';\nimport { useMintAgentCapabilityMutation } from '@/generated/wspace-operations';\nimport { AgentCapabilityScope } from '@/generated/wspace-types';\nimport {\n X,\n Maximize2,\n Minimize2,\n ExternalLink,\n Loader2,\n AlertCircle,\n RotateCcw,\n Wifi,\n WifiOff,\n GripVertical,\n Plus,\n Monitor,\n ChevronDown,\n} from 'lucide-react';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\nexport interface PluginUITab {\n /** Unique tab ID (allows duplicate plugins in multiple tabs) */\n id: string;\n /** The plugin name (e.g. \"ui-ai\") — may be null if nothing selected yet */\n pluginName: string | null;\n /** Display name shown on the tab */\n name: string;\n /** The full URL to load in the iframe */\n uiUrl: string | null;\n}\n\nexport interface UIPluginOption {\n /** The plugin name key (e.g. \"ui-ai\") */\n pluginName: string;\n /** Display name */\n displayName: string;\n /** Description shown in dropdown */\n description?: string;\n /** Version */\n version?: string;\n /** Custom URL to use instead of the default /ui/{pluginName} path (e.g. for code-server proxy) */\n customUrl?: string;\n}\n\ninterface PluginUIPanelProps {\n /** Available UI plugins the user can pick from */\n availablePlugins: UIPluginOption[];\n /** Base URL of the agent tunnel (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /**\n * Agent ID — F4 Wave B replaced the legacy `?apiKey=` URL embed +\n * postMessage handoff with `mintAgentCapability(IFRAME_PLUGIN)`. Each\n * plugin tab loads with `#vt=<token>` so the long-lived agent secret\n * never reaches the browser.\n */\n agentId: string | null;\n /** Canonical CLI profile name; required for capability mint scoping. */\n profile: string;\n}\n\nlet tabIdCounter = 0;\nfunction nextTabId(): string {\n return `ui-tab-${++tabIdCounter}`;\n}\n\n/**\n * Embedded browser-like panel for plugin UIs.\n * Each tab has a dropdown to select which plugin to load. The same plugin can\n * be loaded in multiple tabs, each with its own navigation state.\n */\nexport function PluginUIPanel({\n availablePlugins,\n tunnelUrl,\n agentId,\n profile,\n}: PluginUIPanelProps) {\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const [tabs, setTabs] = useState<PluginUITab[]>(() => {\n // Start with one blank tab\n return [{ id: nextTabId(), pluginName: null, name: 'New Tab', uiUrl: null }];\n });\n const [activeTabId, setActiveTabId] = useState<string>(tabs[0].id);\n const [maximized, setMaximized] = useState(false);\n\n // Connection status per tab\n const [connectionStatus, setConnectionStatus] = useState<\n Record<string, 'connecting' | 'connected' | 'error'>\n >({});\n\n // Dropdown open state per tab\n const [dropdownOpen, setDropdownOpen] = useState<string | null>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n // Drag-to-resize state\n const [resizeHeight, setResizeHeight] = useState<number | null>(null);\n const resizingRef = useRef(false);\n const startYRef = useRef(0);\n const startHeightRef = useRef(0);\n const panelRef = useRef<HTMLDivElement>(null);\n\n // Iframe refs for auth postMessage\n const iframeRefs = useRef<Record<string, HTMLIFrameElement | null>>({});\n\n const activeTab = tabs.find((t) => t.id === activeTabId) || tabs[0] || null;\n\n // Build UI URL for a plugin — F4 Wave B: append a short-lived\n // IFRAME_PLUGIN capability as `#vt=<token>` so the agent's `/ui/<name>`\n // bootstrap can exchange it for a scoped HttpOnly cookie. No\n // long-lived secret in the URL or postMessage.\n const buildUiUrl = useCallback(\n async (pluginName: string, customUrl?: string): Promise<string | null> => {\n if (!tunnelUrl || !agentId) return null;\n const base = tunnelUrl.replace(/\\/$/, '');\n\n // Use customUrl if provided (e.g. for code-server proxy path)\n const urlStr = customUrl\n ? customUrl.startsWith('http')\n ? customUrl\n : `${base}${customUrl}`\n : `${base}/ui/${pluginName}`;\n\n const finalUrl = new URL(urlStr).toString();\n\n try {\n const res = await mintAgentCapability({\n variables: {\n agentId,\n profile: profile ?? 'default',\n scope: AgentCapabilityScope.IframePlugin,\n // Pass the bare plugin name — the svc maps to `/ui/<plugin>`\n // which matches the agent's iframe-token allow-list. Previous\n // `ui:<plugin>` form produced an invalid `/ui/ui:<plugin>`\n // prefix that the agent now rejects (audit-B P0-SEC-03).\n resourceId: pluginName,\n ttlSeconds: 60,\n },\n });\n const token = res.data?.mintAgentCapability?.token;\n if (!token) return finalUrl;\n // Fragment-only — never sent to proxies/logs/Referer.\n return `${finalUrl}#vt=${encodeURIComponent(token)}`;\n } catch {\n return finalUrl;\n }\n },\n [tunnelUrl, agentId, profile, mintAgentCapability]\n );\n\n // Display name for a plugin name\n const getDisplayName = useCallback(\n (pluginName: string): string => {\n const opt = availablePlugins.find((p) => p.pluginName === pluginName);\n return (\n opt?.displayName ||\n pluginName\n .replace(/^ui-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n );\n },\n [availablePlugins]\n );\n\n // ── Tab Actions ───────────────────────────────────────────────────\n\n const handleAddTab = useCallback(() => {\n const newTab: PluginUITab = {\n id: nextTabId(),\n pluginName: null,\n name: 'New Tab',\n uiUrl: null,\n };\n setTabs((prev) => [...prev, newTab]);\n setActiveTabId(newTab.id);\n setDropdownOpen(newTab.id);\n }, []);\n\n const handleCloseTab = useCallback(\n (tabId: string) => {\n setTabs((prev) => {\n const next = prev.filter((t) => t.id !== tabId);\n // If we closed the last tab, add a blank one\n if (next.length === 0) {\n const blank: PluginUITab = {\n id: nextTabId(),\n pluginName: null,\n name: 'New Tab',\n uiUrl: null,\n };\n next.push(blank);\n setActiveTabId(blank.id);\n } else if (activeTabId === tabId) {\n // Select the previous tab or the first one\n const closedIdx = prev.findIndex((t) => t.id === tabId);\n const newIdx = Math.max(0, closedIdx - 1);\n setActiveTabId(next[Math.min(newIdx, next.length - 1)].id);\n }\n return next;\n });\n // Clean up refs\n delete iframeRefs.current[tabId];\n setConnectionStatus((prev) => {\n const next = { ...prev };\n delete next[tabId];\n return next;\n });\n },\n [activeTabId]\n );\n\n const handleSelectPlugin = useCallback(\n async (tabId: string, pluginName: string) => {\n const opt = availablePlugins.find((p) => p.pluginName === pluginName);\n const displayName = getDisplayName(pluginName);\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connecting' }));\n setDropdownOpen(null);\n const uiUrl = await buildUiUrl(pluginName, opt?.customUrl);\n setTabs((prev) =>\n prev.map((t) => (t.id === tabId ? { ...t, pluginName, name: displayName, uiUrl } : t))\n );\n },\n [availablePlugins, buildUiUrl, getDisplayName]\n );\n\n // ── Auth ──────────────────────────────────────────────────────────\n // F4 Wave B: auth is now handled by the `#vt=` fragment in the iframe\n // src. The agent exchanges it for a scoped cookie on first load. No\n // postMessage handoff needed.\n\n const handleIframeLoad = useCallback((tabId: string) => {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connected' }));\n }, []);\n\n const handleIframeError = useCallback((tabId: string) => {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'error' }));\n }, []);\n\n const handleReload = useCallback((tabId: string) => {\n const iframe = iframeRefs.current[tabId];\n if (iframe) {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connecting' }));\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }\n }, []);\n\n const handleOpenExternal = useCallback(\n (tabId: string) => {\n const tab = tabs.find((t) => t.id === tabId);\n if (!tab?.uiUrl) return;\n window.open(tab.uiUrl, '_blank');\n },\n [tabs]\n );\n\n // ── Close dropdown on outside click ───────────────────────────────\n\n useEffect(() => {\n if (!dropdownOpen) return;\n const handler = (e: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {\n setDropdownOpen(null);\n }\n };\n document.addEventListener('mousedown', handler);\n return () => document.removeEventListener('mousedown', handler);\n }, [dropdownOpen]);\n\n // ── Drag-to-resize ────────────────────────────────────────────────\n\n const handleResizeStart = useCallback(\n (e: React.MouseEvent) => {\n if (maximized) return;\n e.preventDefault();\n resizingRef.current = true;\n startYRef.current = e.clientY;\n startHeightRef.current = panelRef.current?.offsetHeight || 500;\n document.body.style.cursor = 'ns-resize';\n document.body.style.userSelect = 'none';\n },\n [maximized]\n );\n\n useEffect(() => {\n const handleMouseMove = (e: MouseEvent) => {\n if (!resizingRef.current) return;\n const delta = startYRef.current - e.clientY;\n const newHeight = Math.max(\n 250,\n Math.min(startHeightRef.current + delta, window.innerHeight - 150)\n );\n setResizeHeight(newHeight);\n };\n const handleMouseUp = () => {\n if (resizingRef.current) {\n resizingRef.current = false;\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n }\n };\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n return () => {\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n }, []);\n\n // ── Render helpers ────────────────────────────────────────────────\n\n const renderStatusDot = (tabId: string) => {\n const status = connectionStatus[tabId];\n if (status === 'connected')\n return <Wifi className=\"size-3 text-status-success-text shrink-0\" />;\n if (status === 'error') return <WifiOff className=\"size-3 text-status-error-text shrink-0\" />;\n if (status === 'connecting')\n return <Loader2 className=\"size-3 animate-spin text-status-warning-text shrink-0\" />;\n return <Monitor className=\"size-3 text-text-muted shrink-0\" />;\n };\n\n const panelHeight = maximized ? 'h-[calc(100vh-200px)]' : resizeHeight ? undefined : 'h-[500px]';\n const panelStyle = !maximized && resizeHeight ? { height: `${resizeHeight}px` } : undefined;\n\n return (\n <div\n ref={panelRef}\n className=\"border border-border-default rounded-lg overflow-hidden bg-bg-sunken\"\n >\n {/* Drag handle */}\n <div\n onMouseDown={handleResizeStart}\n className=\"h-1 cursor-ns-resize hover:bg-action-primary-bg/30 transition-colors flex items-center justify-center group\"\n title=\"Drag to resize\"\n >\n <GripVertical className=\"w-4 h-3 text-text-muted opacity-0 group-hover:opacity-100 transition-opacity rotate-90\" />\n </div>\n\n {/* ── Tab Bar ────────────────────────────────────────────────── */}\n <div className=\"flex items-center bg-bg-secondary border-b border-border-default overflow-x-auto\">\n {tabs.map((tab) => {\n const isActive = tab.id === activeTab?.id;\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => setActiveTabId(tab.id))();\n }\n }}\n key={tab.id}\n className={`group flex items-center gap-1.5 pl-3 pr-1.5 py-1.5 text-xs border-r border-border-subtle cursor-pointer transition-colors min-w-0 max-w-[200px] shrink-0 ${\n isActive\n ? 'bg-bg-surface text-text-primary border-b-2 border-b-action-primary-bg'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface/50'\n }`}\n onClick={() => setActiveTabId(tab.id)}\n >\n {renderStatusDot(tab.id)}\n <span className=\"truncate\">{tab.name}</span>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleCloseTab(tab.id);\n }}\n className=\"ml-auto opacity-0 group-hover:opacity-100 hover:text-status-error-text transition-opacity shrink-0 p-0.5\"\n title=\"Close tab\"\n >\n <X className=\"size-3\" />\n </button>\n </div>\n );\n })}\n\n {/* Add tab button */}\n <button\n type=\"button\"\n onClick={handleAddTab}\n className=\"flex items-center justify-center size-8 shrink-0 text-text-secondary hover:text-text-primary hover:bg-bg-surface/50 transition-colors\"\n title=\"New tab\"\n >\n <Plus className=\"size-3.5\" />\n </button>\n\n {/* Spacer */}\n <div className=\"flex-1 min-w-0\" />\n\n {/* Global panel actions */}\n <div className=\"flex items-center gap-0.5 px-2 shrink-0\">\n {activeTab?.uiUrl && (\n <>\n <button\n type=\"button\"\n onClick={() => activeTab && handleReload(activeTab.id)}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Reload\"\n >\n <RotateCcw className=\"size-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={() => activeTab && handleOpenExternal(activeTab.id)}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Open in new browser tab\"\n >\n <ExternalLink className=\"size-3.5\" />\n </button>\n </>\n )}\n <button\n type=\"button\"\n onClick={() => {\n setMaximized((prev) => !prev);\n setResizeHeight(null);\n }}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title={maximized ? 'Restore size' : 'Maximize'}\n >\n {maximized ? <Minimize2 className=\"size-3.5\" /> : <Maximize2 className=\"size-3.5\" />}\n </button>\n </div>\n </div>\n\n {/* ── Address Bar (Plugin Selector Dropdown) ─────────────────── */}\n {activeTab && (\n <div className=\"flex items-center gap-2 px-3 py-1.5 bg-bg-surface border-b border-border-default\">\n <Monitor className=\"size-4 text-text-muted shrink-0\" />\n <div className=\"relative flex-1 min-w-0\" ref={dropdownRef}>\n <button\n type=\"button\"\n onClick={() => setDropdownOpen(dropdownOpen === activeTab.id ? null : activeTab.id)}\n className=\"w-full flex items-center justify-between gap-2 px-3 py-1.5 text-sm bg-bg-sunken border border-border-default rounded-md text-left hover:border-action-primary-bg/50 focus:outline-none focus:ring-1 focus:ring-action-primary-bg transition-colors\"\n >\n <span\n className={`truncate ${activeTab.pluginName ? 'text-text-primary' : 'text-text-muted'}`}\n >\n {activeTab.pluginName\n ? `${activeTab.name} — ${activeTab.pluginName}`\n : 'Select a plugin to load...'}\n </span>\n <ChevronDown\n className={`size-3.5 text-text-muted shrink-0 transition-transform ${dropdownOpen === activeTab.id ? 'rotate-180' : ''}`}\n />\n </button>\n\n {/* Dropdown list */}\n {dropdownOpen === activeTab.id && (\n <div className=\"absolute z-50 top-full left-0 right-0 mt-1 bg-bg-surface border border-border-default rounded-md shadow-lg overflow-hidden max-h-64 overflow-y-auto\">\n {availablePlugins.length > 0 ? (\n availablePlugins.map((opt) => {\n const isSelected = activeTab.pluginName === opt.pluginName;\n return (\n <button\n type=\"button\"\n key={opt.pluginName}\n onClick={() => handleSelectPlugin(activeTab.id, opt.pluginName)}\n className={`w-full text-left px-3 py-2.5 hover:bg-bg-sunken transition-colors border-b border-border-subtle last:border-b-0 ${\n isSelected ? 'bg-action-primary-bg/10' : ''\n }`}\n >\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium text-text-primary\">\n {opt.displayName}\n </span>\n {opt.version && (\n <span className=\"text-xs text-text-muted\">\n {opt.version === 'latest' ? opt.version : `v${opt.version}`}\n </span>\n )}\n </div>\n {opt.description && (\n <p className=\"text-xs text-text-secondary mt-0.5\">{opt.description}</p>\n )}\n <p className=\"text-xs text-text-muted font-mono mt-0.5\">{opt.pluginName}</p>\n </button>\n );\n })\n ) : (\n <div className=\"px-3 py-4 text-center text-sm text-text-muted\">\n No UI plugins available\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* ── Content Area ───────────────────────────────────────────── */}\n <div\n className={`${panelHeight || ''} bg-bg-inverse text-text-inverse relative`}\n style={panelStyle}\n >\n {tabs.map((tab) => (\n <div\n key={tab.id}\n className={`absolute inset-0 ${tab.id === activeTab?.id ? 'block' : 'hidden'}`}\n >\n {!tab.pluginName ? (\n // Empty state — no plugin selected\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-sm\">\n <Monitor className=\"size-10 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm font-medium\">No plugin selected</p>\n <p className=\"text-text-secondary text-xs mt-1\">\n Use the dropdown above to pick a plugin UI to load in this tab.\n </p>\n <button\n type=\"button\"\n onClick={() => setDropdownOpen(tab.id)}\n className=\"mt-4 px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-muted transition-colors inline-flex items-center gap-2\"\n >\n <ChevronDown className=\"size-3.5\" />\n Select Plugin\n </button>\n </div>\n </div>\n ) : !tab.uiUrl ? (\n // No tunnel\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <AlertCircle className=\"size-8 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm\">Agent tunnel not active</p>\n <p className=\"text-text-secondary text-xs mt-1\">\n Cannot load plugin UI without an active tunnel connection.\n </p>\n </div>\n </div>\n ) : (\n // Iframe\n <iframe\n ref={(el) => {\n iframeRefs.current[tab.id] = el;\n }}\n src={tab.uiUrl}\n title={`Plugin UI — ${tab.name}`}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n onLoad={() => handleIframeLoad(tab.id)}\n onError={() => handleIframeError(tab.id)}\n />\n )}\n </div>\n ))}\n\n {/* Connection status overlay */}\n {activeTab && connectionStatus[activeTab.id] === 'connecting' && activeTab.uiUrl && (\n <div className=\"absolute bottom-3 right-3 flex items-center gap-2 px-3 py-1.5 bg-bg-overlay backdrop-blur-sm rounded-md text-xs text-status-warning-text pointer-events-none\">\n <Loader2 className=\"size-3 animate-spin\" />\n Connecting...\n </div>\n )}\n {activeTab && connectionStatus[activeTab.id] === 'error' && activeTab.uiUrl && (\n <div className=\"absolute bottom-3 right-3 flex items-center gap-2 px-3 py-1.5 bg-bg-overlay backdrop-blur-sm rounded-md text-xs text-status-error-text\">\n <WifiOff className=\"size-3\" />\n Disconnected\n <button\n type=\"button\"\n onClick={() => handleReload(activeTab.id)}\n className=\"underline hover:text-status-error-text/80 transition-colors\"\n >\n Reconnect\n </button>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PluginUIPanel.js","names":[],"sources":["../../../src/components/agents/PluginUIPanel.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useState, useCallback, useRef, useEffect } from 'react';\nimport { useMintAgentCapabilityMutation } from '@/generated/wspace-operations';\nimport { AgentCapabilityScope } from '@/generated/wspace-types';\nimport {\n X,\n Maximize2,\n Minimize2,\n ExternalLink,\n Loader2,\n AlertCircle,\n RotateCcw,\n Wifi,\n WifiOff,\n GripVertical,\n Plus,\n Monitor,\n ChevronDown,\n} from 'lucide-react';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\nexport interface PluginUITab {\n /** Unique tab ID (allows duplicate plugins in multiple tabs) */\n id: string;\n /** The plugin name (e.g. \"ui-ai\") — may be null if nothing selected yet */\n pluginName: string | null;\n /** Display name shown on the tab */\n name: string;\n /** The full URL to load in the iframe */\n uiUrl: string | null;\n}\n\nexport interface UIPluginOption {\n /** The plugin name key (e.g. \"ui-ai\") */\n pluginName: string;\n /** Display name */\n displayName: string;\n /** Description shown in dropdown */\n description?: string;\n /** Version */\n version?: string;\n /** Custom URL to use instead of the default /ui/{pluginName} path (e.g. for code-server proxy) */\n customUrl?: string;\n}\n\ninterface PluginUIPanelProps {\n /** Available UI plugins the user can pick from */\n availablePlugins: UIPluginOption[];\n /** Base URL of the agent tunnel (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /**\n * Agent ID — F4 Wave B replaced the legacy `?apiKey=` URL embed +\n * postMessage handoff with `mintAgentCapability(IFRAME_PLUGIN)`. Each\n * plugin tab loads with `#vt=<token>` so the long-lived agent secret\n * never reaches the browser.\n */\n agentId: string | null;\n /** Canonical CLI profile name; required for capability mint scoping. */\n profile: string;\n}\n\nlet tabIdCounter = 0;\nfunction nextTabId(): string {\n return `ui-tab-${++tabIdCounter}`;\n}\n\n/**\n * Embedded browser-like panel for plugin UIs.\n * Each tab has a dropdown to select which plugin to load. The same plugin can\n * be loaded in multiple tabs, each with its own navigation state.\n */\nexport function PluginUIPanel({\n availablePlugins,\n tunnelUrl,\n agentId,\n profile,\n}: PluginUIPanelProps) {\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const [tabs, setTabs] = useState<PluginUITab[]>(() => {\n // Start with one blank tab\n return [{ id: nextTabId(), pluginName: null, name: 'New Tab', uiUrl: null }];\n });\n const [activeTabId, setActiveTabId] = useState<string>(tabs[0].id);\n const [maximized, setMaximized] = useState(false);\n\n // Connection status per tab\n const [connectionStatus, setConnectionStatus] = useState<\n Record<string, 'connecting' | 'connected' | 'error'>\n >({});\n\n // Dropdown open state per tab\n const [dropdownOpen, setDropdownOpen] = useState<string | null>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n // Drag-to-resize state\n const [resizeHeight, setResizeHeight] = useState<number | null>(null);\n const resizingRef = useRef(false);\n const startYRef = useRef(0);\n const startHeightRef = useRef(0);\n const panelRef = useRef<HTMLDivElement>(null);\n\n // Iframe refs for auth postMessage\n const iframeRefs = useRef<Record<string, HTMLIFrameElement | null>>({});\n\n const activeTab = tabs.find((t) => t.id === activeTabId) || tabs[0] || null;\n\n // Build UI URL for a plugin — F4 Wave B: append a short-lived\n // IFRAME_PLUGIN capability as `#vt=<token>` so the agent's `/ui/<name>`\n // bootstrap can exchange it for a scoped HttpOnly cookie. No\n // long-lived secret in the URL or postMessage.\n const buildUiUrl = useCallback(\n async (pluginName: string, customUrl?: string): Promise<string | null> => {\n if (!tunnelUrl || !agentId) return null;\n const base = tunnelUrl.replace(/\\/$/, '');\n\n // Use customUrl if provided (e.g. for code-server proxy path)\n const urlStr = customUrl\n ? customUrl.startsWith('http')\n ? customUrl\n : `${base}${customUrl}`\n : `${base}/ui/${pluginName}`;\n\n const finalUrl = new URL(urlStr).toString();\n\n try {\n const res = await mintAgentCapability({\n variables: {\n agentId,\n profile: profile ?? 'default',\n scope: AgentCapabilityScope.IframePlugin,\n // Pass the bare plugin name — the svc maps to `/ui/<plugin>`\n // which matches the agent's iframe-token allow-list. Previous\n // `ui:<plugin>` form produced an invalid `/ui/ui:<plugin>`\n // prefix that the agent now rejects (audit-B P0-SEC-03).\n resourceId: pluginName,\n ttlSeconds: 60,\n },\n });\n const token = res.data?.mintAgentCapability?.token;\n if (!token) return finalUrl;\n // code-server's reverse proxy reads the capability from the `?vt=`\n // QUERY (GET-only) and consumes it server-side to mint its session\n // cookie — it has no client-side fragment-exchange shim. `/ui/<plugin>`\n // (and `/terminal`) DO have that shim, so they keep the `#vt=`\n // fragment, which is never sent to proxies/logs/Referer.\n const builtUrl = new URL(finalUrl);\n const isCodeServer =\n pluginName === 'code-server' || builtUrl.pathname.startsWith('/code-server');\n if (isCodeServer) {\n builtUrl.searchParams.set('vt', token);\n return builtUrl.toString();\n }\n return `${finalUrl}#vt=${encodeURIComponent(token)}`;\n } catch {\n return finalUrl;\n }\n },\n [tunnelUrl, agentId, profile, mintAgentCapability]\n );\n\n // Display name for a plugin name\n const getDisplayName = useCallback(\n (pluginName: string): string => {\n const opt = availablePlugins.find((p) => p.pluginName === pluginName);\n return (\n opt?.displayName ||\n pluginName\n .replace(/^ui-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n );\n },\n [availablePlugins]\n );\n\n // ── Tab Actions ───────────────────────────────────────────────────\n\n const handleAddTab = useCallback(() => {\n const newTab: PluginUITab = {\n id: nextTabId(),\n pluginName: null,\n name: 'New Tab',\n uiUrl: null,\n };\n setTabs((prev) => [...prev, newTab]);\n setActiveTabId(newTab.id);\n setDropdownOpen(newTab.id);\n }, []);\n\n const handleCloseTab = useCallback(\n (tabId: string) => {\n setTabs((prev) => {\n const next = prev.filter((t) => t.id !== tabId);\n // If we closed the last tab, add a blank one\n if (next.length === 0) {\n const blank: PluginUITab = {\n id: nextTabId(),\n pluginName: null,\n name: 'New Tab',\n uiUrl: null,\n };\n next.push(blank);\n setActiveTabId(blank.id);\n } else if (activeTabId === tabId) {\n // Select the previous tab or the first one\n const closedIdx = prev.findIndex((t) => t.id === tabId);\n const newIdx = Math.max(0, closedIdx - 1);\n setActiveTabId(next[Math.min(newIdx, next.length - 1)].id);\n }\n return next;\n });\n // Clean up refs\n delete iframeRefs.current[tabId];\n setConnectionStatus((prev) => {\n const next = { ...prev };\n delete next[tabId];\n return next;\n });\n },\n [activeTabId]\n );\n\n const handleSelectPlugin = useCallback(\n async (tabId: string, pluginName: string) => {\n const opt = availablePlugins.find((p) => p.pluginName === pluginName);\n const displayName = getDisplayName(pluginName);\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connecting' }));\n setDropdownOpen(null);\n const uiUrl = await buildUiUrl(pluginName, opt?.customUrl);\n setTabs((prev) =>\n prev.map((t) => (t.id === tabId ? { ...t, pluginName, name: displayName, uiUrl } : t))\n );\n },\n [availablePlugins, buildUiUrl, getDisplayName]\n );\n\n // ── Auth ──────────────────────────────────────────────────────────\n // F4 Wave B: auth is now handled by the `#vt=` fragment in the iframe\n // src. The agent exchanges it for a scoped cookie on first load. No\n // postMessage handoff needed.\n\n const handleIframeLoad = useCallback((tabId: string) => {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connected' }));\n }, []);\n\n const handleIframeError = useCallback((tabId: string) => {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'error' }));\n }, []);\n\n const handleReload = useCallback((tabId: string) => {\n const iframe = iframeRefs.current[tabId];\n if (iframe) {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connecting' }));\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }\n }, []);\n\n const handleOpenExternal = useCallback(\n (tabId: string) => {\n const tab = tabs.find((t) => t.id === tabId);\n if (!tab?.uiUrl) return;\n window.open(tab.uiUrl, '_blank');\n },\n [tabs]\n );\n\n // ── Close dropdown on outside click ───────────────────────────────\n\n useEffect(() => {\n if (!dropdownOpen) return;\n const handler = (e: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {\n setDropdownOpen(null);\n }\n };\n document.addEventListener('mousedown', handler);\n return () => document.removeEventListener('mousedown', handler);\n }, [dropdownOpen]);\n\n // ── Drag-to-resize ────────────────────────────────────────────────\n\n const handleResizeStart = useCallback(\n (e: React.MouseEvent) => {\n if (maximized) return;\n e.preventDefault();\n resizingRef.current = true;\n startYRef.current = e.clientY;\n startHeightRef.current = panelRef.current?.offsetHeight || 500;\n document.body.style.cursor = 'ns-resize';\n document.body.style.userSelect = 'none';\n },\n [maximized]\n );\n\n useEffect(() => {\n const handleMouseMove = (e: MouseEvent) => {\n if (!resizingRef.current) return;\n const delta = startYRef.current - e.clientY;\n const newHeight = Math.max(\n 250,\n Math.min(startHeightRef.current + delta, window.innerHeight - 150)\n );\n setResizeHeight(newHeight);\n };\n const handleMouseUp = () => {\n if (resizingRef.current) {\n resizingRef.current = false;\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n }\n };\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n return () => {\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n }, []);\n\n // ── Render helpers ────────────────────────────────────────────────\n\n const renderStatusDot = (tabId: string) => {\n const status = connectionStatus[tabId];\n if (status === 'connected')\n return <Wifi className=\"size-3 text-status-success-text shrink-0\" />;\n if (status === 'error') return <WifiOff className=\"size-3 text-status-error-text shrink-0\" />;\n if (status === 'connecting')\n return <Loader2 className=\"size-3 animate-spin text-status-warning-text shrink-0\" />;\n return <Monitor className=\"size-3 text-text-muted shrink-0\" />;\n };\n\n const panelHeight = maximized ? 'h-[calc(100vh-200px)]' : resizeHeight ? undefined : 'h-[500px]';\n const panelStyle = !maximized && resizeHeight ? { height: `${resizeHeight}px` } : undefined;\n\n return (\n <div\n ref={panelRef}\n className=\"border border-border-default rounded-lg overflow-hidden bg-bg-sunken\"\n >\n {/* Drag handle */}\n <div\n onMouseDown={handleResizeStart}\n className=\"h-1 cursor-ns-resize hover:bg-action-primary-bg/30 transition-colors flex items-center justify-center group\"\n title=\"Drag to resize\"\n >\n <GripVertical className=\"w-4 h-3 text-text-muted opacity-0 group-hover:opacity-100 transition-opacity rotate-90\" />\n </div>\n\n {/* ── Tab Bar ────────────────────────────────────────────────── */}\n <div className=\"flex items-center bg-bg-secondary border-b border-border-default overflow-x-auto\">\n {tabs.map((tab) => {\n const isActive = tab.id === activeTab?.id;\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => setActiveTabId(tab.id))();\n }\n }}\n key={tab.id}\n className={`group flex items-center gap-1.5 pl-3 pr-1.5 py-1.5 text-xs border-r border-border-subtle cursor-pointer transition-colors min-w-0 max-w-[200px] shrink-0 ${\n isActive\n ? 'bg-bg-surface text-text-primary border-b-2 border-b-action-primary-bg'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface/50'\n }`}\n onClick={() => setActiveTabId(tab.id)}\n >\n {renderStatusDot(tab.id)}\n <span className=\"truncate\">{tab.name}</span>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleCloseTab(tab.id);\n }}\n className=\"ml-auto opacity-0 group-hover:opacity-100 hover:text-status-error-text transition-opacity shrink-0 p-0.5\"\n title=\"Close tab\"\n >\n <X className=\"size-3\" />\n </button>\n </div>\n );\n })}\n\n {/* Add tab button */}\n <button\n type=\"button\"\n onClick={handleAddTab}\n className=\"flex items-center justify-center size-8 shrink-0 text-text-secondary hover:text-text-primary hover:bg-bg-surface/50 transition-colors\"\n title=\"New tab\"\n >\n <Plus className=\"size-3.5\" />\n </button>\n\n {/* Spacer */}\n <div className=\"flex-1 min-w-0\" />\n\n {/* Global panel actions */}\n <div className=\"flex items-center gap-0.5 px-2 shrink-0\">\n {activeTab?.uiUrl && (\n <>\n <button\n type=\"button\"\n onClick={() => activeTab && handleReload(activeTab.id)}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Reload\"\n >\n <RotateCcw className=\"size-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={() => activeTab && handleOpenExternal(activeTab.id)}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Open in new browser tab\"\n >\n <ExternalLink className=\"size-3.5\" />\n </button>\n </>\n )}\n <button\n type=\"button\"\n onClick={() => {\n setMaximized((prev) => !prev);\n setResizeHeight(null);\n }}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title={maximized ? 'Restore size' : 'Maximize'}\n >\n {maximized ? <Minimize2 className=\"size-3.5\" /> : <Maximize2 className=\"size-3.5\" />}\n </button>\n </div>\n </div>\n\n {/* ── Address Bar (Plugin Selector Dropdown) ─────────────────── */}\n {activeTab && (\n <div className=\"flex items-center gap-2 px-3 py-1.5 bg-bg-surface border-b border-border-default\">\n <Monitor className=\"size-4 text-text-muted shrink-0\" />\n <div className=\"relative flex-1 min-w-0\" ref={dropdownRef}>\n <button\n type=\"button\"\n onClick={() => setDropdownOpen(dropdownOpen === activeTab.id ? null : activeTab.id)}\n className=\"w-full flex items-center justify-between gap-2 px-3 py-1.5 text-sm bg-bg-sunken border border-border-default rounded-md text-left hover:border-action-primary-bg/50 focus:outline-none focus:ring-1 focus:ring-action-primary-bg transition-colors\"\n >\n <span\n className={`truncate ${activeTab.pluginName ? 'text-text-primary' : 'text-text-muted'}`}\n >\n {activeTab.pluginName\n ? `${activeTab.name} — ${activeTab.pluginName}`\n : 'Select a plugin to load...'}\n </span>\n <ChevronDown\n className={`size-3.5 text-text-muted shrink-0 transition-transform ${dropdownOpen === activeTab.id ? 'rotate-180' : ''}`}\n />\n </button>\n\n {/* Dropdown list */}\n {dropdownOpen === activeTab.id && (\n <div className=\"absolute z-50 top-full left-0 right-0 mt-1 bg-bg-surface border border-border-default rounded-md shadow-lg overflow-hidden max-h-64 overflow-y-auto\">\n {availablePlugins.length > 0 ? (\n availablePlugins.map((opt) => {\n const isSelected = activeTab.pluginName === opt.pluginName;\n return (\n <button\n type=\"button\"\n key={opt.pluginName}\n onClick={() => handleSelectPlugin(activeTab.id, opt.pluginName)}\n className={`w-full text-left px-3 py-2.5 hover:bg-bg-sunken transition-colors border-b border-border-subtle last:border-b-0 ${\n isSelected ? 'bg-action-primary-bg/10' : ''\n }`}\n >\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium text-text-primary\">\n {opt.displayName}\n </span>\n {opt.version && (\n <span className=\"text-xs text-text-muted\">\n {opt.version === 'latest' ? opt.version : `v${opt.version}`}\n </span>\n )}\n </div>\n {opt.description && (\n <p className=\"text-xs text-text-secondary mt-0.5\">{opt.description}</p>\n )}\n <p className=\"text-xs text-text-muted font-mono mt-0.5\">{opt.pluginName}</p>\n </button>\n );\n })\n ) : (\n <div className=\"px-3 py-4 text-center text-sm text-text-muted\">\n No UI plugins available\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* ── Content Area ───────────────────────────────────────────── */}\n <div\n className={`${panelHeight || ''} bg-bg-inverse text-text-inverse relative`}\n style={panelStyle}\n >\n {tabs.map((tab) => (\n <div\n key={tab.id}\n className={`absolute inset-0 ${tab.id === activeTab?.id ? 'block' : 'hidden'}`}\n >\n {!tab.pluginName ? (\n // Empty state — no plugin selected\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-sm\">\n <Monitor className=\"size-10 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm font-medium\">No plugin selected</p>\n <p className=\"text-text-secondary text-xs mt-1\">\n Use the dropdown above to pick a plugin UI to load in this tab.\n </p>\n <button\n type=\"button\"\n onClick={() => setDropdownOpen(tab.id)}\n className=\"mt-4 px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-muted transition-colors inline-flex items-center gap-2\"\n >\n <ChevronDown className=\"size-3.5\" />\n Select Plugin\n </button>\n </div>\n </div>\n ) : !tab.uiUrl ? (\n // No tunnel\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <AlertCircle className=\"size-8 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm\">Agent tunnel not active</p>\n <p className=\"text-text-secondary text-xs mt-1\">\n Cannot load plugin UI without an active tunnel connection.\n </p>\n </div>\n </div>\n ) : (\n // Iframe\n <iframe\n ref={(el) => {\n iframeRefs.current[tab.id] = el;\n }}\n src={tab.uiUrl}\n title={`Plugin UI — ${tab.name}`}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n onLoad={() => handleIframeLoad(tab.id)}\n onError={() => handleIframeError(tab.id)}\n />\n )}\n </div>\n ))}\n\n {/* Connection status overlay */}\n {activeTab && connectionStatus[activeTab.id] === 'connecting' && activeTab.uiUrl && (\n <div className=\"absolute bottom-3 right-3 flex items-center gap-2 px-3 py-1.5 bg-bg-overlay backdrop-blur-sm rounded-md text-xs text-status-warning-text pointer-events-none\">\n <Loader2 className=\"size-3 animate-spin\" />\n Connecting...\n </div>\n )}\n {activeTab && connectionStatus[activeTab.id] === 'error' && activeTab.uiUrl && (\n <div className=\"absolute bottom-3 right-3 flex items-center gap-2 px-3 py-1.5 bg-bg-overlay backdrop-blur-sm rounded-md text-xs text-status-error-text\">\n <WifiOff className=\"size-3\" />\n Disconnected\n <button\n type=\"button\"\n onClick={() => handleReload(activeTab.id)}\n className=\"underline hover:text-status-error-text/80 transition-colors\"\n >\n Reconnect\n </button>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}