mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-15 19:32:20 +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>
40 lines
1.6 KiB
TypeScript
40 lines
1.6 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
|
|
// Invariantes de segurança/UX do chat com voz (componentes React: vitest-ui advisory + dashboard-typecheck).
|
|
const CHAT = "src/app/(dashboard)/dashboard/conductor/FaroChat.tsx";
|
|
|
|
function src(): string {
|
|
return fs.readFileSync(path.join(process.cwd(), CHAT), "utf8");
|
|
}
|
|
|
|
test("client fala SÓ com o OmniRoute: /api/conductor/ask + /api/v1/audio/* (nunca Faro/hub direto)", () => {
|
|
const s = src();
|
|
assert.match(s, /"use client"/);
|
|
assert.match(s, /\/api\/conductor\/ask/);
|
|
assert.match(s, /\/api\/v1\/audio\/transcriptions/);
|
|
assert.match(s, /\/api\/v1\/audio\/speech/);
|
|
assert.ok(!s.includes(":7920"), "endereço do Faro nunca no client");
|
|
assert.ok(!s.includes("CONDUCTOR_"), "nenhuma env do Conductor no client");
|
|
});
|
|
|
|
test("pending do Faro → botões Sim/Não que enviam 'sim'/'não' (trava de confirmação é do motor do Faro)", () => {
|
|
const s = src();
|
|
assert.match(s, /pending/);
|
|
assert.match(s, /"sim"/);
|
|
assert.match(s, /"não"/);
|
|
});
|
|
|
|
test("voz: push-to-talk com MediaRecorder/getUserMedia; STT multipart sem Content-Type manual; TTS via Blob com revoke", () => {
|
|
const s = src();
|
|
assert.match(s, /navigator\.mediaDevices\.getUserMedia/);
|
|
assert.match(s, /MediaRecorder/);
|
|
assert.match(s, /FormData\(\)/);
|
|
assert.ok(!/audio\/transcriptions[\s\S]{0,300}content-type/i.test(s), "multipart deixa o browser definir o boundary");
|
|
assert.match(s, /URL\.createObjectURL/);
|
|
assert.match(s, /URL\.revokeObjectURL/);
|
|
assert.match(s, /useTranslations\("conductor"\)/);
|
|
});
|