@burdenoff/microfe-vibecontrols 2026.525.1 → 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 {
|
|
2
|
+
import { useActiveAgentProfile as t } from "../../hooks/useActiveAgentProfile.js";
|
|
3
3
|
import "../shared/index.js";
|
|
4
|
-
import { useCallback as
|
|
5
|
-
import { CheckCircle2 as
|
|
6
|
-
import { gql as
|
|
7
|
-
import { useMutation as
|
|
8
|
-
import { jsx as
|
|
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
|
|
11
|
-
mutation CreateVibecontrolsAgentProfile($input:
|
|
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
|
-
`,
|
|
19
|
+
`, h = u`
|
|
20
20
|
mutation DeleteVibecontrolsAgentProfile($id: ID!) {
|
|
21
21
|
deleteVibecontrolsAgentProfile(id: $id)
|
|
22
22
|
}
|
|
23
|
-
`,
|
|
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
|
|
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
|
|
36
|
-
let { profiles:
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
40
|
+
A("Profile name is required");
|
|
59
41
|
return;
|
|
60
42
|
}
|
|
61
|
-
let t =
|
|
62
|
-
if (
|
|
63
|
-
|
|
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
|
|
68
|
-
agentId:
|
|
49
|
+
await j({ variables: { input: {
|
|
50
|
+
agentId: u,
|
|
69
51
|
name: e,
|
|
70
52
|
port: t
|
|
71
|
-
} } })
|
|
53
|
+
} } });
|
|
72
54
|
} catch (e) {
|
|
73
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
}, [
|
|
87
|
-
|
|
75
|
+
}, [N, S]), z = n(async (e) => {
|
|
76
|
+
A(null);
|
|
88
77
|
try {
|
|
89
|
-
await
|
|
78
|
+
await F({ variables: { id: e.id } });
|
|
90
79
|
} catch (e) {
|
|
91
|
-
|
|
80
|
+
A(e instanceof Error ? e.message : "Failed to set default profile");
|
|
81
|
+
return;
|
|
92
82
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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__ */
|
|
88
|
+
children: [/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h3", {
|
|
98
89
|
className: "font-semibold text-text-primary",
|
|
99
90
|
children: "Profiles"
|
|
100
|
-
}), /* @__PURE__ */
|
|
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__ */
|
|
96
|
+
/* @__PURE__ */ f("code", {
|
|
106
97
|
className: "font-mono text-text-primary",
|
|
107
|
-
children:
|
|
98
|
+
children: y
|
|
108
99
|
})
|
|
109
100
|
]
|
|
110
|
-
})] }), /* @__PURE__ */
|
|
101
|
+
})] }), /* @__PURE__ */ p("div", {
|
|
111
102
|
className: "flex items-center gap-2",
|
|
112
|
-
children: [/* @__PURE__ */
|
|
103
|
+
children: [/* @__PURE__ */ f("button", {
|
|
113
104
|
type: "button",
|
|
114
|
-
onClick: () =>
|
|
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__ */
|
|
108
|
+
}), /* @__PURE__ */ p("button", {
|
|
118
109
|
type: "button",
|
|
119
|
-
onClick: () =>
|
|
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__ */
|
|
112
|
+
children: [/* @__PURE__ */ f(o, { className: "w-4 h-4" }), "New profile"]
|
|
122
113
|
})]
|
|
123
114
|
})]
|
|
124
115
|
}),
|
|
125
|
-
|
|
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:
|
|
118
|
+
children: k
|
|
128
119
|
}),
|
|
129
|
-
|
|
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__ */
|
|
122
|
+
children: [/* @__PURE__ */ p("div", {
|
|
132
123
|
className: "grid grid-cols-2 gap-3",
|
|
133
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
127
|
+
}), /* @__PURE__ */ f("input", {
|
|
137
128
|
type: "text",
|
|
138
|
-
value:
|
|
139
|
-
onChange: (e) =>
|
|
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__ */
|
|
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__ */
|
|
137
|
+
}), /* @__PURE__ */ f("input", {
|
|
147
138
|
type: "number",
|
|
148
|
-
value:
|
|
149
|
-
onChange: (e) =>
|
|
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__ */
|
|
144
|
+
}), /* @__PURE__ */ p("div", {
|
|
154
145
|
className: "flex items-center gap-2",
|
|
155
|
-
children: [/* @__PURE__ */
|
|
146
|
+
children: [/* @__PURE__ */ p("button", {
|
|
156
147
|
type: "button",
|
|
157
|
-
disabled:
|
|
158
|
-
onClick: () => void
|
|
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: [
|
|
161
|
-
}), /* @__PURE__ */
|
|
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
|
-
|
|
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__ */
|
|
158
|
+
children: [/* @__PURE__ */ f(l, { className: "w-3.5 h-3.5" }), "Cancel"]
|
|
168
159
|
})]
|
|
169
160
|
})]
|
|
170
161
|
}),
|
|
171
|
-
|
|
162
|
+
x && v.length === 0 ? /* @__PURE__ */ p("div", {
|
|
172
163
|
className: "p-6 text-center text-text-secondary",
|
|
173
|
-
children: [/* @__PURE__ */
|
|
174
|
-
}) :
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
179
|
+
}) : /* @__PURE__ */ f("div", {
|
|
189
180
|
className: "divide-y divide-border-default",
|
|
190
|
-
children:
|
|
191
|
-
let t = e.name ===
|
|
192
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
185
|
+
children: [/* @__PURE__ */ p("div", {
|
|
195
186
|
className: "flex items-center gap-3 min-w-0",
|
|
196
|
-
children: [/* @__PURE__ */
|
|
197
|
-
className: `h-2 w-2 rounded-full flex-shrink-0 ${
|
|
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__ */
|
|
190
|
+
}), /* @__PURE__ */ p("div", {
|
|
200
191
|
className: "min-w-0",
|
|
201
|
-
children: [/* @__PURE__ */
|
|
192
|
+
children: [/* @__PURE__ */ p("div", {
|
|
202
193
|
className: "flex items-center gap-2",
|
|
203
194
|
children: [
|
|
204
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
201
|
+
children: [/* @__PURE__ */ f(s, { className: "w-3 h-3" }), "Default"]
|
|
211
202
|
}),
|
|
212
|
-
t && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
217
|
+
}), /* @__PURE__ */ p("div", {
|
|
227
218
|
className: "flex items-center gap-2 flex-shrink-0",
|
|
228
219
|
children: [
|
|
229
|
-
!t && /* @__PURE__ */
|
|
220
|
+
!t && /* @__PURE__ */ f("button", {
|
|
230
221
|
type: "button",
|
|
231
|
-
onClick: () =>
|
|
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__ */
|
|
226
|
+
!e.isDefault && /* @__PURE__ */ p("button", {
|
|
236
227
|
type: "button",
|
|
237
|
-
disabled:
|
|
238
|
-
onClick: () => void
|
|
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__ */
|
|
231
|
+
children: [/* @__PURE__ */ f(s, { className: "w-3 h-3" }), "Set default"]
|
|
241
232
|
}),
|
|
242
|
-
/* @__PURE__ */
|
|
233
|
+
/* @__PURE__ */ p("button", {
|
|
243
234
|
type: "button",
|
|
244
|
-
disabled:
|
|
235
|
+
disabled: B || e.isDefault,
|
|
245
236
|
title: e.isDefault ? "Cannot delete the default profile" : "Delete",
|
|
246
|
-
onClick: () => void
|
|
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__ */
|
|
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 {
|
|
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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"}
|
|
@@ -40,12 +40,14 @@ function o(r) {
|
|
|
40
40
|
},
|
|
41
41
|
loading: l,
|
|
42
42
|
error: u ?? null,
|
|
43
|
-
refetch: () => {
|
|
44
|
-
|
|
43
|
+
refetch: async () => {
|
|
44
|
+
try {
|
|
45
|
+
await d();
|
|
46
|
+
} catch {}
|
|
45
47
|
}
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
//#endregion
|
|
49
|
-
export {
|
|
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
|
|
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"}
|