@burdenoff/microfe-vibecontrols 2026.527.3 → 2026.527.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ai/AIPlanPanel.js +45 -33
- package/dist/components/ai/AIPlanPanel.js.map +1 -1
- package/dist/components/vibe/VibeLlmContextTabPanel.js +315 -210
- package/dist/components/vibe/VibeLlmContextTabPanel.js.map +1 -1
- package/dist/generated/wspace-operations.js +233 -202
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/index.js +10 -10
- package/package.json +1 -1
|
@@ -30,7 +30,7 @@ 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]), [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) => {
|
|
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, z] = d(!1), B = s(async (t = !1) => {
|
|
34
34
|
t || (S(!0), b(null));
|
|
35
35
|
try {
|
|
36
36
|
if (t) {
|
|
@@ -46,13 +46,13 @@ function E({ agent: e, agentApiKey: t, tunnelUrl: n }) {
|
|
|
46
46
|
}
|
|
47
47
|
}, [e]);
|
|
48
48
|
c(() => {
|
|
49
|
-
|
|
50
|
-
}, [
|
|
51
|
-
let
|
|
49
|
+
B();
|
|
50
|
+
}, [B]);
|
|
51
|
+
let V = s(async () => {
|
|
52
52
|
j(!0), L(null), F(null);
|
|
53
53
|
try {
|
|
54
54
|
let t = await a(e);
|
|
55
|
-
t.ok ? (F(m("plan.cliInstalled", "Plannotator CLI installed.")), await
|
|
55
|
+
t.ok ? (F(m("plan.cliInstalled", "Plannotator CLI installed.")), await B()) : L(t.error ?? "Install failed");
|
|
56
56
|
} catch (e) {
|
|
57
57
|
L(e instanceof Error ? e.message : "Install failed");
|
|
58
58
|
} finally {
|
|
@@ -60,13 +60,13 @@ function E({ agent: e, agentApiKey: t, tunnelUrl: n }) {
|
|
|
60
60
|
}
|
|
61
61
|
}, [
|
|
62
62
|
e,
|
|
63
|
-
|
|
63
|
+
B,
|
|
64
64
|
m
|
|
65
|
-
]),
|
|
65
|
+
]), H = s(async (t) => {
|
|
66
66
|
N(t), L(null), F(null);
|
|
67
67
|
try {
|
|
68
68
|
let n = await r(e, t);
|
|
69
|
-
n.ok ? (F(m("plan.hookConfigured", `Hook configured for ${t}.`)), await
|
|
69
|
+
n.ok ? (F(m("plan.hookConfigured", `Hook configured for ${t}.`)), await B()) : L(n.error ?? "Hook configure failed");
|
|
70
70
|
} catch (e) {
|
|
71
71
|
L(e instanceof Error ? e.message : "Hook configure failed");
|
|
72
72
|
} finally {
|
|
@@ -74,24 +74,25 @@ function E({ agent: e, agentApiKey: t, tunnelUrl: n }) {
|
|
|
74
74
|
}
|
|
75
75
|
}, [
|
|
76
76
|
e,
|
|
77
|
-
|
|
77
|
+
B,
|
|
78
78
|
m
|
|
79
|
-
]),
|
|
79
|
+
]), U = l(() => x ? "loading" : y ? "status-error" : h?.installed ? !T.some((e) => e.hookConfigured) || R ? "wizard" : "ready" : "install-cli", [
|
|
80
80
|
h,
|
|
81
81
|
y,
|
|
82
82
|
x,
|
|
83
|
-
T
|
|
83
|
+
T,
|
|
84
|
+
R
|
|
84
85
|
]);
|
|
85
86
|
return c(() => {
|
|
86
|
-
if (
|
|
87
|
+
if (U !== "ready") return;
|
|
87
88
|
let e = setInterval(() => {
|
|
88
|
-
document.hidden ||
|
|
89
|
+
document.hidden || B(!0);
|
|
89
90
|
}, 5e3);
|
|
90
91
|
return () => clearInterval(e);
|
|
91
|
-
}, [
|
|
92
|
+
}, [U, B]), U === "loading" ? /* @__PURE__ */ w("div", {
|
|
92
93
|
className: "flex items-center gap-2 p-6 text-text-secondary",
|
|
93
94
|
children: [/* @__PURE__ */ C(g, { className: "h-4 w-4 animate-spin" }), /* @__PURE__ */ C("span", { children: m("plan.loadingStatus", "Loading plan status…") })]
|
|
94
|
-
}) :
|
|
95
|
+
}) : U === "status-error" ? /* @__PURE__ */ C("div", {
|
|
95
96
|
className: "rounded-md border border-status-error-border bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
96
97
|
children: /* @__PURE__ */ w("div", {
|
|
97
98
|
className: "flex items-start gap-2",
|
|
@@ -107,7 +108,7 @@ function E({ agent: e, agentApiKey: t, tunnelUrl: n }) {
|
|
|
107
108
|
children: y
|
|
108
109
|
}),
|
|
109
110
|
/* @__PURE__ */ w("button", {
|
|
110
|
-
onClick: () => void
|
|
111
|
+
onClick: () => void B(),
|
|
111
112
|
className: "mt-3 inline-flex items-center gap-1 text-xs font-medium hover:underline",
|
|
112
113
|
children: [
|
|
113
114
|
/* @__PURE__ */ C(v, { className: "h-3 w-3" }),
|
|
@@ -131,24 +132,28 @@ function E({ agent: e, agentApiKey: t, tunnelUrl: n }) {
|
|
|
131
132
|
className: "rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text",
|
|
132
133
|
children: I
|
|
133
134
|
}),
|
|
134
|
-
|
|
135
|
+
U === "install-cli" && /* @__PURE__ */ C(D, {
|
|
135
136
|
installing: A,
|
|
136
|
-
onInstall:
|
|
137
|
+
onInstall: V,
|
|
137
138
|
tr: m
|
|
138
139
|
}),
|
|
139
|
-
|
|
140
|
+
U === "wizard" && /* @__PURE__ */ C(O, {
|
|
140
141
|
agents: T,
|
|
141
142
|
configuring: M,
|
|
142
|
-
onConfigure:
|
|
143
|
+
onConfigure: H,
|
|
144
|
+
canExit: R && T.some((e) => e.hookConfigured),
|
|
145
|
+
onExit: () => z(!1),
|
|
143
146
|
tr: m
|
|
144
147
|
}),
|
|
145
|
-
|
|
148
|
+
U === "ready" && /* @__PURE__ */ C(k, {
|
|
146
149
|
agent: e,
|
|
147
150
|
agents: T,
|
|
148
151
|
agentApiKey: t,
|
|
149
152
|
tunnelUrl: n,
|
|
150
153
|
runningSessions: h?.runningSessions ?? [],
|
|
151
|
-
onReconfigure: () =>
|
|
154
|
+
onReconfigure: () => {
|
|
155
|
+
F(null), z(!0);
|
|
156
|
+
},
|
|
152
157
|
tr: m
|
|
153
158
|
})
|
|
154
159
|
]
|
|
@@ -181,8 +186,8 @@ function D({ installing: e, onInstall: t, tr: n }) {
|
|
|
181
186
|
})
|
|
182
187
|
});
|
|
183
188
|
}
|
|
184
|
-
function O({ agents: e, configuring: t, onConfigure: n,
|
|
185
|
-
let
|
|
189
|
+
function O({ agents: e, configuring: t, onConfigure: n, canExit: r, onExit: i, tr: a }) {
|
|
190
|
+
let o = e.filter((e) => e.cliInstalled);
|
|
186
191
|
return /* @__PURE__ */ C("div", {
|
|
187
192
|
className: "rounded-lg border border-border-default bg-bg-elevated p-6",
|
|
188
193
|
children: /* @__PURE__ */ w("div", {
|
|
@@ -190,17 +195,24 @@ function O({ agents: e, configuring: t, onConfigure: n, tr: r }) {
|
|
|
190
195
|
children: [/* @__PURE__ */ C(y, { className: "h-6 w-6 text-action-primary-bg shrink-0" }), /* @__PURE__ */ w("div", {
|
|
191
196
|
className: "flex-1",
|
|
192
197
|
children: [
|
|
193
|
-
/* @__PURE__ */
|
|
194
|
-
className: "
|
|
195
|
-
children:
|
|
198
|
+
/* @__PURE__ */ w("div", {
|
|
199
|
+
className: "flex items-center justify-between gap-3",
|
|
200
|
+
children: [/* @__PURE__ */ C("h3", {
|
|
201
|
+
className: "text-base font-semibold text-text-primary",
|
|
202
|
+
children: a("plan.wizardTitle", "Connect an AI coding agent")
|
|
203
|
+
}), r && i && /* @__PURE__ */ C("button", {
|
|
204
|
+
onClick: i,
|
|
205
|
+
className: "shrink-0 inline-flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary",
|
|
206
|
+
children: a("plan.wizardDone", "Done")
|
|
207
|
+
})]
|
|
196
208
|
}),
|
|
197
209
|
/* @__PURE__ */ C("p", {
|
|
198
210
|
className: "text-sm text-text-secondary mt-1",
|
|
199
|
-
children:
|
|
211
|
+
children: a("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.")
|
|
200
212
|
}),
|
|
201
|
-
|
|
213
|
+
o.length === 0 && /* @__PURE__ */ C("p", {
|
|
202
214
|
className: "mt-4 text-sm text-status-warning-text",
|
|
203
|
-
children:
|
|
215
|
+
children: a("plan.wizardNoAgents", "No AI coding agents detected in this sandbox. Install one (Claude Code, OpenCode, Codex, Pi, or Gemini) and refresh.")
|
|
204
216
|
}),
|
|
205
217
|
/* @__PURE__ */ C("ul", {
|
|
206
218
|
className: "mt-4 space-y-2",
|
|
@@ -221,15 +233,15 @@ function O({ agents: e, configuring: t, onConfigure: n, tr: r }) {
|
|
|
221
233
|
}),
|
|
222
234
|
!e.cliInstalled && /* @__PURE__ */ C("p", {
|
|
223
235
|
className: "text-xs text-status-warning-text mt-1",
|
|
224
|
-
children:
|
|
236
|
+
children: a("plan.agentCliMissing", "CLI not detected on this sandbox.")
|
|
225
237
|
})
|
|
226
238
|
]
|
|
227
239
|
}),
|
|
228
240
|
/* @__PURE__ */ C("button", {
|
|
229
|
-
disabled: !e.cliInstalled ||
|
|
241
|
+
disabled: !e.cliInstalled || t === e.agent,
|
|
230
242
|
onClick: () => n(e.agent),
|
|
231
243
|
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",
|
|
232
|
-
children: e.
|
|
244
|
+
children: t === e.agent ? a("plan.configuring", "Configuring…") : e.hookConfigured ? a("plan.reconfigureHook", "Reconfigure") : a("plan.configureHook", "Configure hook")
|
|
233
245
|
})
|
|
234
246
|
]
|
|
235
247
|
}, e.agent))
|
|
@@ -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 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":"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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 // \"Manage hooks\" forces the wizard back open even when a hook is already\n // configured — the only way to re-write a stale/broken hook (wrong path or\n // a key from a prior agent identity), which otherwise locks the panel into\n // the ready view forever.\n const [forceWizard, setForceWizard] = useState(false);\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 || forceWizard) return 'wizard';\n return 'ready';\n }, [status, statusError, statusLoading, agents, forceWizard]);\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 // Offer \"Done\" only when a ready view exists to return to — i.e.\n // at least one hook is configured (we got here via Manage hooks),\n // not during the genuine first-run wizard.\n canExit={forceWizard && agents.some((a) => a.hookConfigured)}\n onExit={() => setForceWizard(false)}\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={() => {\n setActionMessage(null);\n setForceWizard(true);\n }}\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 canExit?: boolean;\n onExit?: () => void;\n tr: (key: string, fallback: string) => string;\n}\n\nfunction WizardCard({ agents, configuring, onConfigure, canExit, onExit, 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 <div className=\"flex items-center justify-between gap-3\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('plan.wizardTitle', 'Connect an AI coding agent')}\n </h3>\n {canExit && onExit && (\n <button\n onClick={onExit}\n className=\"shrink-0 inline-flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary\"\n >\n {tr('plan.wizardDone', 'Done')}\n </button>\n )}\n </div>\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 // Re-enabled when already configured: re-writing is the only\n // way to repair a stale hook (wrong path / old agent key).\n disabled={!entry.cliInstalled || configuring === entry.agent}\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 {configuring === entry.agent\n ? tr('plan.configuring', 'Configuring…')\n : entry.hookConfigured\n ? tr('plan.reconfigureHook', 'Reconfigure')\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":"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"}
|