Zum Inhalt springen

Domain-Ereignisse

DomainEventBus ist ein prozessinterner Broadcast-Bus nach dem Publish/Subscribe-Muster. Publisher rufen publish(event) auf; Subscriber konsumieren einen typisierten Strom on<T>(). Jedes Ereignis implementiert DomainEvent und trägt occurredAt.

Der Bus läuft in cc_server. Jeder Publisher und jeder lebende Subscriber lebt dort. Kein Client sieht diesen Bus. Der Server leitet eine kuratierte Teilmenge als JSON-RPC-Frames notifications/* weiter, je Subscriber nach der Mitgliedschaft im Arbeitsbereich gefiltert.

Kennzahl Anzahl
Konkrete Ereignisklassen 54
Quelldateien (packages/cc_domain/lib/core/domain/events/) 14 (plus domain_event_bus.dart)
Abstrakte Basen, die nie direkt veröffentlicht werden 1 (TaskLifecycleEvent, eine sealed Hierarchie)

Jedes Ereignis im Katalog unten lebt in genau diesem Verzeichnis; nirgendwo sonst sind Domain-Ereignisse deklariert.

Ereignis Payload Auslöser
WorkspaceCreated workspaceId Ein Arbeitsbereich wird angelegt
AgentRunCompleted agentId, runId?, workspaceId?, conversationId? Ein Agent beendet einen Lauf (Erfolg oder Fehler)
AgentAwaitingInput workspaceId, kind, summary, spaceId?, conversationId?, agentId?, agentName? Ein Agent beginnt, auf einen Menschen zu warten: auf eine Freigabe, auf eine Antwort auf eine ask_user-Frage oder mit einem Lauf, der an Zugangsdaten festhängt (kind ist approval, question oder credential)
RepoAdded repoId, path, workspaceId Ein Repo wird registriert; stößt die Code-Indizierung im Hintergrund an
SkillUpdated workspaceId, slug, origin, computedHash, scanVerdict? Ein Skill wird installiert oder aktualisiert und trägt das Urteil seines Supply-Chain-Scans

Der erste Block ist der eigene Lebenszyklus des PR. Der zweite ist die Beobachtung eigener PRs: Signale zu einem PR, den du geöffnet hast. Jedes trägt forUserId, damit die Abfrage sie der betroffenen Person zustellt, statt sie an alle zu senden.

Ereignis Payload Auslöser
PullRequestPublished prId, workspaceId, repoOwner, repoName Ein Agent öffnet einen PR
PullRequestStatusChanged status, workspaceId, prId?, repoFullName?, prNumber? PR gemergt, geschlossen, geöffnet, wieder geöffnet oder freigegeben
PrMerged prId, workspaceId, agentId Der PR ist gemergt. Ein schmales Signal nur für den Merge, das für Analysen und Benachrichtigungen bleibt
PrReviewRequested workspaceId, repoOwner, repoName, prNumber, prTitle GitHub bittet um das Review des Server-Benutzers (Abfrage der Benachrichtigungen, review_requested)
PrMentioned workspaceId, repoOwner, repoName, prNumber, prTitle Der Server-Benutzer wird in einem PR erwähnt (Abfrage der Benachrichtigungen, mention)
ExternalPrMerged workspaceId, repoOwner, repoName, prNumber, prTitle, mergedByLogin? Ein extern verfolgter PR (kein Agent) wurde gemergt, erkannt durch die Abfrage der GitHub-Benachrichtigungen; wird auf die Benachrichtigungskategorie prMerged abgebildet
ExternalPrDetected repoOwner, repoName, prNumber, prTitle, author, workspaceId? PR ohne Agent per Abfrage gefunden. Nur für Pipelines (treibt external_pr_welcome an), löst keine Benachrichtigung aus
PrHeadChanged repoOwner, repoName, prNumber, prTitle, previousHeadSha, headSha, workspaceId Die Head-SHA des PR hat sich bewegt. Der Autor hat gepusht
ReviewBecameStale workspaceId, spaceId, repoOwner, repoName, prNumber, prTitle, reviewedHeadSha, headSha Ein abgeschlossenes KI-Review beschreibt den geprüften PR nicht mehr. Es feuert nur, wenn für den ersetzten Commit ein Review existiert, und bleibt so ein Signal statt eines Pings bei jedem Push

Alle sechs tragen workspaceId, repoOwner, repoName, prNumber, prTitle und ein optionales forUserId.

Ereignis Zusätzlicher Payload Auslöser
PrMergeReadinessChanged ready, reason Ein PR, den du verfasst hast, wurde mergebar oder war es nicht mehr
PrReviewDecisionChanged decision, reviewersRemaining, approverLogin? Ein Reviewer hat freigegeben, Änderungen angefordert, oder eine Freigabe wurde verworfen
PrChecksStatusChanged failing, failingCheckName?, failingCheckUrl? Die CI wurde rot oder hat sich erholt
PrCommentMentioned commentId, authorLogin, bodyPreview, isReviewComment, threadId?, path?, line? Jemand hat dich in einem PR-Kommentar oder Review-Kommentar erwähnt
PrThreadReplied commentId, authorLogin, bodyPreview, threadId?, path?, line? Jemand hat in einem Review-Thread geantwortet, in dem du bist
PrThreadResolved threadId, commentId?, path?, line? Jemand hat einen Review-Thread aufgelöst, in dem du bist
Ereignis Payload Auslöser
MessageReceived spaceId, messageId, senderName, contentPreview, isAgentMessage, workspaceId?, mentions, requestedByUserId?, senderUserId? Eine neue Nachricht trifft ein
SpaceCreated spaceId, workspaceId Ein Bereich wird angelegt
SpaceDeleted spaceId, workspaceId Ein Bereich wird gelöscht
SpaceProvisioningChanged workspaceId, spaceId, status, step? Der Arbeitsbereich des Bereichs geht in die Bereitstellung, erreicht einen Schritt (ein Repo klonen, einen PR auschecken, einen Agenten einrichten) oder wechselt auf bereit bzw. fehlgeschlagen. Die Chat-Brücke erzählt das auf ihrer Aufgabenkarte
Ereignis Payload Auslöser
TicketCreated ticketId, workspaceId Ein Ticket wird angelegt
TicketAssigned ticketId, ticketTitle, ticketBody?, ticketUrl?, assignedAgentId?, assigneeType, assignedTeamId?, workspaceId Ticket einem Prinzipal oder einem Team zugewiesen
TicketCompleted ticketId Die Arbeit endet (Endzustand)
TicketFailed ticketId, errorMessage Der Agent schlägt fehl (Endzustand)
TicketCancelled ticketId Ticket abgebrochen (Endzustand)
TicketStatusChanged ticketId, workspaceId, from, to Jede Statusänderung
TicketReassigned ticketId, workspaceId, fromAgentId?, toAgentId? Ticket neu zugewiesen
TicketDetailsUpdated ticketId, workspaceId Titel, Beschreibung oder Priorität ändert sich

Der einheitliche Strom zum Aufgaben-Lebenszyklus: ein ausgeführter Agentenlauf, über die Id seines Laufprotokolls identifiziert. Jedes Ereignis trägt taskId, eine monotone seq je Aufgabe, workspaceId?, agentId? und eine phase. Die Zeilen unten nennen nur die zusätzlichen Felder.

Ereignis Payload Auslöser
TaskDispatched — Aufgabe zur Ausführung an ein Backend übergeben
TaskRunning — Aufgabe hat begonnen, Ausgabe zu erzeugen
TaskWaitingLocalDirectory lockedPath, holderTaskId? Aufgabe geparkt. Eine andere Aufgabe hält die Pfad-Sperre auf der Platte, die sie braucht
TaskProgress note? Fortschritts-Checkpoint mitten im Lauf
TaskMessage messageType, content Gestreamte Nachricht (text/thinking/tool_use/tool_result/error); gemeldet unter der Phase progress
TaskCompleted summary? Aufgabe erfolgreich beendet
TaskFailed errorMessage Aufgabe mit einem Fehler beendet
TaskCancelled — Aufgabe vor dem Abschluss abgebrochen

Das Enum TaskPhase hat auch den Wert queued, veröffentlicht aber niemand ein Ereignis dafür. Eine Klasse TaskQueued gibt es nicht. Der erste beobachtbare Frame einer Aufgabe ist TaskDispatched.

Ereignis Payload Auslöser
PipelineRunCompleted workspaceId, pipelineRunId, templateId Der Lauf endet
PipelineRunCancelled workspaceId, pipelineRunId, templateId Lauf abgebrochen (durch dich oder das System). Ein bewusster Stopp, kein Fehler
PipelineRunFailed workspaceId, pipelineRunId, templateId, errorMessage Der Lauf schlägt fehl

Veröffentlicht werden nur Übergänge in einen Endzustand. PipelineRunStarted gibt es nicht, und auch kein Ereignis je Schritt: Der Fortschritt eines Schritts erreicht die Clients über die eigenen Zeilen des Laufs und über den Strom zum Aufgaben-Lebenszyklus des Agenten, den der Schritt gestartet hat, nicht über den Bus.

Ereignis Payload Auslöser
ActivityLogged id, actorType, actorId?, action, entityType, entityId?, details?, workspaceId?, runId? Eintrag im Audit-Protokoll angelegt
BudgetThresholdCrossed scopeType, scopeId, spentCents, budgetCents, isHardStop Die Ausgaben überschreiten die Schwelle
Ereignis Payload Auslöser
WorkspaceMemberAdded workspaceId, userId, role Ein Benutzer tritt einem Arbeitsbereich bei (Einlösung einer Einladung oder Hinzufügen durch einen Admin)
WorkspaceMemberRemoved workspaceId, userId Ein Mitglied wird entfernt. Lebende Sitzungen dieses Benutzers, die auf den Arbeitsbereich beschränkt sind, müssen den Zugriff sofort neu prüfen
WorkspaceMemberRoleChanged workspaceId, userId, role Die Rolle eines Mitglieds ändert sich

Die Mitgliedschaft ist auf dem Bus die gesamte Identitätsfläche. Benutzer werden angelegt und Geräte widerrufen, als Domain-Ereignisse wird das aber nicht veröffentlicht. Der Widerruf wird direkt an den lebenden Sitzungen durchgesetzt, statt über den Bus geleitet zu werden.

Ereignis Payload Auslöser
CalendarAuthExpired workspaceId, accountEmail Das OAuth-Refresh-Token eines verbundenen Kontos ist dauerhaft ungültig (einmal je Trennung veröffentlicht)
MeetingStartingSoon workspaceId, eventId, title, startTime, meetingUrl? Ein Kalendertermin beginnt innerhalb des konfigurierten Vorlauffensers
MeetingRecordingStopped workspaceId, meetingId, title, userNotes, transcript, summaryInstructions? Eine Besprechungsaufnahme endet und stößt die eingebaute Pipeline meeting_summary an
Ereignis Payload Auslöser
RigClosedEvent workspaceId, rigId, reason Ein Rig ist weg, mit seinem Schließgrund. Konsumenten handeln nur bei backendFailure. Jeder andere Grund ist angefordert oder von einem RigReaped begleitet
RigControlChanged workspaceId, rigId, controller? Ein Mensch hat die exklusive Steuerung eines Rigs übernommen oder zurückgegeben (controller ist bei der Rückgabe null. Das Ereignis hält nicht fest, wer losgelassen hat)
RigReaped workspaceId, rigId, reason, agentId? Das System hat eine Maschine zurückgeholt: Leerlauf, TTL oder Verdrängung unter Speicherdruck (gemeldet als idleTimeout), zusammen mit dem antreibenden Agenten

Das Öffnen eines Rigs ist kein Ereignis. Der Boot endet, und die Zeile des Rigs wechselt auf bereit. Veröffentlicht wird nichts, ein RigOpened zum Abonnieren gibt es also nicht.

Mehrere Subsysteme sehen von außen ereignisgetrieben aus und sind es nicht. Sie stehen hier, weil ihr Fehlen eine Entwurfsentscheidung ist, die du kennen solltest, bevor du nach einem Hook suchst:

Subdomäne Wie der Zustand stattdessen weitergeht
Gedächtnis record_memory_fact und Verwandte schreiben und geben zurück; der Abruf liest. Weder auf einem Fakt noch bei einem Konflikt wird etwas veröffentlicht
Orchestrierung Der Vorschlag, seine Freigabe und der erzeugte Pipeline-Lauf sind Zeilen und RPC-Aufrufe
Plandokumente submit_plan schreibt eine Revision; die Freigabe materialisiert eine Orchestrierung. Angekündigt wird beides nicht
Artefakte publish_artifact / revise_artifact schreiben Revisionen direkt
Freigaben Die Eskalation läuft im Dienst des Freigabe-Workflows
Ticket-Synchronisierung Der Anbieter-Koordinator abonniert die Ticket-Ereignisse oben; eingehende Webhooks werden direkt angewandt
Kalenderaktualisierung Eine Synchronisierung schreibt die Termine per Upsert, der Client liest neu. Eine Broadcast-Aktualisierung gibt es nicht
Worktree-Merges In worktree_merge_log festgehalten und von dort gelesen

Jeder Abonnent unten wird in runCcServer erzeugt (oder in einem Dienst, den diese Funktion baut). Clientseitig abonniert niemand.

Abonnent Ereignisse, die er entgegennimmt
WorkspaceSeeder WorkspaceCreated
Anlegen der Eval-Suiten für den Einstieg (inline) WorkspaceCreated
Bereitstellung des Chat-Bereichs (inline) SpaceCreated
PipelineTriggerDispatcher Jedes DomainEvent; jeder aktivierte Trigger filtert auf seinen eigenen Typ
SubPipelineResumeListener PipelineRunCompleted, PipelineRunFailed
PipelineCostRollupListener AgentRunCompleted
BudgetEvaluationListener AgentRunCompleted
MeetingSummaryReconciler PipelineRunCompleted, PipelineRunFailed, PipelineRunCancelled (nur Läufe von 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. Schleust einen Hinweis in Klartext in den Steuerkanal des antreibenden Agenten
StaleReviewWatcher PrHeadChanged. Veröffentlicht ReviewBecameStale nur, wenn für den ersetzten Commit ein abgeschlossenes Review existiert
GitHubPrConversationBridge AgentRunCompleted. Ausgehende Antworten der PR-Unterhaltung
NotificationFeedRecorder Die 22 Ereignisse der Benachrichtigungsklasse unten
RemoteEventForwarder Diese 22 plus TicketReassigned und jedes TaskLifecycleEvent. Die drei Mitgliedschaftsereignisse machen nur seinen Mitgliedschafts-Cache je Arbeitsbereich ungültig; als Frames werden sie nicht weitergeleitet
LocalRpcServer / RemoteRelayHost WorkspaceMemberRemoved. Verwirft die angehängten Arbeitsbereichsabonnements dieses Benutzers; der Socket bleibt offen

TicketAssigned ist ein Signal für Audit und Benachrichtigung und ein Pipeline-Trigger. Die Zuweisung an einen Agenten vermerkt die Zuständigkeit und startet keinen Lauf. Einen Ticket-Dispatcher gibt es nicht. Die Ausnahme ist die Zuweisung an ein Team (gesetztes assignedTeamId): TeamRoutingService startet die Teamleitung.

TicketRemoteSyncHandler ist deklariert und wird im Produktionscode nie erzeugt, also abonniert er heute nichts. Der clientseitige NotificationEventMapper wird nur in seinem eigenen Test erzeugt. Er ist der Rest des prozessinternen Pfads von vor der Aufteilung in Client und Server. Lebende Toasts kommen aus den Wire-Frames unten.

Auf dem Server machen NotificationFeedRecorder und RemoteEventForwarder aus diesen Ereignissen Wire-Frames notifications/* (notification_wire.dart). Auf dem Client macht RpcNotificationMapper → mapNotificationFrame aus einem Frame eine AppNotification.

Es gibt genau siebzehn Benachrichtigungskategorien. Mehrere Ereignisse bilden auf mehr als eine Wire-Methode ab, weil der Frame die Kante trägt (fehlgeschlagen gegenüber erholt) und die Kategorie die Frage („was macht die CI?“).

Kategorie Quellereignis Wire-Methode
newMessage MessageReceived notifications/message_received (erwähnt dich)
agentRunCompleted MessageReceived notifications/message_received (is_agent_message, ohne Erwähnung von dir)
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

Der Frame zur Rig-Übernahme geht nicht an die Person zurück, die das Steuer übernommen hat. Eine Rückgabe ist ungefiltert (das Ereignis hält nicht fest, wer losgelassen hat), und nur ein Schließen mit backendFailure erzeugt einen Frame rig_closed. Die anderen Gründe sind angefordert oder schon vom Frame zum Zurückholen abgedeckt.

AgentAwaitingInput wird von AgentAwaitingInputPublisher veröffentlicht. Er beobachtet die Register für Freigaben und für blockierte Zugangsdaten auf neue Einträge, und AgentQuestionService meldet ihm jede ask_user-Frage. Eine Wartezeit ergibt ein Ereignis; wenn sie endet, wird nichts veröffentlicht.

Die fünf Kategorien zur Beobachtung eigener PRs tragen forUserId und erreichen damit den Autor, nicht jeden im Arbeitsbereich.

RemoteEventForwarder leitet zusätzlich TicketReassigned als notifications/ticket_reassigned weiter und jedes TaskLifecycleEvent als notifications/task_<phase>. Auf eine Benachrichtigungskategorie bildet keines davon ab. Sie treiben die Live-Oberfläche, nicht die Benachrichtigungszentrale. Mitgliedschaftsereignisse werden nicht als Frames weitergeleitet: Sie machen den Cache des Forwarders ungültig, und die Sitzungs-Hosts verwerfen die Arbeitsbereichsabonnements des entfernten Mitglieds.