Compare commits

...
1 Commits
Author SHA1 Message Date
LogiFlowandClaude d1b12c16c5 fix: portail laissant passer /health mais bloquant /v1 traité comme liaison en échec (v2.4.0.3) (#18)
- Une page web reçue sur /v1/capabilities fait échouer la connexion et le test
  de liaison même si /health répond, ce qui déclenche la recherche de l'API.
- La découverte sonde /v1/capabilities puis /v1/models (JSON exigé, 401/403
  accepté) avant /health, pour ne pas prendre un portail pour l'API.


Claude-Session: https://claude.ai/code/session_017Wn5VX9HNbJ7N54hR24u9Y

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-04 08:22:19 +02:00
5 changed files with 26 additions and 10 deletions

No files matched your search

+1
View File
@@ -77,6 +77,7 @@ Sources : [jarvis-desktop-ai](https://github.com/ccarloshenri/jarvis-desktop-ai)
| Silero VAD sur phrase Kokoro de 2,3 s (2.3.0) | un seul segment, début et fin détectés, 6,7 s d'audio traités en 190 ms |
| Capture d'écran → Hermes (2.3.0) | JPEG de 107 ko reçu côté Hermes (mock chat completions) |
| Intention locale « coupe le son » (2.3.0) | traitée sans Hermes, résultat affiché dans le fil |
| Correctif 2.4.0.3 | un portail qui laisse passer /health mais renvoie une page web sur /v1 est traité comme une liaison en échec et déclenche la recherche de l'API (sondes /v1/capabilities, /v1/models) |
| Correctif 2.4.0.2 | une page web (portail de connexion) reçue à la place de l'API Hermes est reconnue et expliquée ; l'API est recherchée automatiquement sur le même hôte (port 8642, /api, hermes.…) et l'URL corrigée |
| Correctifs 2.4.0.1 | réponse vide en chat completions désormais expliquée (JSON non streamé ou erreur HTTP 200), la liaison ne passe plus en « dégradé » quand seule l'API des crons échoue, transcriptions parasites (« (cliquant) », « *Claire* ») ignorées, préférence de voix masculine/féminine |
| Serveur MCP (2.4.0) | initialize, tools/list (14 outils), tools/call côté principal (presse-papiers, capture image) et côté renderer (état, message dans le fil) |
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "eveflow",
"version": "2.4.0",
"releaseVersion": "2.4.0.2",
"releaseVersion": "2.4.0.3",
"description": "JARVIS-style desktop HUD for Hermes Agent: voice, streaming runs, scheduled jobs, skills and telemetry",
"main": "dist-electron/main.js",
"private": true,
@@ -108,6 +108,7 @@ export function SettingsDrawer({ onClose }: Props) {
caps = ` · modèle ${String(c.model ?? '?')} · ${Object.entries(c.features ?? {}).filter(([, v]) => v === true).length} fonctions`;
} catch (err) {
const message = (err as Error).message;
if (/page web/.test(message)) throw err;
caps = /404/.test(message)
? ' · /v1/capabilities absent (Hermes ancien : transport chat completions)'
: ` · capabilities : ${message}`;
+21 -9
View File
@@ -72,18 +72,30 @@ export function hermesUrlCandidates(url: string): string[] {
return [...out];
}
/** Probe candidate URLs until one answers Hermes JSON on /health or /v1/capabilities. */
/** True when this base URL answers like the Hermes API (JSON on /v1/*, or 401/403 = key required). */
export async function probeHermesUrl(config: HermesConfig, url: string): Promise<boolean> {
const client = new HermesClient({ ...config, url });
for (const path of ['/v1/capabilities', '/v1/models', '/health']) {
try {
const payload = await client.request<unknown>(path, { timeoutMs: 4000 });
if (payload && typeof payload === 'object') return true;
} catch (err) {
if (err instanceof HttpError) {
if (err.status === 401 || err.status === 403) return true; // the API is there, only the key is missing
if (/page web/.test(err.message)) return false; // a portal answers on this host: not the API
if (err.status === 404) continue; // older Hermes without this endpoint
}
return false;
}
}
return false;
}
/** Probe candidate URLs until one answers like the Hermes API. */
export async function discoverHermesUrl(config: HermesConfig, onProgress?: (url: string) => void): Promise<string | null> {
for (const candidate of hermesUrlCandidates(config.url)) {
onProgress?.(candidate);
const client = new HermesClient({ ...config, url: candidate });
try {
await client.request<unknown>('/health', { timeoutMs: 4000 });
return candidate;
} catch (err) {
if (err instanceof HttpError && (err.status === 401 || err.status === 403)) return candidate; // API found, key missing
continue;
}
if (await probeHermesUrl(config, candidate)) return candidate;
}
return null;
}
+2
View File
@@ -122,6 +122,8 @@ export const useHermes = create<HermesStore>((set, get) => ({
try {
capabilities = await client.capabilities();
} catch (err) {
// A web page on /v1/* means the whole API is behind a portal even if /health passed through.
if (/page web/.test((err as Error).message)) throw err;
Log.warn('hermes', `capabilities unavailable: ${(err as Error).message}`);
}
const transport = resolveTransport(config, capabilities);