feat: serveur MCP, heures calmes, mode mission, widget glanceable, barge-in (v2.4.0) (#14)

Serveur MCP local
- Endpoint /mcp (JSON-RPC 2.0, Streamable HTTP, réponses JSON) sur le serveur
  webhook : initialize, tools/list, tools/call, ping. Même secret que le
  webhook. 14 outils : capture_screen (image MCP), lock_session, open_app,
  open_url, media_key, clipboard_get/set, find_files, speak_text,
  notify_user, set_hud_state, get_app_status, get_conversation_history,
  show_message. Les outils UI transitent par IPC vers le renderer.
- Les mêmes actions système sont proposées au modèle en chat completions.

Notifications
- Heures calmes (plage horaire, franchissement de minuit), mots prioritaires,
  échecs de crons toujours lus, résumé vocal des messages entrants (n premières
  phrases), thème nuit automatique, compteur de non-lus.

Conversation
- Mode mission : bouton dans la barre de commande, second modèle Hermes
  (hermes.missionModel) pour les tâches longues.
- Barge-in : la parole de l'utilisateur coupe la voix ; seuil d'énergie et
  ratio relevés pendant la synthèse pour ignorer l'écho.

Widget compact
- Bandeau glanceable : état, dernière phrase, non-lus, indicateurs heures
  calmes et mission.

Tests : 28 tests unitaires (heures calmes, priorité, résumé), e2e Electron
sous Xvfb : MCP initialize/tools/list/tools/call (presse-papiers, capture en
image, état et message via le renderer), bandeau compact, aucune erreur.


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

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
LogiFlowandClaude authored and GitHub committed 2026-09-04 00:47:34 +02:00
1 parent af8bdc5e9d
commit 6fc38d335b
24 files changed
+668 -31

No files matched your search

+42
View File
@@ -3,16 +3,35 @@
* With the runs/sessions transports Hermes uses its own server-side toolsets instead.
*/
import { bridge } from '../../lib/bridge';
import type { SystemAction } from '../../../shared/bridge';
export interface LocalToolContext {
setEmotion: (emotion: string) => void;
showMessage?: (text: string, title?: string) => void;
speak: (text: string) => void;
getStatus: () => Record<string, unknown>;
getHistory: (n: number) => Array<{ role: string; content: string }>;
notify: (title: string, body: string) => void;
}
const fn = (name: string, description: string, properties: Record<string, unknown> = {}, required: string[] = []) => ({
type: 'function',
function: { name, description, parameters: { type: 'object', properties, required } }
});
/** Tools executed on the user's PC through the Electron main process (allow-listed). */
export const SYSTEM_TOOL_DEFINITIONS = [
fn('lock_session', "Verrouille la session de l'utilisateur."),
fn('open_app', "Lance une application du PC de l'utilisateur (bloc-notes, calculatrice, chrome, spotify, vscode, terminal, explorateur…).", { name: { type: 'string' } }, ['name']),
fn('open_url', "Ouvre une URL http(s) dans le navigateur de l'utilisateur.", { url: { type: 'string' } }, ['url']),
fn('media_key', 'Touche média : volume-up, volume-down, mute, play-pause, next, previous.', { key: { type: 'string', enum: ['volume-up', 'volume-down', 'mute', 'play-pause', 'next', 'previous'] } }, ['key']),
fn('clipboard_get', 'Lit le texte du presse-papiers.'),
fn('clipboard_set', 'Place un texte dans le presse-papiers.', { text: { type: 'string' } }, ['text']),
fn('find_files', "Cherche des fichiers par nom dans Documents, Bureau, Téléchargements et Images (25 max).", { query: { type: 'string' } }, ['query'])
];
export const LOCAL_TOOL_DEFINITIONS = [
...SYSTEM_TOOL_DEFINITIONS,
{
type: 'function',
function: {
@@ -95,6 +114,29 @@ export async function executeLocalTool(name: string, rawArgs: string, ctx: Local
case 'notify_user':
ctx.notify(String(args.title ?? 'EveFlow'), String(args.body ?? ''));
return JSON.stringify({ ok: true });
case 'show_message':
ctx.showMessage?.(String(args.text ?? ''), typeof args.title === 'string' ? args.title : undefined);
return JSON.stringify({ ok: true });
case 'lock_session':
case 'open_app':
case 'open_url':
case 'media_key':
case 'clipboard_get':
case 'clipboard_set':
case 'find_files': {
const api = bridge();
if (!api) return JSON.stringify({ error: 'system actions unavailable outside Electron' });
const action: SystemAction =
name === 'lock_session' ? { type: 'lock' }
: name === 'open_app' ? { type: 'open-app', name: String(args.name ?? '') }
: name === 'open_url' ? { type: 'open-url', url: String(args.url ?? '') }
: name === 'media_key' ? { type: 'media', key: String(args.key ?? '') as 'mute' }
: name === 'clipboard_get' ? { type: 'clipboard-read' }
: name === 'clipboard_set' ? { type: 'clipboard-write', text: String(args.text ?? '') }
: { type: 'find-files', query: String(args.query ?? '') };
const result = await api.system.action(action);
return JSON.stringify(result.ok ? { ok: true, message: result.message, data: result.data } : { error: result.message ?? 'failed' });
}
case 'write_shared_file': {
const api = bridge();
if (!api) return JSON.stringify({ error: 'file system unavailable outside Electron' });
+2
View File
@@ -10,6 +10,8 @@ export interface HermesConfig {
reasoningEffort: '' | 'low' | 'medium' | 'high';
/** Extra instructions layered on top of the Hermes system prompt. */
instructions: string;
/** Model used in "mission" mode (long tasks); empty = same as `model`. */
missionModel: string;
/** Expose EveFlow client tools in chat-completions mode. */
localTools: boolean;
}