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,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);
}