@burdenoff/microfe-vibecontrols 2026.527.2 → 2026.527.3
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.
|
@@ -4,41 +4,45 @@ import "../shared/index.js";
|
|
|
4
4
|
import { configureAgentHook as r, getPlannotatorStatus as i, installPlannotatorBinary as a, listSupportedAgents as o } from "../../services/planApi.js";
|
|
5
5
|
import { useCallback as s, useEffect as c, useMemo as l, useRef as u, useState as d } from "react";
|
|
6
6
|
import { useI18n as f } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
7
|
-
import { AlertCircle as p, CheckCircle2 as m,
|
|
8
|
-
import { Fragment as
|
|
7
|
+
import { AlertCircle as p, CheckCircle2 as m, Inbox as h, Loader2 as g, PlayCircle as _, RefreshCcw as v, Settings as y, Wifi as b, WifiOff as x } from "lucide-react";
|
|
8
|
+
import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
9
9
|
//#region src/components/ai/AIPlanPanel.tsx
|
|
10
|
-
function
|
|
10
|
+
function T({ agent: r, agentApiKey: i, tunnelUrl: a }) {
|
|
11
11
|
let { t: o } = f(), s = (e, t) => {
|
|
12
12
|
let n = o(e);
|
|
13
13
|
return n === e ? t : n;
|
|
14
14
|
};
|
|
15
|
-
return /* @__PURE__ */
|
|
15
|
+
return /* @__PURE__ */ C(n, {
|
|
16
16
|
agentId: r.id,
|
|
17
17
|
required: [e, t],
|
|
18
18
|
mode: "all",
|
|
19
19
|
eyebrow: s("plan.pluginRequiredEyebrow", "Plan plugin required"),
|
|
20
20
|
title: s("plan.installPlanProviderTitle", "Install the plan provider"),
|
|
21
21
|
description: s("plan.installPlanProviderDesc", "Plan mode routes plan sessions through a plan provider plugin on your agent. Install the orchestrator and a provider (Plannotator) below to continue."),
|
|
22
|
-
children: /* @__PURE__ */
|
|
22
|
+
children: /* @__PURE__ */ C(E, {
|
|
23
23
|
agent: r,
|
|
24
24
|
agentApiKey: i,
|
|
25
25
|
tunnelUrl: a
|
|
26
26
|
})
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
function E({ agent: e, agentApiKey: t, tunnelUrl: n }) {
|
|
30
30
|
let { t: u } = f(), m = s((e, t) => {
|
|
31
31
|
let n = u(e);
|
|
32
32
|
return n === e ? t : n;
|
|
33
|
-
}, [u]), [
|
|
34
|
-
|
|
33
|
+
}, [u]), [h, _] = d(null), [y, b] = d(null), [x, S] = d(!0), [T, E] = d([]), [A, j] = d(!1), [M, N] = d(null), [P, F] = d(null), [I, L] = d(null), R = s(async (t = !1) => {
|
|
34
|
+
t || (S(!0), b(null));
|
|
35
35
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
if (t) {
|
|
37
|
+
_(await i(e));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let [n, r] = await Promise.all([i(e), o(e).catch(() => ({ agents: [] }))]);
|
|
41
|
+
_(n), E(r.agents ?? []);
|
|
38
42
|
} catch (e) {
|
|
39
|
-
b(e instanceof Error ? e.message : "Failed to load plan status");
|
|
43
|
+
t || b(e instanceof Error ? e.message : "Failed to load plan status");
|
|
40
44
|
} finally {
|
|
41
|
-
|
|
45
|
+
t || S(!1);
|
|
42
46
|
}
|
|
43
47
|
}, [e]);
|
|
44
48
|
c(() => {
|
|
@@ -72,35 +76,41 @@ function T({ agent: e, agentApiKey: t, tunnelUrl: n }) {
|
|
|
72
76
|
e,
|
|
73
77
|
R,
|
|
74
78
|
m
|
|
75
|
-
]), V = l(() => x ? "loading" : y ? "status-error" :
|
|
76
|
-
|
|
79
|
+
]), V = l(() => x ? "loading" : y ? "status-error" : h?.installed ? T.some((e) => e.hookConfigured) ? "ready" : "wizard" : "install-cli", [
|
|
80
|
+
h,
|
|
77
81
|
y,
|
|
78
82
|
x,
|
|
79
83
|
T
|
|
80
84
|
]);
|
|
81
|
-
return
|
|
85
|
+
return c(() => {
|
|
86
|
+
if (V !== "ready") return;
|
|
87
|
+
let e = setInterval(() => {
|
|
88
|
+
document.hidden || R(!0);
|
|
89
|
+
}, 5e3);
|
|
90
|
+
return () => clearInterval(e);
|
|
91
|
+
}, [V, R]), V === "loading" ? /* @__PURE__ */ w("div", {
|
|
82
92
|
className: "flex items-center gap-2 p-6 text-text-secondary",
|
|
83
|
-
children: [/* @__PURE__ */
|
|
84
|
-
}) : V === "status-error" ? /* @__PURE__ */
|
|
93
|
+
children: [/* @__PURE__ */ C(g, { className: "h-4 w-4 animate-spin" }), /* @__PURE__ */ C("span", { children: m("plan.loadingStatus", "Loading plan status…") })]
|
|
94
|
+
}) : V === "status-error" ? /* @__PURE__ */ C("div", {
|
|
85
95
|
className: "rounded-md border border-status-error-border bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
86
|
-
children: /* @__PURE__ */
|
|
96
|
+
children: /* @__PURE__ */ w("div", {
|
|
87
97
|
className: "flex items-start gap-2",
|
|
88
|
-
children: [/* @__PURE__ */
|
|
98
|
+
children: [/* @__PURE__ */ C(p, { className: "h-4 w-4 mt-0.5 shrink-0" }), /* @__PURE__ */ w("div", {
|
|
89
99
|
className: "flex-1",
|
|
90
100
|
children: [
|
|
91
|
-
/* @__PURE__ */
|
|
101
|
+
/* @__PURE__ */ C("p", {
|
|
92
102
|
className: "font-medium",
|
|
93
103
|
children: m("plan.statusError", "Could not load plan status from the agent.")
|
|
94
104
|
}),
|
|
95
|
-
/* @__PURE__ */
|
|
105
|
+
/* @__PURE__ */ C("p", {
|
|
96
106
|
className: "text-xs opacity-80 mt-1",
|
|
97
107
|
children: y
|
|
98
108
|
}),
|
|
99
|
-
/* @__PURE__ */
|
|
109
|
+
/* @__PURE__ */ w("button", {
|
|
100
110
|
onClick: () => void R(),
|
|
101
111
|
className: "mt-3 inline-flex items-center gap-1 text-xs font-medium hover:underline",
|
|
102
112
|
children: [
|
|
103
|
-
/* @__PURE__ */
|
|
113
|
+
/* @__PURE__ */ C(v, { className: "h-3 w-3" }),
|
|
104
114
|
" ",
|
|
105
115
|
m("plan.retry", "Retry")
|
|
106
116
|
]
|
|
@@ -108,113 +118,114 @@ function T({ agent: e, agentApiKey: t, tunnelUrl: n }) {
|
|
|
108
118
|
]
|
|
109
119
|
})]
|
|
110
120
|
})
|
|
111
|
-
}) : /* @__PURE__ */
|
|
121
|
+
}) : /* @__PURE__ */ w("div", {
|
|
112
122
|
className: "space-y-4",
|
|
113
123
|
children: [
|
|
114
|
-
P && /* @__PURE__ */
|
|
124
|
+
P && /* @__PURE__ */ C("div", {
|
|
115
125
|
role: "status",
|
|
116
126
|
className: "rounded-md border border-status-success-border bg-status-success-bg px-3 py-2 text-xs text-status-success-text",
|
|
117
127
|
children: P
|
|
118
128
|
}),
|
|
119
|
-
I && /* @__PURE__ */
|
|
129
|
+
I && /* @__PURE__ */ C("div", {
|
|
120
130
|
role: "alert",
|
|
121
131
|
className: "rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text",
|
|
122
132
|
children: I
|
|
123
133
|
}),
|
|
124
|
-
V === "install-cli" && /* @__PURE__ */
|
|
134
|
+
V === "install-cli" && /* @__PURE__ */ C(D, {
|
|
125
135
|
installing: A,
|
|
126
136
|
onInstall: z,
|
|
127
137
|
tr: m
|
|
128
138
|
}),
|
|
129
|
-
V === "wizard" && /* @__PURE__ */
|
|
139
|
+
V === "wizard" && /* @__PURE__ */ C(O, {
|
|
130
140
|
agents: T,
|
|
131
141
|
configuring: M,
|
|
132
142
|
onConfigure: B,
|
|
133
143
|
tr: m
|
|
134
144
|
}),
|
|
135
|
-
V === "ready" && /* @__PURE__ */
|
|
145
|
+
V === "ready" && /* @__PURE__ */ C(k, {
|
|
136
146
|
agent: e,
|
|
137
147
|
agents: T,
|
|
138
148
|
agentApiKey: t,
|
|
139
149
|
tunnelUrl: n,
|
|
150
|
+
runningSessions: h?.runningSessions ?? [],
|
|
140
151
|
onReconfigure: () => F(null),
|
|
141
152
|
tr: m
|
|
142
153
|
})
|
|
143
154
|
]
|
|
144
155
|
});
|
|
145
156
|
}
|
|
146
|
-
function
|
|
147
|
-
return /* @__PURE__ */
|
|
157
|
+
function D({ installing: e, onInstall: t, tr: n }) {
|
|
158
|
+
return /* @__PURE__ */ C("div", {
|
|
148
159
|
className: "rounded-lg border border-border-default bg-bg-elevated p-6",
|
|
149
|
-
children: /* @__PURE__ */
|
|
160
|
+
children: /* @__PURE__ */ w("div", {
|
|
150
161
|
className: "flex items-start gap-3",
|
|
151
|
-
children: [/* @__PURE__ */
|
|
162
|
+
children: [/* @__PURE__ */ C(_, { className: "h-6 w-6 text-action-primary-bg shrink-0" }), /* @__PURE__ */ w("div", {
|
|
152
163
|
className: "flex-1",
|
|
153
164
|
children: [
|
|
154
|
-
/* @__PURE__ */
|
|
165
|
+
/* @__PURE__ */ C("h3", {
|
|
155
166
|
className: "text-base font-semibold text-text-primary",
|
|
156
167
|
children: n("plan.installCliTitle", "Install the plannotator CLI")
|
|
157
168
|
}),
|
|
158
|
-
/* @__PURE__ */
|
|
169
|
+
/* @__PURE__ */ C("p", {
|
|
159
170
|
className: "text-sm text-text-secondary mt-1",
|
|
160
171
|
children: n("plan.installCliDesc", "Plan mode runs a local plannotator binary on the agent and proxies its UI back here. The plugin installs the binary into ~/.local/bin/plannotator (sha256-pinned). Roughly 30 seconds.")
|
|
161
172
|
}),
|
|
162
|
-
/* @__PURE__ */
|
|
173
|
+
/* @__PURE__ */ C("button", {
|
|
163
174
|
onClick: t,
|
|
164
175
|
disabled: e,
|
|
165
176
|
className: "mt-4 inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50",
|
|
166
|
-
children: e ? /* @__PURE__ */
|
|
177
|
+
children: e ? /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C(g, { className: "h-4 w-4 animate-spin" }), n("plan.installing", "Installing…")] }) : n("plan.installCliCta", "Install plannotator CLI")
|
|
167
178
|
})
|
|
168
179
|
]
|
|
169
180
|
})]
|
|
170
181
|
})
|
|
171
182
|
});
|
|
172
183
|
}
|
|
173
|
-
function
|
|
184
|
+
function O({ agents: e, configuring: t, onConfigure: n, tr: r }) {
|
|
174
185
|
let i = e.filter((e) => e.cliInstalled);
|
|
175
|
-
return /* @__PURE__ */
|
|
186
|
+
return /* @__PURE__ */ C("div", {
|
|
176
187
|
className: "rounded-lg border border-border-default bg-bg-elevated p-6",
|
|
177
|
-
children: /* @__PURE__ */
|
|
188
|
+
children: /* @__PURE__ */ w("div", {
|
|
178
189
|
className: "flex items-start gap-3",
|
|
179
|
-
children: [/* @__PURE__ */
|
|
190
|
+
children: [/* @__PURE__ */ C(y, { className: "h-6 w-6 text-action-primary-bg shrink-0" }), /* @__PURE__ */ w("div", {
|
|
180
191
|
className: "flex-1",
|
|
181
192
|
children: [
|
|
182
|
-
/* @__PURE__ */
|
|
193
|
+
/* @__PURE__ */ C("h3", {
|
|
183
194
|
className: "text-base font-semibold text-text-primary",
|
|
184
195
|
children: r("plan.wizardTitle", "Connect an AI coding agent")
|
|
185
196
|
}),
|
|
186
|
-
/* @__PURE__ */
|
|
197
|
+
/* @__PURE__ */ C("p", {
|
|
187
198
|
className: "text-sm text-text-secondary mt-1",
|
|
188
199
|
children: r("plan.wizardDesc", "Configure a plannotator hook for at least one installed AI coding agent. The hook forwards plan content from the agent to plannotator on every plan-finalisation step.")
|
|
189
200
|
}),
|
|
190
|
-
i.length === 0 && /* @__PURE__ */
|
|
201
|
+
i.length === 0 && /* @__PURE__ */ C("p", {
|
|
191
202
|
className: "mt-4 text-sm text-status-warning-text",
|
|
192
203
|
children: r("plan.wizardNoAgents", "No AI coding agents detected in this sandbox. Install one (Claude Code, OpenCode, Codex, Pi, or Gemini) and refresh.")
|
|
193
204
|
}),
|
|
194
|
-
/* @__PURE__ */
|
|
205
|
+
/* @__PURE__ */ C("ul", {
|
|
195
206
|
className: "mt-4 space-y-2",
|
|
196
|
-
children: e.map((e) => /* @__PURE__ */
|
|
207
|
+
children: e.map((e) => /* @__PURE__ */ w("li", {
|
|
197
208
|
className: "flex items-start gap-3 p-3 border border-border-default rounded-md bg-bg-surface",
|
|
198
209
|
children: [
|
|
199
|
-
e.hookConfigured ? /* @__PURE__ */
|
|
200
|
-
/* @__PURE__ */
|
|
210
|
+
e.hookConfigured ? /* @__PURE__ */ C(m, { className: "h-5 w-5 text-status-success-text shrink-0 mt-0.5" }) : /* @__PURE__ */ C("div", { className: "h-5 w-5 rounded-full border-2 border-border-default shrink-0 mt-0.5" }),
|
|
211
|
+
/* @__PURE__ */ w("div", {
|
|
201
212
|
className: "flex-1",
|
|
202
213
|
children: [
|
|
203
|
-
/* @__PURE__ */
|
|
214
|
+
/* @__PURE__ */ C("p", {
|
|
204
215
|
className: "text-sm font-medium text-text-primary",
|
|
205
216
|
children: e.name
|
|
206
217
|
}),
|
|
207
|
-
/* @__PURE__ */
|
|
218
|
+
/* @__PURE__ */ C("p", {
|
|
208
219
|
className: "text-xs text-text-secondary mt-0.5",
|
|
209
220
|
children: e.instructions
|
|
210
221
|
}),
|
|
211
|
-
!e.cliInstalled && /* @__PURE__ */
|
|
222
|
+
!e.cliInstalled && /* @__PURE__ */ C("p", {
|
|
212
223
|
className: "text-xs text-status-warning-text mt-1",
|
|
213
224
|
children: r("plan.agentCliMissing", "CLI not detected on this sandbox.")
|
|
214
225
|
})
|
|
215
226
|
]
|
|
216
227
|
}),
|
|
217
|
-
/* @__PURE__ */
|
|
228
|
+
/* @__PURE__ */ C("button", {
|
|
218
229
|
disabled: !e.cliInstalled || e.hookConfigured || t === e.agent,
|
|
219
230
|
onClick: () => n(e.agent),
|
|
220
231
|
className: "px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50",
|
|
@@ -228,13 +239,21 @@ function D({ agents: e, configuring: t, onConfigure: n, tr: r }) {
|
|
|
228
239
|
})
|
|
229
240
|
});
|
|
230
241
|
}
|
|
231
|
-
function
|
|
232
|
-
let
|
|
233
|
-
if (!r) return null;
|
|
234
|
-
let e = r.replace(/\/$/, "");
|
|
235
|
-
return
|
|
236
|
-
}, [
|
|
237
|
-
|
|
242
|
+
function k({ agent: e, agents: t, agentApiKey: n, tunnelUrl: r, runningSessions: i, onReconfigure: a, tr: o }) {
|
|
243
|
+
let f = u(null), [m, _] = d("connecting"), T = l(() => i.length > 0 ? [...i].sort((e, t) => (Date.parse(t.startedAt) || 0) - (Date.parse(e.startedAt) || 0))[0] : null, [i]), E = T?.sessionId ?? null, D = l(() => {
|
|
244
|
+
if (!r || !n || !E) return null;
|
|
245
|
+
let e = r.replace(/\/$/, ""), t = new URL(`${e}/plan/${E}/`);
|
|
246
|
+
return t.searchParams.set("apiKey", n), t.toString();
|
|
247
|
+
}, [
|
|
248
|
+
r,
|
|
249
|
+
n,
|
|
250
|
+
E
|
|
251
|
+
]);
|
|
252
|
+
c(() => {
|
|
253
|
+
_("connecting");
|
|
254
|
+
}, [E]);
|
|
255
|
+
let O = s(() => {
|
|
256
|
+
let e = f.current;
|
|
238
257
|
if (!(!e?.contentWindow || !n || !r)) try {
|
|
239
258
|
let t = new URL(r).origin;
|
|
240
259
|
e.contentWindow.postMessage({
|
|
@@ -242,86 +261,100 @@ function O({ agent: e, agents: t, agentApiKey: n, tunnelUrl: r, onReconfigure: i
|
|
|
242
261
|
apiKey: n
|
|
243
262
|
}, t);
|
|
244
263
|
} catch {}
|
|
245
|
-
}, [n, r]),
|
|
246
|
-
|
|
247
|
-
}, [
|
|
248
|
-
|
|
249
|
-
}, []),
|
|
250
|
-
let e =
|
|
264
|
+
}, [n, r]), k = s(() => {
|
|
265
|
+
_("connected"), O();
|
|
266
|
+
}, [O]), A = s(() => {
|
|
267
|
+
_("error");
|
|
268
|
+
}, []), j = s(() => {
|
|
269
|
+
let e = f.current;
|
|
251
270
|
if (!e) return;
|
|
252
|
-
|
|
271
|
+
_("connecting");
|
|
253
272
|
let t = e.src;
|
|
254
273
|
e.src = "about:blank", requestAnimationFrame(() => {
|
|
255
274
|
e.src = t;
|
|
256
275
|
});
|
|
257
276
|
}, []);
|
|
258
|
-
if (!r || !n) return /* @__PURE__ */
|
|
277
|
+
if (!r || !n) return /* @__PURE__ */ C("div", {
|
|
259
278
|
className: "rounded-lg border border-status-warning-border bg-status-warning-bg p-4 text-sm text-status-warning-text",
|
|
260
|
-
children:
|
|
279
|
+
children: o("plan.tunnelMissing", "Agent tunnel URL or API key is missing. Bring the agent online and refresh.")
|
|
261
280
|
});
|
|
262
|
-
let
|
|
263
|
-
return /* @__PURE__ */
|
|
281
|
+
let M = t.filter((e) => e.hookConfigured);
|
|
282
|
+
return /* @__PURE__ */ w("div", {
|
|
264
283
|
className: "space-y-3",
|
|
265
|
-
children: [/* @__PURE__ */
|
|
284
|
+
children: [/* @__PURE__ */ w("div", {
|
|
266
285
|
className: "flex items-center gap-2 text-xs text-text-secondary",
|
|
267
286
|
children: [
|
|
268
|
-
/* @__PURE__ */
|
|
287
|
+
T && /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ w("span", {
|
|
269
288
|
className: "inline-flex items-center gap-1",
|
|
270
289
|
children: [
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
290
|
+
m === "connected" && /* @__PURE__ */ C(b, { className: "h-3.5 w-3.5 text-status-success-text" }),
|
|
291
|
+
m === "connecting" && /* @__PURE__ */ C(g, { className: "h-3.5 w-3.5 animate-spin text-status-warning-text" }),
|
|
292
|
+
m === "error" && /* @__PURE__ */ C(x, { className: "h-3.5 w-3.5 text-status-error-text" }),
|
|
293
|
+
o(`plan.connection.${m}`, m)
|
|
275
294
|
]
|
|
276
|
-
}),
|
|
277
|
-
/* @__PURE__ */ S("span", {
|
|
295
|
+
}), /* @__PURE__ */ C("span", {
|
|
278
296
|
"aria-hidden": "true",
|
|
279
297
|
children: "·"
|
|
280
|
-
}),
|
|
281
|
-
/* @__PURE__ */
|
|
282
|
-
/* @__PURE__ */
|
|
283
|
-
onClick:
|
|
298
|
+
})] }),
|
|
299
|
+
/* @__PURE__ */ C("span", { children: o("plan.hookCount", `${M.length} hook(s) configured`) }),
|
|
300
|
+
/* @__PURE__ */ w("button", {
|
|
301
|
+
onClick: a,
|
|
284
302
|
className: "ml-auto inline-flex items-center gap-1 hover:text-text-primary",
|
|
285
|
-
children: [/* @__PURE__ */
|
|
303
|
+
children: [/* @__PURE__ */ C(y, { className: "h-3.5 w-3.5" }), o("plan.reopenWizard", "Manage hooks")]
|
|
286
304
|
})
|
|
287
305
|
]
|
|
288
|
-
}), /* @__PURE__ */
|
|
306
|
+
}), /* @__PURE__ */ w("div", {
|
|
289
307
|
className: "border border-border-default rounded-lg overflow-hidden bg-bg-sunken relative h-[calc(100vh-320px)] min-h-[500px]",
|
|
290
308
|
children: [
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
309
|
+
!T && /* @__PURE__ */ w("div", {
|
|
310
|
+
className: "absolute inset-0 flex flex-col items-center justify-center gap-2 p-6 text-center",
|
|
311
|
+
children: [
|
|
312
|
+
/* @__PURE__ */ C(h, { className: "h-8 w-8 text-text-muted" }),
|
|
313
|
+
/* @__PURE__ */ C("p", {
|
|
314
|
+
className: "text-sm font-medium text-text-primary",
|
|
315
|
+
children: o("plan.noSessionTitle", "No active plan session")
|
|
316
|
+
}),
|
|
317
|
+
/* @__PURE__ */ C("p", {
|
|
318
|
+
className: "max-w-md text-xs text-text-secondary",
|
|
319
|
+
children: o("plan.noSessionDesc", "Start a plan in a connected coding agent — when it finalises a plan, the configured hook forwards it here for review.")
|
|
320
|
+
})
|
|
321
|
+
]
|
|
322
|
+
}),
|
|
323
|
+
D && /* @__PURE__ */ C("iframe", {
|
|
324
|
+
ref: f,
|
|
325
|
+
src: D,
|
|
326
|
+
title: o("plan.iframeTitle", "Plannotator plan workspace"),
|
|
295
327
|
className: "w-full h-full border-0",
|
|
296
328
|
allow: "clipboard-read; clipboard-write",
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
329
|
+
referrerPolicy: "no-referrer",
|
|
330
|
+
sandbox: "allow-same-origin allow-scripts allow-forms",
|
|
331
|
+
onLoad: k,
|
|
332
|
+
onError: A,
|
|
300
333
|
"data-testid": "plan-iframe"
|
|
301
334
|
}),
|
|
302
|
-
|
|
335
|
+
T && m === "connecting" && /* @__PURE__ */ C("div", {
|
|
303
336
|
className: "absolute inset-0 flex items-center justify-center bg-bg-sunken/80 pointer-events-none",
|
|
304
|
-
children: /* @__PURE__ */
|
|
337
|
+
children: /* @__PURE__ */ C(g, { className: "h-6 w-6 animate-spin text-action-primary-bg" })
|
|
305
338
|
}),
|
|
306
|
-
|
|
339
|
+
T && m === "error" && /* @__PURE__ */ C("div", {
|
|
307
340
|
className: "absolute inset-0 flex items-center justify-center bg-bg-sunken p-6 text-center",
|
|
308
|
-
children: /* @__PURE__ */
|
|
309
|
-
/* @__PURE__ */
|
|
310
|
-
/* @__PURE__ */
|
|
341
|
+
children: /* @__PURE__ */ w("div", { children: [
|
|
342
|
+
/* @__PURE__ */ C(p, { className: "h-6 w-6 text-status-error-text mx-auto" }),
|
|
343
|
+
/* @__PURE__ */ C("p", {
|
|
311
344
|
className: "text-sm font-medium text-text-primary mt-2",
|
|
312
|
-
children:
|
|
345
|
+
children: o("plan.iframeErrorTitle", "Could not load plannotator.")
|
|
313
346
|
}),
|
|
314
|
-
/* @__PURE__ */
|
|
347
|
+
/* @__PURE__ */ C("p", {
|
|
315
348
|
className: "text-xs text-text-secondary mt-1",
|
|
316
|
-
children:
|
|
349
|
+
children: o("plan.iframeErrorDesc", "The agent reverse proxy returned an error. Check the agent status and try again.")
|
|
317
350
|
}),
|
|
318
|
-
/* @__PURE__ */
|
|
319
|
-
onClick:
|
|
351
|
+
/* @__PURE__ */ w("button", {
|
|
352
|
+
onClick: j,
|
|
320
353
|
className: "mt-3 inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm text-action-primary-text hover:bg-action-primary-bgHover",
|
|
321
354
|
children: [
|
|
322
|
-
/* @__PURE__ */
|
|
355
|
+
/* @__PURE__ */ C(v, { className: "h-3.5 w-3.5" }),
|
|
323
356
|
" ",
|
|
324
|
-
|
|
357
|
+
o("plan.retry", "Retry")
|
|
325
358
|
]
|
|
326
359
|
})
|
|
327
360
|
] })
|
|
@@ -331,6 +364,6 @@ function O({ agent: e, agents: t, agentApiKey: n, tunnelUrl: r, onReconfigure: i
|
|
|
331
364
|
});
|
|
332
365
|
}
|
|
333
366
|
//#endregion
|
|
334
|
-
export {
|
|
367
|
+
export { T as AIPlanPanel };
|
|
335
368
|
|
|
336
369
|
//# sourceMappingURL=AIPlanPanel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIPlanPanel.js","names":[],"sources":["../../../src/components/ai/AIPlanPanel.tsx"],"sourcesContent":["/**\n * AIPlanPanel — the body of /ai-tools/plan.\n *\n * Five UI states gated in order:\n * 1. installing-plugin → vibe-plugin-plan + vibe-plugin-plan-plannotator\n * not installed on the agent. Renders the PluginGate install card.\n * 2. installing-cli → plugins installed but plannotator binary\n * missing on disk. Renders an Install CLI CTA.\n * 3. wizard → CLI installed but no AI-agent hook configured\n * yet. Renders the Claude/OpenCode/Codex/Pi/Gemini wizard.\n * 4. ready → all prereqs satisfied. Renders the tunnel\n * iframe pointing at ${tunnelUrl}/plan/, with postMessage auth\n * handoff on iframe load (mirrors PluginUIPanel.tsx).\n * 5. error → unrecoverable load failure for the status\n * query. Renders an error banner with retry.\n *\n * Auth handoff (matches PluginUIPanel.tsx:194–210):\n * onLoad → iframe.contentWindow.postMessage({type:'vibe-auth', apiKey},\n * new URL(tunnelUrl).origin)\n *\n * Surface contracts:\n * GET /api/plan-plannotator/status → PlannotatorStatus\n * POST /api/plan-plannotator/install → install binary\n * GET /api/plan-plannotator/agents/supported → AgentDetectionResult[]\n * POST /api/plan-plannotator/agents/:agent/configure-hook\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n AlertCircle,\n CheckCircle2,\n Loader2,\n PlayCircle,\n RefreshCcw,\n Settings,\n Wifi,\n WifiOff,\n} from 'lucide-react';\n\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PluginGate } from '@/components/shared';\nimport { PLAN_META_PACKAGE, PLAN_PLANNOTATOR_PACKAGE } from '@/constants/pluginCatalog';\nimport {\n configureAgentHook,\n getPlannotatorStatus,\n installPlannotatorBinary,\n listSupportedAgents,\n type AgentDetectionResult,\n type PlannotatorStatus,\n} from '@/services/planApi';\nimport type { AgentRef } from '@/services/aiApi';\n\ninterface AIPlanPanelProps {\n agent: AgentRef;\n agentApiKey: string | null;\n tunnelUrl: string | null;\n}\n\ntype ConnectionStatus = 'connecting' | 'connected' | 'error';\n\nexport function AIPlanPanel({ agent, agentApiKey, tunnelUrl }: AIPlanPanelProps) {\n const { t } = useI18n();\n // PluginGate renders eyebrow/title/description verbatim (no i18n lookup of\n // its own), so resolve them here with English fallbacks until plan.* keys exist.\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n return (\n <PluginGate\n agentId={agent.id}\n required={[PLAN_META_PACKAGE, PLAN_PLANNOTATOR_PACKAGE]}\n mode=\"all\"\n eyebrow={tr('plan.pluginRequiredEyebrow', 'Plan plugin required')}\n title={tr('plan.installPlanProviderTitle', 'Install the plan provider')}\n description={tr(\n 'plan.installPlanProviderDesc',\n 'Plan mode routes plan sessions through a plan provider plugin on your agent. Install the orchestrator and a provider (Plannotator) below to continue.'\n )}\n >\n <PlanPanelInner agent={agent} agentApiKey={agentApiKey} tunnelUrl={tunnelUrl} />\n </PluginGate>\n );\n}\n\nfunction PlanPanelInner({ agent, agentApiKey, tunnelUrl }: AIPlanPanelProps) {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const [status, setStatus] = useState<PlannotatorStatus | null>(null);\n const [statusError, setStatusError] = useState<string | null>(null);\n const [statusLoading, setStatusLoading] = useState(true);\n const [agents, setAgents] = useState<AgentDetectionResult[]>([]);\n const [installing, setInstalling] = useState(false);\n const [configuring, setConfiguring] = useState<string | null>(null);\n const [actionMessage, setActionMessage] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n const refresh = useCallback(async () => {\n setStatusLoading(true);\n setStatusError(null);\n try {\n const [statusResult, agentsResult] = await Promise.all([\n getPlannotatorStatus(agent),\n listSupportedAgents(agent).catch(() => ({ agents: [] })),\n ]);\n setStatus(statusResult);\n setAgents(agentsResult.agents ?? []);\n } catch (err) {\n setStatusError(err instanceof Error ? err.message : 'Failed to load plan status');\n } finally {\n setStatusLoading(false);\n }\n }, [agent]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const handleInstallCli = useCallback(async () => {\n setInstalling(true);\n setActionError(null);\n setActionMessage(null);\n try {\n const result = await installPlannotatorBinary(agent);\n if (result.ok) {\n setActionMessage(tr('plan.cliInstalled', 'Plannotator CLI installed.'));\n await refresh();\n } else {\n setActionError(result.error ?? 'Install failed');\n }\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Install failed');\n } finally {\n setInstalling(false);\n }\n }, [agent, refresh, tr]);\n\n const handleConfigureHook = useCallback(\n async (agentKind: AgentDetectionResult['agent']) => {\n setConfiguring(agentKind);\n setActionError(null);\n setActionMessage(null);\n try {\n const result = await configureAgentHook(agent, agentKind);\n if (result.ok) {\n setActionMessage(tr('plan.hookConfigured', `Hook configured for ${agentKind}.`));\n await refresh();\n } else {\n setActionError(result.error ?? 'Hook configure failed');\n }\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Hook configure failed');\n } finally {\n setConfiguring(null);\n }\n },\n [agent, refresh, tr]\n );\n\n // Derived render state — guarded ordering means we always render the\n // most prerequisite-bound CTA first.\n const view = useMemo<'loading' | 'status-error' | 'install-cli' | 'wizard' | 'ready'>(() => {\n if (statusLoading) return 'loading';\n if (statusError) return 'status-error';\n if (!status?.installed) return 'install-cli';\n const anyConfigured = agents.some((a) => a.hookConfigured);\n if (!anyConfigured) return 'wizard';\n return 'ready';\n }, [status, statusError, statusLoading, agents]);\n\n if (view === 'loading') {\n return (\n <div className=\"flex items-center gap-2 p-6 text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n <span>{tr('plan.loadingStatus', 'Loading plan status…')}</span>\n </div>\n );\n }\n\n if (view === 'status-error') {\n return (\n <div className=\"rounded-md border border-status-error-border bg-status-error-bg p-4 text-sm text-status-error-text\">\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div className=\"flex-1\">\n <p className=\"font-medium\">\n {tr('plan.statusError', 'Could not load plan status from the agent.')}\n </p>\n <p className=\"text-xs opacity-80 mt-1\">{statusError}</p>\n <button\n onClick={() => void refresh()}\n className=\"mt-3 inline-flex items-center gap-1 text-xs font-medium hover:underline\"\n >\n <RefreshCcw className=\"h-3 w-3\" /> {tr('plan.retry', 'Retry')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4\">\n {actionMessage && (\n <div\n role=\"status\"\n className=\"rounded-md border border-status-success-border bg-status-success-bg px-3 py-2 text-xs text-status-success-text\"\n >\n {actionMessage}\n </div>\n )}\n {actionError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text\"\n >\n {actionError}\n </div>\n )}\n\n {view === 'install-cli' && (\n <InstallCliCard installing={installing} onInstall={handleInstallCli} tr={tr} />\n )}\n\n {view === 'wizard' && (\n <WizardCard\n agents={agents}\n configuring={configuring}\n onConfigure={handleConfigureHook}\n tr={tr}\n />\n )}\n\n {view === 'ready' && (\n <PlanReadyPanel\n agent={agent}\n agents={agents}\n agentApiKey={agentApiKey}\n tunnelUrl={tunnelUrl}\n onReconfigure={() => setActionMessage(null)}\n tr={tr}\n />\n )}\n </div>\n );\n}\n\ninterface InstallCliCardProps {\n installing: boolean;\n onInstall: () => void;\n tr: (key: string, fallback: string) => string;\n}\n\nfunction InstallCliCard({ installing, onInstall, tr }: InstallCliCardProps) {\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-elevated p-6\">\n <div className=\"flex items-start gap-3\">\n <PlayCircle className=\"h-6 w-6 text-action-primary-bg shrink-0\" />\n <div className=\"flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('plan.installCliTitle', 'Install the plannotator CLI')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'plan.installCliDesc',\n 'Plan mode runs a local plannotator binary on the agent and proxies its UI back here. The plugin installs the binary into ~/.local/bin/plannotator (sha256-pinned). Roughly 30 seconds.'\n )}\n </p>\n <button\n onClick={onInstall}\n disabled={installing}\n className=\"mt-4 inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {installing ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr('plan.installing', 'Installing…')}\n </>\n ) : (\n tr('plan.installCliCta', 'Install plannotator CLI')\n )}\n </button>\n </div>\n </div>\n </div>\n );\n}\n\ninterface WizardCardProps {\n agents: AgentDetectionResult[];\n configuring: string | null;\n onConfigure: (agentKind: AgentDetectionResult['agent']) => void;\n tr: (key: string, fallback: string) => string;\n}\n\nfunction WizardCard({ agents, configuring, onConfigure, tr }: WizardCardProps) {\n const installed = agents.filter((a) => a.cliInstalled);\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-elevated p-6\">\n <div className=\"flex items-start gap-3\">\n <Settings className=\"h-6 w-6 text-action-primary-bg shrink-0\" />\n <div className=\"flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('plan.wizardTitle', 'Connect an AI coding agent')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'plan.wizardDesc',\n 'Configure a plannotator hook for at least one installed AI coding agent. The hook forwards plan content from the agent to plannotator on every plan-finalisation step.'\n )}\n </p>\n {installed.length === 0 && (\n <p className=\"mt-4 text-sm text-status-warning-text\">\n {tr(\n 'plan.wizardNoAgents',\n 'No AI coding agents detected in this sandbox. Install one (Claude Code, OpenCode, Codex, Pi, or Gemini) and refresh.'\n )}\n </p>\n )}\n <ul className=\"mt-4 space-y-2\">\n {agents.map((entry) => (\n <li\n key={entry.agent}\n className=\"flex items-start gap-3 p-3 border border-border-default rounded-md bg-bg-surface\"\n >\n {entry.hookConfigured ? (\n <CheckCircle2 className=\"h-5 w-5 text-status-success-text shrink-0 mt-0.5\" />\n ) : (\n <div className=\"h-5 w-5 rounded-full border-2 border-border-default shrink-0 mt-0.5\" />\n )}\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">{entry.name}</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">{entry.instructions}</p>\n {!entry.cliInstalled && (\n <p className=\"text-xs text-status-warning-text mt-1\">\n {tr('plan.agentCliMissing', 'CLI not detected on this sandbox.')}\n </p>\n )}\n </div>\n <button\n disabled={\n !entry.cliInstalled || entry.hookConfigured || configuring === entry.agent\n }\n onClick={() => onConfigure(entry.agent)}\n className=\"px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {entry.hookConfigured\n ? tr('plan.hookConfiguredLabel', 'Configured')\n : configuring === entry.agent\n ? tr('plan.configuring', 'Configuring…')\n : tr('plan.configureHook', 'Configure hook')}\n </button>\n </li>\n ))}\n </ul>\n </div>\n </div>\n </div>\n );\n}\n\ninterface PlanReadyPanelProps {\n agent: AgentRef;\n agents: AgentDetectionResult[];\n agentApiKey: string | null;\n tunnelUrl: string | null;\n onReconfigure: () => void;\n tr: (key: string, fallback: string) => string;\n}\n\nfunction PlanReadyPanel({\n agent,\n agents,\n agentApiKey,\n tunnelUrl,\n onReconfigure,\n tr,\n}: PlanReadyPanelProps) {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const [connection, setConnection] = useState<ConnectionStatus>('connecting');\n\n const iframeSrc = useMemo(() => {\n if (!tunnelUrl) return null;\n const base = tunnelUrl.replace(/\\/$/, '');\n return new URL(`${base}/plan/`).toString();\n }, [tunnelUrl]);\n\n const sendAuth = useCallback(() => {\n const iframe = iframeRef.current;\n if (!iframe?.contentWindow || !agentApiKey || !tunnelUrl) return;\n try {\n const targetOrigin = new URL(tunnelUrl).origin;\n iframe.contentWindow.postMessage({ type: 'vibe-auth', apiKey: agentApiKey }, targetOrigin);\n } catch {\n // Origin parsing can throw on malformed tunnelUrl — ignored.\n }\n }, [agentApiKey, tunnelUrl]);\n\n const handleLoad = useCallback(() => {\n setConnection('connected');\n sendAuth();\n }, [sendAuth]);\n\n const handleError = useCallback(() => {\n setConnection('error');\n }, []);\n\n const handleRetry = useCallback(() => {\n const iframe = iframeRef.current;\n if (!iframe) return;\n setConnection('connecting');\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }, []);\n\n // Used elsewhere for analytics / breadcrumbs.\n void agent;\n\n if (!tunnelUrl || !agentApiKey) {\n return (\n <div className=\"rounded-lg border border-status-warning-border bg-status-warning-bg p-4 text-sm text-status-warning-text\">\n {tr(\n 'plan.tunnelMissing',\n 'Agent tunnel URL or API key is missing. Bring the agent online and refresh.'\n )}\n </div>\n );\n }\n\n const configuredAgents = agents.filter((a) => a.hookConfigured);\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n {connection === 'connected' && <Wifi className=\"h-3.5 w-3.5 text-status-success-text\" />}\n {connection === 'connecting' && (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin text-status-warning-text\" />\n )}\n {connection === 'error' && <WifiOff className=\"h-3.5 w-3.5 text-status-error-text\" />}\n {tr(`plan.connection.${connection}`, connection)}\n </span>\n <span aria-hidden=\"true\">·</span>\n <span>{tr('plan.hookCount', `${configuredAgents.length} hook(s) configured`)}</span>\n <button\n onClick={onReconfigure}\n className=\"ml-auto inline-flex items-center gap-1 hover:text-text-primary\"\n >\n <Settings className=\"h-3.5 w-3.5\" />\n {tr('plan.reopenWizard', 'Manage hooks')}\n </button>\n </div>\n\n <div className=\"border border-border-default rounded-lg overflow-hidden bg-bg-sunken relative h-[calc(100vh-320px)] min-h-[500px]\">\n {iframeSrc && (\n <iframe\n ref={iframeRef}\n src={iframeSrc}\n title={tr('plan.iframeTitle', 'Plannotator plan workspace')}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n sandbox=\"allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation-by-user-activation\"\n onLoad={handleLoad}\n onError={handleError}\n data-testid=\"plan-iframe\"\n />\n )}\n {connection === 'connecting' && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-bg-sunken/80 pointer-events-none\">\n <Loader2 className=\"h-6 w-6 animate-spin text-action-primary-bg\" />\n </div>\n )}\n {connection === 'error' && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-bg-sunken p-6 text-center\">\n <div>\n <AlertCircle className=\"h-6 w-6 text-status-error-text mx-auto\" />\n <p className=\"text-sm font-medium text-text-primary mt-2\">\n {tr('plan.iframeErrorTitle', 'Could not load plannotator.')}\n </p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {tr(\n 'plan.iframeErrorDesc',\n 'The agent reverse proxy returned an error. Check the agent status and try again.'\n )}\n </p>\n <button\n onClick={handleRetry}\n className=\"mt-3 inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm text-action-primary-text hover:bg-action-primary-bgHover\"\n >\n <RefreshCcw className=\"h-3.5 w-3.5\" /> {tr('plan.retry', 'Retry')}\n </button>\n </div>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AIPlanPanel.js","names":[],"sources":["../../../src/components/ai/AIPlanPanel.tsx"],"sourcesContent":["/**\n * AIPlanPanel — the body of /ai-tools/plan.\n *\n * Five UI states gated in order:\n * 1. installing-plugin → vibe-plugin-plan + vibe-plugin-plan-plannotator\n * not installed on the agent. Renders the PluginGate install card.\n * 2. installing-cli → plugins installed but plannotator binary\n * missing on disk. Renders an Install CLI CTA.\n * 3. wizard → CLI installed but no AI-agent hook configured\n * yet. Renders the Claude/OpenCode/Codex/Pi/Gemini wizard.\n * 4. ready → all prereqs satisfied. Renders the tunnel\n * iframe pointing at ${tunnelUrl}/plan/, with postMessage auth\n * handoff on iframe load (mirrors PluginUIPanel.tsx).\n * 5. error → unrecoverable load failure for the status\n * query. Renders an error banner with retry.\n *\n * Auth handoff (matches PluginUIPanel.tsx:194–210):\n * onLoad → iframe.contentWindow.postMessage({type:'vibe-auth', apiKey},\n * new URL(tunnelUrl).origin)\n *\n * Surface contracts:\n * GET /api/plan-plannotator/status → PlannotatorStatus\n * POST /api/plan-plannotator/install → install binary\n * GET /api/plan-plannotator/agents/supported → AgentDetectionResult[]\n * POST /api/plan-plannotator/agents/:agent/configure-hook\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n AlertCircle,\n CheckCircle2,\n Inbox,\n Loader2,\n PlayCircle,\n RefreshCcw,\n Settings,\n Wifi,\n WifiOff,\n} from 'lucide-react';\n\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PluginGate } from '@/components/shared';\nimport { PLAN_META_PACKAGE, PLAN_PLANNOTATOR_PACKAGE } from '@/constants/pluginCatalog';\nimport {\n configureAgentHook,\n getPlannotatorStatus,\n installPlannotatorBinary,\n listSupportedAgents,\n type AgentDetectionResult,\n type PlannotatorStatus,\n} from '@/services/planApi';\nimport type { AgentRef } from '@/services/aiApi';\n\ninterface AIPlanPanelProps {\n agent: AgentRef;\n agentApiKey: string | null;\n tunnelUrl: string | null;\n}\n\ntype ConnectionStatus = 'connecting' | 'connected' | 'error';\n\nexport function AIPlanPanel({ agent, agentApiKey, tunnelUrl }: AIPlanPanelProps) {\n const { t } = useI18n();\n // PluginGate renders eyebrow/title/description verbatim (no i18n lookup of\n // its own), so resolve them here with English fallbacks until plan.* keys exist.\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n return (\n <PluginGate\n agentId={agent.id}\n required={[PLAN_META_PACKAGE, PLAN_PLANNOTATOR_PACKAGE]}\n mode=\"all\"\n eyebrow={tr('plan.pluginRequiredEyebrow', 'Plan plugin required')}\n title={tr('plan.installPlanProviderTitle', 'Install the plan provider')}\n description={tr(\n 'plan.installPlanProviderDesc',\n 'Plan mode routes plan sessions through a plan provider plugin on your agent. Install the orchestrator and a provider (Plannotator) below to continue.'\n )}\n >\n <PlanPanelInner agent={agent} agentApiKey={agentApiKey} tunnelUrl={tunnelUrl} />\n </PluginGate>\n );\n}\n\nfunction PlanPanelInner({ agent, agentApiKey, tunnelUrl }: AIPlanPanelProps) {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const [status, setStatus] = useState<PlannotatorStatus | null>(null);\n const [statusError, setStatusError] = useState<string | null>(null);\n const [statusLoading, setStatusLoading] = useState(true);\n const [agents, setAgents] = useState<AgentDetectionResult[]>([]);\n const [installing, setInstalling] = useState(false);\n const [configuring, setConfiguring] = useState<string | null>(null);\n const [actionMessage, setActionMessage] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n const refresh = useCallback(\n // `silent` background refreshes (polling) only update session status: they\n // skip the loading/error toggles AND the agent re-detection, so a transient\n // listSupportedAgents failure can't demote the ready view to the wizard.\n async (silent = false) => {\n if (!silent) {\n setStatusLoading(true);\n setStatusError(null);\n }\n try {\n if (silent) {\n setStatus(await getPlannotatorStatus(agent));\n return;\n }\n const [statusResult, agentsResult] = await Promise.all([\n getPlannotatorStatus(agent),\n listSupportedAgents(agent).catch(() => ({ agents: [] })),\n ]);\n setStatus(statusResult);\n setAgents(agentsResult.agents ?? []);\n } catch (err) {\n if (!silent) {\n setStatusError(err instanceof Error ? err.message : 'Failed to load plan status');\n }\n } finally {\n if (!silent) setStatusLoading(false);\n }\n },\n [agent]\n );\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const handleInstallCli = useCallback(async () => {\n setInstalling(true);\n setActionError(null);\n setActionMessage(null);\n try {\n const result = await installPlannotatorBinary(agent);\n if (result.ok) {\n setActionMessage(tr('plan.cliInstalled', 'Plannotator CLI installed.'));\n await refresh();\n } else {\n setActionError(result.error ?? 'Install failed');\n }\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Install failed');\n } finally {\n setInstalling(false);\n }\n }, [agent, refresh, tr]);\n\n const handleConfigureHook = useCallback(\n async (agentKind: AgentDetectionResult['agent']) => {\n setConfiguring(agentKind);\n setActionError(null);\n setActionMessage(null);\n try {\n const result = await configureAgentHook(agent, agentKind);\n if (result.ok) {\n setActionMessage(tr('plan.hookConfigured', `Hook configured for ${agentKind}.`));\n await refresh();\n } else {\n setActionError(result.error ?? 'Hook configure failed');\n }\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Hook configure failed');\n } finally {\n setConfiguring(null);\n }\n },\n [agent, refresh, tr]\n );\n\n // Derived render state — guarded ordering means we always render the\n // most prerequisite-bound CTA first.\n const view = useMemo<'loading' | 'status-error' | 'install-cli' | 'wizard' | 'ready'>(() => {\n if (statusLoading) return 'loading';\n if (statusError) return 'status-error';\n if (!status?.installed) return 'install-cli';\n const anyConfigured = agents.some((a) => a.hookConfigured);\n if (!anyConfigured) return 'wizard';\n return 'ready';\n }, [status, statusError, statusLoading, agents]);\n\n // While the ready panel is shown, poll status in the background so a plan\n // session created by a coding agent's hook (or one that ends) is reflected\n // without a manual reload — `runningSessions` gates the iframe vs the empty\n // state below.\n useEffect(() => {\n if (view !== 'ready') return;\n // Skip ticks while the tab is hidden — no point polling a panel nobody sees.\n const id = setInterval(() => {\n if (!document.hidden) void refresh(true);\n }, 5000);\n return () => clearInterval(id);\n }, [view, refresh]);\n\n if (view === 'loading') {\n return (\n <div className=\"flex items-center gap-2 p-6 text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n <span>{tr('plan.loadingStatus', 'Loading plan status…')}</span>\n </div>\n );\n }\n\n if (view === 'status-error') {\n return (\n <div className=\"rounded-md border border-status-error-border bg-status-error-bg p-4 text-sm text-status-error-text\">\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div className=\"flex-1\">\n <p className=\"font-medium\">\n {tr('plan.statusError', 'Could not load plan status from the agent.')}\n </p>\n <p className=\"text-xs opacity-80 mt-1\">{statusError}</p>\n <button\n onClick={() => void refresh()}\n className=\"mt-3 inline-flex items-center gap-1 text-xs font-medium hover:underline\"\n >\n <RefreshCcw className=\"h-3 w-3\" /> {tr('plan.retry', 'Retry')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4\">\n {actionMessage && (\n <div\n role=\"status\"\n className=\"rounded-md border border-status-success-border bg-status-success-bg px-3 py-2 text-xs text-status-success-text\"\n >\n {actionMessage}\n </div>\n )}\n {actionError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text\"\n >\n {actionError}\n </div>\n )}\n\n {view === 'install-cli' && (\n <InstallCliCard installing={installing} onInstall={handleInstallCli} tr={tr} />\n )}\n\n {view === 'wizard' && (\n <WizardCard\n agents={agents}\n configuring={configuring}\n onConfigure={handleConfigureHook}\n tr={tr}\n />\n )}\n\n {view === 'ready' && (\n <PlanReadyPanel\n agent={agent}\n agents={agents}\n agentApiKey={agentApiKey}\n tunnelUrl={tunnelUrl}\n runningSessions={status?.runningSessions ?? []}\n onReconfigure={() => setActionMessage(null)}\n tr={tr}\n />\n )}\n </div>\n );\n}\n\ninterface InstallCliCardProps {\n installing: boolean;\n onInstall: () => void;\n tr: (key: string, fallback: string) => string;\n}\n\nfunction InstallCliCard({ installing, onInstall, tr }: InstallCliCardProps) {\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-elevated p-6\">\n <div className=\"flex items-start gap-3\">\n <PlayCircle className=\"h-6 w-6 text-action-primary-bg shrink-0\" />\n <div className=\"flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('plan.installCliTitle', 'Install the plannotator CLI')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'plan.installCliDesc',\n 'Plan mode runs a local plannotator binary on the agent and proxies its UI back here. The plugin installs the binary into ~/.local/bin/plannotator (sha256-pinned). Roughly 30 seconds.'\n )}\n </p>\n <button\n onClick={onInstall}\n disabled={installing}\n className=\"mt-4 inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {installing ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr('plan.installing', 'Installing…')}\n </>\n ) : (\n tr('plan.installCliCta', 'Install plannotator CLI')\n )}\n </button>\n </div>\n </div>\n </div>\n );\n}\n\ninterface WizardCardProps {\n agents: AgentDetectionResult[];\n configuring: string | null;\n onConfigure: (agentKind: AgentDetectionResult['agent']) => void;\n tr: (key: string, fallback: string) => string;\n}\n\nfunction WizardCard({ agents, configuring, onConfigure, tr }: WizardCardProps) {\n const installed = agents.filter((a) => a.cliInstalled);\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-elevated p-6\">\n <div className=\"flex items-start gap-3\">\n <Settings className=\"h-6 w-6 text-action-primary-bg shrink-0\" />\n <div className=\"flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('plan.wizardTitle', 'Connect an AI coding agent')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'plan.wizardDesc',\n 'Configure a plannotator hook for at least one installed AI coding agent. The hook forwards plan content from the agent to plannotator on every plan-finalisation step.'\n )}\n </p>\n {installed.length === 0 && (\n <p className=\"mt-4 text-sm text-status-warning-text\">\n {tr(\n 'plan.wizardNoAgents',\n 'No AI coding agents detected in this sandbox. Install one (Claude Code, OpenCode, Codex, Pi, or Gemini) and refresh.'\n )}\n </p>\n )}\n <ul className=\"mt-4 space-y-2\">\n {agents.map((entry) => (\n <li\n key={entry.agent}\n className=\"flex items-start gap-3 p-3 border border-border-default rounded-md bg-bg-surface\"\n >\n {entry.hookConfigured ? (\n <CheckCircle2 className=\"h-5 w-5 text-status-success-text shrink-0 mt-0.5\" />\n ) : (\n <div className=\"h-5 w-5 rounded-full border-2 border-border-default shrink-0 mt-0.5\" />\n )}\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">{entry.name}</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">{entry.instructions}</p>\n {!entry.cliInstalled && (\n <p className=\"text-xs text-status-warning-text mt-1\">\n {tr('plan.agentCliMissing', 'CLI not detected on this sandbox.')}\n </p>\n )}\n </div>\n <button\n disabled={\n !entry.cliInstalled || entry.hookConfigured || configuring === entry.agent\n }\n onClick={() => onConfigure(entry.agent)}\n className=\"px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {entry.hookConfigured\n ? tr('plan.hookConfiguredLabel', 'Configured')\n : configuring === entry.agent\n ? tr('plan.configuring', 'Configuring…')\n : tr('plan.configureHook', 'Configure hook')}\n </button>\n </li>\n ))}\n </ul>\n </div>\n </div>\n </div>\n );\n}\n\ninterface PlanReadyPanelProps {\n agent: AgentRef;\n agents: AgentDetectionResult[];\n agentApiKey: string | null;\n tunnelUrl: string | null;\n runningSessions: PlannotatorStatus['runningSessions'];\n onReconfigure: () => void;\n tr: (key: string, fallback: string) => string;\n}\n\nfunction PlanReadyPanel({\n agent,\n agents,\n agentApiKey,\n tunnelUrl,\n runningSessions,\n onReconfigure,\n tr,\n}: PlanReadyPanelProps) {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const [connection, setConnection] = useState<ConnectionStatus>('connecting');\n\n // plannotator is session-scoped — it only serves a plan at /plan/<sessionId>/.\n // A session exists once a connected coding agent finalises a plan (its hook\n // POSTs to the agent). With none running there is nothing to embed (the bare\n // /plan/ root 404s), so we show an empty state instead. The status endpoint\n // doesn't guarantee ordering, so pick the most recently started session.\n const activeSession = useMemo(\n () =>\n runningSessions.length > 0\n ? // `|| 0` keeps the sort deterministic if a startedAt is malformed (Date.parse → NaN).\n [...runningSessions].sort(\n (a, b) => (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0)\n )[0]\n : null,\n [runningSessions]\n );\n const activeSessionId = activeSession?.sessionId ?? null;\n\n const iframeSrc = useMemo(() => {\n if (!tunnelUrl || !agentApiKey || !activeSessionId) return null;\n const base = tunnelUrl.replace(/\\/$/, '');\n // The iframe's initial document GET can't carry an auth header; the proxy\n // accepts ?apiKey= for exactly this case and then sets a scoped session\n // cookie for subsequent requests.\n const url = new URL(`${base}/plan/${activeSessionId}/`);\n url.searchParams.set('apiKey', agentApiKey);\n return url.toString();\n }, [tunnelUrl, agentApiKey, activeSessionId]);\n\n // Reset the connection indicator when the embedded session changes so a\n // prior session's 'error'/'connected' state doesn't bleed into the next one.\n useEffect(() => {\n setConnection('connecting');\n }, [activeSessionId]);\n\n const sendAuth = useCallback(() => {\n const iframe = iframeRef.current;\n if (!iframe?.contentWindow || !agentApiKey || !tunnelUrl) return;\n try {\n const targetOrigin = new URL(tunnelUrl).origin;\n iframe.contentWindow.postMessage({ type: 'vibe-auth', apiKey: agentApiKey }, targetOrigin);\n } catch {\n // Origin parsing can throw on malformed tunnelUrl — ignored.\n }\n }, [agentApiKey, tunnelUrl]);\n\n const handleLoad = useCallback(() => {\n setConnection('connected');\n sendAuth();\n }, [sendAuth]);\n\n const handleError = useCallback(() => {\n setConnection('error');\n }, []);\n\n const handleRetry = useCallback(() => {\n const iframe = iframeRef.current;\n if (!iframe) return;\n setConnection('connecting');\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }, []);\n\n // Used elsewhere for analytics / breadcrumbs.\n void agent;\n\n if (!tunnelUrl || !agentApiKey) {\n return (\n <div className=\"rounded-lg border border-status-warning-border bg-status-warning-bg p-4 text-sm text-status-warning-text\">\n {tr(\n 'plan.tunnelMissing',\n 'Agent tunnel URL or API key is missing. Bring the agent online and refresh.'\n )}\n </div>\n );\n }\n\n const configuredAgents = agents.filter((a) => a.hookConfigured);\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n {activeSession && (\n <>\n <span className=\"inline-flex items-center gap-1\">\n {connection === 'connected' && (\n <Wifi className=\"h-3.5 w-3.5 text-status-success-text\" />\n )}\n {connection === 'connecting' && (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin text-status-warning-text\" />\n )}\n {connection === 'error' && <WifiOff className=\"h-3.5 w-3.5 text-status-error-text\" />}\n {tr(`plan.connection.${connection}`, connection)}\n </span>\n <span aria-hidden=\"true\">·</span>\n </>\n )}\n <span>{tr('plan.hookCount', `${configuredAgents.length} hook(s) configured`)}</span>\n <button\n onClick={onReconfigure}\n className=\"ml-auto inline-flex items-center gap-1 hover:text-text-primary\"\n >\n <Settings className=\"h-3.5 w-3.5\" />\n {tr('plan.reopenWizard', 'Manage hooks')}\n </button>\n </div>\n\n <div className=\"border border-border-default rounded-lg overflow-hidden bg-bg-sunken relative h-[calc(100vh-320px)] min-h-[500px]\">\n {!activeSession && (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-2 p-6 text-center\">\n <Inbox className=\"h-8 w-8 text-text-muted\" />\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('plan.noSessionTitle', 'No active plan session')}\n </p>\n <p className=\"max-w-md text-xs text-text-secondary\">\n {tr(\n 'plan.noSessionDesc',\n 'Start a plan in a connected coding agent — when it finalises a plan, the configured hook forwards it here for review.'\n )}\n </p>\n </div>\n )}\n {iframeSrc && (\n /* The src carries ?apiKey= for the initial auth (an iframe can't set\n request headers). Drop popups/top-navigation and send no referrer\n so that key can't leak out of the frame via a navigation or popup. */\n <iframe\n ref={iframeRef}\n src={iframeSrc}\n title={tr('plan.iframeTitle', 'Plannotator plan workspace')}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n referrerPolicy=\"no-referrer\"\n sandbox=\"allow-same-origin allow-scripts allow-forms\"\n onLoad={handleLoad}\n onError={handleError}\n data-testid=\"plan-iframe\"\n />\n )}\n {activeSession && connection === 'connecting' && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-bg-sunken/80 pointer-events-none\">\n <Loader2 className=\"h-6 w-6 animate-spin text-action-primary-bg\" />\n </div>\n )}\n {activeSession && connection === 'error' && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-bg-sunken p-6 text-center\">\n <div>\n <AlertCircle className=\"h-6 w-6 text-status-error-text mx-auto\" />\n <p className=\"text-sm font-medium text-text-primary mt-2\">\n {tr('plan.iframeErrorTitle', 'Could not load plannotator.')}\n </p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {tr(\n 'plan.iframeErrorDesc',\n 'The agent reverse proxy returned an error. Check the agent status and try again.'\n )}\n </p>\n <button\n onClick={handleRetry}\n className=\"mt-3 inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm text-action-primary-text hover:bg-action-primary-bgHover\"\n >\n <RefreshCcw className=\"h-3.5 w-3.5\" /> {tr('plan.retry', 'Retry')}\n </button>\n </div>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AA6DA,SAAgB,EAAY,EAAE,UAAO,gBAAa,gBAA+B;CAC/E,IAAM,EAAE,SAAM,GAAS,EAGjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;;AAGpC,QACE,kBAAC,GAAD;EACE,SAAS,EAAM;EACf,UAAU,CAAC,GAAmB,EAAyB;EACvD,MAAK;EACL,SAAS,EAAG,8BAA8B,uBAAuB;EACjE,OAAO,EAAG,iCAAiC,4BAA4B;EACvE,aAAa,EACX,gCACA,wJACD;YAED,kBAAC,GAAD;GAAuB;GAAoB;GAAwB;GAAa,CAAA;EACrE,CAAA;;AAIjB,SAAS,EAAe,EAAE,UAAO,gBAAa,gBAA+B;CAC3E,IAAM,EAAE,SAAM,GAAS,EACjB,IAAK,GACR,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAEpC,CAAC,EAAE,CACJ,EAEK,CAAC,GAAQ,KAAa,EAAmC,KAAK,EAC9D,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAe,KAAoB,EAAS,GAAK,EAClD,CAAC,GAAQ,KAAa,EAAiC,EAAE,CAAC,EAC1D,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAe,KAAoB,EAAwB,KAAK,EACjE,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAE7D,IAAU,EAId,OAAO,IAAS,OAAU;AACxB,EAAK,MACH,EAAiB,GAAK,EACtB,EAAe,KAAK;AAEtB,MAAI;AACF,OAAI,GAAQ;AACV,MAAU,MAAM,EAAqB,EAAM,CAAC;AAC5C;;GAEF,IAAM,CAAC,GAAc,KAAgB,MAAM,QAAQ,IAAI,CACrD,EAAqB,EAAM,EAC3B,EAAoB,EAAM,CAAC,aAAa,EAAE,QAAQ,EAAE,EAAE,EAAE,CACzD,CAAC;AAEF,GADA,EAAU,EAAa,EACvB,EAAU,EAAa,UAAU,EAAE,CAAC;WAC7B,GAAK;AACZ,GAAK,KACH,EAAe,aAAe,QAAQ,EAAI,UAAU,6BAA6B;YAE3E;AACR,GAAK,KAAQ,EAAiB,GAAM;;IAGxC,CAAC,EAAM,CACR;AAED,SAAgB;AACT,KAAS;IACb,CAAC,EAAQ,CAAC;CAEb,IAAM,IAAmB,EAAY,YAAY;AAG/C,EAFA,EAAc,GAAK,EACnB,EAAe,KAAK,EACpB,EAAiB,KAAK;AACtB,MAAI;GACF,IAAM,IAAS,MAAM,EAAyB,EAAM;AACpD,GAAI,EAAO,MACT,EAAiB,EAAG,qBAAqB,6BAA6B,CAAC,EACvE,MAAM,GAAS,IAEf,EAAe,EAAO,SAAS,iBAAiB;WAE3C,GAAK;AACZ,KAAe,aAAe,QAAQ,EAAI,UAAU,iBAAiB;YAC7D;AACR,KAAc,GAAM;;IAErB;EAAC;EAAO;EAAS;EAAG,CAAC,EAElB,IAAsB,EAC1B,OAAO,MAA6C;AAGlD,EAFA,EAAe,EAAU,EACzB,EAAe,KAAK,EACpB,EAAiB,KAAK;AACtB,MAAI;GACF,IAAM,IAAS,MAAM,EAAmB,GAAO,EAAU;AACzD,GAAI,EAAO,MACT,EAAiB,EAAG,uBAAuB,uBAAuB,EAAU,GAAG,CAAC,EAChF,MAAM,GAAS,IAEf,EAAe,EAAO,SAAS,wBAAwB;WAElD,GAAK;AACZ,KAAe,aAAe,QAAQ,EAAI,UAAU,wBAAwB;YACpE;AACR,KAAe,KAAK;;IAGxB;EAAC;EAAO;EAAS;EAAG,CACrB,EAIK,IAAO,QACP,IAAsB,YACtB,IAAoB,iBACnB,GAAQ,YACS,EAAO,MAAM,MAAM,EAAE,eAAe,GAEnD,UADoB,WAFI,eAI9B;EAAC;EAAQ;EAAa;EAAe;EAAO,CAAC;AA8ChD,QAxCA,QAAgB;AACd,MAAI,MAAS,QAAS;EAEtB,IAAM,IAAK,kBAAkB;AAC3B,GAAK,SAAS,UAAa,EAAQ,GAAK;KACvC,IAAK;AACR,eAAa,cAAc,EAAG;IAC7B,CAAC,GAAM,EAAQ,CAAC,EAEf,MAAS,YAET,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EAC5C,kBAAC,QAAD,EAAA,UAAO,EAAG,sBAAsB,uBAAuB,EAAQ,CAAA,CAC3D;MAIN,MAAS,iBAET,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAa,WAAU,2BAA4B,CAAA,EACnD,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAG,oBAAoB,6CAA6C;MACnE,CAAA;KACJ,kBAAC,KAAD;MAAG,WAAU;gBAA2B;MAAgB,CAAA;KACxD,kBAAC,UAAD;MACE,eAAe,KAAK,GAAS;MAC7B,WAAU;gBAFZ;OAIE,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;;OAAE,EAAG,cAAc,QAAQ;OACtD;;KACL;MACF;;EACF,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG,KACC,kBAAC,OAAD;IACE,MAAK;IACL,WAAU;cAET;IACG,CAAA;GAEP,KACC,kBAAC,OAAD;IACE,MAAK;IACL,WAAU;cAET;IACG,CAAA;GAGP,MAAS,iBACR,kBAAC,GAAD;IAA4B;IAAY,WAAW;IAAsB;IAAM,CAAA;GAGhF,MAAS,YACR,kBAAC,GAAD;IACU;IACK;IACb,aAAa;IACT;IACJ,CAAA;GAGH,MAAS,WACR,kBAAC,GAAD;IACS;IACC;IACK;IACF;IACX,iBAAiB,GAAQ,mBAAmB,EAAE;IAC9C,qBAAqB,EAAiB,KAAK;IACvC;IACJ,CAAA;GAEA;;;AAUV,SAAS,EAAe,EAAE,eAAY,cAAW,SAA2B;AAC1E,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAY,WAAU,2CAA4C,CAAA,EAClE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,wBAAwB,8BAA8B;MACvD,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,uBACA,yLACD;MACC,CAAA;KACJ,kBAAC,UAAD;MACE,SAAS;MACT,UAAU;MACV,WAAU;gBAET,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EAC3C,EAAG,mBAAmB,cAAc,CACpC,EAAA,CAAA,GAEH,EAAG,sBAAsB,0BAA0B;MAE9C,CAAA;KACL;MACF;;EACF,CAAA;;AAWV,SAAS,EAAW,EAAE,WAAQ,gBAAa,gBAAa,SAAuB;CAC7E,IAAM,IAAY,EAAO,QAAQ,MAAM,EAAE,aAAa;AACtD,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAU,WAAU,2CAA4C,CAAA,EAChE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,oBAAoB,6BAA6B;MAClD,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,mBACA,yKACD;MACC,CAAA;KACH,EAAU,WAAW,KACpB,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,uBACA,uHACD;MACC,CAAA;KAEN,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAO,KAAK,MACX,kBAAC,MAAD;OAEE,WAAU;iBAFZ;QAIG,EAAM,iBACL,kBAAC,GAAD,EAAc,WAAU,oDAAqD,CAAA,GAE7E,kBAAC,OAAD,EAAK,WAAU,uEAAwE,CAAA;QAEzF,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,KAAD;WAAG,WAAU;qBAAyC,EAAM;WAAS,CAAA;UACrE,kBAAC,KAAD;WAAG,WAAU;qBAAsC,EAAM;WAAiB,CAAA;UACzE,CAAC,EAAM,gBACN,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAG,wBAAwB,oCAAoC;WAC9D,CAAA;UAEF;;QACN,kBAAC,UAAD;SACE,UACE,CAAC,EAAM,gBAAgB,EAAM,kBAAkB,MAAgB,EAAM;SAEvE,eAAe,EAAY,EAAM,MAAM;SACvC,WAAU;mBAET,EAAM,iBACH,EAAG,4BAA4B,aAAa,GAC5C,MAAgB,EAAM,QACpB,EAAG,oBAAoB,eAAe,GACtC,EAAG,sBAAsB,iBAAiB;SACzC,CAAA;QACN;SA9BE,EAAM,MA8BR,CACL;MACC,CAAA;KACD;MACF;;EACF,CAAA;;AAcV,SAAS,EAAe,EACtB,UACA,WACA,gBACA,cACA,oBACA,kBACA,SACsB;CACtB,IAAM,IAAY,EAAiC,KAAK,EAClD,CAAC,GAAY,KAAiB,EAA2B,aAAa,EAOtE,IAAgB,QAElB,EAAgB,SAAS,IAErB,CAAC,GAAG,EAAgB,CAAC,MAClB,GAAG,OAAO,KAAK,MAAM,EAAE,UAAU,IAAI,MAAM,KAAK,MAAM,EAAE,UAAU,IAAI,GACxE,CAAC,KACF,MACN,CAAC,EAAgB,CAClB,EACK,IAAkB,GAAe,aAAa,MAE9C,IAAY,QAAc;AAC9B,MAAI,CAAC,KAAa,CAAC,KAAe,CAAC,EAAiB,QAAO;EAC3D,IAAM,IAAO,EAAU,QAAQ,OAAO,GAAG,EAInC,IAAM,IAAI,IAAI,GAAG,EAAK,QAAQ,EAAgB,GAAG;AAEvD,SADA,EAAI,aAAa,IAAI,UAAU,EAAY,EACpC,EAAI,UAAU;IACpB;EAAC;EAAW;EAAa;EAAgB,CAAC;AAI7C,SAAgB;AACd,IAAc,aAAa;IAC1B,CAAC,EAAgB,CAAC;CAErB,IAAM,IAAW,QAAkB;EACjC,IAAM,IAAS,EAAU;AACrB,SAAC,GAAQ,iBAAiB,CAAC,KAAe,CAAC,GAC/C,KAAI;GACF,IAAM,IAAe,IAAI,IAAI,EAAU,CAAC;AACxC,KAAO,cAAc,YAAY;IAAE,MAAM;IAAa,QAAQ;IAAa,EAAE,EAAa;UACpF;IAGP,CAAC,GAAa,EAAU,CAAC,EAEtB,IAAa,QAAkB;AAEnC,EADA,EAAc,YAAY,EAC1B,GAAU;IACT,CAAC,EAAS,CAAC,EAER,IAAc,QAAkB;AACpC,IAAc,QAAQ;IACrB,EAAE,CAAC,EAEA,IAAc,QAAkB;EACpC,IAAM,IAAS,EAAU;AACzB,MAAI,CAAC,EAAQ;AACb,IAAc,aAAa;EAC3B,IAAM,IAAM,EAAO;AAEnB,EADA,EAAO,MAAM,eACb,4BAA4B;AAC1B,KAAO,MAAM;IACb;IACD,EAAE,CAAC;AAKN,KAAI,CAAC,KAAa,CAAC,EACjB,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,EACC,sBACA,8EACD;EACG,CAAA;CAIV,IAAM,IAAmB,EAAO,QAAQ,MAAM,EAAE,eAAe;AAE/D,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,MAAe,eACd,kBAAC,GAAD,EAAM,WAAU,wCAAyC,CAAA;MAE1D,MAAe,gBACd,kBAAC,GAAD,EAAS,WAAU,qDAAsD,CAAA;MAE1E,MAAe,WAAW,kBAAC,GAAD,EAAS,WAAU,sCAAuC,CAAA;MACpF,EAAG,mBAAmB,KAAc,EAAW;MAC3C;QACP,kBAAC,QAAD;KAAM,eAAY;eAAO;KAAQ,CAAA,CAChC,EAAA,CAAA;IAEL,kBAAC,QAAD,EAAA,UAAO,EAAG,kBAAkB,GAAG,EAAiB,OAAO,qBAAqB,EAAQ,CAAA;IACpF,kBAAC,UAAD;KACE,SAAS;KACT,WAAU;eAFZ,CAIE,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA,EACnC,EAAG,qBAAqB,eAAe,CACjC;;IACL;MAEN,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,CAAC,KACA,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAO,WAAU,2BAA4B,CAAA;MAC7C,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAG,uBAAuB,yBAAyB;OAClD,CAAA;MACJ,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,sBACA,wHACD;OACC,CAAA;MACA;;IAEP,KAIC,kBAAC,UAAD;KACE,KAAK;KACL,KAAK;KACL,OAAO,EAAG,oBAAoB,6BAA6B;KAC3D,WAAU;KACV,OAAM;KACN,gBAAe;KACf,SAAQ;KACR,QAAQ;KACR,SAAS;KACT,eAAY;KACZ,CAAA;IAEH,KAAiB,MAAe,gBAC/B,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAS,WAAU,+CAAgD,CAAA;KAC/D,CAAA;IAEP,KAAiB,MAAe,WAC/B,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD,EAAA,UAAA;MACE,kBAAC,GAAD,EAAa,WAAU,0CAA2C,CAAA;MAClE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAG,yBAAyB,8BAA8B;OACzD,CAAA;MACJ,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,wBACA,mFACD;OACC,CAAA;MACJ,kBAAC,UAAD;OACE,SAAS;OACT,WAAU;iBAFZ;QAIE,kBAAC,GAAD,EAAY,WAAU,eAAgB,CAAA;;QAAE,EAAG,cAAc,QAAQ;QAC1D;;MACL,EAAA,CAAA;KACF,CAAA;IAEJ;KACF"}
|