Przejdź do głównej zawartości

Zdarzenia domenowe

DomainEventBus to działająca w obrębie procesu szyna publikowania i subskrypcji z rozgłaszaniem. Wydawcy wywołują publish(event); subskrybenci konsumują typowany strumień on<T>(). Każde zdarzenie implementuje DomainEvent i niesie occurredAt.

Szyna działa wewnątrz cc_server. Każdy wydawca i każdy żywy subskrybent mieszka tam. Żaden klient nie widzi tej szyny — serwer przekazuje wyselekcjonowany podzbiór jako ramki JSON-RPC notifications/*, filtrowany dla każdego subskrybenta według członkostwa w obszarze roboczym.

Miara Liczba
Konkretne klasy zdarzeń 54
Pliki źródłowe (packages/cc_domain/lib/core/domain/events/) 14 (plus domain_event_bus.dart)
Abstrakcyjne klasy bazowe, które nigdy nie są publikowane bezpośrednio 1 (TaskLifecycleEvent, zapieczętowana hierarchia)

Każde zdarzenie z poniższego katalogu mieszka w tym jednym katalogu; żadne zdarzenia domenowe nie są zadeklarowane gdzie indziej.

Zdarzenie Ładunek Wywoływane, gdy
WorkspaceCreated workspaceId Tworzony jest obszar roboczy
AgentRunCompleted agentId, runId?, workspaceId?, conversationId? Agent kończy uruchomienie (sukces lub porażka)
AgentAwaitingInput workspaceId, kind, summary, spaceId?, conversationId?, agentId?, agentName? Agent zaczyna czekać na człowieka: na zgodę, na odpowiedź na pytanie ask_user albo z uruchomieniem wstrzymanym na danych logowania (kind to approval, question lub credential)
RepoAdded repoId, path, workspaceId Rejestrowane jest repozytorium; uruchamia indeksowanie kodu w tle
SkillUpdated workspaceId, slug, origin, computedHash, scanVerdict? Skill jest instalowany lub aktualizowany, niosąc werdykt skanowania łańcucha dostaw

Pierwszy blok to własny cykl życia PR-a. Drugi to obserwacja autorskich PR-ów: sygnały o PR-ze, który Ty otworzyłeś, z których każdy niesie forUserId, dzięki czemu odpytywanie może kierować je do osoby, której dotyczą, zamiast rozgłaszać je wszystkim.

Zdarzenie Ładunek Wywoływane, gdy
PullRequestPublished prId, workspaceId, repoOwner, repoName Agent otwiera PR
PullRequestStatusChanged status, workspaceId, prId?, repoFullName?, prNumber? PR scalony/zamknięty/otwarty/ponownie otwarty/zatwierdzony
PrMerged prId, workspaceId, agentId PR zostaje scalony — wąski sygnał wyłącznie o scaleniu, zachowany na potrzeby analityki i powiadomień
PrReviewRequested workspaceId, repoOwner, repoName, prNumber, prTitle GitHub prosi o recenzję od użytkownika serwera (odpytywanie powiadomień, review_requested)
PrMentioned workspaceId, repoOwner, repoName, prNumber, prTitle Użytkownik serwera zostaje wspomniany w PR-ze (odpytywanie powiadomień, mention)
ExternalPrMerged workspaceId, repoOwner, repoName, prNumber, prTitle, mergedByLogin? Scalono śledzony zewnętrznie (nieagentowy) PR, wykryty przez odpytywanie powiadomień GitHuba; mapuje się na kategorię powiadomień prMerged
ExternalPrDetected repoOwner, repoName, prNumber, prTitle, author, workspaceId? Nieagentowy PR znaleziony przez odpytywanie — wyłącznie dla potoków (napędza external_pr_welcome), nie zgłasza powiadomienia
PrHeadChanged repoOwner, repoName, prNumber, prTitle, previousHeadSha, headSha, workspaceId Head SHA PR-a się przesunął — autor wypchnął zmiany
ReviewBecameStale workspaceId, spaceId, repoOwner, repoName, prNumber, prTitle, reviewedHeadSha, headSha Zakończona recenzja AI nie opisuje już PR-a, który recenzowała. Wywoływane tylko wtedy, gdy istnieje recenzja zastąpionego commita, więc pozostaje sygnałem, a nie pingiem przy każdym pushu

Wszystkie sześć niosą workspaceId, repoOwner, repoName, prNumber, prTitle oraz opcjonalny forUserId.

Zdarzenie Dodatkowy ładunek Wywoływane, gdy
PrMergeReadinessChanged ready, reason PR, którego jesteś autorem, stał się możliwy do scalenia albo przestał być
PrReviewDecisionChanged decision, reviewersRemaining, approverLogin? Recenzent zatwierdził, zażądał zmian albo jego zatwierdzenie zostało odrzucone
PrChecksStatusChanged failing, failingCheckName?, failingCheckUrl? CI się zaczerwieniło albo znów zadziałało
PrCommentMentioned commentId, authorLogin, bodyPreview, isReviewComment, threadId?, path?, line? Ktoś wspomniał o Tobie w komentarzu do PR-a albo komentarzu recenzji
PrThreadReplied commentId, authorLogin, bodyPreview, threadId?, path?, line? Ktoś odpowiedział w wątku recenzji, w którym uczestniczysz
PrThreadResolved threadId, commentId?, path?, line? Ktoś rozwiązał wątek recenzji, w którym uczestniczysz
Zdarzenie Ładunek Wywoływane, gdy
MessageReceived spaceId, messageId, senderName, contentPreview, isAgentMessage, workspaceId?, mentions, requestedByUserId?, senderUserId? Przychodzi nowa wiadomość
SpaceCreated spaceId, workspaceId Tworzona jest przestrzeń
SpaceDeleted spaceId, workspaceId Przestrzeń zostaje usunięta
SpaceProvisioningChanged workspaceId, spaceId, status, step? Obszar roboczy przestrzeni wchodzi w provisionowanie, osiąga krok (klonowanie repozytorium, checkout PR-a, konfigurowanie agenta) albo przechodzi w gotowość/awarię — most czatu relacjonuje to na swojej karcie zadania
Zdarzenie Ładunek Wywoływane, gdy
TicketCreated ticketId, workspaceId Zgłoszenie zostaje utworzone
TicketAssigned ticketId, ticketTitle, ticketBody?, ticketUrl?, assignedAgentId?, assigneeType, assignedTeamId?, workspaceId Zgłoszenie przypisane do podmiotu lub zespołu
TicketCompleted ticketId Praca się kończy (stan terminalny)
TicketFailed ticketId, errorMessage Agent zawodzi (stan terminalny)
TicketCancelled ticketId Zgłoszenie anulowane (stan terminalny)
TicketStatusChanged ticketId, workspaceId, from, to Każda zmiana statusu
TicketReassigned ticketId, workspaceId, fromAgentId?, toAgentId? Zgłoszenie przypisane ponownie
TicketDetailsUpdated ticketId, workspaceId Zmiana tytułu, opisu lub priorytetu

Ujednolicony strumień cyklu życia zadania — jedno wysłane uruchomienie agenta, kluczowane identyfikatorem dziennika uruchomienia. Każde zdarzenie niesie taskId, monotoniczny dla danego zadania seq, workspaceId?, agentId? oraz phase; poniższe wiersze wymieniają tylko dodatkowe pola.

Zdarzenie Ładunek Wywoływane, gdy
TaskDispatched — Zadanie przekazane do backendu w celu wykonania
TaskRunning — Zadanie zaczęło produkować wynik
TaskWaitingLocalDirectory lockedPath, holderTaskId? Zadanie zaparkowane — inne zadanie jest właścicielem blokady ścieżki na dysku, której potrzebuje
TaskProgress note? Punkt kontrolny postępu w trakcie uruchomienia
TaskMessage messageType, content Strumieniowana wiadomość (text/thinking/tool_use/tool_result/error); raportowana w fazie progress
TaskCompleted summary? Zadanie zakończone pomyślnie
TaskFailed errorMessage Zadanie zakończone błędem
TaskCancelled — Zadanie anulowane przed ukończeniem

Enum TaskPhase ma także składową queued, ale nic nie publikuje zdarzenia zakolejkowania — nie istnieje klasa TaskQueued. Pierwszą obserwowalną ramką zadania jest TaskDispatched.

Zdarzenie Ładunek Wywoływane, gdy
PipelineRunCompleted workspaceId, pipelineRunId, templateId Uruchomienie się kończy
PipelineRunCancelled workspaceId, pipelineRunId, templateId Uruchomienie anulowane (przez użytkownika lub system) — celowe zatrzymanie, a nie błąd
PipelineRunFailed workspaceId, pipelineRunId, templateId, errorMessage Uruchomienie zawodzi

Publikowane są tylko przejścia terminalne. Nie ma PipelineRunStarted ani zdarzenia na krok: postęp kroku dociera do klientów przez własne wiersze uruchomienia i strumień cyklu życia zadania tego agenta, którego krok wysłał, a nie przez szynę.

Zdarzenie Ładunek Wywoływane, gdy
ActivityLogged id, actorType, actorId?, action, entityType, entityId?, details?, workspaceId?, runId? Utworzono wpis dziennika audytu
BudgetThresholdCrossed scopeType, scopeId, spentCents, budgetCents, isHardStop Wydatki przekraczają próg
Zdarzenie Ładunek Wywoływane, gdy
WorkspaceMemberAdded workspaceId, userId, role Użytkownik dołącza do obszaru roboczego (wykorzystanie zaproszenia albo dodanie przez administratora)
WorkspaceMemberRemoved workspaceId, userId Członek zostaje usunięty — aktywne sesje tego użytkownika ograniczone do obszaru roboczego muszą natychmiast ponownie sprawdzić dostęp
WorkspaceMemberRoleChanged workspaceId, userId, role Zmienia się rola członka

Członkostwo to cała powierzchnia tożsamości na szynie. Utworzenie użytkownika i unieważnienie urządzenia mają miejsce, ale nie są publikowane jako zdarzenia domenowe — unieważnienie jest egzekwowane bezpośrednio wobec aktywnych sesji, a nie kierowane przez szynę.

Zdarzenie Ładunek Wywoływane, gdy
CalendarAuthExpired workspaceId, accountEmail Token odświeżania OAuth połączonego konta jest trwale nieprawidłowy (publikowane raz na epizod rozłączenia)
MeetingStartingSoon workspaceId, eventId, title, startTime, meetingUrl? Wydarzenie kalendarza zaczyna się w skonfigurowanym oknie wyprzedzenia
MeetingRecordingStopped workspaceId, meetingId, title, userNotes, transcript, summaryInstructions? Nagranie spotkania się kończy i wyzwala wbudowany potok meeting_summary
Zdarzenie Ładunek Wywoływane, gdy
RigClosedEvent workspaceId, rigId, reason Rig zniknął, wraz z powodem zamknięcia; konsumenci działają tylko na backendFailure — każdy inny powód wynika z żądania albo towarzyszy mu RigReaped
RigControlChanged workspaceId, rigId, controller? Człowiek przejął wyłączną kontrolę nad rigiem albo ją oddał (controller ma wartość null przy zwolnieniu — zdarzenie nie zapisuje, kto oddał kontrolę)
RigReaped workspaceId, rigId, reason, agentId? System odzyskał maszynę: bezczynność, TTL albo usunięcie z powodu nacisku na pamięć (raportowane jako idleTimeout), wraz z agentem sterującym

Otwarcie riga nie jest zdarzeniem. Rozruch się kończy, a wiersz riga przechodzi w stan gotowości; nic nie jest publikowane, więc nie ma RigOpened, którego można by subskrybować.

Kilka podsystemów, które z zewnątrz wyglądają na sterowane zdarzeniami, takie nie jest. Wymieniamy je tutaj, bo ich brak to decyzja projektowa, którą warto znać, zanim zaczniesz szukać haka:

Podobszar Jak zamiast tego zmienia się stan
Pamięć record_memory_fact i pokrewne zapisują i zwracają; przywoływanie czyta. Nic nie publikuje przy fakcie ani konflikcie
Orkiestracja Propozycja, jej zatwierdzenie i wygenerowane uruchomienie potoku to wiersze i wywołania RPC
Dokumenty planu submit_plan zapisuje wersję; zatwierdzenie materializuje orkiestrację. Żadne z nich nie jest ogłaszane
Artefakty publish_artifact / revise_artifact zapisują wersje bezpośrednio
Zatwierdzenia Eskalacja jest obsługiwana wewnątrz usługi przepływu zatwierdzeń
Synchronizacja zgłoszeń Koordynator dostawców subskrybuje powyższe zdarzenia zgłoszeń; przychodzące webhooki są stosowane bezpośrednio
Odświeżanie kalendarza Synchronizacja wykonuje upsert wydarzeń, a klient czyta je ponownie; nie ma rozgłaszania odświeżenia
Scalanie worktree Zapisywane w worktree_merge_log i stamtąd odczytywane

Każdy z poniższych subskrybentów jest konstruowany wewnątrz runCcServer (albo usługi, którą on buduje). Nie ma subskrybenta po stronie klienta.

Subskrybent Zdarzenia, które konsumuje
WorkspaceSeeder WorkspaceCreated
Zasiewanie startowego zestawu ewaluacyjnego (wbudowane) WorkspaceCreated
Provisionowanie przestrzeni czatu (wbudowane) SpaceCreated
PipelineTriggerDispatcher Każde DomainEvent; każdy włączony wyzwalacz filtruje według własnego typu
SubPipelineResumeListener PipelineRunCompleted, PipelineRunFailed
PipelineCostRollupListener AgentRunCompleted
BudgetEvaluationListener AgentRunCompleted
MeetingSummaryReconciler PipelineRunCompleted, PipelineRunFailed, PipelineRunCancelled (tylko uruchomienia 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 — wstrzykuje powiadomienie sformułowane prostym językiem na tor sterowania agenta sterującego
StaleReviewWatcher PrHeadChanged — publikuje ReviewBecameStale tylko wtedy, gdy istnieje zakończona recenzja zastąpionego commita
GitHubPrConversationBridge AgentRunCompleted — odpowiedzi wychodzące w rozmowach PR-ów
NotificationFeedRecorder Dwadzieścia dwie klasy zdarzeń powiadomień wymienione poniżej
RemoteEventForwarder Tamte dwadzieścia dwie plus TicketReassigned i każde TaskLifecycleEvent. Trzy zdarzenia członkostwa jedynie unieważniają jego pamięć podręczną członkostwa dla danego obszaru roboczego; nie są przekazywane jako ramki
LocalRpcServer / RemoteRelayHost WorkspaceMemberRemoved — usuwa subskrypcje obszarów roboczych dołączone przez tego użytkownika; gniazdo pozostaje otwarte

TicketAssigned to sygnał audytowy i powiadomienie oraz wyzwalacz potoku. Przypisanie do agenta zapisuje własność i nie rozpoczyna żadnego uruchomienia — nie istnieje dyspozytor zgłoszeń. Wyjątkiem jest przypisanie do zespołu (ustawione assignedTeamId): TeamRoutingService wysyła lidera zespołu.

TicketRemoteSyncHandler jest zadeklarowany, ale nigdy nie jest konstruowany w kodzie produkcyjnym, więc dziś nie subskrybuje niczego. NotificationEventMapper po stronie klienta jest konstruowany tylko we własnym teście — to pozostałość ścieżki wewnątrzprocesowej sprzed podziału na klienta i serwer. Powiadomienia typu toast na żywo pochodzą z poniższych ramek transmisyjnych.

Na serwerze NotificationFeedRecorder i RemoteEventForwarder zamieniają te zdarzenia na ramki transmisyjne notifications/* (notification_wire.dart). Na kliencie RpcNotificationMapper → mapNotificationFrame zamienia ramkę na AppNotification.

Kategorii powiadomień jest dokładnie siedemnaście. Kilka zdarzeń mapuje się na więcej niż jedną metodę transmisyjną, bo ramka niesie samo przejście (niepowodzenie kontra powrót do działania), a kategoria niesie pytanie („co robi CI?”).

Kategoria Zdarzenie źródłowe Metoda transmisyjna
newMessage MessageReceived notifications/message_received (wspomina o Tobie)
agentRunCompleted MessageReceived notifications/message_received (is_agent_message, bez wzmianki o Tobie)
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

Ramka przejęcia riga nie jest dostarczana z powrotem do tego, kto przejął ster, zwolnienie jest niefiltrowane (zdarzenie nie zapisuje, kto oddał kontrolę), a tylko zamknięcie typu backendFailure produkuje ramkę rig_closed — pozostałe powody są albo wymuszone żądaniem, albo już objęte ramką odzyskania.

AgentAwaitingInput publikuje AgentAwaitingInputPublisher, który obserwuje rejestry zgód i blokad danych logowania pod kątem nowych wpisów, a o każdym pytaniu ask_user powiadamia go AgentQuestionService. Jedno oczekiwanie to jedno zdarzenie; gdy oczekiwanie się kończy, nic nie jest publikowane.

Pięć kategorii autorskich PR-ów niesie forUserId, więc docierają do autora, a nie do wszystkich w obszarze roboczym.

RemoteEventForwarder przekazuje dodatkowo TicketReassigned jako notifications/ticket_reassigned, a każde TaskLifecycleEvent jako notifications/task_<phase>. Żadne z nich nie mapuje się na kategorię powiadomień — napędzają interfejs na żywo, a nie centrum powiadomień. Zdarzenia członkostwa nie są przekazywane jako ramki: unieważniają pamięć podręczną przekaźnika, a hosty sesji porzucają subskrypcje obszaru roboczego usuniętego członka.