Sari la conținut

Evenimente de domeniu

DomainEventBus este un bus de publicare/abonare de tip broadcast, în proces. Publicatorii apelează publish(event); abonații consumă un flux tipizat on<T>(). Fiecare eveniment implementează DomainEvent și poartă occurredAt.

Bus-ul rulează în interiorul cc_server. Fiecare publicator și fiecare abonat activ se află acolo. Niciun client nu vede acest bus — serverul retransmite un subset selectat ca și cadre JSON-RPC notifications/*, filtrate per abonat după apartenența la spațiul de lucru.

Măsură Număr
Clase concrete de evenimente 54
Fișiere sursă (packages/cc_domain/lib/core/domain/events/) 14 (plus domain_event_bus.dart)
Baze abstracte care nu sunt publicate niciodată direct 1 (TaskLifecycleEvent, o ierarhie sigilată)

Fiecare eveniment din catalogul de mai jos se află în acel singur director; nu există evenimente de domeniu declarate în altă parte.

Eveniment Payload Se declanșează când
WorkspaceCreated workspaceId Spațiul de lucru este creat
AgentRunCompleted agentId, runId?, workspaceId?, conversationId? Agentul încheie o rulare (cu succes sau cu eroare)
AgentAwaitingInput workspaceId, kind, summary, spaceId?, conversationId?, agentId?, agentName? Un agent începe să aștepte un om: o aprobare, o întrebare ask_user sau o rulare oprită din cauza credențialelor (kind este approval, question sau credential)
RepoAdded repoId, path, workspaceId Un repo este înregistrat; declanșează indexarea codului în fundal
SkillUpdated workspaceId, slug, origin, computedHash, scanVerdict? Un skill este instalat sau actualizat, purtând verdictul scanării lanțului de aprovizionare

Primul bloc este chiar ciclul de viață al PR-ului. Al doilea este urmărirea PR-urilor proprii: semnale despre un PR pe care l-ai deschis tu, fiecare purtând forUserId, astfel încât interogarea periodică să le direcționeze către persoana vizată, în loc să le difuzeze tuturor.

Eveniment Payload Se declanșează când
PullRequestPublished prId, workspaceId, repoOwner, repoName Agentul deschide un PR
PullRequestStatusChanged status, workspaceId, prId?, repoFullName?, prNumber? PR îmbinat/închis/deschis/redeschis/aprobat
PrMerged prId, workspaceId, agentId PR-ul este îmbinat — un semnal îngust, doar de îmbinare, păstrat pentru analitice/notificări
PrReviewRequested workspaceId, repoOwner, repoName, prNumber, prTitle GitHub cere revizuirea din partea utilizatorului serverului (interogare periodică a notificărilor, review_requested)
PrMentioned workspaceId, repoOwner, repoName, prNumber, prTitle Utilizatorul serverului este menționat într-un PR (interogare periodică a notificărilor, mention)
ExternalPrMerged workspaceId, repoOwner, repoName, prNumber, prTitle, mergedByLogin? Un PR urmărit extern (care nu vine de la un agent) a fost îmbinat, detectat de interogarea periodică a notificărilor GitHub; se mapează la categoria de notificări prMerged
ExternalPrDetected repoOwner, repoName, prNumber, prTitle, author, workspaceId? PR care nu vine de la un agent, găsit prin interogare periodică — doar pentru pipeline-uri (conduce external_pr_welcome), nu generează nicio notificare
PrHeadChanged repoOwner, repoName, prNumber, prTitle, previousHeadSha, headSha, workspaceId SHA-ul de vârf al PR-ului s-a schimbat — autorul a făcut push
ReviewBecameStale workspaceId, spaceId, repoOwner, repoName, prNumber, prTitle, reviewedHeadSha, headSha O revizuire IA finalizată nu mai descrie PR-ul pe care l-a revizuit. Se declanșează doar când există o revizuire pentru commitul înlocuit, așa că rămâne un semnal, nu un ping la fiecare push

Toate șase poartă workspaceId, repoOwner, repoName, prNumber, prTitle și un forUserId opțional.

Eveniment Payload suplimentar Se declanșează când
PrMergeReadinessChanged ready, reason Un PR pe care l-ai creat tu a devenit îmbinabil sau a încetat să mai fie
PrReviewDecisionChanged decision, reviewersRemaining, approverLogin? Un revizor a aprobat, a cerut modificări sau i-a fost respinsă o aprobare
PrChecksStatusChanged failing, failingCheckName?, failingCheckUrl? CI-ul a devenit roșu sau și-a revenit
PrCommentMentioned commentId, authorLogin, bodyPreview, isReviewComment, threadId?, path?, line? Cineva te-a menționat într-un comentariu de PR sau într-un comentariu de revizuire
PrThreadReplied commentId, authorLogin, bodyPreview, threadId?, path?, line? Cineva a răspuns într-un fir de revizuire în care te afli
PrThreadResolved threadId, commentId?, path?, line? Cineva a marcat ca rezolvat un fir de revizuire în care te afli
Eveniment Payload Se declanșează când
MessageReceived spaceId, messageId, senderName, contentPreview, isAgentMessage, workspaceId?, mentions, requestedByUserId?, senderUserId? Sosește un mesaj nou
SpaceCreated spaceId, workspaceId Spațiul este creat
SpaceDeleted spaceId, workspaceId Spațiul este șters
SpaceProvisioningChanged workspaceId, spaceId, status, step? Spațiul de lucru al spațiului intră în provisionare, ajunge la un pas (clonarea unui repo, checkout la un PR, configurarea unui agent) sau trece în ready/failed — puntea de chat îl povestește pe cardul său de sarcină
Eveniment Payload Se declanșează când
TicketCreated ticketId, workspaceId Tichetul este creat
TicketAssigned ticketId, ticketTitle, ticketBody?, ticketUrl?, assignedAgentId?, assigneeType, assignedTeamId?, workspaceId Tichetul este atribuit unui principal sau unei echipe
TicketCompleted ticketId Munca se încheie (stare finală)
TicketFailed ticketId, errorMessage Agentul eșuează (stare finală)
TicketCancelled ticketId Tichetul este anulat (stare finală)
TicketStatusChanged ticketId, workspaceId, from, to Orice schimbare de stare
TicketReassigned ticketId, workspaceId, fromAgentId?, toAgentId? Tichetul este reatribuit
TicketDetailsUpdated ticketId, workspaceId Titlul, descrierea sau prioritatea se schimbă

Fluxul unificat al ciclului de viață al sarcinilor — o singură rulare de agent trimisă, identificată prin id-ul jurnalului ei de rulare. Fiecare eveniment poartă taskId, un seq monoton per sarcină, workspaceId?, agentId? și o phase; rândurile de mai jos enumeră doar câmpurile suplimentare.

Eveniment Payload Se declanșează când
TaskDispatched — Sarcina este predată unui backend pentru execuție
TaskRunning — Sarcina a început să producă ieșire
TaskWaitingLocalDirectory lockedPath, holderTaskId? Sarcina este parcată — o altă sarcină deține blocajul de cale de pe disc de care are nevoie
TaskProgress note? Punct de control de progres în timpul rulării
TaskMessage messageType, content Mesaj transmis în flux (text/thinking/tool_use/tool_result/error); raportat în faza progress
TaskCompleted summary? Sarcina s-a încheiat cu succes
TaskFailed errorMessage Sarcina s-a încheiat cu o eroare
TaskCancelled — Sarcina este anulată înainte de finalizare

Enumul TaskPhase are și un membru queued, dar nimic nu publică un eveniment de coadă — nu există clasa TaskQueued. Primul cadru observabil al unei sarcini este TaskDispatched.

Eveniment Payload Se declanșează când
PipelineRunCompleted workspaceId, pipelineRunId, templateId Rularea se încheie
PipelineRunCancelled workspaceId, pipelineRunId, templateId Rularea este anulată (de utilizator sau de sistem) — o oprire deliberată, nu o eroare
PipelineRunFailed workspaceId, pipelineRunId, templateId, errorMessage Rularea eșuează

Sunt publicate doar tranzițiile finale. Nu există PipelineRunStarted și niciun eveniment per pas: progresul unui pas ajunge la clienți prin rândurile propriei rulări și prin fluxul ciclului de viață al sarcinilor al agentului pe care l-a trimis, nu prin bus.

Eveniment Payload Se declanșează când
ActivityLogged id, actorType, actorId?, action, entityType, entityId?, details?, workspaceId?, runId? Se creează o intrare în pista de audit
BudgetThresholdCrossed scopeType, scopeId, spentCents, budgetCents, isHardStop Cheltuiala depășește pragul
Eveniment Payload Se declanșează când
WorkspaceMemberAdded workspaceId, userId, role Un utilizator se alătură unui spațiu de lucru (folosirea unei invitații sau adăugarea de către un administrator)
WorkspaceMemberRemoved workspaceId, userId Un membru este eliminat — sesiunile active ale acelui utilizator, limitate la spațiul de lucru, trebuie să reverifice imediat accesul
WorkspaceMemberRoleChanged workspaceId, userId, role Rolul unui membru se schimbă

Apartenența este întreaga suprafață de identitate de pe bus. Crearea utilizatorilor și revocarea dispozitivelor au loc, dar nu sunt publicate ca evenimente de domeniu — revocarea se aplică direct asupra sesiunilor active, în loc să treacă prin bus.

Eveniment Payload Se declanșează când
CalendarAuthExpired workspaceId, accountEmail Tokenul de reîmprospătare OAuth al unui cont conectat este invalid permanent (publicat o dată per episod de deconectare)
MeetingStartingSoon workspaceId, eventId, title, startTime, meetingUrl? Un eveniment de calendar începe în fereastra de anticipare configurată
MeetingRecordingStopped workspaceId, meetingId, title, userNotes, transcript, summaryInstructions? O înregistrare de întâlnire se încheie și declanșează pipeline-ul încorporat meeting_summary
Eveniment Payload Se declanșează când
RigClosedEvent workspaceId, rigId, reason O stație a dispărut, împreună cu motivul închiderii; consumatorii acționează doar la backendFailure — orice alt motiv este cerut sau însoțit de un RigReaped
RigControlChanged workspaceId, rigId, controller? Un om a preluat controlul exclusiv asupra unei stații sau l-a predat înapoi (controller null la eliberare — evenimentul nu înregistrează cine a renunțat)
RigReaped workspaceId, rigId, reason, agentId? Sistemul a recuperat o mașină: inactivitate, TTL sau evacuare din cauza presiunii pe memorie (raportată ca idleTimeout), împreună cu agentul care o conducea

Deschiderea unei stații nu este un eveniment. Pornirea se încheie, iar rândul stației trece în ready; nu se publică nimic, așa că nu există niciun RigOpened la care să te abonezi.

Mai multe subsisteme care par conduse de evenimente din exterior nu sunt. Sunt enumerate aici pentru că absența lor este o decizie de proiectare care merită cunoscută înainte de a porni în căutarea unui hook:

Subdomeniu Cum se propagă starea în schimb
Memorie record_memory_fact și cele înrudite scriu și returnează; rechemarea citește. Nimic nu publică la un fapt sau un conflict
Orchestrare Propunerea, aprobarea ei și rularea de pipeline generată sunt rânduri și apeluri RPC
Documente de plan submit_plan scrie o revizie; aprobarea materializează o orchestrare. Niciuna nu este anunțată
Artefacte publish_artifact / revise_artifact scriu revizii direct
Aprobări Escaladarea este gestionată în interiorul serviciului de flux de aprobare
Sincronizarea tichetelor Coordonatorul de furnizori se abonează la evenimentele de tichet de mai sus; webhook-urile primite sunt aplicate direct
Reîmprospătarea calendarului O sincronizare inserează sau actualizează evenimente, iar clientul recitește; nu există nicio difuzare de reîmprospătare
Îmbinări de worktree-uri Înregistrate în worktree_merge_log și citite de acolo

Fiecare abonat de mai jos este construit în interiorul runCcServer (sau al unui serviciu pe care acesta îl construiește). Nu există niciun abonat pe partea de client.

Abonat Evenimentele pe care le consumă
WorkspaceSeeder WorkspaceCreated
Popularea suitei de eval-uri de început (inline) WorkspaceCreated
Provisionarea spațiului de chat (inline) SpaceCreated
PipelineTriggerDispatcher Fiecare DomainEvent; fiecare trigger activ filtrează după propriul tip
SubPipelineResumeListener PipelineRunCompleted, PipelineRunFailed
PipelineCostRollupListener AgentRunCompleted
BudgetEvaluationListener AgentRunCompleted
MeetingSummaryReconciler PipelineRunCompleted, PipelineRunFailed, PipelineRunCancelled (doar rulările 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 — injectează o notificare în limbaj simplu pe canalul de direcționare al agentului care conduce
StaleReviewWatcher PrHeadChanged — publică ReviewBecameStale doar când există o revizuire finalizată pentru commitul înlocuit
GitHubPrConversationBridge AgentRunCompleted — răspunsuri în conversațiile de PR, în ieșire
NotificationFeedRecorder Cele douăzeci și două de evenimente din clasa notificărilor de mai jos
RemoteEventForwarder Aceleași douăzeci și două, plus TicketReassigned și fiecare TaskLifecycleEvent. Cele trei evenimente de apartenență doar îi invalidează cache-ul de apartenență per spațiu de lucru; ele nu sunt retransmise ca frame-uri
LocalRpcServer / RemoteRelayHost WorkspaceMemberRemoved — elimină abonamentele la spațiile de lucru atașate de acel utilizator; socketul rămâne deschis

TicketAssigned este un semnal de audit și de notificare și un trigger de pipeline. Atribuirea către un agent înregistrează proprietatea și nu pornește nicio rulare — nu există niciun dispecer de tichete. Atribuirea către o echipă (assignedTeamId setat) este excepția: TeamRoutingService trimite liderul echipei.

TicketRemoteSyncHandler este declarat, dar nu este construit niciodată în codul de producție, așa că în prezent nu se abonează la nimic. NotificationEventMapper din partea de client este construit doar în propriul test — este rămășița căii în proces de dinainte de separarea dintre client și server. Toast-urile live vin din cadrele JSON-RPC de mai jos.

Pe server, NotificationFeedRecorder și RemoteEventForwarder transformă aceste evenimente în cadre JSON-RPC notifications/* (notification_wire.dart). Pe client, RpcNotificationMapper → mapNotificationFrame transformă un cadru într-o AppNotification.

Există exact șaptesprezece categorii de notificări. Mai multe evenimente se mapează la mai mult de o metodă JSON-RPC, pentru că respectivul cadru poartă tranziția (eșuat versus revenit), în timp ce categoria poartă întrebarea („ce face CI-ul?”).

Categorie Eveniment sursă Metodă JSON-RPC
newMessage MessageReceived notifications/message_received (te menționează)
agentRunCompleted MessageReceived notifications/message_received (is_agent_message, fără să te menționeze)
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

Cadrul de preluare a stației nu este livrat înapoi celui care a preluat controlul, o eliberare nu este filtrată (evenimentul nu înregistrează cine a renunțat), iar doar o închidere backendFailure produce un cadru rig_closed — celelalte motive sunt fie cerute, fie deja acoperite de cadrul de recuperare.

AgentAwaitingInput este publicat de AgentAwaitingInputPublisher, care urmărește registrele de aprobări și de blocări pe credențiale pentru intrări noi și este anunțat de AgentQuestionService despre fiecare întrebare ask_user. O așteptare înseamnă un singur eveniment; când așteptarea se încheie, nu se publică nimic.

Cele cinci categorii de PR-uri proprii poartă forUserId, așa că ajung la autor, nu la toată lumea din spațiul de lucru.

RemoteEventForwarder retransmite în plus TicketReassigned ca notifications/ticket_reassigned și fiecare TaskLifecycleEvent ca notifications/task_<phase>. Niciuna nu se mapează la o categorie de notificări — ele conduc interfața live, nu centrul de notificări. Evenimentele de apartenență nu sunt retransmise ca frame-uri: ele invalidează cache-ul forwarder-ului, iar gazdele de sesiune elimină abonamentele la spațiile de lucru ale membrului eliminat.