@burdenoff/microfe-vibecontrols 2026.524.8 → 2026.525.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,14 @@
1
1
  import { Card as e } from "../shared/Card.js";
2
- import { VIBECONTROLS_AGENT_PROFILES_QUERY as t, useActiveAgentProfile as n } from "../../hooks/useActiveAgentProfile.js";
2
+ import { useActiveAgentProfile as t } from "../../hooks/useActiveAgentProfile.js";
3
3
  import "../shared/index.js";
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";
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";
9
9
  //#region src/components/agent-manager/ProfilesPanel.tsx
10
- var h = d`
11
- mutation CreateVibecontrolsAgentProfile($input: CreateAgentProfileInput!) {
10
+ var m = u`
11
+ mutation CreateVibecontrolsAgentProfile($input: CreateVibecontrolsAgentProfileInput!) {
12
12
  createVibecontrolsAgentProfile(input: $input) {
13
13
  id
14
14
  name
@@ -16,11 +16,11 @@ var h = d`
16
16
  status
17
17
  }
18
18
  }
19
- `, g = d`
19
+ `, h = u`
20
20
  mutation DeleteVibecontrolsAgentProfile($id: ID!) {
21
21
  deleteVibecontrolsAgentProfile(id: $id)
22
22
  }
23
- `, _ = d`
23
+ `, g = u`
24
24
  mutation SetDefaultVibecontrolsAgentProfile($id: ID!) {
25
25
  setDefaultVibecontrolsAgentProfile(id: $id) {
26
26
  id
@@ -28,156 +28,147 @@ var h = d`
28
28
  }
29
29
  }
30
30
  `;
31
- function v(e) {
31
+ function _(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 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 = { agentId: d }, [N, { loading: P }] = f(h, {
37
- refetchQueries: [{
38
- query: t,
39
- variables: M
40
- }],
41
- awaitRefetchQueries: !0
42
- }), [F, { loading: I }] = f(g, {
43
- refetchQueries: [{
44
- query: t,
45
- variables: M
46
- }],
47
- awaitRefetchQueries: !0
48
- }), [L, { loading: R }] = f(_, {
49
- refetchQueries: [{
50
- query: t,
51
- variables: M
52
- }],
53
- awaitRefetchQueries: !0
54
- }), z = r(async () => {
55
- j(null);
56
- let e = E.trim();
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();
57
39
  if (!e) {
58
- j("Profile name is required");
40
+ A("Profile name is required");
59
41
  return;
60
42
  }
61
- let t = O.trim() ? Number(O.trim()) : null;
62
- if (O.trim() && (Number.isNaN(t) || t === null || t <= 0)) {
63
- j("Port must be a positive number");
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");
64
46
  return;
65
47
  }
66
48
  try {
67
- await N({ variables: { input: {
68
- agentId: d,
49
+ await j({ variables: { input: {
50
+ agentId: u,
69
51
  name: e,
70
52
  port: t
71
- } } }), T(!1), D("default"), k("");
53
+ } } });
72
54
  } catch (e) {
73
- j(e instanceof Error ? e.message : "Failed to create profile");
55
+ A(e instanceof Error ? e.message : "Failed to create profile");
56
+ return;
74
57
  }
58
+ w(!1), E("default"), O(""), await S();
75
59
  }, [
76
- d,
77
- N,
78
- E,
79
- O
80
- ]), B = r(async (e) => {
81
- if (j(null), window.confirm(`Delete profile "${e.name}"? This cannot be undone.`)) try {
82
- await F({ variables: { id: e.id } });
83
- } catch (e) {
84
- j(e instanceof Error ? e.message : "Failed to delete profile");
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.`)) {
67
+ try {
68
+ await N({ variables: { id: e.id } });
69
+ } catch (e) {
70
+ A(e instanceof Error ? e.message : "Failed to delete profile");
71
+ return;
72
+ }
73
+ await S();
85
74
  }
86
- }, [F]), V = r(async (e) => {
87
- j(null);
75
+ }, [N, S]), z = n(async (e) => {
76
+ A(null);
88
77
  try {
89
- await L({ variables: { id: e.id } });
78
+ await F({ variables: { id: e.id } });
90
79
  } catch (e) {
91
- j(e instanceof Error ? e.message : "Failed to set default profile");
80
+ A(e instanceof Error ? e.message : "Failed to set default profile");
81
+ return;
92
82
  }
93
- }, [L]), H = P || I || R;
94
- return /* @__PURE__ */ m(e, { children: [
95
- /* @__PURE__ */ m("div", {
83
+ await S();
84
+ }, [F, S]), B = M || P || I;
85
+ return /* @__PURE__ */ p(e, { children: [
86
+ /* @__PURE__ */ p("div", {
96
87
  className: "p-4 border-b border-border-default flex items-center justify-between",
97
- children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("h3", {
88
+ children: [/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h3", {
98
89
  className: "font-semibold text-text-primary",
99
90
  children: "Profiles"
100
- }), /* @__PURE__ */ m("p", {
91
+ }), /* @__PURE__ */ p("p", {
101
92
  className: "text-xs text-text-secondary mt-0.5",
102
93
  children: [
103
94
  "Each profile runs an isolated CLI on this agent. Active:",
104
95
  " ",
105
- /* @__PURE__ */ p("code", {
96
+ /* @__PURE__ */ f("code", {
106
97
  className: "font-mono text-text-primary",
107
- children: b
98
+ children: y
108
99
  })
109
100
  ]
110
- })] }), /* @__PURE__ */ m("div", {
101
+ })] }), /* @__PURE__ */ p("div", {
111
102
  className: "flex items-center gap-2",
112
- children: [/* @__PURE__ */ p("button", {
103
+ children: [/* @__PURE__ */ f("button", {
113
104
  type: "button",
114
- onClick: () => C(),
105
+ onClick: () => void S(),
115
106
  className: "px-2 py-1 text-xs text-text-secondary hover:text-text-primary",
116
107
  children: "Refresh"
117
- }), /* @__PURE__ */ m("button", {
108
+ }), /* @__PURE__ */ p("button", {
118
109
  type: "button",
119
- onClick: () => T((e) => !e),
110
+ onClick: () => w((e) => !e),
120
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",
121
- children: [/* @__PURE__ */ p(s, { className: "w-4 h-4" }), "New profile"]
112
+ children: [/* @__PURE__ */ f(o, { className: "w-4 h-4" }), "New profile"]
122
113
  })]
123
114
  })]
124
115
  }),
125
- A && /* @__PURE__ */ p("div", {
116
+ k && /* @__PURE__ */ f("div", {
126
117
  className: "px-4 py-2 bg-status-error-bg/30 border-b border-status-error-text/20 text-sm text-status-error-text",
127
- children: A
118
+ children: k
128
119
  }),
129
- w && /* @__PURE__ */ m("div", {
120
+ C && /* @__PURE__ */ p("div", {
130
121
  className: "p-4 border-b border-border-default bg-bg-sunken/40 space-y-3",
131
- children: [/* @__PURE__ */ m("div", {
122
+ children: [/* @__PURE__ */ p("div", {
132
123
  className: "grid grid-cols-2 gap-3",
133
- children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
124
+ children: [/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
134
125
  className: "block text-xs font-medium text-text-secondary mb-1",
135
126
  children: "Profile name"
136
- }), /* @__PURE__ */ p("input", {
127
+ }), /* @__PURE__ */ f("input", {
137
128
  type: "text",
138
- value: E,
139
- onChange: (e) => D(e.target.value),
129
+ value: T,
130
+ onChange: (e) => E(e.target.value),
140
131
  placeholder: "default",
141
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",
142
133
  autoFocus: !0
143
- })] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
134
+ })] }), /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
144
135
  className: "block text-xs font-medium text-text-secondary mb-1",
145
136
  children: "Port (optional)"
146
- }), /* @__PURE__ */ p("input", {
137
+ }), /* @__PURE__ */ f("input", {
147
138
  type: "number",
148
- value: O,
149
- onChange: (e) => k(e.target.value),
139
+ value: D,
140
+ onChange: (e) => O(e.target.value),
150
141
  placeholder: "auto",
151
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"
152
143
  })] })]
153
- }), /* @__PURE__ */ m("div", {
144
+ }), /* @__PURE__ */ p("div", {
154
145
  className: "flex items-center gap-2",
155
- children: [/* @__PURE__ */ m("button", {
146
+ children: [/* @__PURE__ */ p("button", {
156
147
  type: "button",
157
- disabled: P,
158
- onClick: () => void z(),
148
+ disabled: M,
149
+ onClick: () => void L(),
159
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",
160
- children: [P ? /* @__PURE__ */ p(o, { className: "w-3.5 h-3.5 animate-spin" }) : /* @__PURE__ */ p(a, { className: "w-3.5 h-3.5" }), "Create"]
161
- }), /* @__PURE__ */ m("button", {
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", {
162
153
  type: "button",
163
154
  onClick: () => {
164
- T(!1), j(null);
155
+ w(!1), A(null);
165
156
  },
166
157
  className: "inline-flex items-center gap-1 px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary",
167
- children: [/* @__PURE__ */ p(u, { className: "w-3.5 h-3.5" }), "Cancel"]
158
+ children: [/* @__PURE__ */ f(l, { className: "w-3.5 h-3.5" }), "Cancel"]
168
159
  })]
169
160
  })]
170
161
  }),
171
- S && y.length === 0 ? /* @__PURE__ */ m("div", {
162
+ x && v.length === 0 ? /* @__PURE__ */ p("div", {
172
163
  className: "p-6 text-center text-text-secondary",
173
- children: [/* @__PURE__ */ p(o, { className: "w-5 h-5 mx-auto mb-2 animate-spin" }), "Loading profiles…"]
174
- }) : y.length === 0 ? /* @__PURE__ */ m("div", {
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", {
175
166
  className: "p-6 text-center text-text-secondary",
176
- children: [/* @__PURE__ */ p("p", { children: "No profiles configured yet." }), /* @__PURE__ */ m("p", {
167
+ children: [/* @__PURE__ */ f("p", { children: "No profiles configured yet." }), /* @__PURE__ */ p("p", {
177
168
  className: "text-xs mt-1",
178
169
  children: [
179
170
  "The agent will fall back to the implicit ",
180
- /* @__PURE__ */ p("code", {
171
+ /* @__PURE__ */ f("code", {
181
172
  className: "font-mono",
182
173
  children: "default"
183
174
  }),
@@ -185,36 +176,36 @@ function y({ agentId: d }) {
185
176
  "profile until you create one."
186
177
  ]
187
178
  })]
188
- }) : /* @__PURE__ */ p("div", {
179
+ }) : /* @__PURE__ */ f("div", {
189
180
  className: "divide-y divide-border-default",
190
- children: y.map((e) => {
191
- let t = e.name === b;
192
- return /* @__PURE__ */ m("div", {
181
+ children: v.map((e) => {
182
+ let t = e.name === y;
183
+ return /* @__PURE__ */ p("div", {
193
184
  className: `p-4 flex items-center justify-between gap-4 ${t ? "bg-action-primary-bg/5" : ""}`,
194
- children: [/* @__PURE__ */ m("div", {
185
+ children: [/* @__PURE__ */ p("div", {
195
186
  className: "flex items-center gap-3 min-w-0",
196
- children: [/* @__PURE__ */ p("span", {
197
- className: `h-2 w-2 rounded-full flex-shrink-0 ${v(e.status)}`,
187
+ children: [/* @__PURE__ */ f("span", {
188
+ className: `h-2 w-2 rounded-full flex-shrink-0 ${_(e.status)}`,
198
189
  "aria-hidden": !0
199
- }), /* @__PURE__ */ m("div", {
190
+ }), /* @__PURE__ */ p("div", {
200
191
  className: "min-w-0",
201
- children: [/* @__PURE__ */ m("div", {
192
+ children: [/* @__PURE__ */ p("div", {
202
193
  className: "flex items-center gap-2",
203
194
  children: [
204
- /* @__PURE__ */ p("code", {
195
+ /* @__PURE__ */ f("code", {
205
196
  className: "font-mono font-medium text-text-primary truncate",
206
197
  children: e.name
207
198
  }),
208
- e.isDefault && /* @__PURE__ */ m("span", {
199
+ e.isDefault && /* @__PURE__ */ p("span", {
209
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",
210
- children: [/* @__PURE__ */ p(c, { className: "w-3 h-3" }), "Default"]
201
+ children: [/* @__PURE__ */ f(s, { className: "w-3 h-3" }), "Default"]
211
202
  }),
212
- t && /* @__PURE__ */ p("span", {
203
+ t && /* @__PURE__ */ f("span", {
213
204
  className: "px-1.5 py-0.5 text-[10px] uppercase tracking-wide bg-status-success-bg/40 text-status-success-text rounded",
214
205
  children: "Active"
215
206
  })
216
207
  ]
217
- }), /* @__PURE__ */ m("p", {
208
+ }), /* @__PURE__ */ p("p", {
218
209
  className: "text-xs text-text-secondary mt-0.5 truncate",
219
210
  children: [
220
211
  e.status ?? "unknown",
@@ -223,29 +214,29 @@ function y({ agentId: d }) {
223
214
  ]
224
215
  })]
225
216
  })]
226
- }), /* @__PURE__ */ m("div", {
217
+ }), /* @__PURE__ */ p("div", {
227
218
  className: "flex items-center gap-2 flex-shrink-0",
228
219
  children: [
229
- !t && /* @__PURE__ */ p("button", {
220
+ !t && /* @__PURE__ */ f("button", {
230
221
  type: "button",
231
- onClick: () => x(e.name),
222
+ onClick: () => b(e.name),
232
223
  className: "px-2 py-1 text-xs text-text-secondary hover:text-text-primary border border-border-default rounded",
233
224
  children: "Use"
234
225
  }),
235
- !e.isDefault && /* @__PURE__ */ m("button", {
226
+ !e.isDefault && /* @__PURE__ */ p("button", {
236
227
  type: "button",
237
- disabled: H,
238
- onClick: () => void V(e),
228
+ disabled: B,
229
+ onClick: () => void z(e),
239
230
  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",
240
- children: [/* @__PURE__ */ p(c, { className: "w-3 h-3" }), "Set default"]
231
+ children: [/* @__PURE__ */ f(s, { className: "w-3 h-3" }), "Set default"]
241
232
  }),
242
- /* @__PURE__ */ m("button", {
233
+ /* @__PURE__ */ p("button", {
243
234
  type: "button",
244
- disabled: H || e.isDefault,
235
+ disabled: B || e.isDefault,
245
236
  title: e.isDefault ? "Cannot delete the default profile" : "Delete",
246
- onClick: () => void B(e),
237
+ onClick: () => void R(e),
247
238
  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",
248
- children: [/* @__PURE__ */ p(l, { className: "w-3 h-3" }), "Delete"]
239
+ children: [/* @__PURE__ */ f(c, { className: "w-3 h-3" }), "Delete"]
249
240
  })
250
241
  ]
251
242
  })]
@@ -255,6 +246,6 @@ function y({ agentId: d }) {
255
246
  ] });
256
247
  }
257
248
  //#endregion
258
- export { y as ProfilesPanel };
249
+ export { v as ProfilesPanel };
259
250
 
260
251
  //# 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 {\n useActiveAgentProfile,\n VIBECONTROLS_AGENT_PROFILES_QUERY,\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: CreateAgentProfileInput!) {\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 refetchVars = { agentId };\n\n const [createProfile, { loading: creating }] = useMutation<CreateProfileData, CreateProfileVars>(\n CREATE_PROFILE_MUTATION,\n {\n refetchQueries: [{ query: VIBECONTROLS_AGENT_PROFILES_QUERY, variables: refetchVars }],\n awaitRefetchQueries: true,\n }\n );\n\n const [deleteProfile, { loading: deleting }] = useMutation<DeleteProfileData, DeleteProfileVars>(\n DELETE_PROFILE_MUTATION,\n {\n refetchQueries: [{ query: VIBECONTROLS_AGENT_PROFILES_QUERY, variables: refetchVars }],\n awaitRefetchQueries: true,\n }\n );\n\n const [setDefaultProfile, { loading: settingDefault }] = useMutation<\n SetDefaultProfileData,\n SetDefaultProfileVars\n >(SET_DEFAULT_PROFILE_MUTATION, {\n refetchQueries: [{ query: VIBECONTROLS_AGENT_PROFILES_QUERY, variables: refetchVars }],\n awaitRefetchQueries: true,\n });\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 setShowCreate(false);\n setNewName('default');\n setNewPort('');\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Failed to create profile');\n }\n }, [agentId, createProfile, newName, newPort]);\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 }\n },\n [deleteProfile]\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 }\n },\n [setDefaultProfile]\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 onClick={() => 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 { 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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"}
@@ -167,7 +167,7 @@ function b({ agent: s, provider: c, onAuthSatisfiedChange: l }) {
167
167
  C(!0), D(null);
168
168
  try {
169
169
  let e = await Promise.all(c.fields.map(async (e) => [e.key, await n(s, e.key) ?? ""])), t = Object.fromEntries(e);
170
- p(t), _(t);
170
+ p(Object.fromEntries(c.fields.filter((e) => !e.secret).map((e) => [e.key, t[e.key] ?? ""]))), _(t);
171
171
  } catch (e) {
172
172
  D(e instanceof Error ? e.message : u("ai.providerCredentials.loadFailed", "Failed to load provider credentials"));
173
173
  } finally {
@@ -232,28 +232,33 @@ function b({ agent: s, provider: c, onAuthSatisfiedChange: l }) {
232
232
  children: [/* @__PURE__ */ h(f, { className: "h-4 w-4 animate-spin" }), u("ai.providerCredentials.loading", "Loading…")]
233
233
  }) : /* @__PURE__ */ h("div", {
234
234
  className: "space-y-3",
235
- children: c.fields.map((e) => /* @__PURE__ */ h(x, {
236
- label: e.key,
237
- secret: e.secret ?? !1,
238
- value: d[e.key] ?? "",
239
- onChange: (t) => p((n) => ({
240
- ...n,
241
- [e.key]: t
242
- })),
243
- saving: w === e.key,
244
- visible: v[e.key] ?? !1,
245
- onToggleVisible: () => b((t) => ({
246
- ...t,
247
- [e.key]: !t[e.key]
248
- })),
249
- onSave: () => void P(e.key, d[e.key] ?? ""),
250
- onClear: () => void P(e.key, "")
251
- }, e.key))
235
+ children: c.fields.map((e) => {
236
+ let t = e.secret ?? !1, n = !!(m[e.key] ?? "").trim();
237
+ return /* @__PURE__ */ h(x, {
238
+ label: e.key,
239
+ secret: t,
240
+ value: d[e.key] ?? "",
241
+ saved: n,
242
+ placeholder: n ? t ? u("ai.providerCredentials.storedSecretPlaceholder", "•••••••• stored — enter a new value to replace") : u("ai.providerCredentials.storedPlaceholder", "stored — enter a new value to replace") : "",
243
+ onChange: (t) => p((n) => ({
244
+ ...n,
245
+ [e.key]: t
246
+ })),
247
+ saving: w === e.key,
248
+ visible: v[e.key] ?? !1,
249
+ onToggleVisible: () => b((t) => ({
250
+ ...t,
251
+ [e.key]: !t[e.key]
252
+ })),
253
+ onSave: () => void P(e.key, d[e.key] ?? ""),
254
+ onClear: () => void P(e.key, "")
255
+ }, e.key);
256
+ })
252
257
  })
253
258
  ]
254
259
  });
255
260
  }
256
- function x({ label: e, secret: t, value: n, onChange: r, saving: i, visible: a, onToggleVisible: o, onSave: s, onClear: c }) {
261
+ function x({ label: e, secret: t, value: n, saved: r, placeholder: i, onChange: a, saving: o, visible: s, onToggleVisible: c, onSave: d, onClear: _ }) {
257
262
  return /* @__PURE__ */ g("div", {
258
263
  className: "space-y-2 rounded-lg border border-border-default bg-bg-primary p-3",
259
264
  children: [/* @__PURE__ */ h("label", {
@@ -265,30 +270,31 @@ function x({ label: e, secret: t, value: n, onChange: r, saving: i, visible: a,
265
270
  /* @__PURE__ */ g("div", {
266
271
  className: "relative flex-1",
267
272
  children: [/* @__PURE__ */ h("input", {
268
- type: t && !a ? "password" : "text",
273
+ type: t && !s ? "password" : "text",
269
274
  value: n,
270
- onChange: (e) => r(e.target.value),
275
+ onChange: (e) => a(e.target.value),
271
276
  autoComplete: "off",
272
277
  spellCheck: !1,
278
+ placeholder: i,
273
279
  className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 pr-10 text-sm text-text-primary"
274
280
  }), t ? /* @__PURE__ */ h("button", {
275
281
  type: "button",
276
- onClick: o,
282
+ onClick: c,
277
283
  className: "absolute right-2 top-1/2 -translate-y-1/2 text-text-secondary hover:text-text-primary",
278
- children: h(a ? u : l, { className: "h-4 w-4" })
284
+ children: h(s ? u : l, { className: "h-4 w-4" })
279
285
  }) : null]
280
286
  }),
281
287
  /* @__PURE__ */ g("button", {
282
288
  type: "button",
283
- onClick: s,
284
- disabled: i,
289
+ onClick: d,
290
+ disabled: o || !n.trim(),
285
291
  className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover disabled:opacity-50",
286
- children: [i ? /* @__PURE__ */ h(f, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ h(p, { className: "h-4 w-4" }), "Save"]
292
+ children: [o ? /* @__PURE__ */ h(f, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ h(p, { className: "h-4 w-4" }), "Save"]
287
293
  }),
288
294
  /* @__PURE__ */ g("button", {
289
295
  type: "button",
290
- onClick: c,
291
- disabled: i || !n,
296
+ onClick: _,
297
+ disabled: o || !n && !r,
292
298
  className: "inline-flex items-center gap-1.5 rounded-md border border-border-default px-3 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:border-border-strong disabled:opacity-50",
293
299
  children: [/* @__PURE__ */ h(m, { className: "h-4 w-4" }), "Clear"]
294
300
  })
@@ -1 +1 @@
1
- {"version":3,"file":"AIProviderCredentialsCard.js","names":[],"sources":["../../../src/components/ai/AIProviderCredentialsCard.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport {\n ArrowLeft,\n ChevronRight,\n Eye,\n EyeOff,\n KeyRound,\n Loader2,\n Save,\n Trash2,\n} from 'lucide-react';\n\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n deleteAgentConfigValue,\n getAgentConfigValue,\n setAgentConfigValue,\n type AgentRef,\n} from '@/services/aiApi';\n\n/**\n * One provider definition — what env-var-style keys the agent stores under\n * its config bag and what to call the provider in the UI. Kept as a flat\n * static list rather than discovered from the plugin manifest because the\n * config keys are part of the upstream SDK contract (ANTHROPIC_API_KEY,\n * OPENAI_API_KEY, etc.) and don't change with each plugin release.\n */\nexport interface AIProviderCredentialDef {\n /** Stable provider id — usually matches the plugin's pluginName. */\n providerId: string;\n /** Display name shown in the card heading. */\n label: string;\n /** Short helper text under the heading. */\n description: string;\n /**\n * One row per key. The `kind` distinguishes a primary credential (for\n * which the SDK refuses to start without ONE of the listed keys) from\n * an optional URL/host override.\n */\n fields: Array<{\n /** Agent-config key name, also rendered as the input label. */\n key: string;\n /**\n * Whether the field is a secret (rendered as password with eye toggle)\n * or a plain string (URL / host overrides — visible by default).\n */\n secret?: boolean;\n /**\n * Whether saving this field alone (or any sibling field with\n * primary=true) is enough to authenticate the provider. The card\n * surfaces an \"at least one set\" indicator using this — used by the\n * SDK-auth-missing modal to enable the Continue button as soon as\n * either ANTHROPIC_API_KEY *or* ANTHROPIC_AUTH_TOKEN is saved.\n *\n * Defaults to `true` for secret fields and `false` for plain-text\n * overrides (URLs/hosts). Set explicitly when a secret field is\n * supplementary rather than authentication-conferring.\n */\n primary?: boolean;\n }>;\n}\n\n/**\n * Catalog of every AI provider whose plugin reads credentials from the\n * agent's config bag (POST /api/config). CLI-only providers (Aider, Amp,\n * Copilot, Crush, Goose, Plandex, Pi) authenticate via their own harness\n * binary (~/.claude/, ~/.config/opencode/ etc.) — Bridle handles those\n * and they don't appear here.\n */\nexport const AI_PROVIDER_CREDENTIALS: AIProviderCredentialDef[] = [\n {\n providerId: 'claude',\n label: 'Anthropic Claude',\n description:\n 'Claude SDK sessions require either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN on the agent. Save one of them to make SDK sessions work.',\n fields: [\n { key: 'ANTHROPIC_API_KEY', secret: true },\n { key: 'ANTHROPIC_AUTH_TOKEN', secret: true },\n ],\n },\n {\n providerId: 'codex',\n label: 'OpenAI Codex',\n description:\n 'Codex SDK / CLI sessions read OPENAI_API_KEY from the agent. Set it here to enable Codex-backed sessions.',\n fields: [{ key: 'OPENAI_API_KEY', secret: true }],\n },\n {\n providerId: 'gemini',\n label: 'Google Gemini',\n description:\n 'Gemini SDK reads GEMINI_API_KEY (preferred) or GOOGLE_API_KEY. Either one works — both shown so an existing GOOGLE_API_KEY in your environment keeps working.',\n fields: [\n { key: 'GEMINI_API_KEY', secret: true },\n { key: 'GOOGLE_API_KEY', secret: true },\n ],\n },\n {\n providerId: 'openai-compat',\n label: 'OpenAI-Compatible',\n description:\n 'Generic OpenAI-compatible provider (Together, Groq, Mistral, etc.). Set the API key and the base URL the SDK should hit.',\n fields: [{ key: 'OPENAI_COMPAT_API_KEY', secret: true }, { key: 'OPENAI_COMPAT_BASE_URL' }],\n },\n {\n providerId: 'cursor',\n label: 'Cursor',\n description:\n 'Cursor agent (CLI + SDK) reads CURSOR_API_KEY from the agent. Set it here to enable both modes.',\n fields: [{ key: 'CURSOR_API_KEY', secret: true }],\n },\n {\n providerId: 'openrouter',\n label: 'OpenRouter',\n description: 'OpenRouter aggregator (300+ models, SDK only). Set OPENROUTER_API_KEY to enable.',\n fields: [{ key: 'OPENROUTER_API_KEY', secret: true }],\n },\n {\n providerId: 'minimax',\n label: 'Minimax',\n description:\n 'Minimax provider (CLI + Anthropic-compatible SDK). Set MINIMAX_API_KEY to enable.',\n fields: [{ key: 'MINIMAX_API_KEY', secret: true }],\n },\n {\n providerId: 'ollama',\n label: 'Ollama',\n description:\n 'Ollama Cloud + self-hosted (CLI + SDK). OLLAMA_API_KEY for Cloud; OLLAMA_HOST optional override (defaults: https://ollama.com when key present, http://localhost:11434 otherwise).',\n fields: [{ key: 'OLLAMA_API_KEY', secret: true }, { key: 'OLLAMA_HOST' }],\n },\n];\n\ninterface AIProviderCredentialsCardProps {\n agent: AgentRef;\n /**\n * Optional filter — when present, only render providers whose providerId\n * appears in the list. Lets a future caller scope the card to \"only the\n * providers I have plugins installed for\" without changing the catalog.\n */\n providerIds?: string[];\n /**\n * When provided, the staged picker preselects this provider so the\n * caller (e.g. the SDK-auth-missing modal) can drop the user straight\n * onto the right form without an extra click.\n */\n initialProviderId?: string;\n /**\n * Fires whenever the currently-selected provider's saved-state changes\n * on disk. `satisfied` is true when at least one primary (auth-conferring)\n * field has a saved non-empty value. Used by the SDK-auth-missing modal\n * to enable a Continue affordance the moment the user persists any\n * acceptable credential, without forcing them to also fill the others.\n */\n onProviderAuthSatisfiedChange?: (satisfied: boolean) => void;\n}\n\n/**\n * Staged credentials manager: shows a provider picker first, then on\n * select renders one card with that provider's config keys plus a back\n * button. Keeps the rendered DOM small (and the surrounding modal\n * viewport-friendly) regardless of how many providers are in the catalog.\n *\n * Replaces the older Claude-only ClaudeCredentialsCard.\n */\nexport function AIProviderCredentialsCard({\n agent,\n providerIds,\n initialProviderId,\n onProviderAuthSatisfiedChange,\n}: AIProviderCredentialsCardProps) {\n const tr = useTr();\n const providers = providerIds\n ? AI_PROVIDER_CREDENTIALS.filter((p) => providerIds.includes(p.providerId))\n : AI_PROVIDER_CREDENTIALS;\n\n // Preselect when the catalog narrows to exactly one provider so the\n // single-provider case (e.g. claude-only filter) skips the picker.\n const [selectedId, setSelectedId] = useState<string | null>(() => {\n if (initialProviderId && providers.some((p) => p.providerId === initialProviderId)) {\n return initialProviderId;\n }\n return providers.length === 1 ? (providers[0]?.providerId ?? null) : null;\n });\n\n const selected = providers.find((p) => p.providerId === selectedId) ?? null;\n\n if (!selected) {\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"rounded-lg bg-bg-sunken p-2 text-text-secondary\">\n <KeyRound className=\"h-4 w-4\" />\n </div>\n <div className=\"space-y-1\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('ai.providerCredentials.pickTitle', 'Choose a provider')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'ai.providerCredentials.pickDescription',\n 'Pick the provider whose credentials you want to manage. Keys are stored on the agent and read at session start.'\n )}\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {providers.map((p) => (\n <button\n key={p.providerId}\n type=\"button\"\n onClick={() => setSelectedId(p.providerId)}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-left hover:border-border-strong hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"min-w-0\">\n <div className=\"text-sm font-medium text-text-primary truncate\">{p.label}</div>\n <div className=\"text-xs text-text-secondary truncate\">\n {p.fields.map((f) => f.key).join(', ')}\n </div>\n </div>\n <ChevronRight className=\"h-4 w-4 flex-shrink-0 text-text-secondary\" />\n </button>\n ))}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n {providers.length > 1 ? (\n <button\n type=\"button\"\n onClick={() => setSelectedId(null)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-2 py-1 text-xs text-text-secondary hover:text-text-primary hover:border-border-strong\"\n aria-label={tr('ai.providerCredentials.back', 'Back to providers')}\n >\n <ArrowLeft className=\"h-3.5 w-3.5\" />\n {tr('ai.providerCredentials.back', 'Back')}\n </button>\n ) : null}\n <div className=\"rounded-lg bg-bg-sunken p-2 text-text-secondary\">\n <KeyRound className=\"h-4 w-4\" />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary truncate\">{selected.label}</h3>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'ai.providerCredentials.scopedDescription',\n 'Keys are stored on the agent and read at session start.'\n )}\n </p>\n </div>\n </div>\n <ProviderCard\n agent={agent}\n provider={selected}\n onAuthSatisfiedChange={onProviderAuthSatisfiedChange}\n />\n </div>\n );\n}\n\nfunction isPrimaryField(f: AIProviderCredentialDef['fields'][number]): boolean {\n // Defaults: secret fields gate auth; URL/host overrides don't.\n return f.primary ?? f.secret ?? false;\n}\n\nfunction ProviderCard({\n agent,\n provider,\n onAuthSatisfiedChange,\n}: {\n agent: AgentRef;\n provider: AIProviderCredentialDef;\n onAuthSatisfiedChange?: (satisfied: boolean) => void;\n}) {\n const tr = useTr();\n const [values, setValues] = useState<Record<string, string>>({});\n // savedValues tracks what's actually on the agent (last successful\n // load/persist), not the unsaved input buffer. This is what drives the\n // \"auth satisfied\" indicator — typing an API key in the field doesn't\n // count until the user hits Save.\n const [savedValues, setSavedValues] = useState<Record<string, string>>({});\n const [visible, setVisible] = useState<Record<string, boolean>>({});\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState<string | null>(null);\n\n const primaryKeys = provider.fields.filter(isPrimaryField).map((f) => f.key);\n const satisfiedPrimaryKeys = primaryKeys.filter((k) => (savedValues[k] ?? '').trim().length > 0);\n const authSatisfied = satisfiedPrimaryKeys.length > 0;\n\n // Notify parent whenever satisfied flips. Effect (not inline call) so\n // children don't render-during-render of the parent.\n useEffect(() => {\n onAuthSatisfiedChange?.(authSatisfied);\n }, [authSatisfied, onAuthSatisfiedChange]);\n\n const loadValues = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const entries = await Promise.all(\n provider.fields.map(async (f) => [f.key, (await getAgentConfigValue(agent, f.key)) ?? ''])\n );\n const next = Object.fromEntries(entries);\n setValues(next);\n setSavedValues(next);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.providerCredentials.loadFailed', 'Failed to load provider credentials')\n );\n } finally {\n setLoading(false);\n }\n }, [agent, provider.fields, tr]);\n\n useEffect(() => {\n void loadValues();\n }, [loadValues]);\n\n const persistValue = useCallback(\n async (key: string, value: string) => {\n setSaving(key);\n setError(null);\n setSuccess(null);\n const trimmed = value.trim();\n try {\n if (trimmed) {\n await setAgentConfigValue(agent, key, trimmed);\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n // Mirror the persisted result into savedValues so the auth-\n // satisfied banner / Continue button update immediately.\n setSavedValues((cur) => ({ ...cur, [key]: trimmed }));\n setSuccess(\n trimmed\n ? tr('ai.providerCredentials.saved', `${key} saved`)\n : tr('ai.providerCredentials.cleared', `${key} cleared`)\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.providerCredentials.saveFailed', 'Failed to save credential')\n );\n } finally {\n setSaving(null);\n }\n },\n [agent, tr]\n );\n\n return (\n <div className=\"rounded-xl border border-border-default bg-bg-surface p-4 space-y-3\">\n <div className=\"space-y-1\">\n <h4 className=\"text-sm font-semibold text-text-primary\">{provider.label}</h4>\n <p className=\"text-xs text-text-secondary\">{provider.description}</p>\n {primaryKeys.length > 1 ? (\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'ai.providerCredentials.eitherOr',\n `Save any one of: ${primaryKeys.join(' · ')}. You don't need to fill all of them.`\n )}\n </p>\n ) : null}\n </div>\n\n {/* Auth-satisfied banner — surfaces the moment any primary credential\n is persisted on the agent. The SDK-auth-missing modal pairs this\n with a Continue button so the user knows they can proceed. */}\n {authSatisfied ? (\n <div className=\"rounded-lg border border-status-success-border bg-status-success-bg px-3 py-2 text-xs text-status-success-text\">\n {tr(\n 'ai.providerCredentials.authSatisfied',\n `${provider.label} is ready. The agent will use ${satisfiedPrimaryKeys.join(' / ')} on the next SDK session.`\n )}\n </div>\n ) : null}\n\n {error ? (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n {success ? (\n <div className=\"rounded-lg border border-status-success-border bg-status-success-bg px-3 py-2 text-sm text-status-success-text\">\n {success}\n </div>\n ) : null}\n\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr('ai.providerCredentials.loading', 'Loading…')}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {provider.fields.map((f) => (\n <CredentialField\n key={f.key}\n label={f.key}\n secret={f.secret ?? false}\n value={values[f.key] ?? ''}\n onChange={(v) => setValues((cur) => ({ ...cur, [f.key]: v }))}\n saving={saving === f.key}\n visible={visible[f.key] ?? false}\n onToggleVisible={() => setVisible((cur) => ({ ...cur, [f.key]: !cur[f.key] }))}\n onSave={() => void persistValue(f.key, values[f.key] ?? '')}\n onClear={() => void persistValue(f.key, '')}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\ninterface CredentialFieldProps {\n label: string;\n secret: boolean;\n value: string;\n onChange: (value: string) => void;\n saving: boolean;\n visible: boolean;\n onToggleVisible: () => void;\n onSave: () => void;\n onClear: () => void;\n}\n\nfunction CredentialField({\n label,\n secret,\n value,\n onChange,\n saving,\n visible,\n onToggleVisible,\n onSave,\n onClear,\n}: CredentialFieldProps) {\n const showAsPassword = secret && !visible;\n return (\n <div className=\"space-y-2 rounded-lg border border-border-default bg-bg-primary p-3\">\n <label className=\"block text-xs font-medium text-text-secondary\">{label}</label>\n <div className=\"flex gap-2\">\n <div className=\"relative flex-1\">\n <input\n type={showAsPassword ? 'password' : 'text'}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 pr-10 text-sm text-text-primary\"\n />\n {secret ? (\n <button\n type=\"button\"\n onClick={onToggleVisible}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 text-text-secondary hover:text-text-primary\"\n >\n {visible ? <EyeOff className=\"h-4 w-4\" /> : <Eye className=\"h-4 w-4\" />}\n </button>\n ) : null}\n </div>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n Save\n </button>\n <button\n type=\"button\"\n onClick={onClear}\n disabled={saving || !value}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-default px-3 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:border-border-strong disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Clear\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AIProviderCredentialsCard.js","names":[],"sources":["../../../src/components/ai/AIProviderCredentialsCard.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport {\n ArrowLeft,\n ChevronRight,\n Eye,\n EyeOff,\n KeyRound,\n Loader2,\n Save,\n Trash2,\n} from 'lucide-react';\n\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n deleteAgentConfigValue,\n getAgentConfigValue,\n setAgentConfigValue,\n type AgentRef,\n} from '@/services/aiApi';\n\n/**\n * One provider definition — what env-var-style keys the agent stores under\n * its config bag and what to call the provider in the UI. Kept as a flat\n * static list rather than discovered from the plugin manifest because the\n * config keys are part of the upstream SDK contract (ANTHROPIC_API_KEY,\n * OPENAI_API_KEY, etc.) and don't change with each plugin release.\n */\nexport interface AIProviderCredentialDef {\n /** Stable provider id — usually matches the plugin's pluginName. */\n providerId: string;\n /** Display name shown in the card heading. */\n label: string;\n /** Short helper text under the heading. */\n description: string;\n /**\n * One row per key. The `kind` distinguishes a primary credential (for\n * which the SDK refuses to start without ONE of the listed keys) from\n * an optional URL/host override.\n */\n fields: Array<{\n /** Agent-config key name, also rendered as the input label. */\n key: string;\n /**\n * Whether the field is a secret (rendered as password with eye toggle)\n * or a plain string (URL / host overrides — visible by default).\n */\n secret?: boolean;\n /**\n * Whether saving this field alone (or any sibling field with\n * primary=true) is enough to authenticate the provider. The card\n * surfaces an \"at least one set\" indicator using this — used by the\n * SDK-auth-missing modal to enable the Continue button as soon as\n * either ANTHROPIC_API_KEY *or* ANTHROPIC_AUTH_TOKEN is saved.\n *\n * Defaults to `true` for secret fields and `false` for plain-text\n * overrides (URLs/hosts). Set explicitly when a secret field is\n * supplementary rather than authentication-conferring.\n */\n primary?: boolean;\n }>;\n}\n\n/**\n * Catalog of every AI provider whose plugin reads credentials from the\n * agent's config bag (POST /api/config). CLI-only providers (Aider, Amp,\n * Copilot, Crush, Goose, Plandex, Pi) authenticate via their own harness\n * binary (~/.claude/, ~/.config/opencode/ etc.) — Bridle handles those\n * and they don't appear here.\n */\nexport const AI_PROVIDER_CREDENTIALS: AIProviderCredentialDef[] = [\n {\n providerId: 'claude',\n label: 'Anthropic Claude',\n description:\n 'Claude SDK sessions require either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN on the agent. Save one of them to make SDK sessions work.',\n fields: [\n { key: 'ANTHROPIC_API_KEY', secret: true },\n { key: 'ANTHROPIC_AUTH_TOKEN', secret: true },\n ],\n },\n {\n providerId: 'codex',\n label: 'OpenAI Codex',\n description:\n 'Codex SDK / CLI sessions read OPENAI_API_KEY from the agent. Set it here to enable Codex-backed sessions.',\n fields: [{ key: 'OPENAI_API_KEY', secret: true }],\n },\n {\n providerId: 'gemini',\n label: 'Google Gemini',\n description:\n 'Gemini SDK reads GEMINI_API_KEY (preferred) or GOOGLE_API_KEY. Either one works — both shown so an existing GOOGLE_API_KEY in your environment keeps working.',\n fields: [\n { key: 'GEMINI_API_KEY', secret: true },\n { key: 'GOOGLE_API_KEY', secret: true },\n ],\n },\n {\n providerId: 'openai-compat',\n label: 'OpenAI-Compatible',\n description:\n 'Generic OpenAI-compatible provider (Together, Groq, Mistral, etc.). Set the API key and the base URL the SDK should hit.',\n fields: [{ key: 'OPENAI_COMPAT_API_KEY', secret: true }, { key: 'OPENAI_COMPAT_BASE_URL' }],\n },\n {\n providerId: 'cursor',\n label: 'Cursor',\n description:\n 'Cursor agent (CLI + SDK) reads CURSOR_API_KEY from the agent. Set it here to enable both modes.',\n fields: [{ key: 'CURSOR_API_KEY', secret: true }],\n },\n {\n providerId: 'openrouter',\n label: 'OpenRouter',\n description: 'OpenRouter aggregator (300+ models, SDK only). Set OPENROUTER_API_KEY to enable.',\n fields: [{ key: 'OPENROUTER_API_KEY', secret: true }],\n },\n {\n providerId: 'minimax',\n label: 'Minimax',\n description:\n 'Minimax provider (CLI + Anthropic-compatible SDK). Set MINIMAX_API_KEY to enable.',\n fields: [{ key: 'MINIMAX_API_KEY', secret: true }],\n },\n {\n providerId: 'ollama',\n label: 'Ollama',\n description:\n 'Ollama Cloud + self-hosted (CLI + SDK). OLLAMA_API_KEY for Cloud; OLLAMA_HOST optional override (defaults: https://ollama.com when key present, http://localhost:11434 otherwise).',\n fields: [{ key: 'OLLAMA_API_KEY', secret: true }, { key: 'OLLAMA_HOST' }],\n },\n];\n\ninterface AIProviderCredentialsCardProps {\n agent: AgentRef;\n /**\n * Optional filter — when present, only render providers whose providerId\n * appears in the list. Lets a future caller scope the card to \"only the\n * providers I have plugins installed for\" without changing the catalog.\n */\n providerIds?: string[];\n /**\n * When provided, the staged picker preselects this provider so the\n * caller (e.g. the SDK-auth-missing modal) can drop the user straight\n * onto the right form without an extra click.\n */\n initialProviderId?: string;\n /**\n * Fires whenever the currently-selected provider's saved-state changes\n * on disk. `satisfied` is true when at least one primary (auth-conferring)\n * field has a saved non-empty value. Used by the SDK-auth-missing modal\n * to enable a Continue affordance the moment the user persists any\n * acceptable credential, without forcing them to also fill the others.\n */\n onProviderAuthSatisfiedChange?: (satisfied: boolean) => void;\n}\n\n/**\n * Staged credentials manager: shows a provider picker first, then on\n * select renders one card with that provider's config keys plus a back\n * button. Keeps the rendered DOM small (and the surrounding modal\n * viewport-friendly) regardless of how many providers are in the catalog.\n *\n * Replaces the older Claude-only ClaudeCredentialsCard.\n */\nexport function AIProviderCredentialsCard({\n agent,\n providerIds,\n initialProviderId,\n onProviderAuthSatisfiedChange,\n}: AIProviderCredentialsCardProps) {\n const tr = useTr();\n const providers = providerIds\n ? AI_PROVIDER_CREDENTIALS.filter((p) => providerIds.includes(p.providerId))\n : AI_PROVIDER_CREDENTIALS;\n\n // Preselect when the catalog narrows to exactly one provider so the\n // single-provider case (e.g. claude-only filter) skips the picker.\n const [selectedId, setSelectedId] = useState<string | null>(() => {\n if (initialProviderId && providers.some((p) => p.providerId === initialProviderId)) {\n return initialProviderId;\n }\n return providers.length === 1 ? (providers[0]?.providerId ?? null) : null;\n });\n\n const selected = providers.find((p) => p.providerId === selectedId) ?? null;\n\n if (!selected) {\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"rounded-lg bg-bg-sunken p-2 text-text-secondary\">\n <KeyRound className=\"h-4 w-4\" />\n </div>\n <div className=\"space-y-1\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('ai.providerCredentials.pickTitle', 'Choose a provider')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'ai.providerCredentials.pickDescription',\n 'Pick the provider whose credentials you want to manage. Keys are stored on the agent and read at session start.'\n )}\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {providers.map((p) => (\n <button\n key={p.providerId}\n type=\"button\"\n onClick={() => setSelectedId(p.providerId)}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-left hover:border-border-strong hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"min-w-0\">\n <div className=\"text-sm font-medium text-text-primary truncate\">{p.label}</div>\n <div className=\"text-xs text-text-secondary truncate\">\n {p.fields.map((f) => f.key).join(', ')}\n </div>\n </div>\n <ChevronRight className=\"h-4 w-4 flex-shrink-0 text-text-secondary\" />\n </button>\n ))}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n {providers.length > 1 ? (\n <button\n type=\"button\"\n onClick={() => setSelectedId(null)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-2 py-1 text-xs text-text-secondary hover:text-text-primary hover:border-border-strong\"\n aria-label={tr('ai.providerCredentials.back', 'Back to providers')}\n >\n <ArrowLeft className=\"h-3.5 w-3.5\" />\n {tr('ai.providerCredentials.back', 'Back')}\n </button>\n ) : null}\n <div className=\"rounded-lg bg-bg-sunken p-2 text-text-secondary\">\n <KeyRound className=\"h-4 w-4\" />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary truncate\">{selected.label}</h3>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'ai.providerCredentials.scopedDescription',\n 'Keys are stored on the agent and read at session start.'\n )}\n </p>\n </div>\n </div>\n <ProviderCard\n agent={agent}\n provider={selected}\n onAuthSatisfiedChange={onProviderAuthSatisfiedChange}\n />\n </div>\n );\n}\n\nfunction isPrimaryField(f: AIProviderCredentialDef['fields'][number]): boolean {\n // Defaults: secret fields gate auth; URL/host overrides don't.\n return f.primary ?? f.secret ?? false;\n}\n\nfunction ProviderCard({\n agent,\n provider,\n onAuthSatisfiedChange,\n}: {\n agent: AgentRef;\n provider: AIProviderCredentialDef;\n onAuthSatisfiedChange?: (satisfied: boolean) => void;\n}) {\n const tr = useTr();\n const [values, setValues] = useState<Record<string, string>>({});\n // savedValues tracks what's actually on the agent (last successful\n // load/persist), not the unsaved input buffer. This is what drives the\n // \"auth satisfied\" indicator — typing an API key in the field doesn't\n // count until the user hits Save.\n const [savedValues, setSavedValues] = useState<Record<string, string>>({});\n const [visible, setVisible] = useState<Record<string, boolean>>({});\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState<string | null>(null);\n\n const primaryKeys = provider.fields.filter(isPrimaryField).map((f) => f.key);\n const satisfiedPrimaryKeys = primaryKeys.filter((k) => (savedValues[k] ?? '').trim().length > 0);\n const authSatisfied = satisfiedPrimaryKeys.length > 0;\n\n // Notify parent whenever satisfied flips. Effect (not inline call) so\n // children don't render-during-render of the parent.\n useEffect(() => {\n onAuthSatisfiedChange?.(authSatisfied);\n }, [authSatisfied, onAuthSatisfiedChange]);\n\n const loadValues = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const entries = await Promise.all(\n provider.fields.map(async (f) => [f.key, (await getAgentConfigValue(agent, f.key)) ?? ''])\n );\n const next = Object.fromEntries(entries);\n // Don't seed inputs with masked secrets (\"[redacted]\") — Save would re-persist the mask. Non-secret fields (URLs/hosts) return real values, so keep those editable.\n setValues(\n Object.fromEntries(\n provider.fields.filter((f) => !f.secret).map((f) => [f.key, next[f.key] ?? ''])\n )\n );\n setSavedValues(next);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.providerCredentials.loadFailed', 'Failed to load provider credentials')\n );\n } finally {\n setLoading(false);\n }\n }, [agent, provider.fields, tr]);\n\n useEffect(() => {\n void loadValues();\n }, [loadValues]);\n\n const persistValue = useCallback(\n async (key: string, value: string) => {\n setSaving(key);\n setError(null);\n setSuccess(null);\n const trimmed = value.trim();\n try {\n if (trimmed) {\n await setAgentConfigValue(agent, key, trimmed);\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n // Mirror the persisted result into savedValues so the auth-\n // satisfied banner / Continue button update immediately.\n setSavedValues((cur) => ({ ...cur, [key]: trimmed }));\n setSuccess(\n trimmed\n ? tr('ai.providerCredentials.saved', `${key} saved`)\n : tr('ai.providerCredentials.cleared', `${key} cleared`)\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.providerCredentials.saveFailed', 'Failed to save credential')\n );\n } finally {\n setSaving(null);\n }\n },\n [agent, tr]\n );\n\n return (\n <div className=\"rounded-xl border border-border-default bg-bg-surface p-4 space-y-3\">\n <div className=\"space-y-1\">\n <h4 className=\"text-sm font-semibold text-text-primary\">{provider.label}</h4>\n <p className=\"text-xs text-text-secondary\">{provider.description}</p>\n {primaryKeys.length > 1 ? (\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'ai.providerCredentials.eitherOr',\n `Save any one of: ${primaryKeys.join(' · ')}. You don't need to fill all of them.`\n )}\n </p>\n ) : null}\n </div>\n\n {/* Auth-satisfied banner — surfaces the moment any primary credential\n is persisted on the agent. The SDK-auth-missing modal pairs this\n with a Continue button so the user knows they can proceed. */}\n {authSatisfied ? (\n <div className=\"rounded-lg border border-status-success-border bg-status-success-bg px-3 py-2 text-xs text-status-success-text\">\n {tr(\n 'ai.providerCredentials.authSatisfied',\n `${provider.label} is ready. The agent will use ${satisfiedPrimaryKeys.join(' / ')} on the next SDK session.`\n )}\n </div>\n ) : null}\n\n {error ? (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n {success ? (\n <div className=\"rounded-lg border border-status-success-border bg-status-success-bg px-3 py-2 text-sm text-status-success-text\">\n {success}\n </div>\n ) : null}\n\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr('ai.providerCredentials.loading', 'Loading…')}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {provider.fields.map((f) => {\n const isSecret = f.secret ?? false;\n const isSaved = Boolean((savedValues[f.key] ?? '').trim());\n return (\n <CredentialField\n key={f.key}\n label={f.key}\n secret={isSecret}\n value={values[f.key] ?? ''}\n saved={isSaved}\n placeholder={\n isSaved\n ? isSecret\n ? tr(\n 'ai.providerCredentials.storedSecretPlaceholder',\n '•••••••• stored — enter a new value to replace'\n )\n : tr(\n 'ai.providerCredentials.storedPlaceholder',\n 'stored — enter a new value to replace'\n )\n : ''\n }\n onChange={(v) => setValues((cur) => ({ ...cur, [f.key]: v }))}\n saving={saving === f.key}\n visible={visible[f.key] ?? false}\n onToggleVisible={() => setVisible((cur) => ({ ...cur, [f.key]: !cur[f.key] }))}\n onSave={() => void persistValue(f.key, values[f.key] ?? '')}\n onClear={() => void persistValue(f.key, '')}\n />\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\ninterface CredentialFieldProps {\n label: string;\n secret: boolean;\n value: string;\n /** True when a value is already stored on the agent (read back masked). */\n saved: boolean;\n /** Resolved (i18n) placeholder shown when the input is empty but a value is stored. */\n placeholder: string;\n onChange: (value: string) => void;\n saving: boolean;\n visible: boolean;\n onToggleVisible: () => void;\n onSave: () => void;\n onClear: () => void;\n}\n\nfunction CredentialField({\n label,\n secret,\n value,\n saved,\n placeholder,\n onChange,\n saving,\n visible,\n onToggleVisible,\n onSave,\n onClear,\n}: CredentialFieldProps) {\n const showAsPassword = secret && !visible;\n return (\n <div className=\"space-y-2 rounded-lg border border-border-default bg-bg-primary p-3\">\n <label className=\"block text-xs font-medium text-text-secondary\">{label}</label>\n <div className=\"flex gap-2\">\n <div className=\"relative flex-1\">\n <input\n type={showAsPassword ? 'password' : 'text'}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n placeholder={placeholder}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 pr-10 text-sm text-text-primary\"\n />\n {secret ? (\n <button\n type=\"button\"\n onClick={onToggleVisible}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 text-text-secondary hover:text-text-primary\"\n >\n {visible ? <EyeOff className=\"h-4 w-4\" /> : <Eye className=\"h-4 w-4\" />}\n </button>\n ) : null}\n </div>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving || !value.trim()}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n Save\n </button>\n <button\n type=\"button\"\n onClick={onClear}\n disabled={saving || (!value && !saved)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-default px-3 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:border-border-strong disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Clear\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -40,12 +40,14 @@ function o(r) {
40
40
  },
41
41
  loading: l,
42
42
  error: u ?? null,
43
- refetch: () => {
44
- d();
43
+ refetch: async () => {
44
+ try {
45
+ await d();
46
+ } catch {}
45
47
  }
46
48
  };
47
49
  }
48
50
  //#endregion
49
- export { a as VIBECONTROLS_AGENT_PROFILES_QUERY, o as useActiveAgentProfile };
51
+ export { o as useActiveAgentProfile };
50
52
 
51
53
  //# sourceMappingURL=useActiveAgentProfile.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useActiveAgentProfile.js","names":[],"sources":["../../src/hooks/useActiveAgentProfile.ts"],"sourcesContent":["/**\n * useActiveAgentProfile — Phase 5B (multi-profile-per-daemon).\n *\n * Lists `AgentProfile` rows for an agent and tracks the currently-active\n * profile name in the VibeControls Zustand store (`activeProfilePerAgent`).\n *\n * Active-selection rules:\n * 1. If the user has explicitly selected a profile for this agent, use it.\n * 2. Otherwise, fall back to the profile flagged `isDefault`.\n * 3. Otherwise, fall back to the first profile in the list.\n * 4. Otherwise, fall back to the literal string \"default\" so callers can\n * build canonical paths even before any AgentProfile rows exist\n * (server-side compatibility shim).\n *\n * NOTE: The Apollo `gql` document is inlined here (not generated) so this\n * hook compiles cleanly even before the Phase 5A svc work has published\n * the AgentProfile schema to Hive. Once published, the inline document\n * can be replaced with the generated `useVibecontrolsAgentProfilesQuery`\n * hook without touching callsites.\n */\n\nimport { useEffect, useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\n\nexport interface AgentProfile {\n id: string;\n agentId: string;\n name: string;\n isDefault: boolean;\n port: number | null;\n status: string | null;\n tunnelUrl: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface VibecontrolsAgentProfilesData {\n vibecontrolsAgentProfiles: AgentProfile[];\n}\n\ninterface VibecontrolsAgentProfilesVars {\n agentId: string;\n}\n\nconst VIBECONTROLS_AGENT_PROFILES_QUERY = gql`\n query VibecontrolsAgentProfiles($agentId: ID!) {\n vibecontrolsAgentProfiles(agentId: $agentId) {\n id\n agentId\n name\n isDefault\n port\n status\n tunnelUrl\n createdAt\n updatedAt\n }\n }\n`;\n\nexport interface UseActiveAgentProfileResult {\n /** All profiles for the agent (empty until svc Phase 5A is live). */\n profiles: AgentProfile[];\n /** The currently-selected profile name for this agent. */\n active: string;\n /** Persist a new active profile for this agent into the store. */\n setActive: (profile: string) => void;\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useActiveAgentProfile(\n agentId: string | null | undefined\n): UseActiveAgentProfileResult {\n const setActiveProfile = useVibeControlsStore((s) => s.setActiveProfile);\n const storedActive = useVibeControlsStore((s) =>\n agentId ? (s.activeProfilePerAgent[agentId] ?? null) : null\n );\n\n const { data, loading, error, refetch } = useQuery<\n VibecontrolsAgentProfilesData,\n VibecontrolsAgentProfilesVars\n >(VIBECONTROLS_AGENT_PROFILES_QUERY, {\n variables: { agentId: agentId ?? '' },\n skip: !agentId,\n fetchPolicy: 'cache-and-network',\n // The svc Phase 5A schema may not be published yet; swallow schema\n // errors so consumers still render with the canonical \"default\"\n // fallback instead of the whole tree blowing up.\n errorPolicy: 'ignore',\n });\n\n const profiles = useMemo<AgentProfile[]>(() => data?.vibecontrolsAgentProfiles ?? [], [data]);\n\n const defaultProfile = useMemo<AgentProfile | null>(() => {\n if (profiles.length === 0) return null;\n return profiles.find((p) => p.isDefault) ?? profiles[0] ?? null;\n }, [profiles]);\n\n const active: string = storedActive ?? defaultProfile?.name ?? 'default';\n\n // Auto-commit the resolved default into the store the first time we\n // see profiles for this agent so downstream readers (which prefer\n // store-first reads via `getActiveProfile`) match the hook's result.\n useEffect(() => {\n if (!agentId) return;\n if (storedActive) return;\n if (defaultProfile && defaultProfile.name !== 'default') {\n setActiveProfile(agentId, defaultProfile.name);\n }\n }, [agentId, storedActive, defaultProfile, setActiveProfile]);\n\n const setActive = (profile: string): void => {\n if (!agentId) return;\n setActiveProfile(agentId, profile);\n };\n\n return {\n profiles,\n active,\n setActive,\n loading,\n error: (error as Error | undefined) ?? null,\n refetch: () => {\n void refetch();\n },\n };\n}\n\nexport { VIBECONTROLS_AGENT_PROFILES_QUERY };\n"],"mappings":";;;;;AA8CA,IAAM,IAAoC,CAAG;;;;;;;;;;;;;;;AA4B7C,SAAgB,EACd,GAC6B;CAC7B,IAAM,IAAmB,GAAsB,MAAM,EAAE,iBAAiB,EAClE,IAAe,GAAsB,MACzC,IAAW,EAAE,sBAAsB,MAAY,OAAQ,KACxD,EAEK,EAAE,SAAM,YAAS,UAAO,eAAY,EAGxC,GAAmC;EACnC,WAAW,EAAE,SAAS,KAAW,IAAI;EACrC,MAAM,CAAC;EACP,aAAa;EAIb,aAAa;EACd,CAAC,EAEI,IAAW,QAA8B,GAAM,6BAA6B,EAAE,EAAE,CAAC,EAAK,CAAC,EAEvF,IAAiB,QACjB,EAAS,WAAW,IAAU,OAC3B,EAAS,MAAM,MAAM,EAAE,UAAU,IAAI,EAAS,MAAM,MAC1D,CAAC,EAAS,CAAC,EAER,IAAiB,KAAgB,GAAgB,QAAQ;AAkB/D,QAbA,QAAgB;AACT,QACD,KACA,KAAkB,EAAe,SAAS,aAC5C,EAAiB,GAAS,EAAe,KAAK;IAE/C;EAAC;EAAS;EAAc;EAAgB;EAAiB,CAAC,EAOtD;EACL;EACA;EACA,YARiB,MAA0B;AACtC,QACL,EAAiB,GAAS,EAAQ;;EAOlC;EACA,OAAQ,KAA+B;EACvC,eAAe;AACR,MAAS;;EAEjB"}
1
+ {"version":3,"file":"useActiveAgentProfile.js","names":[],"sources":["../../src/hooks/useActiveAgentProfile.ts"],"sourcesContent":["/**\n * useActiveAgentProfile — Phase 5B (multi-profile-per-daemon).\n *\n * Lists `AgentProfile` rows for an agent and tracks the currently-active\n * profile name in the VibeControls Zustand store (`activeProfilePerAgent`).\n *\n * Active-selection rules:\n * 1. If the user has explicitly selected a profile for this agent, use it.\n * 2. Otherwise, fall back to the profile flagged `isDefault`.\n * 3. Otherwise, fall back to the first profile in the list.\n * 4. Otherwise, fall back to the literal string \"default\" so callers can\n * build canonical paths even before any AgentProfile rows exist\n * (server-side compatibility shim).\n *\n * NOTE: The Apollo `gql` document is inlined here (not generated) so this\n * hook compiles cleanly even before the Phase 5A svc work has published\n * the AgentProfile schema to Hive. Once published, the inline document\n * can be replaced with the generated `useVibecontrolsAgentProfilesQuery`\n * hook without touching callsites.\n */\n\nimport { useEffect, useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\n\nexport interface AgentProfile {\n id: string;\n agentId: string;\n name: string;\n isDefault: boolean;\n port: number | null;\n status: string | null;\n tunnelUrl: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface VibecontrolsAgentProfilesData {\n vibecontrolsAgentProfiles: AgentProfile[];\n}\n\ninterface VibecontrolsAgentProfilesVars {\n agentId: string;\n}\n\nconst VIBECONTROLS_AGENT_PROFILES_QUERY = gql`\n query VibecontrolsAgentProfiles($agentId: ID!) {\n vibecontrolsAgentProfiles(agentId: $agentId) {\n id\n agentId\n name\n isDefault\n port\n status\n tunnelUrl\n createdAt\n updatedAt\n }\n }\n`;\n\nexport interface UseActiveAgentProfileResult {\n /** All profiles for the agent (empty until svc Phase 5A is live). */\n profiles: AgentProfile[];\n /** The currently-selected profile name for this agent. */\n active: string;\n /** Persist a new active profile for this agent into the store. */\n setActive: (profile: string) => void;\n loading: boolean;\n error: Error | null;\n /**\n * Re-fetch the profile list. Resolves when the round-trip settles and\n * never rejects (failures are swallowed), so it is safe to call\n * fire-and-forget.\n */\n refetch: () => Promise<void>;\n}\n\nexport function useActiveAgentProfile(\n agentId: string | null | undefined\n): UseActiveAgentProfileResult {\n const setActiveProfile = useVibeControlsStore((s) => s.setActiveProfile);\n const storedActive = useVibeControlsStore((s) =>\n agentId ? (s.activeProfilePerAgent[agentId] ?? null) : null\n );\n\n const { data, loading, error, refetch } = useQuery<\n VibecontrolsAgentProfilesData,\n VibecontrolsAgentProfilesVars\n >(VIBECONTROLS_AGENT_PROFILES_QUERY, {\n variables: { agentId: agentId ?? '' },\n skip: !agentId,\n fetchPolicy: 'cache-and-network',\n // The svc Phase 5A schema may not be published yet; swallow schema\n // errors so consumers still render with the canonical \"default\"\n // fallback instead of the whole tree blowing up.\n errorPolicy: 'ignore',\n });\n\n const profiles = useMemo<AgentProfile[]>(() => data?.vibecontrolsAgentProfiles ?? [], [data]);\n\n const defaultProfile = useMemo<AgentProfile | null>(() => {\n if (profiles.length === 0) return null;\n return profiles.find((p) => p.isDefault) ?? profiles[0] ?? null;\n }, [profiles]);\n\n const active: string = storedActive ?? defaultProfile?.name ?? 'default';\n\n // Auto-commit the resolved default into the store the first time we\n // see profiles for this agent so downstream readers (which prefer\n // store-first reads via `getActiveProfile`) match the hook's result.\n useEffect(() => {\n if (!agentId) return;\n if (storedActive) return;\n if (defaultProfile && defaultProfile.name !== 'default') {\n setActiveProfile(agentId, defaultProfile.name);\n }\n }, [agentId, storedActive, defaultProfile, setActiveProfile]);\n\n const setActive = (profile: string): void => {\n if (!agentId) return;\n setActiveProfile(agentId, profile);\n };\n\n return {\n profiles,\n active,\n setActive,\n loading,\n error: (error as Error | undefined) ?? null,\n // Non-throwing by contract: refetch failures are swallowed here so no\n // caller — including fire-and-forget callsites like the Profiles \"Refresh\"\n // button — can produce an unhandled promise rejection. The list simply\n // stays stale until the next successful fetch.\n refetch: async () => {\n try {\n await refetch();\n } catch {\n /* swallowed — see above */\n }\n },\n };\n}\n"],"mappings":";;;;;AA8CA,IAAM,IAAoC,CAAG;;;;;;;;;;;;;;;AAiC7C,SAAgB,EACd,GAC6B;CAC7B,IAAM,IAAmB,GAAsB,MAAM,EAAE,iBAAiB,EAClE,IAAe,GAAsB,MACzC,IAAW,EAAE,sBAAsB,MAAY,OAAQ,KACxD,EAEK,EAAE,SAAM,YAAS,UAAO,eAAY,EAGxC,GAAmC;EACnC,WAAW,EAAE,SAAS,KAAW,IAAI;EACrC,MAAM,CAAC;EACP,aAAa;EAIb,aAAa;EACd,CAAC,EAEI,IAAW,QAA8B,GAAM,6BAA6B,EAAE,EAAE,CAAC,EAAK,CAAC,EAEvF,IAAiB,QACjB,EAAS,WAAW,IAAU,OAC3B,EAAS,MAAM,MAAM,EAAE,UAAU,IAAI,EAAS,MAAM,MAC1D,CAAC,EAAS,CAAC,EAER,IAAiB,KAAgB,GAAgB,QAAQ;AAkB/D,QAbA,QAAgB;AACT,QACD,KACA,KAAkB,EAAe,SAAS,aAC5C,EAAiB,GAAS,EAAe,KAAK;IAE/C;EAAC;EAAS;EAAc;EAAgB;EAAiB,CAAC,EAOtD;EACL;EACA;EACA,YARiB,MAA0B;AACtC,QACL,EAAiB,GAAS,EAAQ;;EAOlC;EACA,OAAQ,KAA+B;EAKvC,SAAS,YAAY;AACnB,OAAI;AACF,UAAM,GAAS;WACT;;EAIX"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-vibecontrols",
3
- "version": "2026.524.8",
3
+ "version": "2026.525.2",
4
4
  "description": "VibeControls microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [