@burdenoff/microfe-vibecontrols 2026.613.1 → 2026.618.1

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.
Files changed (62) hide show
  1. package/dist/components/agents/AgentConnectionRecovery.js +100 -94
  2. package/dist/components/agents/AgentConnectionRecovery.js.map +1 -1
  3. package/dist/components/agents/AgentMaintenanceTab.js +177 -160
  4. package/dist/components/agents/AgentMaintenanceTab.js.map +1 -1
  5. package/dist/components/agents/OpenInBrowserEditorButton.js +102 -94
  6. package/dist/components/agents/OpenInBrowserEditorButton.js.map +1 -1
  7. package/dist/components/assistant/AssistantChatArea.js +1 -0
  8. package/dist/components/assistant/AssistantChatArea.js.map +1 -1
  9. package/dist/components/assistant/AssistantModeSelector.js +20 -19
  10. package/dist/components/assistant/AssistantModeSelector.js.map +1 -1
  11. package/dist/components/tunnels/TunnelCard.js +3 -3
  12. package/dist/components/tunnels/TunnelCard.js.map +1 -1
  13. package/dist/components/tunnels/TunnelPublicAccessPanel.js +36 -36
  14. package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
  15. package/dist/config/assistantUsecases.js +11 -0
  16. package/dist/config/assistantUsecases.js.map +1 -1
  17. package/dist/constants/permissions.js +38 -6
  18. package/dist/constants/permissions.js.map +1 -1
  19. package/dist/index.js +4 -3
  20. package/dist/pages/CatalogDetailPage.js +155 -155
  21. package/dist/pages/CatalogDetailPage.js.map +1 -1
  22. package/dist/pages/CatalogPage.js +110 -109
  23. package/dist/pages/CatalogPage.js.map +1 -1
  24. package/dist/pages/ComponentDetailPage.js +338 -338
  25. package/dist/pages/ComponentDetailPage.js.map +1 -1
  26. package/dist/pages/ComponentsPage.js +104 -103
  27. package/dist/pages/ComponentsPage.js.map +1 -1
  28. package/dist/pages/TargetDetailsPage.js +140 -132
  29. package/dist/pages/TargetDetailsPage.js.map +1 -1
  30. package/dist/pages/TemplateDetailPage.js +201 -199
  31. package/dist/pages/TemplateDetailPage.js.map +1 -1
  32. package/dist/pages/TemplatesPage.js +62 -61
  33. package/dist/pages/TemplatesPage.js.map +1 -1
  34. package/dist/pages/VibeDetailsPage.js +568 -555
  35. package/dist/pages/VibeDetailsPage.js.map +1 -1
  36. package/dist/pages/ai/AIBookmarksPage.js +104 -89
  37. package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
  38. package/dist/pages/ai/AIPage.js +116 -107
  39. package/dist/pages/ai/AIPage.js.map +1 -1
  40. package/dist/pages/ai/AISessionDetailPage.js +46 -34
  41. package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
  42. package/dist/pages/ai/AgentManagerPage.js +112 -100
  43. package/dist/pages/ai/AgentManagerPage.js.map +1 -1
  44. package/dist/pages/tunnels/TunnelDetailsPage.js +177 -153
  45. package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
  46. package/dist/pages/tunnels/TunnelsTab.js +119 -111
  47. package/dist/pages/tunnels/TunnelsTab.js.map +1 -1
  48. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +24 -24
  49. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
  50. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +9 -8
  51. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
  52. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +13 -13
  53. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
  54. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +19 -15
  55. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
  56. package/dist/services/assistantApi.js +9 -3
  57. package/dist/services/assistantApi.js.map +1 -1
  58. package/dist/services/createSandboxAssistantTransport.js +120 -0
  59. package/dist/services/createSandboxAssistantTransport.js.map +1 -0
  60. package/dist/store/assistantStore.js +1 -1
  61. package/dist/store/assistantStore.js.map +1 -1
  62. package/package.json +5 -1
@@ -1 +1 @@
1
- {"version":3,"file":"AgentMaintenanceTab.js","names":[],"sources":["../../../src/components/agents/AgentMaintenanceTab.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport { ArrowUpCircle, Loader2, RefreshCw, Terminal, Trash2, AlertTriangle } from 'lucide-react';\nimport { Checkbox } from '@burdenoff/fe-libs/ui';\n\nimport {\n useAgentUpdateStatusQuery,\n useAgentUpdateCheckLazyQuery,\n useAgentSelfUpdateMutation,\n useSetAgentAutoUpdateMutation,\n useAgentCommandsQuery,\n useRunAgentCommandMutation,\n useNukeVibecontrolsAgentMutation,\n} from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useField } from '../../shared/hooks/useField';\nimport { ConfirmDialog } from '../shared/ConfirmDialog';\n\ntype Json = Record<string, unknown> | null | undefined;\n\nfunction asRecord(v: unknown): Record<string, unknown> | null {\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : null;\n}\nfunction jstr(obj: Json, key: string): string | null {\n const v = obj?.[key];\n return typeof v === 'string' ? v : null;\n}\nfunction jbool(obj: Json, key: string): boolean | null {\n const v = obj?.[key];\n return typeof v === 'boolean' ? v : null;\n}\n\ninterface CommandInfo {\n name: string;\n description: string;\n destructive?: boolean;\n options?: Array<{ flags: string; description: string }>;\n}\n\ninterface AgentMaintenanceTabProps {\n agentId: string;\n agentVersion?: string | null;\n onAfterDestructiveNuke?: () => void | Promise<void>;\n}\n\nconst SECTION = 'rounded-card border border-border-default bg-bg-surface p-4';\nconst BTN_PRIMARY =\n 'inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-button bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus';\nconst BTN_NEUTRAL =\n 'inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-button bg-bg-sunken text-text-primary hover:bg-bg-elevated transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus';\nconst BTN_DANGER =\n 'inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-button bg-action-danger-bg text-action-danger-text hover:bg-action-danger-bgHover transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus';\nconst FIELD_LABEL = 'text-xs font-medium text-text-muted';\nconst FIELD_INPUT =\n 'px-3 py-2 text-base md:text-sm rounded-input bg-bg-sunken border border-border-default text-text-primary focus:outline-none focus:ring-2 focus:ring-border-focus';\n\nexport function AgentMaintenanceTab({\n agentId,\n agentVersion,\n onAfterDestructiveNuke,\n}: AgentMaintenanceTabProps) {\n const tr = useTr();\n\n // ── Output + toast plumbing (component-local) ───────────────────────────\n const [output, setOutput] = useState<{ title: string; body: string; error?: boolean } | null>(\n null\n );\n const showOutput = useCallback((title: string, body: string, error = false) => {\n setOutput({ title, body, error });\n }, []);\n\n // ── Update / autoupdate ─────────────────────────────────────────────────\n const { data: statusData, refetch: refetchStatus } = useAgentUpdateStatusQuery({\n variables: { agentId },\n fetchPolicy: 'cache-and-network',\n });\n const [checkUpdate, { data: checkData, loading: checking }] = useAgentUpdateCheckLazyQuery({\n fetchPolicy: 'network-only',\n });\n const [selfUpdate, { loading: updating }] = useAgentSelfUpdateMutation();\n const [setAutoUpdate, { loading: togglingAuto }] = useSetAgentAutoUpdateMutation();\n\n const status = asRecord(statusData?.agentUpdateStatus?.data);\n const lastCheck = asRecord(status?.lastCheck);\n const checkResult = asRecord(checkData?.agentUpdateCheck?.data);\n const autoUpdateEnabled = jbool(status, 'enabled') ?? true;\n const latestVersion = jstr(checkResult, 'latest') ?? jstr(lastCheck, 'latest') ?? null;\n const hasUpdate = jbool(checkResult, 'hasUpdate') ?? jbool(lastCheck, 'hasUpdate') ?? false;\n\n const [versionInput, setVersionInput] = useState('');\n const versionField = useField();\n const commandField = useField();\n const argsField = useField();\n\n const handleCheck = useCallback(async () => {\n setOutput(null);\n try {\n const res = await checkUpdate({ variables: { agentId } });\n const d = asRecord(res.data?.agentUpdateCheck?.data);\n if (res.data?.agentUpdateCheck?.error) {\n showOutput(\n tr('agentDetails.maintenance.update', 'Update'),\n res.data.agentUpdateCheck.error,\n true\n );\n } else if (jbool(d, 'hasUpdate')) {\n showOutput(\n tr('agentDetails.maintenance.update', 'Update'),\n `Update available: ${jstr(d, 'current')} → ${jstr(d, 'latest')}`\n );\n } else {\n showOutput(\n tr('agentDetails.maintenance.update', 'Update'),\n `Up to date (${jstr(d, 'current') ?? agentVersion ?? '?'}).`\n );\n }\n } catch (err) {\n showOutput('Update', err instanceof Error ? err.message : String(err), true);\n }\n }, [agentId, checkUpdate, showOutput, tr, agentVersion]);\n\n const runSelfUpdate = useCallback(\n async (version?: string) => {\n setOutput(null);\n try {\n const res = await selfUpdate({ variables: { agentId, version: version || null } });\n const proxy = res.data?.agentSelfUpdate;\n const d = asRecord(proxy?.data);\n if (proxy?.error || jbool(d, 'ok') === false) {\n showOutput(\n 'Update',\n (jstr(d, 'error') ?? proxy?.error ?? 'Update failed') as string,\n true\n );\n } else {\n showOutput(\n 'Update',\n `Updated → ${jstr(d, 'current') ?? version ?? 'latest'} (restart: ${jstr(d, 'restart') ?? 'n/a'}).`\n );\n }\n await refetchStatus();\n } catch (err) {\n showOutput('Update', err instanceof Error ? err.message : String(err), true);\n }\n },\n [agentId, selfUpdate, showOutput, refetchStatus]\n );\n\n const toggleAutoUpdate = useCallback(\n async (enabled: boolean) => {\n try {\n await setAutoUpdate({ variables: { agentId, enabled } });\n await refetchStatus();\n } catch (err) {\n showOutput('Autoupdate', err instanceof Error ? err.message : String(err), true);\n }\n },\n [agentId, setAutoUpdate, refetchStatus, showOutput]\n );\n\n // ── Command runner ──────────────────────────────────────────────────────\n const {\n data: cmdData,\n loading: cmdsLoading,\n refetch: refetchCmds,\n } = useAgentCommandsQuery({\n variables: { agentId },\n fetchPolicy: 'cache-and-network',\n });\n const [runCommand, { loading: running }] = useRunAgentCommandMutation();\n\n const commands = useMemo<CommandInfo[]>(() => {\n const raw = asRecord(cmdData?.agentCommands?.data)?.commands;\n return Array.isArray(raw) ? (raw as CommandInfo[]) : [];\n }, [cmdData]);\n\n const [selectedCommand, setSelectedCommand] = useState('');\n const [argsInput, setArgsInput] = useState('');\n const [pendingCommand, setPendingCommand] = useState<string[] | null>(null);\n\n const selectedMeta = commands.find((c) => c.name === selectedCommand);\n\n const buildArgv = useCallback((): string[] => {\n const extra = argsInput.trim().length > 0 ? argsInput.trim().split(/\\s+/) : [];\n return [selectedCommand, ...extra];\n }, [selectedCommand, argsInput]);\n\n const executeCommand = useCallback(\n async (argv: string[]) => {\n setOutput(null);\n try {\n const res = await runCommand({ variables: { agentId, argv } });\n const proxy = res.data?.runAgentCommand;\n const d = asRecord(proxy?.data);\n const stdout = jstr(d, 'stdout') ?? '';\n const stderr = jstr(d, 'stderr') ?? '';\n const exit = d?.exitCode;\n const body =\n (stdout ? stdout : '') +\n (stderr ? `\\n[stderr]\\n${stderr}` : '') +\n `\\n[exit ${exit ?? '?'}]`;\n showOutput(\n `vibe ${argv.join(' ')}`,\n body.trim() || proxy?.error || '(no output)',\n !jbool(d, 'ok')\n );\n } catch (err) {\n showOutput('Command', err instanceof Error ? err.message : String(err), true);\n }\n },\n [agentId, runCommand, showOutput]\n );\n\n const handleRunClick = useCallback(() => {\n if (!selectedCommand) return;\n const argv = buildArgv();\n if (selectedMeta?.destructive) {\n setPendingCommand(argv);\n } else {\n void executeCommand(argv);\n }\n }, [selectedCommand, buildArgv, selectedMeta, executeCommand]);\n\n // ── Nuke ────────────────────────────────────────────────────────────────\n const [nuke, { loading: nuking }] = useNukeVibecontrolsAgentMutation();\n const [nukeDryRun, setNukeDryRun] = useState(true);\n const [pendingNuke, setPendingNuke] = useState<{ all: boolean; removeConfig: boolean } | null>(\n null\n );\n\n const runNuke = useCallback(\n async (opts: { all: boolean; removeConfig: boolean }) => {\n setOutput(null);\n try {\n const res = await nuke({\n variables: {\n agentId,\n all: opts.all,\n removeConfig: opts.removeConfig,\n dryRun: nukeDryRun,\n },\n });\n const proxy = res.data?.nukeVibecontrolsAgent;\n const d = asRecord(proxy?.data);\n const stdout = jstr(d, 'stdout') ?? '';\n const stderr = jstr(d, 'stderr') ?? '';\n const ok = jbool(d, 'ok') === true && !proxy?.error;\n showOutput(\n `vibe nuke${opts.all ? ' --all' : ''}${nukeDryRun ? ' --dry-run' : ''}`,\n (stdout + (stderr ? `\\n[stderr]\\n${stderr}` : '')).trim() ||\n proxy?.error ||\n '(no output)',\n !ok && !!proxy?.error\n );\n if (ok && !nukeDryRun) {\n await onAfterDestructiveNuke?.();\n }\n } catch (err) {\n showOutput('Nuke', err instanceof Error ? err.message : String(err), true);\n }\n },\n [agentId, nuke, nukeDryRun, onAfterDestructiveNuke, showOutput]\n );\n\n return (\n <div className=\"space-y-5\">\n {/* ── Agent update ────────────────────────────────────────────── */}\n <section className={SECTION}>\n <div className=\"flex items-center gap-2 mb-3\">\n <ArrowUpCircle className=\"size-5 text-text-secondary\" aria-hidden=\"true\" />\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('agentDetails.maintenance.updateTitle', 'Agent version & updates')}\n </h3>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm mb-4\">\n <div>\n <div className=\"text-text-muted\">\n {tr('agentDetails.maintenance.current', 'Current')}\n </div>\n <div className=\"font-mono text-text-primary\">{agentVersion ?? '—'}</div>\n </div>\n <div>\n <div className=\"text-text-muted\">{tr('agentDetails.maintenance.latest', 'Latest')}</div>\n <div className=\"font-mono text-text-primary\">\n {latestVersion ?? '—'}\n {hasUpdate ? (\n <span className=\"ml-2 text-xs text-status-warning-text\">\n {tr('agentDetails.maintenance.updateAvailable', 'update available')}\n </span>\n ) : null}\n </div>\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2 mb-4\">\n <button type=\"button\" onClick={handleCheck} disabled={checking} className={BTN_NEUTRAL}>\n {checking ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <RefreshCw className=\"size-4\" aria-hidden=\"true\" />\n )}\n {tr('agentDetails.maintenance.check', 'Check for updates')}\n </button>\n <button\n type=\"button\"\n onClick={() => runSelfUpdate()}\n disabled={updating}\n aria-busy={updating}\n className={BTN_PRIMARY}\n >\n {updating ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <ArrowUpCircle className=\"size-4\" aria-hidden=\"true\" />\n )}\n {tr('agentDetails.maintenance.updateLatest', 'Update to latest')}\n </button>\n </div>\n\n <div className=\"flex flex-wrap items-end gap-2 mb-4\">\n <div className=\"flex w-full sm:w-44 flex-col gap-1\">\n <label htmlFor={versionField.id} className={FIELD_LABEL}>\n {tr('agentDetails.maintenance.versionLabel', 'Version')}\n </label>\n <input\n {...versionField.inputProps}\n type=\"text\"\n value={versionInput}\n onChange={(e) => setVersionInput(e.target.value)}\n placeholder={tr('agentDetails.maintenance.versionPlaceholder', 'e.g. 2026.601.21')}\n className={`${FIELD_INPUT} font-mono w-full`}\n />\n </div>\n <button\n type=\"button\"\n onClick={() => runSelfUpdate(versionInput.trim())}\n disabled={updating || versionInput.trim().length === 0}\n className={BTN_NEUTRAL}\n >\n {tr('agentDetails.maintenance.updateVersion', 'Update to version')}\n </button>\n </div>\n\n <label className=\"flex items-center gap-3 cursor-pointer\">\n <Checkbox\n checked={autoUpdateEnabled}\n disabled={togglingAuto}\n onCheckedChange={(c) => toggleAutoUpdate(c === true)}\n />\n <span className=\"text-sm text-text-primary\">\n {tr(\n 'agentDetails.maintenance.autoUpdate',\n 'Automatically install updates when idle (daily background check)'\n )}\n </span>\n </label>\n </section>\n\n {/* ── Command runner ──────────────────────────────────────────── */}\n <section className={SECTION}>\n <div className=\"flex items-center justify-between gap-2 mb-3\">\n <div className=\"flex items-center gap-2\">\n <Terminal className=\"size-5 text-text-secondary\" aria-hidden=\"true\" />\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('agentDetails.maintenance.commandsTitle', 'Run a command')}\n </h3>\n </div>\n <button\n type=\"button\"\n onClick={() => refetchCmds()}\n disabled={cmdsLoading}\n className=\"inline-flex size-9 max-md:size-11 items-center justify-center rounded-button text-text-muted transition-colors hover:bg-bg-sunken hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n aria-label={tr('agentDetails.maintenance.refresh', 'Refresh')}\n >\n <RefreshCw\n className={`size-4 ${cmdsLoading ? 'animate-spin' : ''}`}\n aria-hidden=\"true\"\n />\n </button>\n </div>\n <p className=\"text-xs text-text-muted mb-3\">\n {tr(\n 'agentDetails.maintenance.commandsHint',\n 'Every agent command can be run here. Destructive commands ask for confirmation.'\n )}\n </p>\n <div className=\"flex flex-wrap items-end gap-2 mb-3\">\n <div className=\"flex w-full sm:w-auto sm:min-w-44 flex-col gap-1\">\n <label htmlFor={commandField.id} className={FIELD_LABEL}>\n {tr('agentDetails.maintenance.command', 'Command')}\n </label>\n <select\n {...commandField.inputProps}\n value={selectedCommand}\n onChange={(e) => setSelectedCommand(e.target.value)}\n className={`${FIELD_INPUT} w-full`}\n >\n <option value=\"\">\n {cmdsLoading\n ? tr('agentDetails.maintenance.loading', 'Loading…')\n : tr('agentDetails.maintenance.selectCommand', 'Select a command…')}\n </option>\n {commands.map((c) => (\n <option key={c.name} value={c.name}>\n {c.name}\n {c.destructive\n ? ` ${tr('agentDetails.maintenance.destructiveSuffix', '(destructive)')}`\n : ''}\n </option>\n ))}\n </select>\n </div>\n <div className=\"flex flex-col gap-1 flex-1 min-w-0 basis-full sm:basis-auto\">\n <label htmlFor={argsField.id} className={FIELD_LABEL}>\n {tr('agentDetails.maintenance.args', 'Arguments (optional)')}\n </label>\n <input\n {...argsField.inputProps}\n type=\"text\"\n value={argsInput}\n onChange={(e) => setArgsInput(e.target.value)}\n placeholder=\"--json --tail 50\"\n className={`${FIELD_INPUT} font-mono w-full`}\n />\n </div>\n <button\n type=\"button\"\n onClick={handleRunClick}\n disabled={running || !selectedCommand}\n aria-busy={running}\n className={BTN_PRIMARY}\n >\n {running ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Terminal className=\"size-4\" aria-hidden=\"true\" />\n )}\n {tr('agentDetails.maintenance.run', 'Run')}\n </button>\n </div>\n {selectedMeta?.description ? (\n <p className=\"text-xs text-text-muted\">{selectedMeta.description}</p>\n ) : null}\n </section>\n\n {/* ── Danger zone (nuke) ──────────────────────────────────────── */}\n <section className={`${SECTION} border-status-error-border`}>\n <div className=\"flex items-center gap-2 mb-3\">\n <AlertTriangle className=\"size-5 text-status-error-text\" aria-hidden=\"true\" />\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('agentDetails.maintenance.dangerTitle', 'Danger zone')}\n </h3>\n </div>\n <label className=\"flex items-center gap-3 cursor-pointer mb-3\">\n <Checkbox checked={nukeDryRun} onCheckedChange={(c) => setNukeDryRun(c === true)} />\n <span className=\"text-sm text-text-primary\">\n {tr('agentDetails.maintenance.dryRun', 'Dry run (preview only — nothing is deleted)')}\n </span>\n </label>\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => setPendingNuke({ all: false, removeConfig: false })}\n disabled={nuking}\n className={BTN_NEUTRAL}\n >\n {nuking ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Trash2 className=\"size-4\" aria-hidden=\"true\" />\n )}\n {tr('agentDetails.maintenance.nukeState', 'Nuke state')}\n </button>\n <button\n type=\"button\"\n onClick={() => setPendingNuke({ all: true, removeConfig: true })}\n disabled={nuking}\n className={BTN_DANGER}\n >\n <Trash2 className=\"size-4\" aria-hidden=\"true\" />\n {tr('agentDetails.maintenance.nukeAll', 'Nuke everything (--all)')}\n </button>\n </div>\n <p className=\"text-xs text-text-muted mt-2\">\n {tr(\n 'agentDetails.maintenance.nukeHint',\n '“Nuke state” wipes this agent’s data + tracked binaries. “Nuke everything” also removes all VibeControls plugins, secrets, and config across every profile on the host.'\n )}\n </p>\n </section>\n\n {/* ── Output pane ─────────────────────────────────────────────── */}\n {output ? (\n <section className={SECTION}>\n <div className=\"flex items-center justify-between gap-2 mb-2\">\n <h4 className=\"text-sm font-semibold text-text-primary min-w-0 truncate\">\n {output.title}\n </h4>\n <button\n type=\"button\"\n onClick={() => setOutput(null)}\n className=\"shrink-0 text-xs text-text-muted hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded-button px-1.5 py-1\"\n >\n {tr('agentDetails.maintenance.dismiss', 'Dismiss')}\n </button>\n </div>\n {/* Terse live status line — announced politely; the (potentially\n huge) <pre> body below is intentionally NOT in a live region. */}\n <p role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {output.error\n ? tr('agentDetails.maintenance.outputFailed', '{{title}} finished with errors', {\n title: output.title,\n })\n : tr('agentDetails.maintenance.outputDone', '{{title}} finished', {\n title: output.title,\n })}\n </p>\n <pre\n className={`text-xs whitespace-pre-wrap break-words max-h-96 overflow-auto rounded-md p-3 ${\n output.error\n ? 'bg-status-error-bg-subtle text-status-error-text'\n : 'bg-bg-sunken text-text-primary'\n }`}\n >\n {output.body}\n </pre>\n </section>\n ) : null}\n\n {/* ── Confirms ────────────────────────────────────────────────── */}\n <ConfirmDialog\n isOpen={pendingCommand !== null}\n onClose={() => setPendingCommand(null)}\n onConfirm={() => {\n const argv = pendingCommand;\n setPendingCommand(null);\n if (argv) void executeCommand(argv);\n }}\n title={tr('agentDetails.maintenance.confirmCommandTitle', 'Run destructive command?')}\n message={tr(\n 'agentDetails.maintenance.confirmCommandMsg',\n 'This command can change or destroy agent state: {{cmd}}',\n { cmd: (pendingCommand ?? []).join(' ') }\n )}\n confirmLabel={tr('agentDetails.maintenance.run', 'Run')}\n variant=\"danger\"\n isLoading={running}\n />\n <ConfirmDialog\n isOpen={pendingNuke !== null}\n onClose={() => setPendingNuke(null)}\n onConfirm={() => {\n const opts = pendingNuke;\n setPendingNuke(null);\n if (opts) void runNuke(opts);\n }}\n title={\n nukeDryRun\n ? tr('agentDetails.maintenance.confirmNukeDryTitle', 'Preview nuke?')\n : tr('agentDetails.maintenance.confirmNukeTitle', 'Nuke agent?')\n }\n message={\n pendingNuke?.all\n ? tr(\n 'agentDetails.maintenance.confirmNukeAllMsg',\n 'This wipes ALL state, plugins, secrets and config on the host.'\n )\n : tr(\n 'agentDetails.maintenance.confirmNukeStateMsg',\n 'This wipes this agent’s state and tracked binaries.'\n )\n }\n confirmLabel={\n nukeDryRun\n ? tr('agentDetails.maintenance.preview', 'Preview')\n : tr('agentDetails.maintenance.nuke', 'Nuke')\n }\n variant=\"danger\"\n isLoading={nuking}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AgentMaintenanceTab.js","names":[],"sources":["../../../src/components/agents/AgentMaintenanceTab.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport { ArrowUpCircle, Loader2, RefreshCw, Terminal, Trash2, AlertTriangle } from 'lucide-react';\nimport { Checkbox } from '@burdenoff/fe-libs/ui';\n\nimport {\n useAgentUpdateStatusQuery,\n useAgentUpdateCheckLazyQuery,\n useAgentSelfUpdateMutation,\n useSetAgentAutoUpdateMutation,\n useAgentCommandsQuery,\n useRunAgentCommandMutation,\n useNukeVibecontrolsAgentMutation,\n} from '@/generated/wspace-operations';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useField } from '../../shared/hooks/useField';\nimport { ConfirmDialog } from '../shared/ConfirmDialog';\n\ntype Json = Record<string, unknown> | null | undefined;\n\nfunction asRecord(v: unknown): Record<string, unknown> | null {\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : null;\n}\nfunction jstr(obj: Json, key: string): string | null {\n const v = obj?.[key];\n return typeof v === 'string' ? v : null;\n}\nfunction jbool(obj: Json, key: string): boolean | null {\n const v = obj?.[key];\n return typeof v === 'boolean' ? v : null;\n}\n\ninterface CommandInfo {\n name: string;\n description: string;\n destructive?: boolean;\n options?: Array<{ flags: string; description: string }>;\n}\n\ninterface AgentMaintenanceTabProps {\n agentId: string;\n agentVersion?: string | null;\n onAfterDestructiveNuke?: () => void | Promise<void>;\n}\n\nconst SECTION = 'rounded-card border border-border-default bg-bg-surface p-4';\nconst BTN_PRIMARY =\n 'inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-button bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus';\nconst BTN_NEUTRAL =\n 'inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-button bg-bg-sunken text-text-primary hover:bg-bg-elevated transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus';\nconst BTN_DANGER =\n 'inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-button bg-action-danger-bg text-action-danger-text hover:bg-action-danger-bgHover transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus';\nconst FIELD_LABEL = 'text-xs font-medium text-text-muted';\nconst FIELD_INPUT =\n 'px-3 py-2 text-base md:text-sm rounded-input bg-bg-sunken border border-border-default text-text-primary focus:outline-none focus:ring-2 focus:ring-border-focus';\n\nexport function AgentMaintenanceTab({\n agentId,\n agentVersion,\n onAfterDestructiveNuke,\n}: AgentMaintenanceTabProps) {\n const tr = useTr();\n\n // ── Output + toast plumbing (component-local) ───────────────────────────\n const [output, setOutput] = useState<{ title: string; body: string; error?: boolean } | null>(\n null\n );\n const showOutput = useCallback((title: string, body: string, error = false) => {\n setOutput({ title, body, error });\n }, []);\n\n // ── Update / autoupdate ─────────────────────────────────────────────────\n const { data: statusData, refetch: refetchStatus } = useAgentUpdateStatusQuery({\n variables: { agentId },\n fetchPolicy: 'cache-and-network',\n });\n const [checkUpdate, { data: checkData, loading: checking }] = useAgentUpdateCheckLazyQuery({\n fetchPolicy: 'network-only',\n });\n const [selfUpdate, { loading: updating }] = useAgentSelfUpdateMutation();\n const [setAutoUpdate, { loading: togglingAuto }] = useSetAgentAutoUpdateMutation();\n\n const status = asRecord(statusData?.agentUpdateStatus?.data);\n const lastCheck = asRecord(status?.lastCheck);\n const checkResult = asRecord(checkData?.agentUpdateCheck?.data);\n const autoUpdateEnabled = jbool(status, 'enabled') ?? true;\n const latestVersion = jstr(checkResult, 'latest') ?? jstr(lastCheck, 'latest') ?? null;\n const hasUpdate = jbool(checkResult, 'hasUpdate') ?? jbool(lastCheck, 'hasUpdate') ?? false;\n\n const [versionInput, setVersionInput] = useState('');\n const versionField = useField();\n const commandField = useField();\n const argsField = useField();\n\n const handleCheck = useCallback(async () => {\n setOutput(null);\n try {\n const res = await checkUpdate({ variables: { agentId } });\n const d = asRecord(res.data?.agentUpdateCheck?.data);\n if (res.data?.agentUpdateCheck?.error) {\n showOutput(\n tr('agentDetails.maintenance.update', 'Update'),\n res.data.agentUpdateCheck.error,\n true\n );\n } else if (jbool(d, 'hasUpdate')) {\n showOutput(\n tr('agentDetails.maintenance.update', 'Update'),\n `Update available: ${jstr(d, 'current')} → ${jstr(d, 'latest')}`\n );\n } else {\n showOutput(\n tr('agentDetails.maintenance.update', 'Update'),\n `Up to date (${jstr(d, 'current') ?? agentVersion ?? '?'}).`\n );\n }\n } catch (err) {\n showOutput('Update', err instanceof Error ? err.message : String(err), true);\n }\n }, [agentId, checkUpdate, showOutput, tr, agentVersion]);\n\n const runSelfUpdate = useCallback(\n async (version?: string) => {\n setOutput(null);\n try {\n const res = await selfUpdate({ variables: { agentId, version: version || null } });\n const proxy = res.data?.agentSelfUpdate;\n const d = asRecord(proxy?.data);\n if (proxy?.error || jbool(d, 'ok') === false) {\n showOutput(\n 'Update',\n (jstr(d, 'error') ?? proxy?.error ?? 'Update failed') as string,\n true\n );\n } else {\n showOutput(\n 'Update',\n `Updated → ${jstr(d, 'current') ?? version ?? 'latest'} (restart: ${jstr(d, 'restart') ?? 'n/a'}).`\n );\n }\n await refetchStatus();\n } catch (err) {\n showOutput('Update', err instanceof Error ? err.message : String(err), true);\n }\n },\n [agentId, selfUpdate, showOutput, refetchStatus]\n );\n\n const toggleAutoUpdate = useCallback(\n async (enabled: boolean) => {\n try {\n await setAutoUpdate({ variables: { agentId, enabled } });\n await refetchStatus();\n } catch (err) {\n showOutput('Autoupdate', err instanceof Error ? err.message : String(err), true);\n }\n },\n [agentId, setAutoUpdate, refetchStatus, showOutput]\n );\n\n // ── Command runner ──────────────────────────────────────────────────────\n const {\n data: cmdData,\n loading: cmdsLoading,\n refetch: refetchCmds,\n } = useAgentCommandsQuery({\n variables: { agentId },\n fetchPolicy: 'cache-and-network',\n });\n const [runCommand, { loading: running }] = useRunAgentCommandMutation();\n\n const commands = useMemo<CommandInfo[]>(() => {\n const raw = asRecord(cmdData?.agentCommands?.data)?.commands;\n return Array.isArray(raw) ? (raw as CommandInfo[]) : [];\n }, [cmdData]);\n\n const [selectedCommand, setSelectedCommand] = useState('');\n const [argsInput, setArgsInput] = useState('');\n const [pendingCommand, setPendingCommand] = useState<string[] | null>(null);\n\n const selectedMeta = commands.find((c) => c.name === selectedCommand);\n\n const buildArgv = useCallback((): string[] => {\n const extra = argsInput.trim().length > 0 ? argsInput.trim().split(/\\s+/) : [];\n return [selectedCommand, ...extra];\n }, [selectedCommand, argsInput]);\n\n const executeCommand = useCallback(\n async (argv: string[]) => {\n setOutput(null);\n try {\n const res = await runCommand({ variables: { agentId, argv } });\n const proxy = res.data?.runAgentCommand;\n const d = asRecord(proxy?.data);\n const stdout = jstr(d, 'stdout') ?? '';\n const stderr = jstr(d, 'stderr') ?? '';\n const exit = d?.exitCode;\n const body =\n (stdout ? stdout : '') +\n (stderr ? `\\n[stderr]\\n${stderr}` : '') +\n `\\n[exit ${exit ?? '?'}]`;\n showOutput(\n `vibe ${argv.join(' ')}`,\n body.trim() || proxy?.error || '(no output)',\n !jbool(d, 'ok')\n );\n } catch (err) {\n showOutput('Command', err instanceof Error ? err.message : String(err), true);\n }\n },\n [agentId, runCommand, showOutput]\n );\n\n const handleRunClick = useCallback(() => {\n if (!selectedCommand) return;\n const argv = buildArgv();\n if (selectedMeta?.destructive) {\n setPendingCommand(argv);\n } else {\n void executeCommand(argv);\n }\n }, [selectedCommand, buildArgv, selectedMeta, executeCommand]);\n\n // ── Nuke ────────────────────────────────────────────────────────────────\n const [nuke, { loading: nuking }] = useNukeVibecontrolsAgentMutation();\n const [nukeDryRun, setNukeDryRun] = useState(true);\n const [pendingNuke, setPendingNuke] = useState<{ all: boolean; removeConfig: boolean } | null>(\n null\n );\n\n const runNuke = useCallback(\n async (opts: { all: boolean; removeConfig: boolean }) => {\n setOutput(null);\n try {\n const res = await nuke({\n variables: {\n agentId,\n all: opts.all,\n removeConfig: opts.removeConfig,\n dryRun: nukeDryRun,\n },\n });\n const proxy = res.data?.nukeVibecontrolsAgent;\n const d = asRecord(proxy?.data);\n const stdout = jstr(d, 'stdout') ?? '';\n const stderr = jstr(d, 'stderr') ?? '';\n const ok = jbool(d, 'ok') === true && !proxy?.error;\n showOutput(\n `vibe nuke${opts.all ? ' --all' : ''}${nukeDryRun ? ' --dry-run' : ''}`,\n (stdout + (stderr ? `\\n[stderr]\\n${stderr}` : '')).trim() ||\n proxy?.error ||\n '(no output)',\n !ok && !!proxy?.error\n );\n if (ok && !nukeDryRun) {\n await onAfterDestructiveNuke?.();\n }\n } catch (err) {\n showOutput('Nuke', err instanceof Error ? err.message : String(err), true);\n }\n },\n [agentId, nuke, nukeDryRun, onAfterDestructiveNuke, showOutput]\n );\n\n // Maintenance is the agent control plane: command runner, version pinning\n // and nuke. The gateway enforces `agent:mutate` on these and `agent:admin`\n // Gate each section by the action the gateway actually enforces:\n // version / auto-update controls are `agent:update`; the command runner\n // and the destructive nuke are `agent:admin`. (agent:mutate is for\n // code-server/plugins, not these — see graphql/wspace.graphql.)\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canUpdate = hasPermission(VIBE_PERMISSIONS.AGENT_UPDATE);\n const canAdmin = hasPermission(VIBE_PERMISSIONS.AGENT_ADMIN);\n\n // Don't flash the no-permission message while permissions are still loading.\n if (permissionsLoading) return null;\n\n if (!canUpdate && !canAdmin) {\n return (\n <div className=\"space-y-5\">\n <section className={SECTION}>\n <div className=\"flex items-center gap-2\">\n <AlertTriangle className=\"size-5 text-text-secondary\" aria-hidden=\"true\" />\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'agentDetails.maintenance.noPermission',\n 'You do not have permission to manage this agent.'\n )}\n </p>\n </div>\n </section>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-5\">\n {/* ── Agent update (agent:update) ─────────────────────────────── */}\n {canUpdate && (\n <section className={SECTION}>\n <div className=\"flex items-center gap-2 mb-3\">\n <ArrowUpCircle className=\"size-5 text-text-secondary\" aria-hidden=\"true\" />\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('agentDetails.maintenance.updateTitle', 'Agent version & updates')}\n </h3>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm mb-4\">\n <div>\n <div className=\"text-text-muted\">\n {tr('agentDetails.maintenance.current', 'Current')}\n </div>\n <div className=\"font-mono text-text-primary\">{agentVersion ?? '—'}</div>\n </div>\n <div>\n <div className=\"text-text-muted\">\n {tr('agentDetails.maintenance.latest', 'Latest')}\n </div>\n <div className=\"font-mono text-text-primary\">\n {latestVersion ?? '—'}\n {hasUpdate ? (\n <span className=\"ml-2 text-xs text-status-warning-text\">\n {tr('agentDetails.maintenance.updateAvailable', 'update available')}\n </span>\n ) : null}\n </div>\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2 mb-4\">\n <button type=\"button\" onClick={handleCheck} disabled={checking} className={BTN_NEUTRAL}>\n {checking ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <RefreshCw className=\"size-4\" aria-hidden=\"true\" />\n )}\n {tr('agentDetails.maintenance.check', 'Check for updates')}\n </button>\n <button\n type=\"button\"\n onClick={() => runSelfUpdate()}\n disabled={updating}\n aria-busy={updating}\n className={BTN_PRIMARY}\n >\n {updating ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <ArrowUpCircle className=\"size-4\" aria-hidden=\"true\" />\n )}\n {tr('agentDetails.maintenance.updateLatest', 'Update to latest')}\n </button>\n </div>\n\n <div className=\"flex flex-wrap items-end gap-2 mb-4\">\n <div className=\"flex w-full sm:w-44 flex-col gap-1\">\n <label htmlFor={versionField.id} className={FIELD_LABEL}>\n {tr('agentDetails.maintenance.versionLabel', 'Version')}\n </label>\n <input\n {...versionField.inputProps}\n type=\"text\"\n value={versionInput}\n onChange={(e) => setVersionInput(e.target.value)}\n placeholder={tr('agentDetails.maintenance.versionPlaceholder', 'e.g. 2026.601.21')}\n className={`${FIELD_INPUT} font-mono w-full`}\n />\n </div>\n <button\n type=\"button\"\n onClick={() => runSelfUpdate(versionInput.trim())}\n disabled={updating || versionInput.trim().length === 0}\n className={BTN_NEUTRAL}\n >\n {tr('agentDetails.maintenance.updateVersion', 'Update to version')}\n </button>\n </div>\n\n <label className=\"flex items-center gap-3 cursor-pointer\">\n <Checkbox\n checked={autoUpdateEnabled}\n disabled={togglingAuto}\n onCheckedChange={(c) => toggleAutoUpdate(c === true)}\n />\n <span className=\"text-sm text-text-primary\">\n {tr(\n 'agentDetails.maintenance.autoUpdate',\n 'Automatically install updates when idle (daily background check)'\n )}\n </span>\n </label>\n </section>\n )}\n\n {/* ── Command runner (agent:admin) ────────────────────────────── */}\n {canAdmin && (\n <section className={SECTION}>\n <div className=\"flex items-center justify-between gap-2 mb-3\">\n <div className=\"flex items-center gap-2\">\n <Terminal className=\"size-5 text-text-secondary\" aria-hidden=\"true\" />\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('agentDetails.maintenance.commandsTitle', 'Run a command')}\n </h3>\n </div>\n <button\n type=\"button\"\n onClick={() => refetchCmds()}\n disabled={cmdsLoading}\n className=\"inline-flex size-9 max-md:size-11 items-center justify-center rounded-button text-text-muted transition-colors hover:bg-bg-sunken hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n aria-label={tr('agentDetails.maintenance.refresh', 'Refresh')}\n >\n <RefreshCw\n className={`size-4 ${cmdsLoading ? 'animate-spin' : ''}`}\n aria-hidden=\"true\"\n />\n </button>\n </div>\n <p className=\"text-xs text-text-muted mb-3\">\n {tr(\n 'agentDetails.maintenance.commandsHint',\n 'Every agent command can be run here. Destructive commands ask for confirmation.'\n )}\n </p>\n <div className=\"flex flex-wrap items-end gap-2 mb-3\">\n <div className=\"flex w-full sm:w-auto sm:min-w-44 flex-col gap-1\">\n <label htmlFor={commandField.id} className={FIELD_LABEL}>\n {tr('agentDetails.maintenance.command', 'Command')}\n </label>\n <select\n {...commandField.inputProps}\n value={selectedCommand}\n onChange={(e) => setSelectedCommand(e.target.value)}\n className={`${FIELD_INPUT} w-full`}\n >\n <option value=\"\">\n {cmdsLoading\n ? tr('agentDetails.maintenance.loading', 'Loading…')\n : tr('agentDetails.maintenance.selectCommand', 'Select a command…')}\n </option>\n {commands.map((c) => (\n <option key={c.name} value={c.name}>\n {c.name}\n {c.destructive\n ? ` ${tr('agentDetails.maintenance.destructiveSuffix', '(destructive)')}`\n : ''}\n </option>\n ))}\n </select>\n </div>\n <div className=\"flex flex-col gap-1 flex-1 min-w-0 basis-full sm:basis-auto\">\n <label htmlFor={argsField.id} className={FIELD_LABEL}>\n {tr('agentDetails.maintenance.args', 'Arguments (optional)')}\n </label>\n <input\n {...argsField.inputProps}\n type=\"text\"\n value={argsInput}\n onChange={(e) => setArgsInput(e.target.value)}\n placeholder=\"--json --tail 50\"\n className={`${FIELD_INPUT} font-mono w-full`}\n />\n </div>\n <button\n type=\"button\"\n onClick={handleRunClick}\n disabled={running || !selectedCommand}\n aria-busy={running}\n className={BTN_PRIMARY}\n >\n {running ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Terminal className=\"size-4\" aria-hidden=\"true\" />\n )}\n {tr('agentDetails.maintenance.run', 'Run')}\n </button>\n </div>\n {selectedMeta?.description ? (\n <p className=\"text-xs text-text-muted\">{selectedMeta.description}</p>\n ) : null}\n </section>\n )}\n\n {/* ── Danger zone (nuke, agent:admin) ─────────────────────────── */}\n {canAdmin && (\n <section className={`${SECTION} border-status-error-border`}>\n <div className=\"flex items-center gap-2 mb-3\">\n <AlertTriangle className=\"size-5 text-status-error-text\" aria-hidden=\"true\" />\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('agentDetails.maintenance.dangerTitle', 'Danger zone')}\n </h3>\n </div>\n <label className=\"flex items-center gap-3 cursor-pointer mb-3\">\n <Checkbox checked={nukeDryRun} onCheckedChange={(c) => setNukeDryRun(c === true)} />\n <span className=\"text-sm text-text-primary\">\n {tr('agentDetails.maintenance.dryRun', 'Dry run (preview only — nothing is deleted)')}\n </span>\n </label>\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => setPendingNuke({ all: false, removeConfig: false })}\n disabled={nuking}\n className={BTN_NEUTRAL}\n >\n {nuking ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Trash2 className=\"size-4\" aria-hidden=\"true\" />\n )}\n {tr('agentDetails.maintenance.nukeState', 'Nuke state')}\n </button>\n <button\n type=\"button\"\n onClick={() => setPendingNuke({ all: true, removeConfig: true })}\n disabled={nuking}\n className={BTN_DANGER}\n >\n <Trash2 className=\"size-4\" aria-hidden=\"true\" />\n {tr('agentDetails.maintenance.nukeAll', 'Nuke everything (--all)')}\n </button>\n </div>\n <p className=\"text-xs text-text-muted mt-2\">\n {tr(\n 'agentDetails.maintenance.nukeHint',\n '“Nuke state” wipes this agent’s data + tracked binaries. “Nuke everything” also removes all VibeControls plugins, secrets, and config across every profile on the host.'\n )}\n </p>\n </section>\n )}\n\n {/* ── Output pane ─────────────────────────────────────────────── */}\n {output ? (\n <section className={SECTION}>\n <div className=\"flex items-center justify-between gap-2 mb-2\">\n <h4 className=\"text-sm font-semibold text-text-primary min-w-0 truncate\">\n {output.title}\n </h4>\n <button\n type=\"button\"\n onClick={() => setOutput(null)}\n className=\"shrink-0 text-xs text-text-muted hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded-button px-1.5 py-1\"\n >\n {tr('agentDetails.maintenance.dismiss', 'Dismiss')}\n </button>\n </div>\n {/* Terse live status line — announced politely; the (potentially\n huge) <pre> body below is intentionally NOT in a live region. */}\n <p role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {output.error\n ? tr('agentDetails.maintenance.outputFailed', '{{title}} finished with errors', {\n title: output.title,\n })\n : tr('agentDetails.maintenance.outputDone', '{{title}} finished', {\n title: output.title,\n })}\n </p>\n <pre\n className={`text-xs whitespace-pre-wrap break-words max-h-96 overflow-auto rounded-md p-3 ${\n output.error\n ? 'bg-status-error-bg-subtle text-status-error-text'\n : 'bg-bg-sunken text-text-primary'\n }`}\n >\n {output.body}\n </pre>\n </section>\n ) : null}\n\n {/* ── Confirms ────────────────────────────────────────────────── */}\n <ConfirmDialog\n isOpen={pendingCommand !== null}\n onClose={() => setPendingCommand(null)}\n onConfirm={() => {\n const argv = pendingCommand;\n setPendingCommand(null);\n if (argv) void executeCommand(argv);\n }}\n title={tr('agentDetails.maintenance.confirmCommandTitle', 'Run destructive command?')}\n message={tr(\n 'agentDetails.maintenance.confirmCommandMsg',\n 'This command can change or destroy agent state: {{cmd}}',\n { cmd: (pendingCommand ?? []).join(' ') }\n )}\n confirmLabel={tr('agentDetails.maintenance.run', 'Run')}\n variant=\"danger\"\n isLoading={running}\n />\n <ConfirmDialog\n isOpen={pendingNuke !== null}\n onClose={() => setPendingNuke(null)}\n onConfirm={() => {\n const opts = pendingNuke;\n setPendingNuke(null);\n if (opts) void runNuke(opts);\n }}\n title={\n nukeDryRun\n ? tr('agentDetails.maintenance.confirmNukeDryTitle', 'Preview nuke?')\n : tr('agentDetails.maintenance.confirmNukeTitle', 'Nuke agent?')\n }\n message={\n pendingNuke?.all\n ? tr(\n 'agentDetails.maintenance.confirmNukeAllMsg',\n 'This wipes ALL state, plugins, secrets and config on the host.'\n )\n : tr(\n 'agentDetails.maintenance.confirmNukeStateMsg',\n 'This wipes this agent’s state and tracked binaries.'\n )\n }\n confirmLabel={\n nukeDryRun\n ? tr('agentDetails.maintenance.preview', 'Preview')\n : tr('agentDetails.maintenance.nuke', 'Nuke')\n }\n variant=\"danger\"\n isLoading={nuking}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAqBA,SAAS,EAAS,GAA4C;AAC5D,QAAO,KAAK,OAAO,KAAM,YAAY,CAAC,MAAM,QAAQ,EAAE,GAAI,IAAgC;;AAE5F,SAAS,EAAK,GAAW,GAA4B;CACnD,IAAM,IAAI,IAAM;AAChB,QAAO,OAAO,KAAM,WAAW,IAAI;;AAErC,SAAS,EAAM,GAAW,GAA6B;CACrD,IAAM,IAAI,IAAM;AAChB,QAAO,OAAO,KAAM,YAAY,IAAI;;AAgBtC,IAAM,IAAU,+DACV,KACJ,mRACI,IACJ,yPACI,KACJ,gRACI,IAAc,uCACd,IACJ;AAEF,SAAgB,EAAoB,EAClC,YACA,iBACA,8BAC2B;CAC3B,IAAM,IAAK,IAAO,EAGZ,CAAC,GAAQ,KAAa,EAC1B,KACD,EACK,IAAa,GAAa,GAAe,GAAc,IAAQ,OAAU;AAC7E,IAAU;GAAE;GAAO;GAAM;GAAO,CAAC;IAChC,EAAE,CAAC,EAGA,EAAE,MAAM,IAAY,SAAS,MAAkB,EAA0B;EAC7E,WAAW,EAAE,YAAS;EACtB,aAAa;EACd,CAAC,EACI,CAAC,GAAa,EAAE,MAAM,IAAW,SAAS,OAAc,EAA6B,EACzF,aAAa,gBACd,CAAC,EACI,CAAC,GAAY,EAAE,SAAS,OAAc,GAA4B,EAClE,CAAC,GAAe,EAAE,SAAS,QAAkB,IAA+B,EAE5E,IAAS,EAAS,IAAY,mBAAmB,KAAK,EACtD,IAAY,EAAS,GAAQ,UAAU,EACvC,IAAc,EAAS,IAAW,kBAAkB,KAAK,EACzD,KAAoB,EAAM,GAAQ,UAAU,IAAI,IAChD,KAAgB,EAAK,GAAa,SAAS,IAAI,EAAK,GAAW,SAAS,IAAI,MAC5E,KAAY,EAAM,GAAa,YAAY,IAAI,EAAM,GAAW,YAAY,IAAI,IAEhF,CAAC,GAAc,MAAmB,EAAS,GAAG,EAC9C,IAAe,GAAU,EACzB,IAAe,GAAU,EACzB,IAAY,GAAU,EAEtB,KAAc,EAAY,YAAY;AAC1C,IAAU,KAAK;AACf,MAAI;GACF,IAAM,IAAM,MAAM,EAAY,EAAE,WAAW,EAAE,YAAS,EAAE,CAAC,EACnD,IAAI,EAAS,EAAI,MAAM,kBAAkB,KAAK;AACpD,GAAI,EAAI,MAAM,kBAAkB,QAC9B,EACE,EAAG,mCAAmC,SAAS,EAC/C,EAAI,KAAK,iBAAiB,OAC1B,GACD,GACQ,EAAM,GAAG,YAAY,GAC9B,EACE,EAAG,mCAAmC,SAAS,EAC/C,qBAAqB,EAAK,GAAG,UAAU,CAAC,KAAK,EAAK,GAAG,SAAS,GAC/D,GAED,EACE,EAAG,mCAAmC,SAAS,EAC/C,eAAe,EAAK,GAAG,UAAU,IAAI,KAAgB,IAAI,IAC1D;WAEI,GAAK;AACZ,KAAW,UAAU,aAAe,QAAQ,EAAI,UAAU,OAAO,EAAI,EAAE,GAAK;;IAE7E;EAAC;EAAS;EAAa;EAAY;EAAI;EAAa,CAAC,EAElD,IAAgB,EACpB,OAAO,MAAqB;AAC1B,IAAU,KAAK;AACf,MAAI;GAEF,IAAM,KADM,MAAM,EAAW,EAAE,WAAW;IAAE;IAAS,SAAS,KAAW;IAAM,EAAE,CAAC,EAChE,MAAM,iBAClB,IAAI,EAAS,GAAO,KAAK;AAa/B,GAZI,GAAO,SAAS,EAAM,GAAG,KAAK,KAAK,KACrC,EACE,UACC,EAAK,GAAG,QAAQ,IAAI,GAAO,SAAS,iBACrC,GACD,GAED,EACE,UACA,aAAa,EAAK,GAAG,UAAU,IAAI,KAAW,SAAS,aAAa,EAAK,GAAG,UAAU,IAAI,MAAM,IACjG,EAEH,MAAM,GAAe;WACd,GAAK;AACZ,KAAW,UAAU,aAAe,QAAQ,EAAI,UAAU,OAAO,EAAI,EAAE,GAAK;;IAGhF;EAAC;EAAS;EAAY;EAAY;EAAc,CACjD,EAEK,KAAmB,EACvB,OAAO,MAAqB;AAC1B,MAAI;AAEF,GADA,MAAM,EAAc,EAAE,WAAW;IAAE;IAAS;IAAS,EAAE,CAAC,EACxD,MAAM,GAAe;WACd,GAAK;AACZ,KAAW,cAAc,aAAe,QAAQ,EAAI,UAAU,OAAO,EAAI,EAAE,GAAK;;IAGpF;EAAC;EAAS;EAAe;EAAe;EAAW,CACpD,EAGK,EACJ,MAAM,IACN,SAAS,GACT,SAAS,OACP,EAAsB;EACxB,WAAW,EAAE,YAAS;EACtB,aAAa;EACd,CAAC,EACI,CAAC,IAAY,EAAE,SAAS,OAAa,GAA4B,EAEjE,KAAW,SAA6B;EAC5C,IAAM,IAAM,EAAS,IAAS,eAAe,KAAK,EAAE;AACpD,SAAO,MAAM,QAAQ,EAAI,GAAI,IAAwB,EAAE;IACtD,CAAC,GAAQ,CAAC,EAEP,CAAC,GAAiB,MAAsB,EAAS,GAAG,EACpD,CAAC,GAAW,MAAgB,EAAS,GAAG,EACxC,CAAC,GAAgB,KAAqB,EAA0B,KAAK,EAErE,IAAe,GAAS,MAAM,MAAM,EAAE,SAAS,EAAgB,EAE/D,KAAY,QAET,CAAC,GAAiB,GADX,EAAU,MAAM,CAAC,SAAS,IAAI,EAAU,MAAM,CAAC,MAAM,MAAM,GAAG,EAAE,CAC5C,EACjC,CAAC,GAAiB,EAAU,CAAC,EAE1B,IAAiB,EACrB,OAAO,MAAmB;AACxB,IAAU,KAAK;AACf,MAAI;GAEF,IAAM,KADM,MAAM,GAAW,EAAE,WAAW;IAAE;IAAS;IAAM,EAAE,CAAC,EAC5C,MAAM,iBAClB,IAAI,EAAS,GAAO,KAAK,EACzB,IAAS,EAAK,GAAG,SAAS,IAAI,IAC9B,IAAS,EAAK,GAAG,SAAS,IAAI,IAC9B,IAAO,GAAG,UACV,MACH,KAAkB,OAClB,IAAS,eAAe,MAAW,MACpC,WAAW,KAAQ,IAAI;AACzB,KACE,QAAQ,EAAK,KAAK,IAAI,IACtB,GAAK,MAAM,IAAI,GAAO,SAAS,eAC/B,CAAC,EAAM,GAAG,KAAK,CAChB;WACM,GAAK;AACZ,KAAW,WAAW,aAAe,QAAQ,EAAI,UAAU,OAAO,EAAI,EAAE,GAAK;;IAGjF;EAAC;EAAS;EAAY;EAAW,CAClC,EAEK,KAAiB,QAAkB;AACvC,MAAI,CAAC,EAAiB;EACtB,IAAM,IAAO,IAAW;AACxB,EAAI,GAAc,cAChB,EAAkB,EAAK,GAElB,EAAe,EAAK;IAE1B;EAAC;EAAiB;EAAW;EAAc;EAAe,CAAC,EAGxD,CAAC,IAAM,EAAE,SAAS,OAAY,GAAkC,EAChE,CAAC,GAAY,MAAiB,EAAS,GAAK,EAC5C,CAAC,GAAa,KAAkB,EACpC,KACD,EAEK,KAAU,EACd,OAAO,MAAkD;AACvD,IAAU,KAAK;AACf,MAAI;GASF,IAAM,KARM,MAAM,GAAK,EACrB,WAAW;IACT;IACA,KAAK,EAAK;IACV,cAAc,EAAK;IACnB,QAAQ;IACT,EACF,CAAC,EACgB,MAAM,uBAClB,IAAI,EAAS,GAAO,KAAK,EACzB,IAAS,EAAK,GAAG,SAAS,IAAI,IAC9B,IAAS,EAAK,GAAG,SAAS,IAAI,IAC9B,IAAK,EAAM,GAAG,KAAK,KAAK,MAAQ,CAAC,GAAO;AAQ9C,GAPA,EACE,YAAY,EAAK,MAAM,WAAW,KAAK,IAAa,eAAe,OAClE,KAAU,IAAS,eAAe,MAAW,KAAK,MAAM,IACvD,GAAO,SACP,eACF,CAAC,KAAM,CAAC,CAAC,GAAO,MACjB,EACG,KAAM,CAAC,KACT,MAAM,MAA0B;WAE3B,GAAK;AACZ,KAAW,QAAQ,aAAe,QAAQ,EAAI,UAAU,OAAO,EAAI,EAAE,GAAK;;IAG9E;EAAC;EAAS;EAAM;EAAY;EAAwB;EAAW,CAChE,EAQK,EAAE,mBAAe,WAAW,OAAuB,IAAgB,EACnE,KAAY,GAAc,EAAiB,aAAa,EACxD,IAAW,GAAc,EAAiB,YAAY;AAuB5D,QApBI,KAA2B,OAE3B,CAAC,MAAa,CAAC,IAEf,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,WAAD;GAAS,WAAW;aAClB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,IAAD;KAAe,WAAU;KAA6B,eAAY;KAAS,CAAA,EAC3E,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,yCACA,mDACD;KACC,CAAA,CACA;;GACE,CAAA;EACN,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEG,MACC,kBAAC,WAAD;IAAS,WAAW;cAApB;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,IAAD;OAAe,WAAU;OAA6B,eAAY;OAAS,CAAA,EAC3E,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,wCAAwC,0BAA0B;OACnE,CAAA,CACD;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAG,oCAAoC,UAAU;OAC9C,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAA+B,KAAgB;OAAU,CAAA,CACpE,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAG,mCAAmC,SAAS;OAC5C,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACG,MAAiB,KACjB,KACC,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAG,4CAA4C,mBAAmB;QAC9D,CAAA,GACL,KACA;SACF,EAAA,CAAA,CACF;;KAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OAAQ,MAAK;OAAS,SAAS;OAAa,UAAU;OAAU,WAAW;iBAA3E,CACG,IACC,kBAAC,GAAD;QAAS,WAAU;QAAsB,eAAY;QAAS,CAAA,GAE9D,kBAAC,IAAD;QAAW,WAAU;QAAS,eAAY;QAAS,CAAA,EAEpD,EAAG,kCAAkC,oBAAoB,CACnD;UACT,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAe;OAC9B,UAAU;OACV,aAAW;OACX,WAAW;iBALb,CAOG,IACC,kBAAC,GAAD;QAAS,WAAU;QAAsB,eAAY;QAAS,CAAA,GAE9D,kBAAC,IAAD;QAAe,WAAU;QAAS,eAAY;QAAS,CAAA,EAExD,EAAG,yCAAyC,mBAAmB,CACzD;SACL;;KAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,SAAD;QAAO,SAAS,EAAa;QAAI,WAAW;kBACzC,EAAG,yCAAyC,UAAU;QACjD,CAAA,EACR,kBAAC,SAAD;QACE,GAAI,EAAa;QACjB,MAAK;QACL,OAAO;QACP,WAAW,MAAM,GAAgB,EAAE,OAAO,MAAM;QAChD,aAAa,EAAG,+CAA+C,mBAAmB;QAClF,WAAW,GAAG,EAAY;QAC1B,CAAA,CACE;UACN,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAc,EAAa,MAAM,CAAC;OACjD,UAAU,KAAY,EAAa,MAAM,CAAC,WAAW;OACrD,WAAW;iBAEV,EAAG,0CAA0C,oBAAoB;OAC3D,CAAA,CACL;;KAEN,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,IAAD;OACE,SAAS;OACT,UAAU;OACV,kBAAkB,MAAM,GAAiB,MAAM,GAAK;OACpD,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EACC,uCACA,mEACD;OACI,CAAA,CACD;;KACA;;GAIX,KACC,kBAAC,WAAD;IAAS,WAAW;cAApB;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,IAAD;QAAU,WAAU;QAA6B,eAAY;QAAS,CAAA,EACtE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,0CAA0C,gBAAgB;QAC3D,CAAA,CACD;UACN,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,IAAa;OAC5B,UAAU;OACV,WAAU;OACV,cAAY,EAAG,oCAAoC,UAAU;iBAE7D,kBAAC,IAAD;QACE,WAAW,UAAU,IAAc,iBAAiB;QACpD,eAAY;QACZ,CAAA;OACK,CAAA,CACL;;KACN,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,yCACA,kFACD;MACC,CAAA;KACJ,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SAAO,SAAS,EAAa;SAAI,WAAW;mBACzC,EAAG,oCAAoC,UAAU;SAC5C,CAAA,EACR,kBAAC,UAAD;SACE,GAAI,EAAa;SACjB,OAAO;SACP,WAAW,MAAM,GAAmB,EAAE,OAAO,MAAM;SACnD,WAAW,GAAG,EAAY;mBAJ5B,CAME,kBAAC,UAAD;UAAQ,OAAM;oBACX,IACG,EAAG,oCAAoC,WAAW,GAClD,EAAG,0CAA0C,oBAAoB;UAC9D,CAAA,EACR,GAAS,KAAK,MACb,kBAAC,UAAD;UAAqB,OAAO,EAAE;oBAA9B,CACG,EAAE,MACF,EAAE,cACC,IAAI,EAAG,8CAA8C,gBAAgB,KACrE,GACG;YALI,EAAE,KAKN,CACT,CACK;WACL;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SAAO,SAAS,EAAU;SAAI,WAAW;mBACtC,EAAG,iCAAiC,uBAAuB;SACtD,CAAA,EACR,kBAAC,SAAD;SACE,GAAI,EAAU;SACd,MAAK;SACL,OAAO;SACP,WAAW,MAAM,GAAa,EAAE,OAAO,MAAM;SAC7C,aAAY;SACZ,WAAW,GAAG,EAAY;SAC1B,CAAA,CACE;;OACN,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,UAAU,KAAW,CAAC;QACtB,aAAW;QACX,WAAW;kBALb,CAOG,IACC,kBAAC,GAAD;SAAS,WAAU;SAAsB,eAAY;SAAS,CAAA,GAE9D,kBAAC,IAAD;SAAU,WAAU;SAAS,eAAY;SAAS,CAAA,EAEnD,EAAG,gCAAgC,MAAM,CACnC;;OACL;;KACL,GAAc,cACb,kBAAC,KAAD;MAAG,WAAU;gBAA2B,EAAa;MAAgB,CAAA,GACnE;KACI;;GAIX,KACC,kBAAC,WAAD;IAAS,WAAW,GAAG,EAAQ;cAA/B;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,IAAD;OAAe,WAAU;OAAgC,eAAY;OAAS,CAAA,EAC9E,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,wCAAwC,cAAc;OACvD,CAAA,CACD;;KACN,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,IAAD;OAAU,SAAS;OAAY,kBAAkB,MAAM,GAAc,MAAM,GAAK;OAAI,CAAA,EACpF,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,mCAAmC,8CAA8C;OAChF,CAAA,CACD;;KACR,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAe;QAAE,KAAK;QAAO,cAAc;QAAO,CAAC;OAClE,UAAU;OACV,WAAW;iBAJb,CAMG,IACC,kBAAC,GAAD;QAAS,WAAU;QAAsB,eAAY;QAAS,CAAA,GAE9D,kBAAC,IAAD;QAAQ,WAAU;QAAS,eAAY;QAAS,CAAA,EAEjD,EAAG,sCAAsC,aAAa,CAChD;UACT,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAe;QAAE,KAAK;QAAM,cAAc;QAAM,CAAC;OAChE,UAAU;OACV,WAAW;iBAJb,CAME,kBAAC,IAAD;QAAQ,WAAU;QAAS,eAAY;QAAS,CAAA,EAC/C,EAAG,oCAAoC,0BAA0B,CAC3D;SACL;;KACN,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,qCACA,0KACD;MACC,CAAA;KACI;;GAIX,IACC,kBAAC,WAAD;IAAS,WAAW;cAApB;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAO;OACL,CAAA,EACL,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAU,KAAK;OAC9B,WAAU;iBAET,EAAG,oCAAoC,UAAU;OAC3C,CAAA,CACL;;KAGN,kBAAC,KAAD;MAAG,MAAK;MAAS,aAAU;MAAS,WAAU;gBAC3C,EAAO,QACJ,EAAG,yCAAyC,kCAAkC,EAC5E,OAAO,EAAO,OACf,CAAC,GACF,EAAG,uCAAuC,sBAAsB,EAC9D,OAAO,EAAO,OACf,CAAC;MACJ,CAAA;KACJ,kBAAC,OAAD;MACE,WAAW,iFACT,EAAO,QACH,qDACA;gBAGL,EAAO;MACJ,CAAA;KACE;QACR;GAGJ,kBAAC,GAAD;IACE,QAAQ,MAAmB;IAC3B,eAAe,EAAkB,KAAK;IACtC,iBAAiB;KACf,IAAM,IAAO;AAEb,KADA,EAAkB,KAAK,EACnB,KAAW,EAAe,EAAK;;IAErC,OAAO,EAAG,gDAAgD,2BAA2B;IACrF,SAAS,EACP,8CACA,2DACA,EAAE,MAAM,KAAkB,EAAE,EAAE,KAAK,IAAI,EAAE,CAC1C;IACD,cAAc,EAAG,gCAAgC,MAAM;IACvD,SAAQ;IACR,WAAW;IACX,CAAA;GACF,kBAAC,GAAD;IACE,QAAQ,MAAgB;IACxB,eAAe,EAAe,KAAK;IACnC,iBAAiB;KACf,IAAM,IAAO;AAEb,KADA,EAAe,KAAK,EAChB,KAAW,GAAQ,EAAK;;IAE9B,OACE,IACI,EAAG,gDAAgD,gBAAgB,GACnE,EAAG,6CAA6C,cAAc;IAEpE,SACE,GAAa,MACT,EACE,8CACA,iEACD,GACD,EACE,gDACA,sDACD;IAEP,cACE,IACI,EAAG,oCAAoC,UAAU,GACjD,EAAG,iCAAiC,OAAO;IAEjD,SAAQ;IACR,WAAW;IACX,CAAA;GACE"}
@@ -1,17 +1,19 @@
1
1
  import { useAgentCodeServerStartMutation as e, useAgentCodeServerStatusLazyQuery as t, useAgentCodeServerStopMutation as n, useMintAgentCapabilityMutation as r } from "../../generated/wspace-operations.js";
2
2
  import { useTr as i } from "../../shared/hooks/useTr.js";
3
3
  import { AgentCapabilityScope as a } from "../../generated/wspace-types.js";
4
- import { useCallback as o, useEffect as s, useState as c } from "react";
5
- import { AlertCircle as l, Check as u, Code as d, Loader2 as f, X as p } from "lucide-react";
6
- import { jsx as m, jsxs as h } from "react/jsx-runtime";
4
+ import { VIBE_PERMISSIONS as o } from "../../constants/permissions.js";
5
+ import { useCallback as s, useEffect as c, useState as l } from "react";
6
+ import { AlertCircle as u, Check as d, Code as f, Loader2 as p, X as m } from "lucide-react";
7
+ import { usePermissions as h } from "@burdenoff/fe-libs/shared/providers/shell";
8
+ import { jsx as g, jsxs as _ } from "react/jsx-runtime";
7
9
  //#region src/components/agents/OpenInBrowserEditorButton.tsx
8
- var g = "default";
9
- function _({ agentId: s, tunnelUrl: p, profile: m, workspacePath: h, mode: _ = "newtab", onIframeUrl: v }) {
10
- let y = i(), [b, x] = c("idle"), [S, C] = c(null), [w] = t({ fetchPolicy: "network-only" }), [T] = e(), [E] = n(), [D] = r(), O = o(async (e) => {
11
- let t = (await w({ variables: {
12
- agentId: s,
10
+ var v = "default";
11
+ function y({ agentId: c, tunnelUrl: m, profile: g, workspacePath: _, mode: y = "newtab", onIframeUrl: b }) {
12
+ let x = i(), { hasPermission: S } = h(), C = S(o.AGENT_MUTATE), w = S(o.AGENT_INVOKE), [T, E] = l("idle"), [D, O] = l(null), [k] = t({ fetchPolicy: "network-only" }), [A] = e(), [j] = n(), [M] = r(), N = s(async (e) => {
13
+ let t = (await k({ variables: {
14
+ agentId: c,
13
15
  profile: e,
14
- name: g
16
+ name: v
15
17
  } })).data?.agentCodeServerStatus;
16
18
  if (!t) return {
17
19
  effective: null,
@@ -29,161 +31,167 @@ function _({ agentId: s, tunnelUrl: p, profile: m, workspacePath: h, mode: _ = "
29
31
  status: t.data ?? {},
30
32
  missing: !1
31
33
  };
32
- }, [w, s]), k = o(async () => {
33
- if (!s || !p) {
34
- C("Agent not selected"), x("error");
34
+ }, [k, c]), P = s(async () => {
35
+ if (!C || !w) {
36
+ O(x("common.noPermission", "You do not have permission")), E("error");
35
37
  return;
36
38
  }
37
- C(null);
39
+ if (!c || !m) {
40
+ O("Agent not selected"), E("error");
41
+ return;
42
+ }
43
+ O(null);
38
44
  let e = null;
39
- _ !== "iframe" && (e = window.open("about:blank", "_blank"));
45
+ y !== "iframe" && (e = window.open("about:blank", "_blank"));
40
46
  try {
41
- x("checking");
42
- let t = m === "default" ? ["default"] : [m, "default"], n = null, r = null;
47
+ E("checking");
48
+ let t = g === "default" ? ["default"] : [g, "default"], n = null, r = null;
43
49
  for (let e of t) {
44
- let { effective: t, status: i, missing: a } = await O(e);
50
+ let { effective: t, status: i, missing: a } = await N(e);
45
51
  if (!a && t) {
46
52
  n = t, r = i;
47
53
  break;
48
54
  }
49
55
  }
50
- if (!n || !r || !r.installed) throw Error(y("vibecontrols.agents.editor.notInstalled", "Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor."));
56
+ if (!n || !r || !r.installed) throw Error(x("vibecontrols.agents.editor.notInstalled", "Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor."));
51
57
  if (!r.running) {
52
- x("starting");
53
- let e = (await T({ variables: {
54
- agentId: s,
58
+ E("starting");
59
+ let e = (await A({ variables: {
60
+ agentId: c,
55
61
  profile: n,
56
- name: g,
57
- ...h ? { path: h } : {}
62
+ name: v,
63
+ ..._ ? { path: _ } : {}
58
64
  } })).data?.agentCodeServerStart;
59
65
  if (!e?.ok) throw Error(e?.error || `Start failed: HTTP ${e?.status ?? 0}`);
60
66
  await new Promise((e) => setTimeout(e, 1e3));
61
- } else if (h) {
62
- x("starting"), await E({ variables: {
63
- agentId: s,
67
+ } else if (_) {
68
+ E("starting"), await j({ variables: {
69
+ agentId: c,
64
70
  profile: n,
65
- name: g
71
+ name: v
66
72
  } });
67
- let e = (await T({ variables: {
68
- agentId: s,
73
+ let e = (await A({ variables: {
74
+ agentId: c,
69
75
  profile: n,
70
- name: g,
71
- path: h
76
+ name: v,
77
+ path: _
72
78
  } })).data?.agentCodeServerStart;
73
79
  if (!e?.ok) throw Error(e?.error || `Restart failed: HTTP ${e?.status ?? 0}`);
74
80
  await new Promise((e) => setTimeout(e, 1e3));
75
81
  }
76
- x("ready");
77
- let i = (await D({ variables: {
78
- agentId: s,
82
+ E("ready");
83
+ let i = (await M({ variables: {
84
+ agentId: c,
79
85
  profile: n,
80
86
  scope: a.IframePlugin,
81
- resourceId: `code-server:${g}`,
87
+ resourceId: `code-server:${v}`,
82
88
  ttlSeconds: 60
83
89
  } })).data?.mintAgentCapability?.token;
84
90
  if (!i) throw Error("Failed to mint editor capability");
85
- let o = `${p.replace(/\/$/, "")}/code-server/?vt=${encodeURIComponent(i)}`;
86
- if (_ === "iframe" && v) v(o);
91
+ let o = `${m.replace(/\/$/, "")}/code-server/?vt=${encodeURIComponent(i)}`;
92
+ if (y === "iframe" && b) b(o);
87
93
  else if (e && !e.closed) e.location.replace(o);
88
- else if (!window.open(o, "_blank")) throw Error(y("vibecontrols.agents.editor.popupBlocked", "Your browser blocked the editor tab. Allow pop-ups for this site, then click Open Editor again."));
89
- setTimeout(() => x("idle"), 2e3);
94
+ else if (!window.open(o, "_blank")) throw Error(x("vibecontrols.agents.editor.popupBlocked", "Your browser blocked the editor tab. Allow pop-ups for this site, then click Open Editor again."));
95
+ setTimeout(() => E("idle"), 2e3);
90
96
  } catch (t) {
91
- e && !e.closed && e.close(), C(t instanceof Error ? t.message : "Failed to open editor"), x("error");
97
+ e && !e.closed && e.close(), O(t instanceof Error ? t.message : "Failed to open editor"), E("error");
92
98
  }
93
99
  }, [
94
- s,
95
- p,
100
+ c,
96
101
  m,
97
- h,
102
+ g,
98
103
  _,
99
- v,
100
104
  y,
101
- O,
102
- T,
103
- E,
104
- D
105
- ]), A = !s || !p || b !== "idle" && b !== "error" && b !== "ready", j = (() => {
106
- switch (b) {
107
- case "checking": return y("vibecontrols.agents.editor.checking", "Checking...");
108
- case "installing": return y("vibecontrols.agents.editor.installing", "Installing...");
109
- case "starting": return y("vibecontrols.agents.editor.starting", "Starting...");
110
- case "ready": return y("vibecontrols.agents.editor.opened", "Opened");
111
- case "error": return y("vibecontrols.agents.editor.retry", "Retry");
112
- default: return y("vibecontrols.agents.editor.openEditor", "Open Editor");
105
+ b,
106
+ x,
107
+ C,
108
+ w,
109
+ N,
110
+ A,
111
+ j,
112
+ M
113
+ ]), F = !C || !w || !c || !m || T !== "idle" && T !== "error" && T !== "ready", I = (() => {
114
+ switch (T) {
115
+ case "checking": return x("vibecontrols.agents.editor.checking", "Checking...");
116
+ case "installing": return x("vibecontrols.agents.editor.installing", "Installing...");
117
+ case "starting": return x("vibecontrols.agents.editor.starting", "Starting...");
118
+ case "ready": return x("vibecontrols.agents.editor.opened", "Opened");
119
+ case "error": return x("vibecontrols.agents.editor.retry", "Retry");
120
+ default: return x("vibecontrols.agents.editor.openEditor", "Open Editor");
113
121
  }
114
- })(), M = (() => {
115
- switch (b) {
122
+ })(), L = (() => {
123
+ switch (T) {
116
124
  case "checking":
117
125
  case "installing":
118
- case "starting": return f;
119
- case "ready": return u;
120
- case "error": return l;
121
- default: return d;
126
+ case "starting": return p;
127
+ case "ready": return d;
128
+ case "error": return u;
129
+ default: return f;
122
130
  }
123
- })(), N = b === "checking" || b === "installing" || b === "starting";
131
+ })(), R = T === "checking" || T === "installing" || T === "starting";
124
132
  return {
125
- launch: k,
126
- state: b,
127
- errorMsg: S,
128
- dismissError: o(() => C(null), []),
129
- label: j,
130
- Icon: M,
131
- isSpinning: N,
132
- isDisabled: A
133
+ launch: P,
134
+ state: T,
135
+ errorMsg: D,
136
+ dismissError: s(() => O(null), []),
137
+ label: I,
138
+ Icon: L,
139
+ isSpinning: R,
140
+ isDisabled: F
133
141
  };
134
142
  }
135
- function v({ agentId: e, tunnelUrl: t, profile: n, workspacePath: r, mode: a = "newtab", onIframeUrl: o, className: c }) {
136
- let u = i(), { launch: d, state: f, errorMsg: g, dismissError: v, label: y, Icon: b, isSpinning: x, isDisabled: S } = _({
143
+ function b({ agentId: e, tunnelUrl: t, profile: n, workspacePath: r, mode: a = "newtab", onIframeUrl: o, className: s }) {
144
+ let l = i(), { launch: d, state: f, errorMsg: p, dismissError: h, label: v, Icon: b, isSpinning: x, isDisabled: S } = y({
137
145
  agentId: e,
138
146
  tunnelUrl: t,
139
147
  profile: n,
140
148
  workspacePath: r,
141
149
  mode: a,
142
150
  onIframeUrl: o
143
- }), C = f === "error" && !!g;
144
- return s(() => {
151
+ }), C = f === "error" && !!p;
152
+ return c(() => {
145
153
  if (!C) return;
146
154
  let e = (e) => {
147
- e.key === "Escape" && v();
155
+ e.key === "Escape" && h();
148
156
  };
149
157
  return document.addEventListener("keydown", e), () => document.removeEventListener("keydown", e);
150
- }, [C, v]), /* @__PURE__ */ h("div", {
158
+ }, [C, h]), /* @__PURE__ */ _("div", {
151
159
  className: "relative inline-flex",
152
- children: [/* @__PURE__ */ h("button", {
160
+ children: [/* @__PURE__ */ _("button", {
153
161
  type: "button",
154
162
  onClick: () => void d(),
155
163
  disabled: S,
156
- "aria-label": y,
164
+ "aria-label": v,
157
165
  "aria-busy": x,
158
- className: `flex items-center gap-1.5 px-2.5 py-1.5 sm:px-3 sm:py-2 max-md:min-h-11 text-xs sm:text-sm rounded-md transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${f === "error" ? "bg-status-error-bg-subtle text-status-error-text hover:opacity-80" : f === "ready" ? "bg-status-success-bg-subtle text-status-success-text" : "bg-bg-sunken text-text-primary hover:bg-bg-elevated"} ${c || ""}`,
159
- title: g || "Open VS Code in browser via code-server",
160
- children: [/* @__PURE__ */ m(b, {
166
+ className: `flex items-center gap-1.5 px-2.5 py-1.5 sm:px-3 sm:py-2 max-md:min-h-11 text-xs sm:text-sm rounded-md transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${f === "error" ? "bg-status-error-bg-subtle text-status-error-text hover:opacity-80" : f === "ready" ? "bg-status-success-bg-subtle text-status-success-text" : "bg-bg-sunken text-text-primary hover:bg-bg-elevated"} ${s || ""}`,
167
+ title: p || "Open VS Code in browser via code-server",
168
+ children: [/* @__PURE__ */ g(b, {
161
169
  "aria-hidden": "true",
162
170
  className: `size-3.5 sm:size-4 ${x ? "animate-spin" : ""}`
163
- }), /* @__PURE__ */ m("span", {
171
+ }), /* @__PURE__ */ g("span", {
164
172
  className: "hidden sm:inline",
165
- children: y
173
+ children: v
166
174
  })]
167
- }), C && /* @__PURE__ */ m("div", {
175
+ }), C && /* @__PURE__ */ g("div", {
168
176
  role: "alert",
169
177
  className: "absolute top-full right-0 mt-1.5 z-50 w-72 max-w-[calc(100vw-2rem)] rounded-md border border-status-error-border bg-bg-surface p-3 text-xs leading-relaxed text-status-error-text whitespace-normal break-words shadow-[var(--shadow-elevation-2)]",
170
- children: /* @__PURE__ */ h("div", {
178
+ children: /* @__PURE__ */ _("div", {
171
179
  className: "flex items-start gap-2",
172
180
  children: [
173
- /* @__PURE__ */ m(l, {
181
+ /* @__PURE__ */ g(u, {
174
182
  className: "mt-0.5 size-4 shrink-0",
175
183
  "aria-hidden": "true"
176
184
  }),
177
- /* @__PURE__ */ m("span", {
185
+ /* @__PURE__ */ g("span", {
178
186
  className: "min-w-0 flex-1",
179
- children: g
187
+ children: p
180
188
  }),
181
- /* @__PURE__ */ m("button", {
189
+ /* @__PURE__ */ g("button", {
182
190
  type: "button",
183
- onClick: v,
184
- "aria-label": u("vibecontrols.agents.editor.close", "Close"),
191
+ onClick: h,
192
+ "aria-label": l("vibecontrols.agents.editor.close", "Close"),
185
193
  className: "-m-1 inline-flex size-6 shrink-0 items-center justify-center rounded-button text-status-error-text transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
186
- children: /* @__PURE__ */ m(p, {
194
+ children: /* @__PURE__ */ g(m, {
187
195
  className: "size-3.5",
188
196
  "aria-hidden": "true"
189
197
  })
@@ -194,6 +202,6 @@ function v({ agentId: e, tunnelUrl: t, profile: n, workspacePath: r, mode: a = "
194
202
  });
195
203
  }
196
204
  //#endregion
197
- export { v as OpenInBrowserEditorButton, _ as useBrowserEditorLauncher };
205
+ export { b as OpenInBrowserEditorButton, y as useBrowserEditorLauncher };
198
206
 
199
207
  //# sourceMappingURL=OpenInBrowserEditorButton.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"OpenInBrowserEditorButton.js","names":[],"sources":["../../../src/components/agents/OpenInBrowserEditorButton.tsx"],"sourcesContent":["import { useState, useCallback, useEffect } from 'react';\nimport type { LucideIcon } from 'lucide-react';\nimport { Code, Loader2, AlertCircle, Check, X } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n useAgentCodeServerStatusLazyQuery,\n useAgentCodeServerStartMutation,\n useAgentCodeServerStopMutation,\n useMintAgentCapabilityMutation,\n} from '@/generated/wspace-operations';\nimport { AgentCapabilityScope } from '@/generated/wspace-types';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\ntype EditorState = 'idle' | 'checking' | 'installing' | 'starting' | 'ready' | 'error';\n\nexport interface BrowserEditorLauncherOptions {\n /**\n * Agent ID — F4 Wave B replaced the direct browser fetch (which relied\n * on `agentApiKey` headers) with svc-mediated `agentCodeServer*`\n * mutations. The agent's tunnel URL is still used for the iframe `src`,\n * but every control-plane call now routes through the gateway.\n */\n agentId: string | null;\n /** Agent tunnel URL (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /**\n * Canonical CLI profile name on the remote agent. Maps to the\n * `/api/profiles/<profile>/code-server/...` URL prefix.\n */\n profile: string;\n /** Directory to open as workspace root. Defaults to agent home if not set. */\n workspacePath?: string;\n /** How to open — new browser tab or call back with URL for iframe embedding */\n mode?: 'newtab' | 'iframe';\n /** Called with the ready URL when mode is 'iframe' */\n onIframeUrl?: (url: string) => void;\n}\n\nexport interface OpenInBrowserEditorButtonProps extends BrowserEditorLauncherOptions {\n /** Additional CSS class names */\n className?: string;\n}\n\ninterface CodeServerStatusPayload {\n installed?: boolean;\n installing?: boolean;\n running?: boolean;\n port?: number;\n error?: string;\n}\n\n// Code-server is a single named instance per profile; the proxy resolver\n// requires a `name`. Keep the legacy default — operators who installed\n// the plugin pre-rename still have an instance keyed under \"default\".\nconst CODE_SERVER_INSTANCE = 'default';\n\n// ── Launcher hook ──────────────────────────────────────────────────────\n\nexport interface BrowserEditorLauncher {\n /** Probe → start → mint → open. MUST be called inside a user gesture\n * (click / menu select) so the pre-opened tab survives popup blockers. */\n launch: () => Promise<void>;\n state: EditorState;\n errorMsg: string | null;\n dismissError: () => void;\n label: string;\n Icon: LucideIcon;\n isSpinning: boolean;\n isDisabled: boolean;\n}\n\n/**\n * State machine + control-plane flow for opening the browser-hosted\n * code-server. Extracted from the button so menu items (Radix\n * DropdownMenuItem) can trigger the identical flow with full keyboard\n * support while the standalone button keeps its inline presentation.\n */\nexport function useBrowserEditorLauncher({\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode = 'newtab',\n onIframeUrl,\n}: BrowserEditorLauncherOptions): BrowserEditorLauncher {\n const tr = useTr();\n const [state, setState] = useState<EditorState>('idle');\n const [errorMsg, setErrorMsg] = useState<string | null>(null);\n\n const [fetchStatus] = useAgentCodeServerStatusLazyQuery({ fetchPolicy: 'network-only' });\n const [startCodeServer] = useAgentCodeServerStartMutation();\n const [stopCodeServer] = useAgentCodeServerStopMutation();\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n\n const probe = useCallback(\n async (\n candidate: string\n ): Promise<{\n effective: string | null;\n status: CodeServerStatusPayload | null;\n missing: boolean;\n }> => {\n const res = await fetchStatus({\n variables: { agentId: agentId!, profile: candidate, name: CODE_SERVER_INSTANCE },\n });\n const proxy = res.data?.agentCodeServerStatus;\n if (!proxy) {\n return { effective: null, status: null, missing: false };\n }\n if (proxy.status === 404) {\n return { effective: null, status: null, missing: true };\n }\n if (!proxy.ok) {\n throw new Error(proxy.error || `Status check failed: HTTP ${proxy.status}`);\n }\n return {\n effective: candidate,\n status: (proxy.data as CodeServerStatusPayload | null) ?? {},\n missing: false,\n };\n },\n [fetchStatus, agentId]\n );\n\n const launch = useCallback(async () => {\n if (!agentId || !tunnelUrl) {\n setErrorMsg('Agent not selected');\n setState('error');\n return;\n }\n\n setErrorMsg(null);\n\n // Pre-open the editor tab *synchronously*, inside the click gesture, so the\n // browser's popup blocker lets it through. The control-plane calls below\n // (probe → start → mint) are async, and a `window.open` issued after those\n // awaits has lost the user-activation and gets blocked — which is the\n // \"popup blocker blocks the editor\" report. We open `about:blank` now and\n // navigate it once the real URL is ready; on failure we close it again.\n let editorWindow: Window | null = null;\n if (mode !== 'iframe') {\n editorWindow = window.open('about:blank', '_blank');\n }\n\n try {\n // Step 1: Probe the active profile, falling back to \"default\" if the\n // current profile reports 404 (code-server isn't a per-profile\n // service — its management API is profile-scoped though).\n setState('checking');\n const candidates = profile === 'default' ? ['default'] : [profile, 'default'];\n let effectiveProfile: string | null = null;\n let status: CodeServerStatusPayload | null = null;\n for (const candidate of candidates) {\n const { effective, status: probed, missing } = await probe(candidate);\n if (missing) continue;\n if (effective) {\n effectiveProfile = effective;\n status = probed;\n break;\n }\n }\n if (!effectiveProfile || !status) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.notInstalled',\n \"Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor.\"\n )\n );\n }\n\n // Step 2: code-server needs to be installed up-front via the\n // Plugins tab. The deprecated direct-browser install fetch isn't\n // proxied; surface a clear message instead of silently hanging.\n if (!status.installed) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.notInstalled',\n \"Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor.\"\n )\n );\n }\n\n // Step 3: Start if not running, or restart with a fresh workspace path.\n if (!status.running) {\n setState('starting');\n const startRes = await startCodeServer({\n variables: {\n agentId,\n profile: effectiveProfile,\n name: CODE_SERVER_INSTANCE,\n ...(workspacePath ? { path: workspacePath } : {}),\n },\n });\n const startProxy = startRes.data?.agentCodeServerStart;\n if (!startProxy?.ok) {\n throw new Error(startProxy?.error || `Start failed: HTTP ${startProxy?.status ?? 0}`);\n }\n await new Promise((r) => setTimeout(r, 1000));\n } else if (workspacePath) {\n setState('starting');\n // No dedicated restart proxy yet — stop, then start with the new path.\n await stopCodeServer({\n variables: { agentId, profile: effectiveProfile, name: CODE_SERVER_INSTANCE },\n });\n const restartRes = await startCodeServer({\n variables: {\n agentId,\n profile: effectiveProfile,\n name: CODE_SERVER_INSTANCE,\n path: workspacePath,\n },\n });\n const restartProxy = restartRes.data?.agentCodeServerStart;\n if (!restartProxy?.ok) {\n throw new Error(\n restartProxy?.error || `Restart failed: HTTP ${restartProxy?.status ?? 0}`\n );\n }\n await new Promise((r) => setTimeout(r, 1000));\n }\n\n // Step 4: Mint a short-lived IFRAME_PLUGIN capability so the\n // editor URL doesn't embed the long-lived `agentApiKey`. The agent\n // consumes the `?vt=` query on the first `/code-server/` request and\n // mints a scoped proxy session cookie server-side before proxying.\n setState('ready');\n const mintRes = await mintAgentCapability({\n variables: {\n agentId,\n profile: effectiveProfile,\n scope: AgentCapabilityScope.IframePlugin,\n resourceId: `code-server:${CODE_SERVER_INSTANCE}`,\n ttlSeconds: 60,\n },\n });\n const token = mintRes.data?.mintAgentCapability?.token;\n if (!token) {\n throw new Error('Failed to mint editor capability');\n }\n const base = tunnelUrl.replace(/\\/$/, '');\n // code-server reads the capability from the `?vt=` QUERY (GET-only): the\n // agent consumes it server-side on the first `/code-server/` request to\n // mint the proxy session cookie. A `#vt=` fragment is never sent to the\n // server, so the editor would 401 before it could bootstrap (code-server\n // has no client-side fragment-exchange shim, unlike `/ui/*` / `/terminal`).\n const editorUrl = `${base}/code-server/?vt=${encodeURIComponent(token)}`;\n\n if (mode === 'iframe' && onIframeUrl) {\n onIframeUrl(editorUrl);\n } else if (editorWindow && !editorWindow.closed) {\n // Navigate the tab we pre-opened inside the click gesture.\n editorWindow.location.replace(editorUrl);\n } else {\n // The pre-opened tab was blocked (or closed by the user). Try once\n // more — this still happens within the original gesture's task — and\n // surface a hint if the blocker rejects it again.\n const opened = window.open(editorUrl, '_blank');\n if (!opened) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.popupBlocked',\n 'Your browser blocked the editor tab. Allow pop-ups for this site, then click Open Editor again.'\n )\n );\n }\n }\n\n // Reset after a moment\n setTimeout(() => setState('idle'), 2000);\n } catch (err) {\n // Close the placeholder tab so a blank \"about:blank\" window isn't left\n // orphaned when the editor fails to come up.\n if (editorWindow && !editorWindow.closed) {\n editorWindow.close();\n }\n setErrorMsg(err instanceof Error ? err.message : 'Failed to open editor');\n setState('error');\n }\n }, [\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode,\n onIframeUrl,\n tr,\n probe,\n startCodeServer,\n stopCodeServer,\n mintAgentCapability,\n ]);\n\n const isDisabled =\n !agentId || !tunnelUrl || (state !== 'idle' && state !== 'error' && state !== 'ready');\n\n const label = (() => {\n switch (state) {\n case 'checking':\n return tr('vibecontrols.agents.editor.checking', 'Checking...');\n case 'installing':\n return tr('vibecontrols.agents.editor.installing', 'Installing...');\n case 'starting':\n return tr('vibecontrols.agents.editor.starting', 'Starting...');\n case 'ready':\n return tr('vibecontrols.agents.editor.opened', 'Opened');\n case 'error':\n return tr('vibecontrols.agents.editor.retry', 'Retry');\n default:\n return tr('vibecontrols.agents.editor.openEditor', 'Open Editor');\n }\n })();\n\n const Icon: LucideIcon = (() => {\n switch (state) {\n case 'checking':\n case 'installing':\n case 'starting':\n return Loader2;\n case 'ready':\n return Check;\n case 'error':\n return AlertCircle;\n default:\n return Code;\n }\n })();\n\n const isSpinning = state === 'checking' || state === 'installing' || state === 'starting';\n const dismissError = useCallback(() => setErrorMsg(null), []);\n\n return { launch, state, errorMsg, dismissError, label, Icon, isSpinning, isDisabled };\n}\n\n// ── Component ──────────────────────────────────────────────────────────\n\nexport function OpenInBrowserEditorButton({\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode = 'newtab',\n onIframeUrl,\n className,\n}: OpenInBrowserEditorButtonProps) {\n const tr = useTr();\n const { launch, state, errorMsg, dismissError, label, Icon, isSpinning, isDisabled } =\n useBrowserEditorLauncher({ agentId, tunnelUrl, profile, workspacePath, mode, onIframeUrl });\n\n const errorPopoverVisible = state === 'error' && !!errorMsg;\n\n // Escape dismisses the error popover. Listener attached only while visible.\n useEffect(() => {\n if (!errorPopoverVisible) return;\n const onKeyDown = (e: globalThis.KeyboardEvent) => {\n if (e.key === 'Escape') dismissError();\n };\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [errorPopoverVisible, dismissError]);\n\n return (\n <div className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => void launch()}\n disabled={isDisabled}\n aria-label={label}\n aria-busy={isSpinning}\n className={`flex items-center gap-1.5 px-2.5 py-1.5 sm:px-3 sm:py-2 max-md:min-h-11 text-xs sm:text-sm rounded-md transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${\n state === 'error'\n ? 'bg-status-error-bg-subtle text-status-error-text hover:opacity-80'\n : state === 'ready'\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-elevated'\n } ${className || ''}`}\n title={errorMsg || 'Open VS Code in browser via code-server'}\n >\n <Icon\n aria-hidden=\"true\"\n className={`size-3.5 sm:size-4 ${isSpinning ? 'animate-spin' : ''}`}\n />\n <span className=\"hidden sm:inline\">{label}</span>\n </button>\n\n {/* Error popover. Anchored to the right edge (the button sits at the\n header's right side) with a width cap so the message wraps as\n normal prose instead of being squeezed into a narrow column by the\n inline-flex wrapper. */}\n {errorPopoverVisible && (\n <div\n role=\"alert\"\n className=\"absolute top-full right-0 mt-1.5 z-50 w-72 max-w-[calc(100vw-2rem)] rounded-md border border-status-error-border bg-bg-surface p-3 text-xs leading-relaxed text-status-error-text whitespace-normal break-words shadow-[var(--shadow-elevation-2)]\"\n >\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" aria-hidden=\"true\" />\n <span className=\"min-w-0 flex-1\">{errorMsg}</span>\n <button\n type=\"button\"\n onClick={dismissError}\n aria-label={tr('vibecontrols.agents.editor.close', 'Close')}\n className=\"-m-1 inline-flex size-6 shrink-0 items-center justify-center rounded-button text-status-error-text transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <X className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"OpenInBrowserEditorButton.js","names":[],"sources":["../../../src/components/agents/OpenInBrowserEditorButton.tsx"],"sourcesContent":["import { useState, useCallback, useEffect } from 'react';\nimport type { LucideIcon } from 'lucide-react';\nimport { Code, Loader2, AlertCircle, Check, X } from 'lucide-react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n useAgentCodeServerStatusLazyQuery,\n useAgentCodeServerStartMutation,\n useAgentCodeServerStopMutation,\n useMintAgentCapabilityMutation,\n} from '@/generated/wspace-operations';\nimport { AgentCapabilityScope } from '@/generated/wspace-types';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\ntype EditorState = 'idle' | 'checking' | 'installing' | 'starting' | 'ready' | 'error';\n\nexport interface BrowserEditorLauncherOptions {\n /**\n * Agent ID — F4 Wave B replaced the direct browser fetch (which relied\n * on `agentApiKey` headers) with svc-mediated `agentCodeServer*`\n * mutations. The agent's tunnel URL is still used for the iframe `src`,\n * but every control-plane call now routes through the gateway.\n */\n agentId: string | null;\n /** Agent tunnel URL (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /**\n * Canonical CLI profile name on the remote agent. Maps to the\n * `/api/profiles/<profile>/code-server/...` URL prefix.\n */\n profile: string;\n /** Directory to open as workspace root. Defaults to agent home if not set. */\n workspacePath?: string;\n /** How to open — new browser tab or call back with URL for iframe embedding */\n mode?: 'newtab' | 'iframe';\n /** Called with the ready URL when mode is 'iframe' */\n onIframeUrl?: (url: string) => void;\n}\n\nexport interface OpenInBrowserEditorButtonProps extends BrowserEditorLauncherOptions {\n /** Additional CSS class names */\n className?: string;\n}\n\ninterface CodeServerStatusPayload {\n installed?: boolean;\n installing?: boolean;\n running?: boolean;\n port?: number;\n error?: string;\n}\n\n// Code-server is a single named instance per profile; the proxy resolver\n// requires a `name`. Keep the legacy default — operators who installed\n// the plugin pre-rename still have an instance keyed under \"default\".\nconst CODE_SERVER_INSTANCE = 'default';\n\n// ── Launcher hook ──────────────────────────────────────────────────────\n\nexport interface BrowserEditorLauncher {\n /** Probe → start → mint → open. MUST be called inside a user gesture\n * (click / menu select) so the pre-opened tab survives popup blockers. */\n launch: () => Promise<void>;\n state: EditorState;\n errorMsg: string | null;\n dismissError: () => void;\n label: string;\n Icon: LucideIcon;\n isSpinning: boolean;\n isDisabled: boolean;\n}\n\n/**\n * State machine + control-plane flow for opening the browser-hosted\n * code-server. Extracted from the button so menu items (Radix\n * DropdownMenuItem) can trigger the identical flow with full keyboard\n * support while the standalone button keeps its inline presentation.\n */\nexport function useBrowserEditorLauncher({\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode = 'newtab',\n onIframeUrl,\n}: BrowserEditorLauncherOptions): BrowserEditorLauncher {\n const tr = useTr();\n // Opening the browser editor uses two gateway-enforced operations:\n // code-server start/stop (`agent:mutate`) and capability-token minting\n // (`agent:invoke`). Gate here in the hook so every caller — the standalone\n // button AND the Vibe-detail OpenInEditorMenu — is covered.\n const { hasPermission } = usePermissions();\n const canMutate = hasPermission(VIBE_PERMISSIONS.AGENT_MUTATE);\n const canInvoke = hasPermission(VIBE_PERMISSIONS.AGENT_INVOKE);\n const [state, setState] = useState<EditorState>('idle');\n const [errorMsg, setErrorMsg] = useState<string | null>(null);\n\n const [fetchStatus] = useAgentCodeServerStatusLazyQuery({ fetchPolicy: 'network-only' });\n const [startCodeServer] = useAgentCodeServerStartMutation();\n const [stopCodeServer] = useAgentCodeServerStopMutation();\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n\n const probe = useCallback(\n async (\n candidate: string\n ): Promise<{\n effective: string | null;\n status: CodeServerStatusPayload | null;\n missing: boolean;\n }> => {\n const res = await fetchStatus({\n variables: { agentId: agentId!, profile: candidate, name: CODE_SERVER_INSTANCE },\n });\n const proxy = res.data?.agentCodeServerStatus;\n if (!proxy) {\n return { effective: null, status: null, missing: false };\n }\n if (proxy.status === 404) {\n return { effective: null, status: null, missing: true };\n }\n if (!proxy.ok) {\n throw new Error(proxy.error || `Status check failed: HTTP ${proxy.status}`);\n }\n return {\n effective: candidate,\n status: (proxy.data as CodeServerStatusPayload | null) ?? {},\n missing: false,\n };\n },\n [fetchStatus, agentId]\n );\n\n const launch = useCallback(async () => {\n if (!canMutate || !canInvoke) {\n setErrorMsg(tr('common.noPermission', 'You do not have permission'));\n setState('error');\n return;\n }\n if (!agentId || !tunnelUrl) {\n setErrorMsg('Agent not selected');\n setState('error');\n return;\n }\n\n setErrorMsg(null);\n\n // Pre-open the editor tab *synchronously*, inside the click gesture, so the\n // browser's popup blocker lets it through. The control-plane calls below\n // (probe → start → mint) are async, and a `window.open` issued after those\n // awaits has lost the user-activation and gets blocked — which is the\n // \"popup blocker blocks the editor\" report. We open `about:blank` now and\n // navigate it once the real URL is ready; on failure we close it again.\n let editorWindow: Window | null = null;\n if (mode !== 'iframe') {\n editorWindow = window.open('about:blank', '_blank');\n }\n\n try {\n // Step 1: Probe the active profile, falling back to \"default\" if the\n // current profile reports 404 (code-server isn't a per-profile\n // service — its management API is profile-scoped though).\n setState('checking');\n const candidates = profile === 'default' ? ['default'] : [profile, 'default'];\n let effectiveProfile: string | null = null;\n let status: CodeServerStatusPayload | null = null;\n for (const candidate of candidates) {\n const { effective, status: probed, missing } = await probe(candidate);\n if (missing) continue;\n if (effective) {\n effectiveProfile = effective;\n status = probed;\n break;\n }\n }\n if (!effectiveProfile || !status) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.notInstalled',\n \"Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor.\"\n )\n );\n }\n\n // Step 2: code-server needs to be installed up-front via the\n // Plugins tab. The deprecated direct-browser install fetch isn't\n // proxied; surface a clear message instead of silently hanging.\n if (!status.installed) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.notInstalled',\n \"Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor.\"\n )\n );\n }\n\n // Step 3: Start if not running, or restart with a fresh workspace path.\n if (!status.running) {\n setState('starting');\n const startRes = await startCodeServer({\n variables: {\n agentId,\n profile: effectiveProfile,\n name: CODE_SERVER_INSTANCE,\n ...(workspacePath ? { path: workspacePath } : {}),\n },\n });\n const startProxy = startRes.data?.agentCodeServerStart;\n if (!startProxy?.ok) {\n throw new Error(startProxy?.error || `Start failed: HTTP ${startProxy?.status ?? 0}`);\n }\n await new Promise((r) => setTimeout(r, 1000));\n } else if (workspacePath) {\n setState('starting');\n // No dedicated restart proxy yet — stop, then start with the new path.\n await stopCodeServer({\n variables: { agentId, profile: effectiveProfile, name: CODE_SERVER_INSTANCE },\n });\n const restartRes = await startCodeServer({\n variables: {\n agentId,\n profile: effectiveProfile,\n name: CODE_SERVER_INSTANCE,\n path: workspacePath,\n },\n });\n const restartProxy = restartRes.data?.agentCodeServerStart;\n if (!restartProxy?.ok) {\n throw new Error(\n restartProxy?.error || `Restart failed: HTTP ${restartProxy?.status ?? 0}`\n );\n }\n await new Promise((r) => setTimeout(r, 1000));\n }\n\n // Step 4: Mint a short-lived IFRAME_PLUGIN capability so the\n // editor URL doesn't embed the long-lived `agentApiKey`. The agent\n // consumes the `?vt=` query on the first `/code-server/` request and\n // mints a scoped proxy session cookie server-side before proxying.\n setState('ready');\n const mintRes = await mintAgentCapability({\n variables: {\n agentId,\n profile: effectiveProfile,\n scope: AgentCapabilityScope.IframePlugin,\n resourceId: `code-server:${CODE_SERVER_INSTANCE}`,\n ttlSeconds: 60,\n },\n });\n const token = mintRes.data?.mintAgentCapability?.token;\n if (!token) {\n throw new Error('Failed to mint editor capability');\n }\n const base = tunnelUrl.replace(/\\/$/, '');\n // code-server reads the capability from the `?vt=` QUERY (GET-only): the\n // agent consumes it server-side on the first `/code-server/` request to\n // mint the proxy session cookie. A `#vt=` fragment is never sent to the\n // server, so the editor would 401 before it could bootstrap (code-server\n // has no client-side fragment-exchange shim, unlike `/ui/*` / `/terminal`).\n const editorUrl = `${base}/code-server/?vt=${encodeURIComponent(token)}`;\n\n if (mode === 'iframe' && onIframeUrl) {\n onIframeUrl(editorUrl);\n } else if (editorWindow && !editorWindow.closed) {\n // Navigate the tab we pre-opened inside the click gesture.\n editorWindow.location.replace(editorUrl);\n } else {\n // The pre-opened tab was blocked (or closed by the user). Try once\n // more — this still happens within the original gesture's task — and\n // surface a hint if the blocker rejects it again.\n const opened = window.open(editorUrl, '_blank');\n if (!opened) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.popupBlocked',\n 'Your browser blocked the editor tab. Allow pop-ups for this site, then click Open Editor again.'\n )\n );\n }\n }\n\n // Reset after a moment\n setTimeout(() => setState('idle'), 2000);\n } catch (err) {\n // Close the placeholder tab so a blank \"about:blank\" window isn't left\n // orphaned when the editor fails to come up.\n if (editorWindow && !editorWindow.closed) {\n editorWindow.close();\n }\n setErrorMsg(err instanceof Error ? err.message : 'Failed to open editor');\n setState('error');\n }\n }, [\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode,\n onIframeUrl,\n tr,\n canMutate,\n canInvoke,\n probe,\n startCodeServer,\n stopCodeServer,\n mintAgentCapability,\n ]);\n\n const isDisabled =\n !canMutate ||\n !canInvoke ||\n !agentId ||\n !tunnelUrl ||\n (state !== 'idle' && state !== 'error' && state !== 'ready');\n\n const label = (() => {\n switch (state) {\n case 'checking':\n return tr('vibecontrols.agents.editor.checking', 'Checking...');\n case 'installing':\n return tr('vibecontrols.agents.editor.installing', 'Installing...');\n case 'starting':\n return tr('vibecontrols.agents.editor.starting', 'Starting...');\n case 'ready':\n return tr('vibecontrols.agents.editor.opened', 'Opened');\n case 'error':\n return tr('vibecontrols.agents.editor.retry', 'Retry');\n default:\n return tr('vibecontrols.agents.editor.openEditor', 'Open Editor');\n }\n })();\n\n const Icon: LucideIcon = (() => {\n switch (state) {\n case 'checking':\n case 'installing':\n case 'starting':\n return Loader2;\n case 'ready':\n return Check;\n case 'error':\n return AlertCircle;\n default:\n return Code;\n }\n })();\n\n const isSpinning = state === 'checking' || state === 'installing' || state === 'starting';\n const dismissError = useCallback(() => setErrorMsg(null), []);\n\n return { launch, state, errorMsg, dismissError, label, Icon, isSpinning, isDisabled };\n}\n\n// ── Component ──────────────────────────────────────────────────────────\n\nexport function OpenInBrowserEditorButton({\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode = 'newtab',\n onIframeUrl,\n className,\n}: OpenInBrowserEditorButtonProps) {\n const tr = useTr();\n // Permission gating (agent:mutate) lives inside the hook, so isDisabled\n // already reflects it for every caller of useBrowserEditorLauncher.\n const { launch, state, errorMsg, dismissError, label, Icon, isSpinning, isDisabled } =\n useBrowserEditorLauncher({ agentId, tunnelUrl, profile, workspacePath, mode, onIframeUrl });\n\n const errorPopoverVisible = state === 'error' && !!errorMsg;\n\n // Escape dismisses the error popover. Listener attached only while visible.\n useEffect(() => {\n if (!errorPopoverVisible) return;\n const onKeyDown = (e: globalThis.KeyboardEvent) => {\n if (e.key === 'Escape') dismissError();\n };\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [errorPopoverVisible, dismissError]);\n\n return (\n <div className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => void launch()}\n disabled={isDisabled}\n aria-label={label}\n aria-busy={isSpinning}\n className={`flex items-center gap-1.5 px-2.5 py-1.5 sm:px-3 sm:py-2 max-md:min-h-11 text-xs sm:text-sm rounded-md transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${\n state === 'error'\n ? 'bg-status-error-bg-subtle text-status-error-text hover:opacity-80'\n : state === 'ready'\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-elevated'\n } ${className || ''}`}\n title={errorMsg || 'Open VS Code in browser via code-server'}\n >\n <Icon\n aria-hidden=\"true\"\n className={`size-3.5 sm:size-4 ${isSpinning ? 'animate-spin' : ''}`}\n />\n <span className=\"hidden sm:inline\">{label}</span>\n </button>\n\n {/* Error popover. Anchored to the right edge (the button sits at the\n header's right side) with a width cap so the message wraps as\n normal prose instead of being squeezed into a narrow column by the\n inline-flex wrapper. */}\n {errorPopoverVisible && (\n <div\n role=\"alert\"\n className=\"absolute top-full right-0 mt-1.5 z-50 w-72 max-w-[calc(100vw-2rem)] rounded-md border border-status-error-border bg-bg-surface p-3 text-xs leading-relaxed text-status-error-text whitespace-normal break-words shadow-[var(--shadow-elevation-2)]\"\n >\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" aria-hidden=\"true\" />\n <span className=\"min-w-0 flex-1\">{errorMsg}</span>\n <button\n type=\"button\"\n onClick={dismissError}\n aria-label={tr('vibecontrols.agents.editor.close', 'Close')}\n className=\"-m-1 inline-flex size-6 shrink-0 items-center justify-center rounded-button text-status-error-text transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <X className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -14,6 +14,7 @@ import { useAuthToken as ae } from "@burdenoff/fe-libs/shared/providers/shell";
14
14
  import { jsx as x, jsxs as S } from "react/jsx-runtime";
15
15
  //#region src/components/assistant/AssistantChatArea.tsx
16
16
  var oe = {
17
+ assistant: 18e4,
17
18
  general: 18e4,
18
19
  "api-calls": 18e4,
19
20
  "vibe-plugins": 6e5