feat(dashboard): Conductor panel — fleet, tasks and cancel over server-side proxy (PRD RF3) (#8221)

* feat(a2a): conductor bridge core — event mapping with canceled->cancelled

* feat(a2a): incremental SSE parser for conductor bridge

* feat(a2a): conductor bridge connection loop with persisted cursor and backoff

* feat(a2a): start conductor bridge at boot behind CONDUCTOR_HUB_URL

* fix(a2a): conductor bridge parses the hub's real SSE wire format (id/type in frame, data={ts,payload})

* docs(reference): document CONDUCTOR_HUB_URL/TOKEN in ENVIRONMENT.md (env-doc-sync gate)

* feat(a2a): fleet skills derived from the Conductor hub for the agent card

* feat(a2a): agent card announces Conductor fleet skills

* feat(dashboard): server-side hub proxy for the Conductor panel (whitelisted shapes, fail-open)

* feat(dashboard): /api/conductor proxy routes (fleet, task detail, cancel) behind management auth

* feat(dashboard): Conductor panel — fleet live view, task detail and cancel over /api/conductor proxy

---------

Co-authored-by: backryun <bakryun0718@proton.me>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-12 01:53:14 -03:00
committed by GitHub
parent 0b158209ee
commit 3db785dc41
16 changed files with 863 additions and 0 deletions

View File

@@ -0,0 +1,233 @@
"use client";
/**
* Conductor panel (PRD Conductor RF3): fleet + task queue live view over the
* /api/conductor proxy routes. The browser never talks to the hub — everything
* goes through the server-side proxy (hub token stays in server env).
*/
import { useCallback, useEffect, useState } from "react";
import { useTranslations } from "next-intl";
import { Badge, Card, ConfirmModal, DataTable, EmptyState, Modal } from "@/shared/components";
interface FleetRunner {
id: string;
name: string;
clis: string[];
online: boolean;
draining: boolean;
}
interface FleetTask {
id: string;
status: string;
mode: string;
repo: string | null;
runner: string | null;
summary: string | null;
branch: string | null;
error: string | null;
updated_at: string | null;
}
interface FleetSnapshot {
offline: boolean;
runners: FleetRunner[];
tasks: FleetTask[];
}
interface TaskDetail extends FleetTask {
prompt: string | null;
base_ref: string | null;
council: { candidate_task_ids?: string[] } | null;
}
const REFRESH_MS = 5000;
const TERMINAL = new Set(["completed", "failed", "canceled"]);
function statusVariant(status: string): "success" | "error" | "warning" | "info" | "default" {
if (status === "completed") return "success";
if (status === "failed") return "error";
if (status === "canceled" || status === "input_required") return "warning";
if (status === "working") return "info";
return "default";
}
export default function ConductorPageClient() {
const t = useTranslations("conductor");
const [snapshot, setSnapshot] = useState<FleetSnapshot | null>(null);
const [detail, setDetail] = useState<TaskDetail | null>(null);
const [cancelTarget, setCancelTarget] = useState<string | null>(null);
const [canceling, setCanceling] = useState(false);
const [err, setErr] = useState("");
const load = useCallback(async () => {
try {
const res = await fetch("/api/conductor/fleet");
if (res.ok) setSnapshot(await res.json());
} catch {
// rede local instável: mantém o último snapshot; o banner offline vem do servidor
}
}, []);
useEffect(() => {
void load();
const timer = setInterval(() => void load(), REFRESH_MS);
return () => clearInterval(timer);
}, [load]);
const openDetail = async (taskId: string) => {
setErr("");
try {
const res = await fetch(`/api/conductor/tasks/${encodeURIComponent(taskId)}`);
if (res.ok) setDetail(await res.json());
else setErr(`${t("error")}: HTTP ${res.status}`);
} catch {
setErr(t("error"));
}
};
const confirmCancel = async () => {
if (!cancelTarget) return;
setCanceling(true);
setErr("");
try {
const res = await fetch(`/api/conductor/tasks/${encodeURIComponent(cancelTarget)}/cancel`, { method: "POST" });
if (!res.ok) setErr(`${t("cancelFailed")} (HTTP ${res.status})`);
else {
setDetail(null);
await load();
}
} catch {
setErr(t("cancelFailed"));
} finally {
setCanceling(false);
setCancelTarget(null);
}
};
const runners = snapshot?.runners ?? [];
const tasks = snapshot?.tasks ?? [];
return (
<div className="space-y-6">
<div>
<h1 className="text-xl font-semibold">{t("title")}</h1>
<p className="text-sm text-text-muted">{t("subtitle")}</p>
</div>
{err && <Badge variant="error">{err}</Badge>}
{snapshot?.offline ? (
<Card>
<EmptyState icon="cloud_off" title={t("hubOffline")} />
</Card>
) : (
<>
<Card title={t("runners")}>
<DataTable
columns={[
{ key: "name", label: t("colName") },
{ key: "clis", label: t("colClis") },
{ key: "status", label: t("colStatus") },
]}
data={runners.map((r) => ({ ...r, id: r.id }))}
emptyMessage={t("noRunners")}
loading={snapshot === null}
renderCell={(row, column) => {
const r = row as unknown as FleetRunner;
if (column.key === "name") return <span className="font-medium">{r.name}</span>;
if (column.key === "clis") return r.clis.join(" / ");
if (r.draining) return <Badge variant="warning" dot>{t("draining")}</Badge>;
return r.online ? (
<Badge variant="success" dot>{t("online")}</Badge>
) : (
<Badge variant="error" dot>{t("offline")}</Badge>
);
}}
/>
</Card>
<Card title={t("tasks")}>
<DataTable
columns={[
{ key: "id", label: t("colTask") },
{ key: "status", label: t("colStatus") },
{ key: "mode", label: t("colMode") },
{ key: "runner", label: t("colRunner") },
{ key: "summary", label: t("colSummary"), maxWidth: "28rem" },
]}
data={tasks.map((task) => ({ ...task, id: task.id }))}
emptyMessage={t("noTasks")}
loading={snapshot === null}
onRowClick={(row) => void openDetail(String(row.id))}
renderCell={(row, column) => {
const task = row as unknown as FleetTask;
if (column.key === "status") return <Badge variant={statusVariant(task.status)} dot>{task.status}</Badge>;
if (column.key === "id") return <code className="text-xs">{task.id}</code>;
if (column.key === "summary") return task.summary ?? task.error ?? "—";
return (task as unknown as Record<string, unknown>)[column.key]?.toString() ?? "—";
}}
/>
</Card>
</>
)}
<Modal isOpen={detail !== null} onClose={() => setDetail(null)} title={t("detailTitle")} size="lg">
{detail && (
<div className="space-y-4 text-sm">
<div className="flex items-center gap-2">
<code className="text-xs">{detail.id}</code>
<Badge variant={statusVariant(detail.status)} dot>{detail.status}</Badge>
<Badge>{detail.mode}</Badge>
{detail.runner && <Badge variant="info">{detail.runner}</Badge>}
</div>
{detail.prompt && (
<div>
<div className="font-medium">{t("prompt")}</div>
<pre className="whitespace-pre-wrap text-xs bg-black/5 dark:bg-white/5 rounded p-2">{detail.prompt}</pre>
</div>
)}
{detail.summary && <p>{detail.summary}</p>}
{detail.error && <Badge variant="error">{detail.error}</Badge>}
{detail.branch && (
<div>
<div className="font-medium">{t("branch")}</div>
<code className="text-xs">{detail.branch}</code>
<p className="text-xs text-text-muted">{t("fetchHint", { branch: detail.branch })}</p>
</div>
)}
{detail.mode.startsWith("council") && detail.council?.candidate_task_ids && (
<div>
<div className="font-medium">{t("council")}</div>
<p className="text-xs">
{t("candidates")}: {detail.council.candidate_task_ids.join(", ")}
</p>
</div>
)}
{!TERMINAL.has(detail.status) && (
<button
type="button"
className="text-sm text-red-600 dark:text-red-400 underline"
onClick={() => setCancelTarget(detail.id)}
>
{t("cancel")}
</button>
)}
</div>
)}
</Modal>
<ConfirmModal
isOpen={cancelTarget !== null}
onClose={() => setCancelTarget(null)}
onConfirm={confirmCancel}
title={t("cancelConfirmTitle")}
message={t("cancelConfirmMessage")}
confirmText={t("cancel")}
loading={canceling}
/>
</div>
);
}

View File

@@ -0,0 +1,10 @@
import ConductorPageClient from "./ConductorPageClient";
export const metadata = {
title: "Conductor — OmniRoute",
description: "OmniConductor CLI-agent fleet: runners, task queue and councils, live.",
};
export default function ConductorPage() {
return <ConductorPageClient />;
}

View File

@@ -0,0 +1,17 @@
/**
* GET /api/conductor/fleet — fleet snapshot for the Conductor dashboard panel
* (PRD Conductor RF3). Server-side proxy: the hub token lives only in env; the
* response is the whitelisted shape from hubProxy (degraded {offline:true} when
* the hub is unset/offline — never a 5xx for that).
*/
import { NextResponse } from "next/server";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { getFleetSnapshot } from "@/lib/conductor/hubProxy";
export async function GET(request: Request) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
return NextResponse.json(await getFleetSnapshot());
}

View File

@@ -0,0 +1,26 @@
/**
* POST /api/conductor/tasks/[id]/cancel — cancela a task no hub do Conductor.
* Ação destrutiva: auth de gerência + confirmação na UI (ConfirmModal). A
* recusa do hub volta só como status + mensagem sanitizada (nunca o corpo
* upstream — Hard Rule #12).
*/
import { NextResponse } from "next/server";
import { createErrorResponse } from "@/lib/api/errorResponse";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { cancelConductorTask } from "@/lib/conductor/hubProxy";
export async function POST(request: Request, ctx: { params: Promise<{ id: string }> }) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
const { id } = await ctx.params;
const result = await cancelConductorTask(id);
if (!result.ok) {
return createErrorResponse({
status: result.status,
message: `Conductor hub refused the cancellation (HTTP ${result.status})`,
});
}
return NextResponse.json({ ok: true });
}

View File

@@ -0,0 +1,22 @@
/**
* GET /api/conductor/tasks/[id] — whitelisted task detail (manifest, prompt,
* council funnel) from the Conductor hub. 404 sanitizado quando o hub não
* conhece a task — o corpo do hub nunca é repassado.
*/
import { NextResponse } from "next/server";
import { createErrorResponse } from "@/lib/api/errorResponse";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { getConductorTaskDetail } from "@/lib/conductor/hubProxy";
export async function GET(request: Request, ctx: { params: Promise<{ id: string }> }) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
const { id } = await ctx.params;
const detail = await getConductorTaskDetail(id);
if (!detail) {
return createErrorResponse({ status: 404, message: "Conductor task not found (or hub offline)" });
}
return NextResponse.json(detail);
}

View File

@@ -1275,6 +1275,8 @@
"groupSeparatorLabel": "Separator",
"discovery": "Discovery",
"discoverySubtitle": "Scan providers for free access",
"conductor": "Conductor",
"conductorSubtitle": "CLI-agent fleet",
"resilienceConnections": "Connection Resilience",
"resilienceConnectionsSubtitle": "Cooldown, breaker, lockout state",
"settingsModalityBridge": "Modality Bridge",
@@ -13521,5 +13523,36 @@
"relayDescription": "Serverless relay proxy endpoints for your AI infrastructure",
"trafficInspectorTitle": "Traffic Inspector — OmniRoute",
"trafficInspectorDescription": "Monitor LLM calls + debug any application's HTTPS traffic"
},
"conductor": {
"title": "Conductor — CLI-agent fleet",
"subtitle": "OmniConductor hub: runners, task queue and councils, live",
"runners": "Runners",
"tasks": "Tasks",
"colName": "Name",
"colClis": "CLIs",
"colStatus": "Status",
"colTask": "Task",
"colMode": "Mode",
"colRunner": "Runner",
"colSummary": "Summary",
"online": "online",
"offline": "offline",
"draining": "draining",
"hubOffline": "Conductor hub offline or not configured (CONDUCTOR_HUB_URL) — showing nothing rather than stale data.",
"noRunners": "No runners registered",
"noTasks": "No tasks yet",
"detailTitle": "Task detail",
"prompt": "Prompt",
"branch": "Branch",
"fetchHint": "Fetch the result with: git fetch origin {branch}",
"council": "Council",
"candidates": "Candidates",
"cancel": "Cancel task",
"cancelConfirmTitle": "Cancel this task?",
"cancelConfirmMessage": "The hub will abort the execution on the runner. This cannot be undone.",
"cancelFailed": "The hub refused the cancellation",
"close": "Close",
"error": "Error"
}
}

View File

@@ -1263,6 +1263,8 @@
"groupSeparatorLabel": "Separador",
"discovery": "Descoberta",
"discoverySubtitle": "Buscar acesso gratuito em provedores",
"conductor": "Conductor",
"conductorSubtitle": "Frota de agentes CLI",
"resilienceConnections": "Resiliência de Conexão",
"resilienceConnectionsSubtitle": "Cooldown, disjuntor, estado de bloqueio",
"settingsModalityBridge": "Ponte de Modalidade",
@@ -13506,6 +13508,37 @@
"relayDescription": "__MISSING__:Serverless relay proxy endpoints for your AI infrastructure",
"trafficInspectorTitle": "__MISSING__:Traffic Inspector — OmniRoute",
"trafficInspectorDescription": "__MISSING__:Monitor LLM calls + debug any application's HTTPS traffic"
},
"conductor": {
"title": "Conductor — frota de agentes CLI",
"subtitle": "Hub OmniConductor: runners, fila de tasks e councils, ao vivo",
"runners": "Runners",
"tasks": "Tasks",
"colName": "Nome",
"colClis": "CLIs",
"colStatus": "Status",
"colTask": "Task",
"colMode": "Modo",
"colRunner": "Runner",
"colSummary": "Resumo",
"online": "online",
"offline": "offline",
"draining": "drenando",
"hubOffline": "Hub do Conductor fora do ar ou não configurado (CONDUCTOR_HUB_URL) — melhor nada do que dado velho.",
"noRunners": "Nenhum runner registrado",
"noTasks": "Nenhuma task ainda",
"detailTitle": "Detalhe da task",
"prompt": "Prompt",
"branch": "Branch",
"fetchHint": "Busque o resultado com: git fetch origin {branch}",
"council": "Council",
"candidates": "Candidatos",
"cancel": "Cancelar task",
"cancelConfirmTitle": "Cancelar esta task?",
"cancelConfirmMessage": "O hub aborta a execução no runner. Não dá para desfazer.",
"cancelFailed": "O hub recusou o cancelamento",
"close": "Fechar",
"error": "Erro"
}
}

View File

@@ -0,0 +1,176 @@
/**
* Server-side proxy to the OmniConductor hub (Conductor PRD RF3).
*
* The browser NEVER talks to the hub: these helpers run only in API routes,
* authenticate with the server-side env token, and return WHITELISTED shapes —
* runner/hub tokens can never leak to the client. Fail-open: hub unset/offline
* yields a degraded snapshot ({offline: true}) instead of an error.
*/
import { z } from "zod";
// ============ Whitelisted client-facing shapes ============
export interface FleetRunner {
id: string;
name: string;
clis: string[];
online: boolean;
draining: boolean;
}
export interface FleetTask {
id: string;
status: string;
mode: string;
repo: string | null;
runner: string | null;
summary: string | null;
branch: string | null;
error: string | null;
updated_at: string | null;
}
export interface FleetSnapshot {
offline: boolean;
runners: FleetRunner[];
tasks: FleetTask[];
}
export interface ConductorTaskDetail extends FleetTask {
prompt: string | null;
base_ref: string | null;
tests: unknown;
council: unknown;
created_at: string | null;
}
// ============ Untrusted hub shapes (parse only what we read) ============
const hubRunnerSchema = z.object({
id: z.string(),
online: z.boolean().optional(),
draining: z.boolean().optional(),
capabilities: z.object({
name: z.string().optional(),
clis: z.array(z.object({ profile: z.string() })).optional(),
}),
});
const hubTaskSchema = z.object({
id: z.string(),
status: z.string(),
mode: z.string().optional(),
repo: z.object({ url: z.string().optional(), base_ref: z.string().optional() }).nullish(),
spec: z.object({ prompt: z.string().optional() }).nullish(),
assigned_runner: z.string().nullish(),
manifest: z
.object({
summary: z.string().nullish(),
branch: z.string().nullish(),
error: z.string().nullish(),
tests: z.unknown().optional(),
})
.nullish(),
council: z.unknown().optional(),
created_at: z.string().optional(),
updated_at: z.string().optional(),
});
export interface HubProxyOptions {
fetchImpl?: typeof fetch;
}
function hubConfig(): { url: string; token: string } | null {
const url = process.env.CONDUCTOR_HUB_URL?.trim();
if (!url) return null;
return { url, token: process.env.CONDUCTOR_HUB_TOKEN?.trim() ?? "" };
}
async function hubGet(path: string, opts: HubProxyOptions): Promise<unknown | null> {
const cfg = hubConfig();
if (!cfg) return null;
const doFetch = opts.fetchImpl ?? fetch;
const res = await doFetch(`${cfg.url}${path}`, {
headers: { authorization: `Bearer ${cfg.token}` },
});
if (!res.ok) return null;
return res.json();
}
function toFleetTask(t: z.infer<typeof hubTaskSchema>): FleetTask {
return {
id: t.id,
status: t.status,
mode: t.mode ?? "solo",
repo: t.repo?.url ?? null,
runner: t.assigned_runner ?? null,
summary: t.manifest?.summary ?? null,
branch: t.manifest?.branch ?? null,
error: t.manifest?.error ?? null,
updated_at: t.updated_at ?? null,
};
}
/** Fleet snapshot for the dashboard panel. Degraded ({offline: true}) on any failure. */
export async function getFleetSnapshot(opts: HubProxyOptions = {}): Promise<FleetSnapshot> {
try {
const [rawRunners, rawTasks] = await Promise.all([
hubGet("/v1/runners", opts),
hubGet("/v1/tasks", opts),
]);
if (rawRunners === null || rawTasks === null) return { offline: true, runners: [], tasks: [] };
const runners = z.array(hubRunnerSchema).parse(rawRunners).map((r) => ({
id: r.id,
name: r.capabilities.name ?? "?",
clis: (r.capabilities.clis ?? []).map((c) => c.profile),
online: r.online !== false,
draining: r.draining === true,
}));
const tasks = z.array(hubTaskSchema).parse(rawTasks).map(toFleetTask);
return { offline: false, runners, tasks };
} catch {
return { offline: true, runners: [], tasks: [] };
}
}
/** Full whitelisted detail of one task (manifest, prompt, council funnel data). */
export async function getConductorTaskDetail(
taskId: string,
opts: HubProxyOptions = {}
): Promise<ConductorTaskDetail | null> {
try {
const raw = await hubGet(`/v1/tasks/${encodeURIComponent(taskId)}`, opts);
if (raw === null) return null;
const t = hubTaskSchema.parse(raw);
return {
...toFleetTask(t),
prompt: t.spec?.prompt ?? null,
base_ref: t.repo?.base_ref ?? null,
tests: t.manifest?.tests ?? null,
council: t.council ?? null,
created_at: t.created_at ?? null,
};
} catch {
return null;
}
}
/** Cancels a task on the hub. Returns the hub's verdict without leaking its body on error. */
export async function cancelConductorTask(
taskId: string,
opts: HubProxyOptions = {}
): Promise<{ ok: boolean; status: number }> {
const cfg = hubConfig();
if (!cfg) return { ok: false, status: 503 };
try {
const doFetch = opts.fetchImpl ?? fetch;
const res = await doFetch(`${cfg.url}/v1/tasks/${encodeURIComponent(taskId)}/cancel`, {
method: "POST",
headers: { authorization: `Bearer ${cfg.token}` },
});
return { ok: res.ok, status: res.status };
} catch {
return { ok: false, status: 503 };
}
}

View File

@@ -243,6 +243,15 @@ const TOOLS_GROUP: SidebarItemGroup = {
subtitleKey: "cloudAgentsSubtitle",
icon: "cloud",
},
{
id: "conductor",
href: "/dashboard/conductor",
i18nKey: "conductor",
subtitleKey: "conductorSubtitle",
icon: "account_tree",
labelFallback: "Conductor",
subtitleFallback: "CLI-agent fleet",
},
{
id: "agent-bridge",
href: "/dashboard/tools/agent-bridge",

View File

@@ -29,6 +29,7 @@ export const HIDEABLE_SIDEBAR_ITEM_IDS = [
"cli-agents",
"acp-agents",
"cloud-agents",
"conductor",
"agent-bridge",
"traffic-inspector",
"discovery",