Sari la conținut

Pași de pipeline

Un pas de pipeline are două axe independente:

  • Tip (StepKind) — cum programează motorul pasul în raport cu trigger-ele sale.
  • Cheie de corp (BuiltInBodyKeys) — ce closure de corp înregistrat execută munca pasului.

Definite în packages/cc_domain/lib/features/pipelines/domain/entities/step_kind.dart.

Tip Semantica de programare
trigger Punct de intrare obligatoriu — exact unul per șablon, întotdeauna primul nod. Nu face el însuși nicio muncă; ceea ce pornește rularea (manual / eveniment / program) este urmărit separat ca rânduri PipelineTrigger. Corpul său este pipeline.trigger, un no-op.
listen Se declanșează când toți pașii sursă (din StepTrigger.sourceStepIds) se încheie.
join Se declanșează când toți pașii din waitForStepIds ajung într-o stare terminală.
router Ramificare condiționată — corpul returnează StepResult.route(key), care declanșează doar muchia din aval a cărei routeKey se potrivește; ramurile neselectate sunt marcate ca sărite.
forEach Programat ca listen (se declanșează când toți sourceStepIds se încheie). Corpul înregistrat flow.forEach este cel care iterează; motorul nu creează câte un pas copil pentru fiecare element.
terminal Santinelă a planificatorului, fără corp. Rularea pipeline-ului se încheie când un pas terminal se termină.

Closure-uri de corp înregistrate, indexate după constantele din BuiltInBodyKeys (packages/cc_domain/lib/features/pipelines/domain/templates/builtin_template_seeds.dart). Sunt 25, toate enumerate mai jos. Configurația se citește din PipelineNodeConfig al pasului (vezi mai jos), dacă nu se specifică altfel; „cheile de stare” se citesc din starea pipeline-ului / payload-ul de trigger în momentul execuției.

Un pas al cărui bodyKey nu este înregistrat produce un avertisment la încărcarea șablonului și apoi nu se execută niciodată — nu face rularea să eșueze. Pașii trigger și terminal sunt exceptați de la acel avertisment: trigger-ul rulează totuși corpul no-op pipeline.trigger; un pas terminal nu are corp.

Corp no-op al nodului obligatoriu trigger — se încheie imediat, ca motorul să se ramifice către ascultătorii din aval. Fără configurație.

Pas shell fără agent (bash -c). Marcajele {{key}} din script sunt substituite din stare + payload-ul de trigger, cu valori escapate pentru shell.

Câmp de configurare Descriere
script Comandă shell de executat (obligatoriu)
outputKey Cheia de stare care primește stdout-ul curățat la ieșirea cu cod 0

Rulează cu cwd <cc_root>/pipelines/<pipelineRunId>/ (partajat de toți pașii bash dintr-o rulare) și cu GITHUB_TOKEN în mediu. Scrie de asemenea <stepId>_runDir în stare. O ieșire cu cod diferit de zero face pasul să eșueze, cu finalul din stderr.

Nod generic de tip prompt și trimitere: redă promptul, trimite agentul într-o conversație și suspendă până când rularea se încheie. Payload-ul submit_output al agentului este colectat în stare sub outputKey.

Câmp de configurare Descriere
agentId UUID-ul agentului din spațiul de lucru care se trimite (obligatoriu)
prompt Text de instrucțiune; acceptă substituția {{key}} (obligatoriu)
outputKey Cheia de stare pentru ieșirea colectată
outputSchema Schema JSON pe care trebuie să o respecte ieșirea
label Eticheta afișată pe pânză
extras.spaceId Conversația existentă în care se lucrează (implicit: una ascunsă)
extras.conversationTitle Fluxul cu nume din interiorul acelui spațiu (opțional)
extras.mode Se citește pe nodul care deschide camera (messaging.createSpace), nu aici. Implicit review.

Corp de router — citește config.extras, evaluează o condiție și returnează StepResult.route(key). Condițiile nu sunt expresii JavaScript; sunt acceptate trei forme de redactare, în ordinea priorității:

  • Arbore de predicate (extras.predicate) — un arbore boolean care rutează "true" / "false". Fiecare nod este o hartă cu un type:
    • {"type": "fileExists", "paths": [...], "baseKey": "repo_local_path", "negate": false, "recursive": false} — adevărat când oricare dintre căile listate există pe disc; negate îl inversează; recursive caută și în subdirectoare (sărind peste .git, node_modules, build, .dart_tool). Căile relative se rezolvă față de state[baseKey] (implicit repo_local_path), altfel față de directorul de spațiu de lucru al rulării.
    • {"type": "comparison", "left": "{{score}}", "op": "gt", "right": 80} — operatorii equals, notEquals, contains, exists, notExists, gt, lt; citește starea pipeline-ului, nu sistemul de fișiere.
    • {"type": "and" | "or", "of": [<predicate>, ...]} — grupuri booleene.
    • {"type": "not", "of": <predicate>} — negație.
  • Switch (extras.switchKey) — {"switchKey": "pr_class", "cases": [...], "default": "standard"}; rutează către primul caz pe care valoarea de stare îl conține fără a ține cont de majuscule, altfel către default.
  • Comparație (extras.left / extras.op / extras.right, la nivel superior, variantă veche) — echivalentă cu un predicat comparison, păstrată pentru șabloanele redactate înainte de arbore.

Muchiile care ies din nod poartă un routeKey care trebuie să corespundă cheii returnate.

Publică constatările consolidate ca un comentariu de revizuire pe PR, prin clientul GitHub de PR.

Cheie de stare Descriere
repo_full_name owner/repo (obligatoriu)
pr_number Numărul PR-ului — int sau șir numeric (obligatoriu)
consolidated_findings Corpul comentariului (obligatoriu)

Returnează StepResult.terminal, cu comment_review_id și commented_at în stare.

Publică un mesaj într-un spațiu de mesagerie prin MessagingPort.

Cheie de stare Descriere
space_id Spațiul țintă (obligatoriu)
content Corpul mesajului, citit textual din stare (obligatoriu)

Scrie posted_space_id și posted_at în stare.

Trimite o echipă întreagă în loc de un singur agent, apoi suspendă până când rulările membrilor se încheie.

Câmp de configurare Descriere
teamId Echipa din spațiul de lucru care se trimite (obligatoriu; exclusivă mutual cu agentId)
prompt Textul obiectivului; acceptă substituția {{key}} (obligatoriu)
dispatchMode allParallel (implicit — o sarcină per membru) sau manager (se trimite doar liderul, ca să coordoneze)
outputKey Cheia de stare pentru ieșirile colectate ale membrilor
outputSchema, label, extras.spaceId Ca la conversation.promptAgent

Poartă de aprobare. Trimite agentul aprobator într-o conversație (modul review), cu promptul porții și un contract de ieșire {decision, reason}, apoi suspendă până când aprobatorul trimite prin submit_output.

Câmp de configurare Descriere
agentId Agentul aprobator (obligatoriu)
prompt Promptul porții (opțional; când lipsește, se folosește unul implicit)
outputKey Cheia de stare pentru payload-ul {decision, reason} colectat
outputSchema Implicit, o schemă de aprobare (decision: approved / rejected, reason)
label, extras.spaceId Ca la conversation.promptAgent

Poarta se rezolvă doar prin submit_output. BuiltInBodyKeys.humanGate, biblioteca de tipuri de noduri și textul de prompt al unui șablon inclus numesc toate uneltele MCP approve_step și reject_step; niciuna dintre aceste unelte nu există în vreun pachet sau registru.

Elimină worktree-urile izolate învechite; își alege modul din payload-ul de trigger. Respectă indicatorul dryRun al rulării.

Cheie de stare Descriere
ticket_id Eliberează worktree-urile acelui tichet
repo_full_name + pr_number Eliberează worktree-ul efemer al editorului de PR
space_id Desființează worktree-urile și folderul acelei conversații
(niciuna dintre cele de mai sus) Mătură spațiul de lucru: directoare dispărute, spații moarte, foldere orfane de conversații

outputKey (opțional) primește un șir rezumat.

Mapare / fan-out peste o colecție de stare: redă promptul o dată pentru fiecare element, apoi trimite agentul o singură dată, cu acele prompturi concatenate (separator ---), și suspendă până când acea rulare se încheie. O colecție goală se încheie imediat, cu o listă goală. Nu este o trimitere paralelă per element.

Câmp de configurare Descriere
agentId Agentul de trimis per element (obligatoriu)
prompt Promptul per element; acceptă substituția {{key}} (obligatoriu)
extras.iterableKey Cheia de stare care conține colecția (obligatoriu)
extras.itemKey Cheia la care este legat elementul curent în prompt (implicit item)
outputKey Cheia de stare pentru ieșirea colectată (o singură trimitere, nu o listă)

Rulează un alt șablon de pipeline ca sub-pas imbricat (sub-pipeline-ul rulează ca rulare copil, cu parentPipelineRunId setat). Pasul suspendă până când copilul ajunge într-o stare terminală, apoi starea finală a copilului este fuzionată sub outputKey.

Câmp de configurare Descriere
extras.templateId Șablonul copil de rulat (obligatoriu; un pipeline nu se poate apela pe sine)
inputKeys Chei de stare copiate în payload-ul de trigger al copilului
outputKey Cheia de stare pentru starea finală fuzionată a copilului

Indexare tree-sitter în fundal: parcurge un depozit, extrage simboluri/muchii în izolate de worker și le ingeră în graful de cod al spațiului de lucru. Transmite progresul în flux către rândul de rulare al pasului; acceptă dry-run. Tree-sitter și gramaticile sale sunt native obligatorii — o bibliotecă dinamică lipsă înseamnă că serverul nu a pornit niciodată, iar o gramatică ce nu se poate rezolva în timpul rulării face pasul să eșueze, în loc să îl sară.

Cheie de stare Descriere
repo_id Depozitul de indexat (obligatoriu)
repo_local_path Calea locală a checkout-ului (obligatoriu)

Diarizare offline a vorbitorilor. Citește audio păstrat al întâlnirii, îl grupează în vorbitori (Person 1, Person 2, …), reetichetează segmentele transcrierii și rescrie starea transcript; emite de asemenea diarization_spans pentru pasul paralel meeting.updateTranscript. Trece transcrierea mai departe nealterată când nu s-a păstrat niciun audio sau modelele nu sunt instalate.

Cheie de stare Descriere
meeting_id Întâlnirea de diarizat (obligatoriu)

meeting.saveNotes, meeting.addActionItems, meeting.addDecisions

Secțiune intitulată „meeting.saveNotes, meeting.addActionItems, meeting.addDecisions”

Corpuri deterministe de persistare a rezumatului întâlnirii. Fiecare citește din stare meeting_id și payload-ul structurat meeting_outcome al pasului de agent ({summary, enhancedNotes, actionItems[], decisions[]}) și își scrie partea în propriul tabel — note, elemente de acțiune sau decizii. Rândurile salvate anterior nu sunt niciodată șterse într-o rulare degradată.

Cheie de corp Descriere
meeting.identifySpeakers Recunoaștere a vorbitorilor între întâlniri: potrivește vorbitorii diarizați cu profilurile vocale salvate și aplică automat potrivirile sigure. No-op fără profiluri/embedding-uri.
meeting.updateTranscript Aplică etichetele Person N din diarization_spans și fuzionează fragmentele per fereastră în replici per vorbitor, apoi persistă transcrierea curățată. No-op când diarizarea nu a produs nimic.
meeting.assemblePlayback Asamblează pista de redare mixată a întâlnirii din fișierele WAV per canal păstrate. No-op când nu s-a păstrat niciun audio.
orchestration.markPhase La finalul DAG-ului de lucru, scrie santinele de eșec pentru sub-tichetele fără ieșire și comută orchestrarea în synthesizing.
orchestration.persistDeliverable Scrie ieșirea sintezei în tichetul părinte, îl finalizează, publică livrabilul și marchează orchestrarea ca completed.
orchestration.awaitApproval Poartă de aprobare parțială: se încheie imediat când cheia de nod a pasului se află în setul aprobat al orchestrării, altfel suspendă până când o aprobare ulterioară o reia. Acesta este singurul corp care setează extras.awaitApproval, ceea ce îl scutește de timeout-ul de 24 de ore pentru pașii suspendați.
messaging.createSpace Deschide conversația în care lucrează restul șablonului, publicată sub outputKey al pasului (corpurile incluse folosesc pipeline_space_id). O listă repoIds goală nu extrage nimic; extras['allRepos'] extrage fiecare depozit al spațiului de lucru. extras['createConversation'] (oprit implicit) deschide și un flux cu nume. Un pas de agent SE ALĂTURĂ unei camere și nu deschide niciodată una, așa că un fan-out fără acest nod eșuează la trimitere — iar cablarea lui o singură dată înseamnă că întregul fan-out partajează un singur checkout, în loc să provizioneze câte unul pentru fiecare.
prReview.finalize Ultimul pas al șablonului de revizuire PR: sortează nodurile de revizuire acumulate în grupe de consens și calculează verdictul per PR.
skills.analyze Rulează scanarea lanțului de aprovizionare pentru skill-uri peste skill-urile instalate ale unui spațiu de lucru și înregistrează fiecare verdict.

Un corp de pas returnează un StepResult (packages/cc_domain/lib/features/pipelines/domain/entities/step_result.dart). Toate fabricile acceptă o hartă opțională mutatedState, fuzionată în starea pipeline-ului, cu excepția lui failed.

Fabrică Semnătură Descriere
ok StepResult.ok({mutatedState}) Încheiere normală; ascultătorii din aval sunt evaluați
route StepResult.route(nextRouterKey, {mutatedState}) Încheiere de router; selectează ramura din aval a cărei routeKey se potrivește
suspendUntilEvent StepResult.suspendUntilEvent(eventType, {mutatedState}) Suspendă până când se declanșează un anumit tip de eveniment de domeniu
suspendUntilTasksComplete StepResult.suspendUntilTasksComplete(taskIds, {mutatedState}) Suspendă până când toate sarcinile listate ajung într-o stare terminală
terminal StepResult.terminal({mutatedState}) Rularea pipeline-ului s-a încheiat — nu mai urmează pași
failed StepResult.failed(errorMessage) Pasul a eșuat cu un mesaj de eroare

Comportamentul de reîncercare pentru un corp de nod care eșuează (PipelineNodeConfig.retryPolicy; null dezactivează reîncercările).

Câmp Tip Descriere
maxAttempts int Numărul total de încercări, inclusiv prima (3 = 1 încercare + 2 reîncercări); trebuie să fie ≥ 1
backoff String linear sau exponential (implicit)
initialDelayMs int Întârzierea înainte de prima reîncercare, în milisecunde; întârzierile ulterioare cresc conform backoff

Configurația per nod, purtată în interiorul definiției pasului (packages/cc_domain/lib/features/pipelines/domain/entities/pipeline_node_config.dart). Atât nodurile incluse, cât și cele personalizate au aceeași formă; fiecare corp citește doar câmpurile care îl interesează.

Câmp Tip Descriere
prompt String? Șablon de prompt cu substituție {{key}}
script String? Corp de script Bash cu substituție {{key}}
agentId String? UUID-ul agentului din spațiul de lucru de trimis
inputKeys List<String> Chei de stare pe care acest nod le consumă ca intrare
repoIds List<String> Depozitele pe care le extrage nodul messaging.createSpace (<repoId> sau <repoId>@<branch>). Gol = niciunul; extras['allRepos'] = fiecare depozit al spațiului de lucru. Ignorat pe pașii de agent.
outputKey String? Cheia de stare sub care se scrie ieșirea acestui nod
label String? Eticheta afișată pe pânză (implicit, ID-ul pasului)
outputSchema Map<String, dynamic>? Schema JSON (subset) pe care trebuie să o respecte valoarea de ieșire
reducer String? Strategia de fuzionare când ramuri paralele scriu aceeași outputKey: append, mergeLists, mergeMaps, sum sau override (implicit)
retryPolicy StepRetryPolicy? Politica de reîncercare la eșecul corpului; null = o singură încercare
continueOnFail bool Când este adevărat, un eșec terminal nu face rularea să eșueze; eroarea este pusă deoparte sub state['_stepErrors'][stepId]
timeoutMs int? Timeout în timp real pentru corpul nodului, în milisecunde; null = fără timeout
teamId String? Echipa de trimis (nodurile team.dispatch); exclusivă mutual cu agentId
dispatchMode String? Modul de execuție al echipei: allParallel sau manager
extras Map<String, dynamic> Configurație liberă, specifică corpului (de ex. iterableKey, templateId, arbori de condiții)

Accesori derivați: spaceId (extras['spaceId'] — conversația existentă în care se lucrează), createsConversation (extras['createConversation']), conversationTitle (extras['conversationTitle']), modeName (extras['mode'] — chat / review / plan, citit pe nodul care deschide spațiul) și runWhen (extras['runWhen'] — sare peste nod când condiția nu este îndeplinită).