Evenimente de domeniu
Bus-ul de evenimente
Secțiune intitulată „Bus-ul de evenimente”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.
Evenimente de spațiu de lucru, agent și repo
Secțiune intitulată „Evenimente de spațiu de lucru, agent și repo”| 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 |
Evenimente de PR și de revizuire
Secțiune intitulată „Evenimente de PR și de revizuire”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 |
Urmărirea PR-urilor proprii
Secțiune intitulată „Urmărirea PR-urilor proprii”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 |
Evenimente de mesagerie
Secțiune intitulată „Evenimente de mesagerie”| 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ă |
Evenimente de tichete
Secțiune intitulată „Evenimente de tichete”| 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ă |
Evenimente de ciclu de viață al sarcinilor
Secțiune intitulată „Evenimente de ciclu de viață al sarcinilor”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.
Evenimente de pipeline
Secțiune intitulată „Evenimente de pipeline”| 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.
Evenimente de observabilitate
Secțiune intitulată „Evenimente de observabilitate”| 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 |
Evenimente de identitate și apartenență
Secțiune intitulată „Evenimente de identitate și apartenență”| 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.
Evenimente de calendar și întâlniri
Secțiune intitulată „Evenimente de calendar și întâlniri”| 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 |
Evenimente de stații
Secțiune intitulată „Evenimente de stații”| 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.
Subdomenii fără niciun eveniment
Secțiune intitulată „Subdomenii fără niciun eveniment”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.
Maparea notificărilor
Secțiune intitulată „Maparea notificărilor”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.