mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-15 03:32:21 +03:00
* 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
* feat(dashboard): /api/conductor/ask — server-side proxy to Faro (spokesperson) with whitelisted {text,pending}
* chore(env): CONDUCTOR_SPOKESPERSON_URL declared in schema, .env.example and ENVIRONMENT.md
* feat(dashboard): Faro chat with push-to-talk voice on the Conductor panel
---------
Co-authored-by: backryun <bakryun0718@proton.me>
47 lines
1.5 KiB
TypeScript
47 lines
1.5 KiB
TypeScript
/**
|
|
* Server-side proxy to Faro, the OmniConductor spokesperson (Conductor PRD RF4).
|
|
*
|
|
* Faro's `/ask` requires a valid hub credential (Bearer) — that token lives only
|
|
* in server env, so the browser talks to our /api/conductor/ask route, never to
|
|
* Faro directly. The response is whitelisted to {text, pending}: `pending` set
|
|
* means Faro is asking for confirmation (the UI offers Sim/Não); the safety gate
|
|
* itself lives in Faro's engine and is never bypassed here.
|
|
*/
|
|
|
|
import { z } from "zod";
|
|
|
|
const faroResponseSchema = z.object({
|
|
text: z.string(),
|
|
pending: z.unknown().nullish(),
|
|
});
|
|
|
|
export interface FaroAnswer {
|
|
ok: boolean;
|
|
text: string;
|
|
pending: unknown;
|
|
}
|
|
|
|
export interface FaroProxyOptions {
|
|
fetchImpl?: typeof fetch;
|
|
}
|
|
|
|
const DEFAULT_FARO_URL = "http://127.0.0.1:7920";
|
|
|
|
export async function askFaro(message: string, opts: FaroProxyOptions = {}): Promise<FaroAnswer> {
|
|
const base = process.env.CONDUCTOR_SPOKESPERSON_URL?.trim() || DEFAULT_FARO_URL;
|
|
const token = process.env.CONDUCTOR_HUB_TOKEN?.trim() ?? "";
|
|
try {
|
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
const res = await doFetch(`${base}/ask`, {
|
|
method: "POST",
|
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
body: JSON.stringify({ message }),
|
|
});
|
|
if (!res.ok) return { ok: false, text: "", pending: null };
|
|
const parsed = faroResponseSchema.parse(await res.json());
|
|
return { ok: true, text: parsed.text, pending: parsed.pending ?? null };
|
|
} catch {
|
|
return { ok: false, text: "", pending: null };
|
|
}
|
|
}
|