@burdenoff/microfe-vibecontrols 2026.525.3 → 2026.525.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,13 @@
1
1
  import { Card as e } from "../shared/Card.js";
2
- import { useActiveAgentProfile as t } from "../../hooks/useActiveAgentProfile.js";
2
+ import { isUsableProfile as t, useActiveAgentProfile as n } from "../../hooks/useActiveAgentProfile.js";
3
3
  import "../shared/index.js";
4
- import { useCallback as n, useState as r } from "react";
5
- import { CheckCircle2 as i, Loader2 as a, Plus as o, Star as s, Trash2 as c, X as l } from "lucide-react";
6
- import { gql as u } from "@apollo/client";
7
- import { useMutation as d } from "@apollo/client/react";
8
- import { jsx as f, jsxs as p } from "react/jsx-runtime";
4
+ import { useCallback as r, useState as i } from "react";
5
+ import { CheckCircle2 as a, Loader2 as o, Plus as s, Star as c, Trash2 as l, X as u } from "lucide-react";
6
+ import { gql as d } from "@apollo/client";
7
+ import { useMutation as f } from "@apollo/client/react";
8
+ import { jsx as p, jsxs as m } from "react/jsx-runtime";
9
9
  //#region src/components/agent-manager/ProfilesPanel.tsx
10
- var m = u`
10
+ var h = d`
11
11
  mutation CreateVibecontrolsAgentProfile($input: CreateVibecontrolsAgentProfileInput!) {
12
12
  createVibecontrolsAgentProfile(input: $input) {
13
13
  id
@@ -16,11 +16,11 @@ var m = u`
16
16
  status
17
17
  }
18
18
  }
19
- `, h = u`
19
+ `, g = d`
20
20
  mutation DeleteVibecontrolsAgentProfile($id: ID!) {
21
21
  deleteVibecontrolsAgentProfile(id: $id)
22
22
  }
23
- `, g = u`
23
+ `, _ = d`
24
24
  mutation SetDefaultVibecontrolsAgentProfile($id: ID!) {
25
25
  setDefaultVibecontrolsAgentProfile(id: $id) {
26
26
  id
@@ -28,147 +28,147 @@ var m = u`
28
28
  }
29
29
  }
30
30
  `;
31
- function _(e) {
31
+ function v(e) {
32
32
  let t = (e ?? "").toLowerCase();
33
33
  return t === "running" || t === "active" || t === "healthy" ? "bg-status-success-text" : t === "stopped" || t === "inactive" ? "bg-text-placeholder" : t === "error" || t === "failed" ? "bg-status-error-text" : "bg-status-warning-text";
34
34
  }
35
- function v({ agentId: u }) {
36
- let { profiles: v, active: y, setActive: b, loading: x, refetch: S } = t(u), [C, w] = r(!1), [T, E] = r("default"), [D, O] = r(""), [k, A] = r(null), [j, { loading: M }] = d(m), [N, { loading: P }] = d(h), [F, { loading: I }] = d(g), L = n(async () => {
37
- A(null);
38
- let e = T.trim();
35
+ function y({ agentId: d }) {
36
+ let { profiles: y, active: b, setActive: x, loading: S, refetch: C } = n(d), [w, T] = i(!1), [E, D] = i("default"), [O, k] = i(""), [A, j] = i(null), [M, { loading: N }] = f(h), [P, { loading: F }] = f(g), [I, { loading: L }] = f(_), R = r(async () => {
37
+ j(null);
38
+ let e = E.trim();
39
39
  if (!e) {
40
- A("Profile name is required");
40
+ j("Profile name is required");
41
41
  return;
42
42
  }
43
- let t = D.trim() ? Number(D.trim()) : null;
44
- if (D.trim() && (Number.isNaN(t) || t === null || t <= 0)) {
45
- A("Port must be a positive number");
43
+ let t = O.trim() ? Number(O.trim()) : null;
44
+ if (O.trim() && (Number.isNaN(t) || t === null || t <= 0)) {
45
+ j("Port must be a positive number");
46
46
  return;
47
47
  }
48
48
  try {
49
- await j({ variables: { input: {
50
- agentId: u,
49
+ await M({ variables: { input: {
50
+ agentId: d,
51
51
  name: e,
52
52
  port: t
53
53
  } } });
54
54
  } catch (e) {
55
- A(e instanceof Error ? e.message : "Failed to create profile");
55
+ j(e instanceof Error ? e.message : "Failed to create profile");
56
56
  return;
57
57
  }
58
- w(!1), E("default"), O(""), await S();
58
+ T(!1), D("default"), k(""), await C();
59
59
  }, [
60
- u,
61
- j,
62
- T,
63
- D,
64
- S
65
- ]), R = n(async (e) => {
66
- if (A(null), window.confirm(`Delete profile "${e.name}"? This cannot be undone.`)) {
60
+ d,
61
+ M,
62
+ E,
63
+ O,
64
+ C
65
+ ]), z = r(async (e) => {
66
+ if (j(null), window.confirm(`Delete profile "${e.name}"? This cannot be undone.`)) {
67
67
  try {
68
- await N({ variables: { id: e.id } });
68
+ await P({ variables: { id: e.id } });
69
69
  } catch (e) {
70
- A(e instanceof Error ? e.message : "Failed to delete profile");
70
+ j(e instanceof Error ? e.message : "Failed to delete profile");
71
71
  return;
72
72
  }
73
- await S();
73
+ await C();
74
74
  }
75
- }, [N, S]), z = n(async (e) => {
76
- A(null);
75
+ }, [P, C]), B = r(async (e) => {
76
+ j(null);
77
77
  try {
78
- await F({ variables: { id: e.id } });
78
+ await I({ variables: { id: e.id } });
79
79
  } catch (e) {
80
- A(e instanceof Error ? e.message : "Failed to set default profile");
80
+ j(e instanceof Error ? e.message : "Failed to set default profile");
81
81
  return;
82
82
  }
83
- await S();
84
- }, [F, S]), B = M || P || I;
85
- return /* @__PURE__ */ p(e, { children: [
86
- /* @__PURE__ */ p("div", {
83
+ await C();
84
+ }, [I, C]), V = N || F || L;
85
+ return /* @__PURE__ */ m(e, { children: [
86
+ /* @__PURE__ */ m("div", {
87
87
  className: "p-4 border-b border-border-default flex items-center justify-between",
88
- children: [/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h3", {
88
+ children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("h3", {
89
89
  className: "font-semibold text-text-primary",
90
90
  children: "Profiles"
91
- }), /* @__PURE__ */ p("p", {
91
+ }), /* @__PURE__ */ m("p", {
92
92
  className: "text-xs text-text-secondary mt-0.5",
93
93
  children: [
94
94
  "Each profile runs an isolated CLI on this agent. Active:",
95
95
  " ",
96
- /* @__PURE__ */ f("code", {
96
+ /* @__PURE__ */ p("code", {
97
97
  className: "font-mono text-text-primary",
98
- children: y
98
+ children: b
99
99
  })
100
100
  ]
101
- })] }), /* @__PURE__ */ p("div", {
101
+ })] }), /* @__PURE__ */ m("div", {
102
102
  className: "flex items-center gap-2",
103
- children: [/* @__PURE__ */ f("button", {
103
+ children: [/* @__PURE__ */ p("button", {
104
104
  type: "button",
105
- onClick: () => void S(),
105
+ onClick: () => void C(),
106
106
  className: "px-2 py-1 text-xs text-text-secondary hover:text-text-primary",
107
107
  children: "Refresh"
108
- }), /* @__PURE__ */ p("button", {
108
+ }), /* @__PURE__ */ m("button", {
109
109
  type: "button",
110
- onClick: () => w((e) => !e),
110
+ onClick: () => T((e) => !e),
111
111
  className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:opacity-90",
112
- children: [/* @__PURE__ */ f(o, { className: "w-4 h-4" }), "New profile"]
112
+ children: [/* @__PURE__ */ p(s, { className: "w-4 h-4" }), "New profile"]
113
113
  })]
114
114
  })]
115
115
  }),
116
- k && /* @__PURE__ */ f("div", {
116
+ A && /* @__PURE__ */ p("div", {
117
117
  className: "px-4 py-2 bg-status-error-bg/30 border-b border-status-error-text/20 text-sm text-status-error-text",
118
- children: k
118
+ children: A
119
119
  }),
120
- C && /* @__PURE__ */ p("div", {
120
+ w && /* @__PURE__ */ m("div", {
121
121
  className: "p-4 border-b border-border-default bg-bg-sunken/40 space-y-3",
122
- children: [/* @__PURE__ */ p("div", {
122
+ children: [/* @__PURE__ */ m("div", {
123
123
  className: "grid grid-cols-2 gap-3",
124
- children: [/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
124
+ children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
125
125
  className: "block text-xs font-medium text-text-secondary mb-1",
126
126
  children: "Profile name"
127
- }), /* @__PURE__ */ f("input", {
127
+ }), /* @__PURE__ */ p("input", {
128
128
  type: "text",
129
- value: T,
130
- onChange: (e) => E(e.target.value),
129
+ value: E,
130
+ onChange: (e) => D(e.target.value),
131
131
  placeholder: "default",
132
132
  className: "w-full px-3 py-1.5 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono",
133
133
  autoFocus: !0
134
- })] }), /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
134
+ })] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
135
135
  className: "block text-xs font-medium text-text-secondary mb-1",
136
136
  children: "Port (optional)"
137
- }), /* @__PURE__ */ f("input", {
137
+ }), /* @__PURE__ */ p("input", {
138
138
  type: "number",
139
- value: D,
140
- onChange: (e) => O(e.target.value),
139
+ value: O,
140
+ onChange: (e) => k(e.target.value),
141
141
  placeholder: "auto",
142
142
  className: "w-full px-3 py-1.5 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono"
143
143
  })] })]
144
- }), /* @__PURE__ */ p("div", {
144
+ }), /* @__PURE__ */ m("div", {
145
145
  className: "flex items-center gap-2",
146
- children: [/* @__PURE__ */ p("button", {
146
+ children: [/* @__PURE__ */ m("button", {
147
147
  type: "button",
148
- disabled: M,
149
- onClick: () => void L(),
148
+ disabled: N,
149
+ onClick: () => void R(),
150
150
  className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:opacity-90 disabled:opacity-50",
151
- children: [M ? /* @__PURE__ */ f(a, { className: "w-3.5 h-3.5 animate-spin" }) : /* @__PURE__ */ f(i, { className: "w-3.5 h-3.5" }), "Create"]
152
- }), /* @__PURE__ */ p("button", {
151
+ children: [N ? /* @__PURE__ */ p(o, { className: "w-3.5 h-3.5 animate-spin" }) : /* @__PURE__ */ p(a, { className: "w-3.5 h-3.5" }), "Create"]
152
+ }), /* @__PURE__ */ m("button", {
153
153
  type: "button",
154
154
  onClick: () => {
155
- w(!1), A(null);
155
+ T(!1), j(null);
156
156
  },
157
157
  className: "inline-flex items-center gap-1 px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary",
158
- children: [/* @__PURE__ */ f(l, { className: "w-3.5 h-3.5" }), "Cancel"]
158
+ children: [/* @__PURE__ */ p(u, { className: "w-3.5 h-3.5" }), "Cancel"]
159
159
  })]
160
160
  })]
161
161
  }),
162
- x && v.length === 0 ? /* @__PURE__ */ p("div", {
162
+ S && y.length === 0 ? /* @__PURE__ */ m("div", {
163
163
  className: "p-6 text-center text-text-secondary",
164
- children: [/* @__PURE__ */ f(a, { className: "w-5 h-5 mx-auto mb-2 animate-spin" }), "Loading profiles…"]
165
- }) : v.length === 0 ? /* @__PURE__ */ p("div", {
164
+ children: [/* @__PURE__ */ p(o, { className: "w-5 h-5 mx-auto mb-2 animate-spin" }), "Loading profiles…"]
165
+ }) : y.length === 0 ? /* @__PURE__ */ m("div", {
166
166
  className: "p-6 text-center text-text-secondary",
167
- children: [/* @__PURE__ */ f("p", { children: "No profiles configured yet." }), /* @__PURE__ */ p("p", {
167
+ children: [/* @__PURE__ */ p("p", { children: "No profiles configured yet." }), /* @__PURE__ */ m("p", {
168
168
  className: "text-xs mt-1",
169
169
  children: [
170
170
  "The agent will fall back to the implicit ",
171
- /* @__PURE__ */ f("code", {
171
+ /* @__PURE__ */ p("code", {
172
172
  className: "font-mono",
173
173
  children: "default"
174
174
  }),
@@ -176,36 +176,36 @@ function v({ agentId: u }) {
176
176
  "profile until you create one."
177
177
  ]
178
178
  })]
179
- }) : /* @__PURE__ */ f("div", {
179
+ }) : /* @__PURE__ */ p("div", {
180
180
  className: "divide-y divide-border-default",
181
- children: v.map((e) => {
182
- let t = e.name === y;
183
- return /* @__PURE__ */ p("div", {
184
- className: `p-4 flex items-center justify-between gap-4 ${t ? "bg-action-primary-bg/5" : ""}`,
185
- children: [/* @__PURE__ */ p("div", {
181
+ children: y.map((e) => {
182
+ let n = e.name === b, r = t(e);
183
+ return /* @__PURE__ */ m("div", {
184
+ className: `p-4 flex items-center justify-between gap-4 ${n ? "bg-action-primary-bg/5" : ""}`,
185
+ children: [/* @__PURE__ */ m("div", {
186
186
  className: "flex items-center gap-3 min-w-0",
187
- children: [/* @__PURE__ */ f("span", {
188
- className: `h-2 w-2 rounded-full flex-shrink-0 ${_(e.status)}`,
187
+ children: [/* @__PURE__ */ p("span", {
188
+ className: `h-2 w-2 rounded-full flex-shrink-0 ${v(e.status)}`,
189
189
  "aria-hidden": !0
190
- }), /* @__PURE__ */ p("div", {
190
+ }), /* @__PURE__ */ m("div", {
191
191
  className: "min-w-0",
192
- children: [/* @__PURE__ */ p("div", {
192
+ children: [/* @__PURE__ */ m("div", {
193
193
  className: "flex items-center gap-2",
194
194
  children: [
195
- /* @__PURE__ */ f("code", {
195
+ /* @__PURE__ */ p("code", {
196
196
  className: "font-mono font-medium text-text-primary truncate",
197
197
  children: e.name
198
198
  }),
199
- e.isDefault && /* @__PURE__ */ p("span", {
199
+ e.isDefault && /* @__PURE__ */ m("span", {
200
200
  className: "inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10px] uppercase tracking-wide bg-action-primary-bg/10 text-action-primary-bg rounded",
201
- children: [/* @__PURE__ */ f(s, { className: "w-3 h-3" }), "Default"]
201
+ children: [/* @__PURE__ */ p(c, { className: "w-3 h-3" }), "Default"]
202
202
  }),
203
- t && /* @__PURE__ */ f("span", {
203
+ n && /* @__PURE__ */ p("span", {
204
204
  className: "px-1.5 py-0.5 text-[10px] uppercase tracking-wide bg-status-success-bg/40 text-status-success-text rounded",
205
205
  children: "Active"
206
206
  })
207
207
  ]
208
- }), /* @__PURE__ */ p("p", {
208
+ }), /* @__PURE__ */ m("p", {
209
209
  className: "text-xs text-text-secondary mt-0.5 truncate",
210
210
  children: [
211
211
  e.status ?? "unknown",
@@ -214,29 +214,31 @@ function v({ agentId: u }) {
214
214
  ]
215
215
  })]
216
216
  })]
217
- }), /* @__PURE__ */ p("div", {
217
+ }), /* @__PURE__ */ m("div", {
218
218
  className: "flex items-center gap-2 flex-shrink-0",
219
219
  children: [
220
- !t && /* @__PURE__ */ f("button", {
220
+ !n && /* @__PURE__ */ p("button", {
221
221
  type: "button",
222
- onClick: () => b(e.name),
223
- className: "px-2 py-1 text-xs text-text-secondary hover:text-text-primary border border-border-default rounded",
222
+ disabled: !r,
223
+ title: r ? "Make this the active profile" : "This profile hasn't been started yet (awaiting config), so it can't be the active profile.",
224
+ onClick: () => x(e.name),
225
+ className: "px-2 py-1 text-xs text-text-secondary hover:text-text-primary border border-border-default rounded disabled:opacity-50 disabled:cursor-not-allowed",
224
226
  children: "Use"
225
227
  }),
226
- !e.isDefault && /* @__PURE__ */ p("button", {
228
+ !e.isDefault && /* @__PURE__ */ m("button", {
227
229
  type: "button",
228
- disabled: B,
229
- onClick: () => void z(e),
230
+ disabled: V,
231
+ onClick: () => void B(e),
230
232
  className: "inline-flex items-center gap-1 px-2 py-1 text-xs text-text-secondary hover:text-text-primary border border-border-default rounded disabled:opacity-50",
231
- children: [/* @__PURE__ */ f(s, { className: "w-3 h-3" }), "Set default"]
233
+ children: [/* @__PURE__ */ p(c, { className: "w-3 h-3" }), "Set default"]
232
234
  }),
233
- /* @__PURE__ */ p("button", {
235
+ /* @__PURE__ */ m("button", {
234
236
  type: "button",
235
- disabled: B || e.isDefault,
237
+ disabled: V || e.isDefault,
236
238
  title: e.isDefault ? "Cannot delete the default profile" : "Delete",
237
- onClick: () => void R(e),
239
+ onClick: () => void z(e),
238
240
  className: "inline-flex items-center gap-1 px-2 py-1 text-xs text-status-error-text hover:bg-status-error-bg/30 border border-status-error-text/20 rounded disabled:opacity-50",
239
- children: [/* @__PURE__ */ f(c, { className: "w-3 h-3" }), "Delete"]
241
+ children: [/* @__PURE__ */ p(l, { className: "w-3 h-3" }), "Delete"]
240
242
  })
241
243
  ]
242
244
  })]
@@ -246,6 +248,6 @@ function v({ agentId: u }) {
246
248
  ] });
247
249
  }
248
250
  //#endregion
249
- export { v as ProfilesPanel };
251
+ export { y as ProfilesPanel };
250
252
 
251
253
  //# sourceMappingURL=ProfilesPanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProfilesPanel.js","names":[],"sources":["../../../src/components/agent-manager/ProfilesPanel.tsx"],"sourcesContent":["/**\n * ProfilesPanel — Phase 5B (multi-profile-per-daemon).\n *\n * Mounted on AgentDetailsPage as the \"Profiles\" tab. Lists every\n * `AgentProfile` row for the current agent and exposes full CRUDL:\n * - List with status indicators\n * - Create (autofilled name \"default\" but editable)\n * - Delete\n * - Set-default\n *\n * Apollo `gql` documents are inlined so the component compiles cleanly\n * before Phase 5A (svc) publishes the AgentProfile schema to Hive.\n */\n\nimport { useCallback, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { CheckCircle2, Loader2, Plus, Star, Trash2, X } from 'lucide-react';\nimport { Card } from '@/components/shared';\nimport { useActiveAgentProfile, type AgentProfile } from '@/hooks/useActiveAgentProfile';\n\ninterface ProfilesPanelProps {\n agentId: string;\n}\n\ninterface CreateAgentProfileInput {\n agentId: string;\n name: string;\n port?: number | null;\n}\n\ninterface CreateProfileData {\n createVibecontrolsAgentProfile: Pick<AgentProfile, 'id' | 'name' | 'isDefault' | 'status'>;\n}\n\ninterface CreateProfileVars {\n input: CreateAgentProfileInput;\n}\n\ninterface DeleteProfileData {\n deleteVibecontrolsAgentProfile: boolean;\n}\n\ninterface DeleteProfileVars {\n id: string;\n}\n\ninterface SetDefaultProfileData {\n setDefaultVibecontrolsAgentProfile: Pick<AgentProfile, 'id' | 'isDefault'>;\n}\n\ninterface SetDefaultProfileVars {\n id: string;\n}\n\nconst CREATE_PROFILE_MUTATION = gql`\n mutation CreateVibecontrolsAgentProfile($input: CreateVibecontrolsAgentProfileInput!) {\n createVibecontrolsAgentProfile(input: $input) {\n id\n name\n isDefault\n status\n }\n }\n`;\n\nconst DELETE_PROFILE_MUTATION = gql`\n mutation DeleteVibecontrolsAgentProfile($id: ID!) {\n deleteVibecontrolsAgentProfile(id: $id)\n }\n`;\n\nconst SET_DEFAULT_PROFILE_MUTATION = gql`\n mutation SetDefaultVibecontrolsAgentProfile($id: ID!) {\n setDefaultVibecontrolsAgentProfile(id: $id) {\n id\n isDefault\n }\n }\n`;\n\nfunction statusDot(status: string | null): string {\n const s = (status ?? '').toLowerCase();\n if (s === 'running' || s === 'active' || s === 'healthy') return 'bg-status-success-text';\n if (s === 'stopped' || s === 'inactive') return 'bg-text-placeholder';\n if (s === 'error' || s === 'failed') return 'bg-status-error-text';\n return 'bg-status-warning-text';\n}\n\nexport function ProfilesPanel({ agentId }: ProfilesPanelProps) {\n const { profiles, active, setActive, loading, refetch } = useActiveAgentProfile(agentId);\n\n const [showCreate, setShowCreate] = useState(false);\n const [newName, setNewName] = useState('default');\n const [newPort, setNewPort] = useState<string>('');\n const [actionError, setActionError] = useState<string | null>(null);\n\n const [createProfile, { loading: creating }] = useMutation<CreateProfileData, CreateProfileVars>(\n CREATE_PROFILE_MUTATION\n );\n\n const [deleteProfile, { loading: deleting }] = useMutation<DeleteProfileData, DeleteProfileVars>(\n DELETE_PROFILE_MUTATION\n );\n\n const [setDefaultProfile, { loading: settingDefault }] = useMutation<\n SetDefaultProfileData,\n SetDefaultProfileVars\n >(SET_DEFAULT_PROFILE_MUTATION);\n\n const handleCreate = useCallback(async () => {\n setActionError(null);\n const trimmedName = newName.trim();\n if (!trimmedName) {\n setActionError('Profile name is required');\n return;\n }\n const portNum = newPort.trim() ? Number(newPort.trim()) : null;\n if (newPort.trim() && (Number.isNaN(portNum) || portNum === null || portNum <= 0)) {\n setActionError('Port must be a positive number');\n return;\n }\n try {\n await createProfile({\n variables: {\n input: { agentId, name: trimmedName, port: portNum },\n },\n });\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Failed to create profile');\n return;\n }\n setShowCreate(false);\n setNewName('default');\n setNewPort('');\n // The mutation already succeeded — refresh the list off the same observable\n // the panel renders from so the new profile shows without a manual\n // \"Refresh\". refetch() is non-throwing, so a refresh hiccup can never\n // surface as a \"Failed to create\" error; the list re-syncs on the next action.\n await refetch();\n }, [agentId, createProfile, newName, newPort, refetch]);\n\n const handleDelete = useCallback(\n async (profile: AgentProfile) => {\n setActionError(null);\n if (!window.confirm(`Delete profile \"${profile.name}\"? This cannot be undone.`)) {\n return;\n }\n try {\n await deleteProfile({ variables: { id: profile.id } });\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Failed to delete profile');\n return;\n }\n // Refresh after a confirmed delete; refetch() is non-throwing so a\n // network hiccup can't masquerade as a failed delete (see handleCreate).\n await refetch();\n },\n [deleteProfile, refetch]\n );\n\n const handleSetDefault = useCallback(\n async (profile: AgentProfile) => {\n setActionError(null);\n try {\n await setDefaultProfile({ variables: { id: profile.id } });\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Failed to set default profile');\n return;\n }\n // Refresh after a confirmed change; refetch() is non-throwing so a\n // network hiccup can't masquerade as a failed update (see handleCreate).\n await refetch();\n },\n [setDefaultProfile, refetch]\n );\n\n const busy = creating || deleting || settingDefault;\n\n return (\n <Card>\n <div className=\"p-4 border-b border-border-default flex items-center justify-between\">\n <div>\n <h3 className=\"font-semibold text-text-primary\">Profiles</h3>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Each profile runs an isolated CLI on this agent. Active:{' '}\n <code className=\"font-mono text-text-primary\">{active}</code>\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n // Fire-and-forget from a click handler: refetch() is non-throwing,\n // so ignoring the returned promise can't leak an unhandled rejection.\n onClick={() => void refetch()}\n className=\"px-2 py-1 text-xs text-text-secondary hover:text-text-primary\"\n >\n Refresh\n </button>\n <button\n type=\"button\"\n onClick={() => setShowCreate((v) => !v)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:opacity-90\"\n >\n <Plus className=\"w-4 h-4\" />\n New profile\n </button>\n </div>\n </div>\n\n {actionError && (\n <div className=\"px-4 py-2 bg-status-error-bg/30 border-b border-status-error-text/20 text-sm text-status-error-text\">\n {actionError}\n </div>\n )}\n\n {showCreate && (\n <div className=\"p-4 border-b border-border-default bg-bg-sunken/40 space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">\n Profile name\n </label>\n <input\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n placeholder=\"default\"\n className=\"w-full px-3 py-1.5 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono\"\n autoFocus\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">\n Port (optional)\n </label>\n <input\n type=\"number\"\n value={newPort}\n onChange={(e) => setNewPort(e.target.value)}\n placeholder=\"auto\"\n className=\"w-full px-3 py-1.5 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono\"\n />\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n disabled={creating}\n onClick={() => void handleCreate()}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:opacity-90 disabled:opacity-50\"\n >\n {creating ? (\n <Loader2 className=\"w-3.5 h-3.5 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"w-3.5 h-3.5\" />\n )}\n Create\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setShowCreate(false);\n setActionError(null);\n }}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary\"\n >\n <X className=\"w-3.5 h-3.5\" />\n Cancel\n </button>\n </div>\n </div>\n )}\n\n {loading && profiles.length === 0 ? (\n <div className=\"p-6 text-center text-text-secondary\">\n <Loader2 className=\"w-5 h-5 mx-auto mb-2 animate-spin\" />\n Loading profiles…\n </div>\n ) : profiles.length === 0 ? (\n <div className=\"p-6 text-center text-text-secondary\">\n <p>No profiles configured yet.</p>\n <p className=\"text-xs mt-1\">\n The agent will fall back to the implicit <code className=\"font-mono\">default</code>{' '}\n profile until you create one.\n </p>\n </div>\n ) : (\n <div className=\"divide-y divide-border-default\">\n {profiles.map((profile) => {\n const isActive = profile.name === active;\n return (\n <div\n key={profile.id}\n className={`p-4 flex items-center justify-between gap-4 ${\n isActive ? 'bg-action-primary-bg/5' : ''\n }`}\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <span\n className={`h-2 w-2 rounded-full flex-shrink-0 ${statusDot(profile.status)}`}\n aria-hidden\n />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <code className=\"font-mono font-medium text-text-primary truncate\">\n {profile.name}\n </code>\n {profile.isDefault && (\n <span className=\"inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10px] uppercase tracking-wide bg-action-primary-bg/10 text-action-primary-bg rounded\">\n <Star className=\"w-3 h-3\" />\n Default\n </span>\n )}\n {isActive && (\n <span className=\"px-1.5 py-0.5 text-[10px] uppercase tracking-wide bg-status-success-bg/40 text-status-success-text rounded\">\n Active\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-secondary mt-0.5 truncate\">\n {profile.status ?? 'unknown'}\n {profile.port ? ` · port ${profile.port}` : ''}\n {profile.tunnelUrl ? ` · ${profile.tunnelUrl}` : ''}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n {!isActive && (\n <button\n type=\"button\"\n onClick={() => setActive(profile.name)}\n className=\"px-2 py-1 text-xs text-text-secondary hover:text-text-primary border border-border-default rounded\"\n >\n Use\n </button>\n )}\n {!profile.isDefault && (\n <button\n type=\"button\"\n disabled={busy}\n onClick={() => void handleSetDefault(profile)}\n className=\"inline-flex items-center gap-1 px-2 py-1 text-xs text-text-secondary hover:text-text-primary border border-border-default rounded disabled:opacity-50\"\n >\n <Star className=\"w-3 h-3\" />\n Set default\n </button>\n )}\n <button\n type=\"button\"\n disabled={busy || profile.isDefault}\n title={profile.isDefault ? 'Cannot delete the default profile' : 'Delete'}\n onClick={() => void handleDelete(profile)}\n className=\"inline-flex items-center gap-1 px-2 py-1 text-xs text-status-error-text hover:bg-status-error-bg/30 border border-status-error-text/20 rounded disabled:opacity-50\"\n >\n <Trash2 className=\"w-3 h-3\" />\n Delete\n </button>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </Card>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProfilesPanel.js","names":[],"sources":["../../../src/components/agent-manager/ProfilesPanel.tsx"],"sourcesContent":["/**\n * ProfilesPanel — Phase 5B (multi-profile-per-daemon).\n *\n * Mounted on AgentDetailsPage as the \"Profiles\" tab. Lists every\n * `AgentProfile` row for the current agent and exposes full CRUDL:\n * - List with status indicators\n * - Create (autofilled name \"default\" but editable)\n * - Delete\n * - Set-default\n *\n * Apollo `gql` documents are inlined so the component compiles cleanly\n * before Phase 5A (svc) publishes the AgentProfile schema to Hive.\n */\n\nimport { useCallback, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { CheckCircle2, Loader2, Plus, Star, Trash2, X } from 'lucide-react';\nimport { Card } from '@/components/shared';\nimport {\n useActiveAgentProfile,\n isUsableProfile,\n type AgentProfile,\n} from '@/hooks/useActiveAgentProfile';\n\ninterface ProfilesPanelProps {\n agentId: string;\n}\n\ninterface CreateAgentProfileInput {\n agentId: string;\n name: string;\n port?: number | null;\n}\n\ninterface CreateProfileData {\n createVibecontrolsAgentProfile: Pick<AgentProfile, 'id' | 'name' | 'isDefault' | 'status'>;\n}\n\ninterface CreateProfileVars {\n input: CreateAgentProfileInput;\n}\n\ninterface DeleteProfileData {\n deleteVibecontrolsAgentProfile: boolean;\n}\n\ninterface DeleteProfileVars {\n id: string;\n}\n\ninterface SetDefaultProfileData {\n setDefaultVibecontrolsAgentProfile: Pick<AgentProfile, 'id' | 'isDefault'>;\n}\n\ninterface SetDefaultProfileVars {\n id: string;\n}\n\nconst CREATE_PROFILE_MUTATION = gql`\n mutation CreateVibecontrolsAgentProfile($input: CreateVibecontrolsAgentProfileInput!) {\n createVibecontrolsAgentProfile(input: $input) {\n id\n name\n isDefault\n status\n }\n }\n`;\n\nconst DELETE_PROFILE_MUTATION = gql`\n mutation DeleteVibecontrolsAgentProfile($id: ID!) {\n deleteVibecontrolsAgentProfile(id: $id)\n }\n`;\n\nconst SET_DEFAULT_PROFILE_MUTATION = gql`\n mutation SetDefaultVibecontrolsAgentProfile($id: ID!) {\n setDefaultVibecontrolsAgentProfile(id: $id) {\n id\n isDefault\n }\n }\n`;\n\nfunction statusDot(status: string | null): string {\n const s = (status ?? '').toLowerCase();\n if (s === 'running' || s === 'active' || s === 'healthy') return 'bg-status-success-text';\n if (s === 'stopped' || s === 'inactive') return 'bg-text-placeholder';\n if (s === 'error' || s === 'failed') return 'bg-status-error-text';\n return 'bg-status-warning-text';\n}\n\nexport function ProfilesPanel({ agentId }: ProfilesPanelProps) {\n const { profiles, active, setActive, loading, refetch } = useActiveAgentProfile(agentId);\n\n const [showCreate, setShowCreate] = useState(false);\n const [newName, setNewName] = useState('default');\n const [newPort, setNewPort] = useState<string>('');\n const [actionError, setActionError] = useState<string | null>(null);\n\n const [createProfile, { loading: creating }] = useMutation<CreateProfileData, CreateProfileVars>(\n CREATE_PROFILE_MUTATION\n );\n\n const [deleteProfile, { loading: deleting }] = useMutation<DeleteProfileData, DeleteProfileVars>(\n DELETE_PROFILE_MUTATION\n );\n\n const [setDefaultProfile, { loading: settingDefault }] = useMutation<\n SetDefaultProfileData,\n SetDefaultProfileVars\n >(SET_DEFAULT_PROFILE_MUTATION);\n\n const handleCreate = useCallback(async () => {\n setActionError(null);\n const trimmedName = newName.trim();\n if (!trimmedName) {\n setActionError('Profile name is required');\n return;\n }\n const portNum = newPort.trim() ? Number(newPort.trim()) : null;\n if (newPort.trim() && (Number.isNaN(portNum) || portNum === null || portNum <= 0)) {\n setActionError('Port must be a positive number');\n return;\n }\n try {\n await createProfile({\n variables: {\n input: { agentId, name: trimmedName, port: portNum },\n },\n });\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Failed to create profile');\n return;\n }\n setShowCreate(false);\n setNewName('default');\n setNewPort('');\n // The mutation already succeeded — refresh the list off the same observable\n // the panel renders from so the new profile shows without a manual\n // \"Refresh\". refetch() is non-throwing, so a refresh hiccup can never\n // surface as a \"Failed to create\" error; the list re-syncs on the next action.\n await refetch();\n }, [agentId, createProfile, newName, newPort, refetch]);\n\n const handleDelete = useCallback(\n async (profile: AgentProfile) => {\n setActionError(null);\n if (!window.confirm(`Delete profile \"${profile.name}\"? This cannot be undone.`)) {\n return;\n }\n try {\n await deleteProfile({ variables: { id: profile.id } });\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Failed to delete profile');\n return;\n }\n // Refresh after a confirmed delete; refetch() is non-throwing so a\n // network hiccup can't masquerade as a failed delete (see handleCreate).\n await refetch();\n },\n [deleteProfile, refetch]\n );\n\n const handleSetDefault = useCallback(\n async (profile: AgentProfile) => {\n setActionError(null);\n try {\n await setDefaultProfile({ variables: { id: profile.id } });\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Failed to set default profile');\n return;\n }\n // Refresh after a confirmed change; refetch() is non-throwing so a\n // network hiccup can't masquerade as a failed update (see handleCreate).\n await refetch();\n },\n [setDefaultProfile, refetch]\n );\n\n const busy = creating || deleting || settingDefault;\n\n return (\n <Card>\n <div className=\"p-4 border-b border-border-default flex items-center justify-between\">\n <div>\n <h3 className=\"font-semibold text-text-primary\">Profiles</h3>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Each profile runs an isolated CLI on this agent. Active:{' '}\n <code className=\"font-mono text-text-primary\">{active}</code>\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n // Fire-and-forget from a click handler: refetch() is non-throwing,\n // so ignoring the returned promise can't leak an unhandled rejection.\n onClick={() => void refetch()}\n className=\"px-2 py-1 text-xs text-text-secondary hover:text-text-primary\"\n >\n Refresh\n </button>\n <button\n type=\"button\"\n onClick={() => setShowCreate((v) => !v)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:opacity-90\"\n >\n <Plus className=\"w-4 h-4\" />\n New profile\n </button>\n </div>\n </div>\n\n {actionError && (\n <div className=\"px-4 py-2 bg-status-error-bg/30 border-b border-status-error-text/20 text-sm text-status-error-text\">\n {actionError}\n </div>\n )}\n\n {showCreate && (\n <div className=\"p-4 border-b border-border-default bg-bg-sunken/40 space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">\n Profile name\n </label>\n <input\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n placeholder=\"default\"\n className=\"w-full px-3 py-1.5 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono\"\n autoFocus\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">\n Port (optional)\n </label>\n <input\n type=\"number\"\n value={newPort}\n onChange={(e) => setNewPort(e.target.value)}\n placeholder=\"auto\"\n className=\"w-full px-3 py-1.5 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono\"\n />\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n disabled={creating}\n onClick={() => void handleCreate()}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:opacity-90 disabled:opacity-50\"\n >\n {creating ? (\n <Loader2 className=\"w-3.5 h-3.5 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"w-3.5 h-3.5\" />\n )}\n Create\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setShowCreate(false);\n setActionError(null);\n }}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary\"\n >\n <X className=\"w-3.5 h-3.5\" />\n Cancel\n </button>\n </div>\n </div>\n )}\n\n {loading && profiles.length === 0 ? (\n <div className=\"p-6 text-center text-text-secondary\">\n <Loader2 className=\"w-5 h-5 mx-auto mb-2 animate-spin\" />\n Loading profiles…\n </div>\n ) : profiles.length === 0 ? (\n <div className=\"p-6 text-center text-text-secondary\">\n <p>No profiles configured yet.</p>\n <p className=\"text-xs mt-1\">\n The agent will fall back to the implicit <code className=\"font-mono\">default</code>{' '}\n profile until you create one.\n </p>\n </div>\n ) : (\n <div className=\"divide-y divide-border-default\">\n {profiles.map((profile) => {\n const isActive = profile.name === active;\n // A never-started (awaiting-config) profile can't be the active\n // one — selecting it would just bounce back to a usable profile.\n // Disable \"Use\" and explain why instead of silently reverting.\n const usable = isUsableProfile(profile);\n return (\n <div\n key={profile.id}\n className={`p-4 flex items-center justify-between gap-4 ${\n isActive ? 'bg-action-primary-bg/5' : ''\n }`}\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <span\n className={`h-2 w-2 rounded-full flex-shrink-0 ${statusDot(profile.status)}`}\n aria-hidden\n />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <code className=\"font-mono font-medium text-text-primary truncate\">\n {profile.name}\n </code>\n {profile.isDefault && (\n <span className=\"inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10px] uppercase tracking-wide bg-action-primary-bg/10 text-action-primary-bg rounded\">\n <Star className=\"w-3 h-3\" />\n Default\n </span>\n )}\n {isActive && (\n <span className=\"px-1.5 py-0.5 text-[10px] uppercase tracking-wide bg-status-success-bg/40 text-status-success-text rounded\">\n Active\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-secondary mt-0.5 truncate\">\n {profile.status ?? 'unknown'}\n {profile.port ? ` · port ${profile.port}` : ''}\n {profile.tunnelUrl ? ` · ${profile.tunnelUrl}` : ''}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n {!isActive && (\n <button\n type=\"button\"\n disabled={!usable}\n title={\n usable\n ? 'Make this the active profile'\n : \"This profile hasn't been started yet (awaiting config), so it can't be the active profile.\"\n }\n onClick={() => setActive(profile.name)}\n className=\"px-2 py-1 text-xs text-text-secondary hover:text-text-primary border border-border-default rounded disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n Use\n </button>\n )}\n {!profile.isDefault && (\n <button\n type=\"button\"\n disabled={busy}\n onClick={() => void handleSetDefault(profile)}\n className=\"inline-flex items-center gap-1 px-2 py-1 text-xs text-text-secondary hover:text-text-primary border border-border-default rounded disabled:opacity-50\"\n >\n <Star className=\"w-3 h-3\" />\n Set default\n </button>\n )}\n <button\n type=\"button\"\n disabled={busy || profile.isDefault}\n title={profile.isDefault ? 'Cannot delete the default profile' : 'Delete'}\n onClick={() => void handleDelete(profile)}\n className=\"inline-flex items-center gap-1 px-2 py-1 text-xs text-status-error-text hover:bg-status-error-bg/30 border border-status-error-text/20 rounded disabled:opacity-50\"\n >\n <Trash2 className=\"w-3 h-3\" />\n Delete\n </button>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </Card>\n );\n}\n"],"mappings":"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"}
@@ -39,23 +39,29 @@ function m({ tunnelUrl: u, agentApiKey: d, profile: m, workspacePath: h, mode: g
39
39
  C(null);
40
40
  try {
41
41
  x("checking");
42
- let e = await l(u, f(m, "/status"), d);
43
- if (e.status === 404) throw Error(y("vibecontrols.agents.editor.notInstalled", "Code-server isn't available on this agent. Install the code-server plugin (Plugins tab) to enable Open Editor."));
44
- if (!e.ok) throw Error(`Status check failed: ${e.status}`);
45
- let t = await e.json();
46
- if (!t.installed && !t.installing) {
42
+ let e = m === "default" ? ["default"] : [m, "default"], t = null, n = null;
43
+ for (let r of e) {
44
+ let i = r === e[e.length - 1], a = await l(u, f(r, "/status"), d);
45
+ if (a.status !== 404 && !(a.status >= 500 && !i)) {
46
+ if (!a.ok) throw Error(`Status check failed: ${a.status}`);
47
+ t = r, n = await a.json();
48
+ break;
49
+ }
50
+ }
51
+ if (!t || !n) throw Error(y("vibecontrols.agents.editor.notInstalled", "Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor."));
52
+ if (!n.installed && !n.installing) {
47
53
  x("installing");
48
- let e = await l(u, f(m, "/install"), d, { method: "POST" });
54
+ let e = await l(u, f(t, "/install"), d, { method: "POST" });
49
55
  if (!e.ok) throw Error(`Install failed: ${e.status}`);
50
- await p(u, d, m, (e) => e.installed, (e) => e.error || null);
51
- } else t.installing && (x("installing"), await p(u, d, m, (e) => e.installed, (e) => e.error || null));
52
- if (t.running) h && t.running && (x("starting"), await l(u, f(m, "/restart"), d, {
56
+ n = await p(u, d, t, (e) => e.installed, (e) => e.error || null);
57
+ } else n.installing && (x("installing"), n = await p(u, d, t, (e) => e.installed, (e) => e.error || null));
58
+ if (n.running) h && n.running && (x("starting"), await l(u, f(t, "/restart"), d, {
53
59
  method: "POST",
54
60
  body: JSON.stringify({ workspacePath: h })
55
61
  }), await new Promise((e) => setTimeout(e, 1e3)));
56
62
  else {
57
63
  x("starting");
58
- let e = await l(u, f(m, "/start"), d, {
64
+ let e = await l(u, f(t, "/start"), d, {
59
65
  method: "POST",
60
66
  ...h ? { body: JSON.stringify({ workspacePath: h }) } : {}
61
67
  });
@@ -66,8 +72,8 @@ function m({ tunnelUrl: u, agentApiKey: d, profile: m, workspacePath: h, mode: g
66
72
  await new Promise((e) => setTimeout(e, 1e3));
67
73
  }
68
74
  x("ready");
69
- let n = `${u.replace(/\/$/, "")}/code-server/?apiKey=${encodeURIComponent(d)}`;
70
- g === "iframe" && _ ? _(n) : window.open(n, "_blank"), setTimeout(() => x("idle"), 2e3);
75
+ let r = `${u.replace(/\/$/, "")}/code-server/?apiKey=${encodeURIComponent(d)}`;
76
+ g === "iframe" && _ ? _(r) : window.open(r, "_blank"), setTimeout(() => x("idle"), 2e3);
71
77
  } catch (e) {
72
78
  C(e instanceof Error ? e.message : "Failed to open editor"), x("error");
73
79
  }
@@ -110,8 +116,12 @@ function m({ tunnelUrl: u, agentApiKey: d, profile: m, workspacePath: h, mode: g
110
116
  children: E
111
117
  })]
112
118
  }), b === "error" && S && /* @__PURE__ */ s("div", {
113
- 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-[250px]",
114
- children: S
119
+ role: "alert",
120
+ 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",
121
+ children: /* @__PURE__ */ c("div", {
122
+ className: "flex items-start gap-2",
123
+ children: [/* @__PURE__ */ s(r, { className: "mt-0.5 h-4 w-4 shrink-0" }), /* @__PURE__ */ s("span", { children: S })]
124
+ })
115
125
  })]
116
126
  });
117
127
  }
@@ -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';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\ntype EditorState = 'idle' | 'checking' | 'installing' | 'starting' | 'ready' | 'error';\n\nexport interface OpenInBrowserEditorButtonProps {\n /** Agent tunnel URL (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /** Agent API key for authentication */\n agentApiKey: 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\n// ── Helpers ────────────────────────────────────────────────────────────\n\nasync function agentFetch(\n tunnelUrl: string,\n path: string,\n apiKey: string,\n options?: RequestInit\n): Promise<Response> {\n const base = tunnelUrl.replace(/\\/$/, '');\n // Only advertise a JSON body when we actually send one. The agent's\n // `/api/*` delegation consumes the request body once; a POST that carries a\n // Content-Type but an (already-read) body fails on the agent with\n // \"ReadableStream has already been used\". No body → no Content-Type → safe.\n const hasBody = options?.body != null;\n return fetch(`${base}${path}`, {\n ...options,\n headers: {\n ...(hasBody ? { 'Content-Type': 'application/json' } : {}),\n 'x-agent-api-key': apiKey,\n ...options?.headers,\n },\n });\n}\n\ninterface CodeServerStatus {\n installed: boolean;\n installing: boolean;\n running: boolean;\n port?: number;\n error?: string;\n}\n\nconst POLL_INTERVAL_MS = 2000;\nconst POLL_TIMEOUT_MS = 120_000; // 2 minutes max\n\n/**\n * Build a profile-scoped agent endpoint path. The agent moved every\n * data-plane endpoint under `/api/profiles/<profile>/…` and now returns\n * 410 Gone for bare `/api/code-server/*` paths — hitting the old paths\n * surfaced in the browser as a misleading CORS error (the 410 response\n * omits Access-Control-Allow-Origin).\n */\nfunction codeServerPath(profile: string, sub: string): string {\n return `/api/profiles/${encodeURIComponent(profile)}/code-server${sub}`;\n}\n\n/**\n * Poll until a condition is met on the status response.\n */\nasync function pollUntil(\n tunnelUrl: string,\n apiKey: string,\n profile: string,\n predicate: (status: CodeServerStatus) => boolean,\n errorCheck: (status: CodeServerStatus) => string | null\n): Promise<CodeServerStatus> {\n const deadline = Date.now() + POLL_TIMEOUT_MS;\n\n while (Date.now() < deadline) {\n const res = await agentFetch(tunnelUrl, codeServerPath(profile, '/status'), apiKey);\n if (!res.ok) throw new Error(`Status check failed: ${res.status}`);\n const status: CodeServerStatus = await res.json();\n\n const err = errorCheck(status);\n if (err) throw new Error(err);\n\n if (predicate(status)) return status;\n\n await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));\n }\n\n throw new Error('Operation timed out');\n}\n\n// ── Component ──────────────────────────────────────────────────────────\n\nexport function OpenInBrowserEditorButton({\n tunnelUrl,\n agentApiKey,\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 handleClick = useCallback(async () => {\n if (!tunnelUrl || !agentApiKey) {\n setErrorMsg('Agent tunnel or API key not available');\n setState('error');\n return;\n }\n\n setErrorMsg(null);\n\n try {\n // Step 1: Check status\n setState('checking');\n const res = await agentFetch(tunnelUrl, codeServerPath(profile, '/status'), agentApiKey);\n if (res.status === 404) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.notInstalled',\n \"Code-server isn't available on this agent. Install the code-server plugin (Plugins tab) to enable Open Editor.\"\n )\n );\n }\n if (!res.ok) throw new Error(`Status check failed: ${res.status}`);\n const status: CodeServerStatus = await res.json();\n\n // Step 2: Install if needed\n if (!status.installed && !status.installing) {\n setState('installing');\n const installRes = await agentFetch(\n tunnelUrl,\n codeServerPath(profile, '/install'),\n agentApiKey,\n {\n method: 'POST',\n }\n );\n if (!installRes.ok) throw new Error(`Install failed: ${installRes.status}`);\n\n // Poll until installed\n await pollUntil(\n tunnelUrl,\n agentApiKey,\n profile,\n (s) => s.installed,\n (s) => s.error || null\n );\n } else if (status.installing) {\n setState('installing');\n await pollUntil(\n tunnelUrl,\n agentApiKey,\n profile,\n (s) => s.installed,\n (s) => s.error || null\n );\n }\n\n // Step 3: Start if not running\n if (!status.running) {\n setState('starting');\n const startRes = await agentFetch(\n tunnelUrl,\n codeServerPath(profile, '/start'),\n agentApiKey,\n {\n method: 'POST',\n ...(workspacePath ? { body: JSON.stringify({ workspacePath }) } : {}),\n }\n );\n if (!startRes.ok) {\n const err = await startRes.json().catch(() => ({}));\n throw new Error((err as { error?: string }).error || `Start failed: ${startRes.status}`);\n }\n\n // Brief wait for code-server to be ready\n await new Promise((r) => setTimeout(r, 1000));\n } else if (workspacePath && status.running) {\n // Restart with new workspace path if different\n setState('starting');\n // workspacePath is guaranteed truthy by the `else if` guard above.\n await agentFetch(tunnelUrl, codeServerPath(profile, '/restart'), agentApiKey, {\n method: 'POST',\n body: JSON.stringify({ workspacePath }),\n });\n await new Promise((r) => setTimeout(r, 1000));\n }\n\n // Step 4: Open the editor\n setState('ready');\n const base = tunnelUrl.replace(/\\/$/, '');\n const editorUrl = `${base}/code-server/?apiKey=${encodeURIComponent(agentApiKey)}`;\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 }, [tunnelUrl, agentApiKey, profile, workspacePath, mode, onIframeUrl, tr]);\n\n const isDisabled =\n !tunnelUrl || !agentApiKey || (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 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={`w-3.5 h-3.5 sm:w-4 sm:h-4 ${isSpinning ? 'animate-spin' : ''}`} />\n <span className=\"hidden sm:inline\">{label}</span>\n </button>\n\n {/* Error tooltip */}\n {state === 'error' && errorMsg && (\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-[250px]\">\n {errorMsg}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;AA8BA,eAAe,EACb,GACA,GACA,GACA,GACmB;CACnB,IAAM,IAAO,EAAU,QAAQ,OAAO,GAAG,EAKnC,IAAU,GAAS,QAAQ;AACjC,QAAO,MAAM,GAAG,IAAO,KAAQ;EAC7B,GAAG;EACH,SAAS;GACP,GAAI,IAAU,EAAE,gBAAgB,oBAAoB,GAAG,EAAE;GACzD,mBAAmB;GACnB,GAAG,GAAS;GACb;EACF,CAAC;;AAWJ,IAAM,IAAmB,KACnB,IAAkB;AASxB,SAAS,EAAe,GAAiB,GAAqB;AAC5D,QAAO,iBAAiB,mBAAmB,EAAQ,CAAC,cAAc;;AAMpE,eAAe,EACb,GACA,GACA,GACA,GACA,GAC2B;CAC3B,IAAM,IAAW,KAAK,KAAK,GAAG;AAE9B,QAAO,KAAK,KAAK,GAAG,IAAU;EAC5B,IAAM,IAAM,MAAM,EAAW,GAAW,EAAe,GAAS,UAAU,EAAE,EAAO;AACnF,MAAI,CAAC,EAAI,GAAI,OAAU,MAAM,wBAAwB,EAAI,SAAS;EAClE,IAAM,IAA2B,MAAM,EAAI,MAAM,EAE3C,IAAM,EAAW,EAAO;AAC9B,MAAI,EAAK,OAAU,MAAM,EAAI;AAE7B,MAAI,EAAU,EAAO,CAAE,QAAO;AAE9B,QAAM,IAAI,SAAS,MAAM,WAAW,GAAG,EAAiB,CAAC;;AAG3D,OAAU,MAAM,sBAAsB;;AAKxC,SAAgB,EAA0B,EACxC,cACA,gBACA,YACA,kBACA,UAAO,UACP,gBACA,gBACiC;CACjC,IAAM,IAAK,GAAO,EACZ,CAAC,GAAO,KAAY,EAAsB,OAAO,EACjD,CAAC,GAAU,KAAe,EAAwB,KAAK,EAEvD,IAAc,EAAY,YAAY;AAC1C,MAAI,CAAC,KAAa,CAAC,GAAa;AAE9B,GADA,EAAY,wCAAwC,EACpD,EAAS,QAAQ;AACjB;;AAGF,IAAY,KAAK;AAEjB,MAAI;AAEF,KAAS,WAAW;GACpB,IAAM,IAAM,MAAM,EAAW,GAAW,EAAe,GAAS,UAAU,EAAE,EAAY;AACxF,OAAI,EAAI,WAAW,IACjB,OAAU,MACR,EACE,2CACA,iHACD,CACF;AAEH,OAAI,CAAC,EAAI,GAAI,OAAU,MAAM,wBAAwB,EAAI,SAAS;GAClE,IAAM,IAA2B,MAAM,EAAI,MAAM;AAGjD,OAAI,CAAC,EAAO,aAAa,CAAC,EAAO,YAAY;AAC3C,MAAS,aAAa;IACtB,IAAM,IAAa,MAAM,EACvB,GACA,EAAe,GAAS,WAAW,EACnC,GACA,EACE,QAAQ,QACT,CACF;AACD,QAAI,CAAC,EAAW,GAAI,OAAU,MAAM,mBAAmB,EAAW,SAAS;AAG3E,UAAM,EACJ,GACA,GACA,IACC,MAAM,EAAE,YACR,MAAM,EAAE,SAAS,KACnB;UACQ,EAAO,eAChB,EAAS,aAAa,EACtB,MAAM,EACJ,GACA,GACA,IACC,MAAM,EAAE,YACR,MAAM,EAAE,SAAS,KACnB;AAIH,OAAK,EAAO,SAkBD,KAAiB,EAAO,YAEjC,EAAS,WAAW,EAEpB,MAAM,EAAW,GAAW,EAAe,GAAS,WAAW,EAAE,GAAa;IAC5E,QAAQ;IACR,MAAM,KAAK,UAAU,EAAE,kBAAe,CAAC;IACxC,CAAC,EACF,MAAM,IAAI,SAAS,MAAM,WAAW,GAAG,IAAK,CAAC;QA1B1B;AACnB,MAAS,WAAW;IACpB,IAAM,IAAW,MAAM,EACrB,GACA,EAAe,GAAS,SAAS,EACjC,GACA;KACE,QAAQ;KACR,GAAI,IAAgB,EAAE,MAAM,KAAK,UAAU,EAAE,kBAAe,CAAC,EAAE,GAAG,EAAE;KACrE,CACF;AACD,QAAI,CAAC,EAAS,IAAI;KAChB,IAAM,IAAM,MAAM,EAAS,MAAM,CAAC,aAAa,EAAE,EAAE;AACnD,WAAU,MAAO,EAA2B,SAAS,iBAAiB,EAAS,SAAS;;AAI1F,UAAM,IAAI,SAAS,MAAM,WAAW,GAAG,IAAK,CAAC;;AAa/C,KAAS,QAAQ;GAEjB,IAAM,IAAY,GADL,EAAU,QAAQ,OAAO,GAAG,CACf,uBAAuB,mBAAmB,EAAY;AAShF,GAPI,MAAS,YAAY,IACvB,EAAY,EAAU,GAEtB,OAAO,KAAK,GAAW,SAAS,EAIlC,iBAAiB,EAAS,OAAO,EAAE,IAAK;WACjC,GAAK;AAEZ,GADA,EAAY,aAAe,QAAQ,EAAI,UAAU,wBAAwB,EACzE,EAAS,QAAQ;;IAElB;EAAC;EAAW;EAAa;EAAS;EAAe;EAAM;EAAa;EAAG,CAAC,EAErE,IACJ,CAAC,KAAa,CAAC,KAAgB,MAAU,UAAU,MAAU,WAAW,MAAU,SAE9E,WAAe;AACnB,UAAQ,GAAR;GACE,KAAK,WACH,QAAO,EAAG,uCAAuC,cAAc;GACjE,KAAK,aACH,QAAO,EAAG,yCAAyC,gBAAgB;GACrE,KAAK,WACH,QAAO,EAAG,uCAAuC,cAAc;GACjE,KAAK,QACH,QAAO,EAAG,qCAAqC,SAAS;GAC1D,KAAK,QACH,QAAO,EAAG,oCAAoC,QAAQ;GACxD,QACE,QAAO,EAAG,yCAAyC,cAAc;;KAEnE,EAEE,WAAc;AAClB,UAAQ,GAAR;GACE,KAAK;GACL,KAAK;GACL,KAAK,WACH,QAAO;GACT,KAAK,QACH,QAAO;GACT,KAAK,QACH,QAAO;GACT,QACE,QAAO;;KAET,EAEE,IAAa,MAAU,cAAc,MAAU,gBAAgB,MAAU;AAE/E,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,UAAD;GACE,SAAS;GACT,UAAU;GACV,WAAW,+HACT,MAAU,UACN,+DACA,MAAU,UACR,kDACA,uDACP,GAAG,KAAa;GACjB,OAAO,KAAY;aAVrB,CAYE,kBAAC,GAAD,EAAM,WAAW,6BAA6B,IAAa,iBAAiB,MAAQ,CAAA,EACpF,kBAAC,QAAD;IAAM,WAAU;cAAoB;IAAa,CAAA,CAC1C;MAGR,MAAU,WAAW,KACpB,kBAAC,OAAD;GAAK,WAAU;aACZ;GACG,CAAA,CAEJ"}
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';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\ntype EditorState = 'idle' | 'checking' | 'installing' | 'starting' | 'ready' | 'error';\n\nexport interface OpenInBrowserEditorButtonProps {\n /** Agent tunnel URL (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /** Agent API key for authentication */\n agentApiKey: 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\n// ── Helpers ────────────────────────────────────────────────────────────\n\nasync function agentFetch(\n tunnelUrl: string,\n path: string,\n apiKey: string,\n options?: RequestInit\n): Promise<Response> {\n const base = tunnelUrl.replace(/\\/$/, '');\n // Only advertise a JSON body when we actually send one. The agent's\n // `/api/*` delegation consumes the request body once; a POST that carries a\n // Content-Type but an (already-read) body fails on the agent with\n // \"ReadableStream has already been used\". No body → no Content-Type → safe.\n const hasBody = options?.body != null;\n return fetch(`${base}${path}`, {\n ...options,\n headers: {\n ...(hasBody ? { 'Content-Type': 'application/json' } : {}),\n 'x-agent-api-key': apiKey,\n ...options?.headers,\n },\n });\n}\n\ninterface CodeServerStatus {\n installed: boolean;\n installing: boolean;\n running: boolean;\n port?: number;\n error?: string;\n}\n\nconst POLL_INTERVAL_MS = 2000;\nconst POLL_TIMEOUT_MS = 120_000; // 2 minutes max\n\n/**\n * Build a profile-scoped agent endpoint path. The agent moved every\n * data-plane endpoint under `/api/profiles/<profile>/…` and now returns\n * 410 Gone for bare `/api/code-server/*` paths — hitting the old paths\n * surfaced in the browser as a misleading CORS error (the 410 response\n * omits Access-Control-Allow-Origin).\n */\nfunction codeServerPath(profile: string, sub: string): string {\n return `/api/profiles/${encodeURIComponent(profile)}/code-server${sub}`;\n}\n\n/**\n * Poll until a condition is met on the status response.\n */\nasync function pollUntil(\n tunnelUrl: string,\n apiKey: string,\n profile: string,\n predicate: (status: CodeServerStatus) => boolean,\n errorCheck: (status: CodeServerStatus) => string | null\n): Promise<CodeServerStatus> {\n const deadline = Date.now() + POLL_TIMEOUT_MS;\n\n while (Date.now() < deadline) {\n const res = await agentFetch(tunnelUrl, codeServerPath(profile, '/status'), apiKey);\n if (!res.ok) throw new Error(`Status check failed: ${res.status}`);\n const status: CodeServerStatus = await res.json();\n\n const err = errorCheck(status);\n if (err) throw new Error(err);\n\n if (predicate(status)) return status;\n\n await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));\n }\n\n throw new Error('Operation timed out');\n}\n\n// ── Component ──────────────────────────────────────────────────────────\n\nexport function OpenInBrowserEditorButton({\n tunnelUrl,\n agentApiKey,\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 handleClick = useCallback(async () => {\n if (!tunnelUrl || !agentApiKey) {\n setErrorMsg('Agent tunnel or API key not available');\n setState('error');\n return;\n }\n\n setErrorMsg(null);\n\n try {\n // Step 1: Check status.\n //\n // code-server runs once per agent (daemon-wide) but its management API\n // is profile-scoped. The active profile may be a not-yet-started profile\n // that returns 404 — which means \"not on this profile\", NOT \"not\n // installed\". So probe the active profile first, then fall back to the\n // daemon's \"default\" boot profile. A 404 (not on this profile) or a\n // transient 5xx (daemon restarting/unreachable) on a non-final candidate\n // falls through to the next; only when every candidate is exhausted do we\n // conclude code-server is genuinely absent (or surface a real error).\n setState('checking');\n const candidates = profile === 'default' ? ['default'] : [profile, 'default'];\n let effectiveProfile: string | null = null;\n let status: CodeServerStatus | null = null;\n for (const candidate of candidates) {\n const isLastCandidate = candidate === candidates[candidates.length - 1];\n const res = await agentFetch(tunnelUrl, codeServerPath(candidate, '/status'), agentApiKey);\n if (res.status === 404) continue; // not on this profile — try the next\n // Transient daemon error on a non-final candidate: the active profile\n // may be flaky while code-server on `default` could still answer.\n if (res.status >= 500 && !isLastCandidate) continue;\n if (!res.ok) throw new Error(`Status check failed: ${res.status}`);\n effectiveProfile = candidate;\n status = await res.json();\n break;\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: Install if needed\n if (!status.installed && !status.installing) {\n setState('installing');\n const installRes = await agentFetch(\n tunnelUrl,\n codeServerPath(effectiveProfile, '/install'),\n agentApiKey,\n {\n method: 'POST',\n }\n );\n if (!installRes.ok) throw new Error(`Install failed: ${installRes.status}`);\n\n // Poll until installed, and adopt the polled status so the\n // running check below reflects the post-install state rather than\n // the stale initial probe (avoids a redundant /start if code-server\n // ever auto-starts on install).\n status = await pollUntil(\n tunnelUrl,\n agentApiKey,\n effectiveProfile,\n (s) => s.installed,\n (s) => s.error || null\n );\n } else if (status.installing) {\n setState('installing');\n status = await pollUntil(\n tunnelUrl,\n agentApiKey,\n effectiveProfile,\n (s) => s.installed,\n (s) => s.error || null\n );\n }\n\n // Step 3: Start if not running\n if (!status.running) {\n setState('starting');\n const startRes = await agentFetch(\n tunnelUrl,\n codeServerPath(effectiveProfile, '/start'),\n agentApiKey,\n {\n method: 'POST',\n ...(workspacePath ? { body: JSON.stringify({ workspacePath }) } : {}),\n }\n );\n if (!startRes.ok) {\n const err = await startRes.json().catch(() => ({}));\n throw new Error((err as { error?: string }).error || `Start failed: ${startRes.status}`);\n }\n\n // Brief wait for code-server to be ready\n await new Promise((r) => setTimeout(r, 1000));\n } else if (workspacePath && status.running) {\n // Restart with new workspace path if different\n setState('starting');\n // workspacePath is guaranteed truthy by the `else if` guard above.\n await agentFetch(tunnelUrl, codeServerPath(effectiveProfile, '/restart'), agentApiKey, {\n method: 'POST',\n body: JSON.stringify({ workspacePath }),\n });\n await new Promise((r) => setTimeout(r, 1000));\n }\n\n // Step 4: Open the editor\n setState('ready');\n const base = tunnelUrl.replace(/\\/$/, '');\n const editorUrl = `${base}/code-server/?apiKey=${encodeURIComponent(agentApiKey)}`;\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 }, [tunnelUrl, agentApiKey, profile, workspacePath, mode, onIframeUrl, tr]);\n\n const isDisabled =\n !tunnelUrl || !agentApiKey || (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 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={`w-3.5 h-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 h-4 w-4 shrink-0\" />\n <span>{errorMsg}</span>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}