Architecture

Couches & dépendances

Architecture en couches classique .NET avec un projet dédié à l'intégration CLI externe.

Stack technique

Framework
.NET 10 / C# 13
UI
Blazor Server + SignalR
Base de données
SQLite (EF Core 10)
Auth
ASP.NET Identity
IA
CLI Claude Code (sub-process)
Email
Brevo API + Webhooks
Style
Tailwind CSS 4 (CLI)
Déploiement
Docker + nginx + Let's Encrypt
CI/CD
GitHub Actions

Vue couches

graph TD subgraph Web["ElynavCommand.Web — Blazor Server"] Pages[Pages Razor] Hubs[SignalR Hubs
MeetingHub, ConversationHub] Program[Program.cs
DI + middleware] end subgraph Core["ElynavCommand.Core — Business"] MeetingSvc[MeetingService] ConvSvc[ConversationService] ReportSvc[ReportService] RunnerMgr[ConversationRunnerManager] TurnMgr[TurnManager] HostedSvcs[HostedServices
MeetingActivation, DailyReport, RoleSeeder] Brevo[BrevoClient + EventProcessor] Sourcing[ProspectCsvImporter] end subgraph Bridge["ElynavCommand.AgentBridge"] Runner[ClaudeCliRunner] Registry[AgentRegistry] Events[AgentEvent records] end subgraph Data["ElynavCommand.Data — Persistence"] Ctx[ApplicationDbContext] AppEnt[App entities
Meeting, Conversation, AgentDailyReport] CrmEnt[CRM entities
Account, Contact, Opportunity, ...] Ident[Identity
ApplicationUser] end Pages --> MeetingSvc Pages --> ConvSvc Pages --> ReportSvc Pages --> Registry Hubs --> RunnerMgr Hubs --> TurnMgr MeetingSvc --> Ctx ConvSvc --> Ctx ReportSvc --> Ctx RunnerMgr --> Runner TurnMgr --> Runner HostedSvcs --> Ctx HostedSvcs --> Runner Brevo --> Ctx Ctx --> AppEnt Ctx --> CrmEnt Ctx --> Ident

Services principaux — diagramme de classes

classDiagram class IConversationService { <> +CreateAsync(title, agentNames, userId) Task~Conversation~ +GetAsync(id) Task~Conversation?~ +AppendUserMessageAsync(id, text, userId) +AppendAgentMessageAsync(id, agent, content) +ToggleAgentAsync(id, agent, active) } class IMeetingService { <> +ScheduleAsync(title, datetime, agents, userId) Task~Meeting~ +ActivateAsync(meetingId) Task +EndMeetingAsync(meetingId) Task +AppendMessageAsync(...) } class IReportService { <> +GenerateAllForTodayAsync() Task +GenerateForAgentAsync(agentName) Task~AgentDailyReport~ +ListAsync(date) Task~IEnumerable~AgentDailyReport~~ } class IConversationRunnerManager { <> +GetOrCreateAsync(convId, agent) Task~IClaudeCliRunner~ +StopAllForConversationAsync(convId) Task +ReadEventsAsync(convId, agent) IAsyncEnumerable~AgentEvent~ } class ITurnManager { <> +GiveFloorAsync(meetingId, agent) Task +EndTurnAsync(meetingId) Task } class IAgentRegistry { <> +All IReadOnlyList~AgentDefinition~ +Get(name) AgentDefinition? } class IClaudeCliRunner { <> +AgentName string +StartAsync(ct) Task +SendUserMessageAsync(text, ct) Task +ReadEventsAsync(ct) IAsyncEnumerable~AgentEvent~ +StopAsync() Task } class AgentDefinition { +Name string +DisplayName string +Description string +Color string +PermissionMode string? } IConversationRunnerManager --> IClaudeCliRunner : crée ITurnManager --> IClaudeCliRunner : crée IAgentRegistry --> AgentDefinition : expose

Rôle de chaque projet

Point d'entrée

ElynavCommand.Web

Host Blazor Server. Configure DI, middleware ASP.NET, Identity, hubs SignalR (/hubs/meeting, /hubs/conversation), endpoint webhook Brevo, et l'ensemble des Pages Razor.

Fichiers clés : Program.cs, Components/Pages/*, Hubs/*
Business

ElynavCommand.Core

Toute la logique métier réutilisable. Services CRUD (Meeting, Conversation, Report), HostedServices pour les tâches background (MeetingActivationHostedService qui passe Scheduled → Live à l'heure dite, DailyReportHostedService au cron 20h, RoleSeederHostedService au boot), intégration Brevo, sourcing CSV.

Sous-dossiers : Meetings/, Conversations/, Reports/, Brevo/, Sourcing/, Identity/, Tooling/
Persistance

ElynavCommand.Data

EF Core 10 sur SQLite. ApplicationDbContext unique qui mappe tables Identity (AspNet*, owned by EF migrations), tables App (meetings, conversations, agent_daily_reports, owned by EF migrations), et tables CRM existantes (accounts, contacts, etc., excluded from migrations — schéma géré par Salesforce/crm/schema.sql).

Sous-dossiers : App/ (entities App), Crm/ (entities CRM), Identity/, Migrations/
Pont IA

ElynavCommand.AgentBridge

ClaudeCliRunner spawn le binaire claude en sous-processus avec --print --output-format stream-json --input-format stream-json, écrit les messages user en JSON sur stdin, lit les events JSON sur stdout, parse en AgentEvent typés (TextDelta, ToolUse, ToolResult, AgentStarted, TurnEnded, Error). AgentRegistry expose la liste des agents lue depuis appsettings.json.

Fichiers clés : ClaudeCliRunner.cs, Agents/IAgentRegistry.cs, Agents/ElynavOptions.cs, AgentEvent.cs

Cycle de vie d'un service injecté (DI)

Service Lifetime Raison
IAgentRegistrySingletonConf statique lue au boot
IClaudeCliRunnerFactorySingletonFactory stateless, instancie des Runners scoped
ITurnManagerSingletonÉtat partagé des meetings live entre clients SignalR
IConversationRunnerManagerSingletonCache de runners actifs par (convId, agent)
IMeetingService / IConversationService / IReportServiceScopedLiés au DbContext scoped d'EF Core
ApplicationDbContextScopedStandard EF Core (per-request)
EmailTemplateServiceSingletonTemplates Markdig stateless