Aller au contenu

Événements de domaine

Le DomainEventBus est un bus de publication/abonnement par diffusion à l’intérieur du processus. Les éditeurs appellent publish(event); les abonnés consomment un flux typé on<T>(). Chaque événement implémente DomainEvent et porte occurredAt.

Le bus s’exécute à l’intérieur de cc_server. Chaque éditeur et chaque abonné actif y réside. Aucun client ne voit ce bus — le serveur réémet un sous-ensemble choisi sous forme de trames JSON-RPC notifications/*, filtrées par abonné selon l’appartenance à l’espace de travail.

Mesure Nombre
Classes d’événements concrètes 54
Fichiers sources (packages/cc_domain/lib/core/domain/events/) 14 (plus domain_event_bus.dart)
Bases abstraites jamais publiées directement 1 (TaskLifecycleEvent, une hiérarchie scellée)

Chaque événement du catalogue ci-dessous réside dans ce seul répertoire; aucun événement de domaine n’est déclaré ailleurs.

Événements d’espace de travail, d’agent et de dépôt

Section intitulée “Événements d’espace de travail, d’agent et de dépôt”
Événement Charge utile Se déclenche quand
WorkspaceCreated workspaceId L’espace de travail est créé
AgentRunCompleted agentId, runId?, workspaceId?, conversationId? Un agent termine une exécution (réussie ou échouée)
AgentAwaitingInput workspaceId, kind, summary, spaceId?, conversationId?, agentId?, agentName? Un agent commence à attendre une personne : une approbation, une question ask_user ou une exécution bloquée sur des identifiants (kind vaut approval, question ou credential)
RepoAdded repoId, path, workspaceId Un dépôt est enregistré; déclenche l’indexation du code en arrière-plan
SkillUpdated workspaceId, slug, origin, computedHash, scanVerdict? Un skill est installé ou mis à jour, avec le verdict de son analyse de chaîne d’approvisionnement

Le premier bloc est le cycle de vie propre à la PR. Le second est la surveillance des PR dont vous êtes l’auteur : des signaux au sujet d’une PR que vous avez ouverte, chacun portant forUserId afin que le sondage puisse les acheminer vers la personne concernée plutôt que de les diffuser.

Événement Charge utile Se déclenche quand
PullRequestPublished prId, workspaceId, repoOwner, repoName Un agent ouvre une PR
PullRequestStatusChanged status, workspaceId, prId?, repoFullName?, prNumber? La PR est fusionnée/fermée/ouverte/rouverte/approuvée
PrMerged prId, workspaceId, agentId La PR est fusionnée — un signal étroit de fusion seule conservé pour l’analytique et les notifications
PrReviewRequested workspaceId, repoOwner, repoName, prNumber, prTitle GitHub demande la revue de l’utilisateur du serveur (sondage des notifications, review_requested)
PrMentioned workspaceId, repoOwner, repoName, prNumber, prTitle L’utilisateur du serveur est mentionné sur une PR (sondage des notifications, mention)
ExternalPrMerged workspaceId, repoOwner, repoName, prNumber, prTitle, mergedByLogin? Une PR suivie de l’extérieur (hors agent) est fusionnée, détectée par le sondage des notifications de GitHub; correspond à la catégorie de notification prMerged
ExternalPrDetected repoOwner, repoName, prNumber, prTitle, author, workspaceId? Une PR hors agent est trouvée par sondage — réservé aux pipelines (alimente external_pr_welcome), ne lève aucune notification
PrHeadChanged repoOwner, repoName, prNumber, prTitle, previousHeadSha, headSha, workspaceId Le SHA de tête de la PR a changé — l’auteur a poussé
ReviewBecameStale workspaceId, spaceId, repoOwner, repoName, prNumber, prTitle, reviewedHeadSha, headSha Une révision par IA terminée ne décrit plus la PR qu’elle a révisée. Ne se déclenche que lorsqu’une révision existe pour le commit remplacé, ce qui en fait un signal plutôt qu’un ping à chaque poussée

Tous les six portent workspaceId, repoOwner, repoName, prNumber, prTitle et un forUserId facultatif.

Événement Charge utile supplémentaire Se déclenche quand
PrMergeReadinessChanged ready, reason Une PR dont vous êtes l’auteur est devenue fusionnable, ou a cessé de l’être
PrReviewDecisionChanged decision, reviewersRemaining, approverLogin? Un réviseur a approuvé, demandé des changements, ou a vu une approbation rejetée
PrChecksStatusChanged failing, failingCheckName?, failingCheckUrl? La CI est passée au rouge, ou s’est rétablie
PrCommentMentioned commentId, authorLogin, bodyPreview, isReviewComment, threadId?, path?, line? Quelqu’un vous a mentionné dans un commentaire de PR ou de revue
PrThreadReplied commentId, authorLogin, bodyPreview, threadId?, path?, line? Quelqu’un a répondu dans un fil de revue auquel vous participez
PrThreadResolved threadId, commentId?, path?, line? Quelqu’un a résolu un fil de revue auquel vous participez
Événement Charge utile Se déclenche quand
MessageReceived spaceId, messageId, senderName, contentPreview, isAgentMessage, workspaceId?, mentions, requestedByUserId?, senderUserId? Un nouveau message arrive
SpaceCreated spaceId, workspaceId L’espace est créé
SpaceDeleted spaceId, workspaceId L’espace est supprimé
SpaceProvisioningChanged workspaceId, spaceId, status, step? L’espace de travail de l’espace entre en provisionnement, atteint une étape (clonage d’un dépôt, extraction d’une PR, configuration d’un agent) ou bascule vers prêt/échoué — la passerelle de clavardage le raconte sur sa carte de tâche
Événement Charge utile Se déclenche quand
TicketCreated ticketId, workspaceId Le ticket est créé
TicketAssigned ticketId, ticketTitle, ticketBody?, ticketUrl?, assignedAgentId?, assigneeType, assignedTeamId?, workspaceId Le ticket est assigné à un principal ou à une équipe
TicketCompleted ticketId Le travail se termine (terminal)
TicketFailed ticketId, errorMessage L’agent échoue (terminal)
TicketCancelled ticketId Le ticket est annulé (terminal)
TicketStatusChanged ticketId, workspaceId, from, to Tout changement de statut
TicketReassigned ticketId, workspaceId, fromAgentId?, toAgentId? Le ticket est réassigné
TicketDetailsUpdated ticketId, workspaceId Le titre, la description ou la priorité change

Le flux unifié du cycle de vie des tâches — une exécution d’agent lancée, indexée par l’identifiant de son journal d’exécution. Chaque événement porte taskId, un seq monotone par tâche, workspaceId?, agentId? et une phase; les lignes ci-dessous n’énumèrent que les champs supplémentaires.

Événement Charge utile Se déclenche quand
TaskDispatched — La tâche est confiée à un backend pour exécution
TaskRunning — La tâche a commencé à produire de la sortie
TaskWaitingLocalDirectory lockedPath, holderTaskId? La tâche est mise en attente — une autre tâche détient le verrou de chemin sur disque dont elle a besoin
TaskProgress note? Point de contrôle de progression en cours d’exécution
TaskMessage messageType, content Message diffusé en continu (text/thinking/tool_use/tool_result/error); signalé sous la phase progress
TaskCompleted summary? La tâche s’est terminée avec succès
TaskFailed errorMessage La tâche s’est terminée par une erreur
TaskCancelled — La tâche est annulée avant la fin

L’énumération TaskPhase comporte aussi un membre queued, mais rien ne publie d’événement de mise en file — il n’existe pas de classe TaskQueued. La première trame observable d’une tâche est TaskDispatched.

Événement Charge utile Se déclenche quand
PipelineRunCompleted workspaceId, pipelineRunId, templateId L’exécution se termine
PipelineRunCancelled workspaceId, pipelineRunId, templateId L’exécution est annulée (par l’utilisateur ou le système) — un arrêt délibéré, pas une erreur
PipelineRunFailed workspaceId, pipelineRunId, templateId, errorMessage L’exécution échoue

Seules les transitions terminales sont publiées. Il n’existe aucun PipelineRunStarted ni aucun événement par étape : la progression d’une étape parvient aux clients par les lignes de l’exécution elle-même et par le flux de cycle de vie des tâches de l’agent qu’elle a lancé, et non par le bus.

Événement Charge utile Se déclenche quand
ActivityLogged id, actorType, actorId?, action, entityType, entityId?, details?, workspaceId?, runId? Une entrée de la piste d’audit est créée
BudgetThresholdCrossed scopeType, scopeId, spentCents, budgetCents, isHardStop Les dépenses dépassent le seuil
Événement Charge utile Se déclenche quand
WorkspaceMemberAdded workspaceId, userId, role Un utilisateur rejoint un espace de travail (acceptation d’une invitation ou ajout par un administrateur)
WorkspaceMemberRemoved workspaceId, userId Un membre est retiré — les sessions actives de cet utilisateur rattachées à l’espace de travail doivent revérifier l’accès immédiatement
WorkspaceMemberRoleChanged workspaceId, userId, role Le rôle d’un membre change

L’appartenance est toute la surface d’identité sur le bus. La création d’utilisateur et la révocation d’appareil ont lieu, mais elles ne sont pas publiées comme événements de domaine — la révocation est appliquée directement aux sessions actives plutôt que d’être acheminée par le bus.

Événement Charge utile Se déclenche quand
CalendarAuthExpired workspaceId, accountEmail Le jeton de rafraîchissement OAuth d’un compte connecté est définitivement invalide (publié une seule fois par épisode de déconnexion)
MeetingStartingSoon workspaceId, eventId, title, startTime, meetingUrl? Un événement du calendrier commence dans la fenêtre d’anticipation configurée
MeetingRecordingStopped workspaceId, meetingId, title, userNotes, transcript, summaryInstructions? L’enregistrement d’une réunion se termine et déclenche le pipeline intégré meeting_summary
Événement Charge utile Se déclenche quand
RigClosedEvent workspaceId, rigId, reason Un rig a disparu, avec sa raison de fermeture; les consommateurs n’agissent que sur backendFailure — toute autre raison est demandée ou accompagnée d’un RigReaped
RigControlChanged workspaceId, rigId, controller? Un humain a pris le contrôle exclusif d’un rig ou l’a rendu (controller vaut null à la remise — l’événement ne consigne pas qui a lâché)
RigReaped workspaceId, rigId, reason, agentId? Le système a récupéré une machine : inactivité, TTL ou éviction pour pression mémoire (signalée comme idleTimeout), avec l’agent qui la pilotait

L’ouverture d’un rig n’est pas un événement. L’amorçage se termine et la ligne du rig bascule vers prêt; rien n’est publié, il n’y a donc aucun RigOpened auquel s’abonner.

Plusieurs sous-systèmes qui, de l’extérieur, semblent pilotés par les événements ne le sont pas. Ils figurent ici parce que leur absence est une décision de conception qu’il vaut la peine de connaître avant d’aller chercher un point d’accroche :

Sous-domaine Comment l’état progresse à la place
Mémoire record_memory_fact et ses acolytes écrivent et retournent; le rappel lit. Rien ne publie sur un fait ou un conflit
Orchestration La proposition, son approbation et l’exécution de pipeline générée sont des lignes et des appels RPC
Documents de plan submit_plan écrit une révision; l’approbation matérialise une orchestration. Ni l’un ni l’autre n’est annoncé
Artéfacts publish_artifact / revise_artifact écrivent les révisions directement
Approbations L’escalade est traitée à l’intérieur du service de flux d’approbation
Synchronisation des tickets Le coordinateur de fournisseurs s’abonne aux événements de ticket ci-dessus; les webhooks entrants sont appliqués directement
Actualisation du calendrier Une synchronisation insère ou met à jour les événements et le client les relit; il n’y a aucune diffusion d’actualisation
Fusions d’arbres de travail Consignées dans worktree_merge_log et lues à partir de là

Chaque abonné ci-dessous est construit à l’intérieur de runCcServer (ou d’un service qu’il construit). Il n’existe aucun abonné côté client.

Abonné Événements qu’il consomme
WorkspaceSeeder WorkspaceCreated
Amorçage des suites d’évaluation de départ (en ligne) WorkspaceCreated
Provisionnement de l’espace de clavardage (en ligne) SpaceCreated
PipelineTriggerDispatcher Tout DomainEvent; chaque déclencheur activé filtre selon son propre type
SubPipelineResumeListener PipelineRunCompleted, PipelineRunFailed
PipelineCostRollupListener AgentRunCompleted
BudgetEvaluationListener AgentRunCompleted
MeetingSummaryReconciler PipelineRunCompleted, PipelineRunFailed, PipelineRunCancelled (uniquement les exécutions meeting_summary)
PipelineStepResumeListener AgentRunCompleted
AgentRunTaskCompleter AgentRunCompleted
GoalSupervisor AgentRunCompleted
CheckerDispatchListener AgentRunCompleted
TeamRoutingService AgentRunCompleted, TicketAssigned
OrchestrationRunListener PipelineRunCancelled, PipelineRunFailed
MultiVendorTicketSyncCoordinator TicketCreated, TicketAssigned, TicketReassigned, TicketStatusChanged, TicketDetailsUpdated
WorktreeGcListener TicketCompleted, TicketCancelled, SpaceDeleted, PrMerged, PullRequestStatusChanged
ChatBridgeService MessageReceived, SpaceProvisioningChanged
ActivityLogPersister ActivityLogged
RigEventListener RigControlChanged, RigReaped, RigClosedEvent — injecte un avis en langage clair dans la voie de pilotage de l’agent qui pilote
StaleReviewWatcher PrHeadChanged — ne publie ReviewBecameStale que lorsqu’une révision terminée existe pour le commit remplacé
GitHubPrConversationBridge AgentRunCompleted — réponses sortantes de conversation de PR
NotificationFeedRecorder Les vingt-deux événements de classe notification ci-dessous
RemoteEventForwarder Ces vingt-deux, plus TicketReassigned et chaque TaskLifecycleEvent. Les trois événements d’appartenance ne font qu’invalider son cache d’appartenance par espace de travail; ils ne sont pas réémis sous forme de trames
LocalRpcServer / RemoteRelayHost WorkspaceMemberRemoved — abandonne les abonnements d’espace de travail rattachés à cet utilisateur; la socket reste ouverte

TicketAssigned est un signal d’audit et de notification, et un déclencheur de pipeline. L’assignation à un agent consigne la propriété et ne démarre aucune exécution — il n’existe pas de répartiteur de tickets. L’assignation à une équipe (assignedTeamId défini) est l’exception : TeamRoutingService lance le chef d’équipe.

TicketRemoteSyncHandler est déclaré mais jamais construit dans le code de production, il ne s’abonne donc à rien aujourd’hui. Le NotificationEventMapper côté client n’est construit que dans son propre test — c’est le vestige du chemin intra-processus d’avant la séparation client/serveur. Les toasts en direct proviennent des trames ci-dessous.

Sur le serveur, NotificationFeedRecorder et RemoteEventForwarder transforment ces événements en trames notifications/* (notification_wire.dart). Sur le client, RpcNotificationMapper → mapNotificationFrame transforme une trame en AppNotification.

Il y a exactement dix-sept catégories de notification. Plusieurs événements correspondent à plus d’une méthode de trame, parce que la trame porte le changement (échoué ou rétabli) tandis que la catégorie porte la question (« que fait la CI ? »).

Catégorie Événement source Méthode de trame
newMessage MessageReceived notifications/message_received (vous mentionne)
agentRunCompleted MessageReceived notifications/message_received (is_agent_message, sans mention de vous)
agentAwaitingInput AgentAwaitingInput notifications/agent_awaiting_input
ticketAssigned TicketAssigned notifications/ticket_assigned
ticketStatusChanged TicketStatusChanged notifications/ticket_status_changed
pullRequestPublished PullRequestPublished notifications/pr_published
prMerged PrMerged notifications/pr_merged
prMerged ExternalPrMerged notifications/external_pr_merged
prMentioned PrMentioned notifications/pr_mentioned
prMentioned PrCommentMentioned notifications/pr_comment_mentioned
reviewRequested PrReviewRequested notifications/pr_review_requested
reviewStale ReviewBecameStale notifications/review_stale
prMergeReadiness PrMergeReadinessChanged notifications/pr_ready_to_merge / pr_merge_blocked
prReviewDecision PrReviewDecisionChanged notifications/pr_approved / pr_changes_requested / pr_review_dismissed
prChecksStatus PrChecksStatusChanged notifications/pr_checks_failed / pr_checks_recovered
prThreadActivity PrThreadReplied notifications/pr_thread_replied
prThreadActivity PrThreadResolved notifications/pr_thread_resolved
meetingStartsSoon MeetingStartingSoon notifications/meeting_starting_soon
calendarAuthExpired CalendarAuthExpired notifications/calendar_auth_expired
rigStatusChanged RigControlChanged notifications/rig_control_changed
rigStatusChanged RigReaped notifications/rig_reaped
rigStatusChanged RigClosedEvent notifications/rig_closed

La trame de prise de contrôle d’un rig n’est pas livrée à la personne qui a pris le volant, une remise n’est pas filtrée (l’événement ne consigne pas qui a lâché), et seule une fermeture backendFailure produit une trame rig_closed — les autres raisons sont soit demandées, soit déjà couvertes par la trame de récupération.

AgentAwaitingInput est publié par AgentAwaitingInputPublisher, qui surveille l’apparition de nouvelles entrées dans les registres des approbations et des identifiants bloqués, et que AgentQuestionService prévient de chaque question ask_user. Une attente produit un seul événement, et rien n’est publié lorsqu’elle prend fin.

Les cinq catégories de PR dont vous êtes l’auteur portent forUserId, de sorte qu’elles atteignent l’auteur plutôt que tout le monde dans l’espace de travail.

RemoteEventForwarder réémet en plus TicketReassigned sous notifications/ticket_reassigned et chaque TaskLifecycleEvent sous notifications/task_<phase>. Ni l’un ni l’autre ne correspond à une catégorie de notification — ils pilotent l’interface en direct, pas le centre de notifications. Les événements d’appartenance ne sont pas réémis sous forme de trames : ils invalident le cache du réémetteur, et les hôtes de session abandonnent les abonnements d’espace de travail du membre retiré.