Kroki potoku
Krok potoku ma dwie niezależne osie:
- Rodzaj (
StepKind) — jak silnik szereguje krok względem jego wyzwalaczy. - Klucz ciała (
BuiltInBodyKeys) — które zarejestrowane domknięcie ciała wykonuje pracę kroku.
Rodzaje kroków
Dział zatytułowany „Rodzaje kroków”Zdefiniowane w packages/cc_domain/lib/features/pipelines/domain/entities/step_kind.dart.
| Rodzaj | Semantyka szeregowania |
|---|---|
trigger |
Obowiązkowy punkt wejścia — dokładnie jeden na szablon, zawsze pierwszy węzeł. Sam nie wykonuje żadnej pracy; to, co rozpoczyna uruchomienie (ręcznie / zdarzenie / harmonogram), jest śledzone osobno jako wiersze PipelineTrigger. Jego ciało to puste pipeline.trigger. |
listen |
Wyzwala się, gdy wszystkie kroki źródłowe (w StepTrigger.sourceStepIds) zakończą się. |
join |
Wyzwala się, gdy wszystkie kroki w waitForStepIds osiągną stan końcowy. |
router |
Rozgałęzianie warunkowe — ciało zwraca StepResult.route(key), co uruchamia tylko tę dalszą krawędź, której routeKey się zgadza; niewybrane gałęzie są oznaczane jako pominięte. |
forEach |
Szeregowany jak listen (wyzwala się, gdy wszystkie sourceStepIds się zakończą). To zarejestrowane ciało flow.forEach iteruje; silnik nie tworzy po jednym kroku potomnym na element. |
terminal |
Wartownik harmonogramu bez ciała. Uruchomienie potoku kończy się, gdy zakończy się krok terminalny. |
Ciała kroków
Dział zatytułowany „Ciała kroków”Zarejestrowane domknięcia ciał kluczowane stałymi w BuiltInBodyKeys (packages/cc_domain/lib/features/pipelines/domain/templates/builtin_template_seeds.dart). Jest ich 25 i wszystkie wymieniono poniżej. Konfiguracja jest odczytywana z PipelineNodeConfig kroku (patrz poniżej), o ile nie zaznaczono inaczej; „klucze stanu” są odczytywane ze stanu potoku / ładunku wyzwalacza w momencie wykonania.
Krok, którego bodyKey nie jest zarejestrowany, generuje ostrzeżenie przy wczytywaniu szablonu i nigdy się nie wykonuje — nie powoduje jednak niepowodzenia uruchomienia. Kroki trigger i terminal są zwolnione z tego ostrzeżenia: wyzwalacz nadal wykonuje puste ciało pipeline.trigger, a krok terminal nie ma ciała.
pipeline.trigger
Dział zatytułowany „pipeline.trigger”Puste ciało obowiązkowego węzła trigger — kończy się natychmiast, aby silnik rozgałęził pracę do dalszych kroków nasłuchujących. Bez konfiguracji.
pipeline.bashScript
Dział zatytułowany „pipeline.bashScript”Krok powłoki bez agenta (bash -c). Symbole zastępcze {{key}} w skrypcie są podstawiane ze stanu i ładunku wyzwalacza wartościami escapowanymi dla powłoki.
| Pole konfiguracji | Opis |
|---|---|
script |
Polecenie powłoki do wykonania (wymagane) |
outputKey |
Klucz stanu, który przy kodzie wyjścia 0 otrzymuje przycięty stdout |
Działa z cwd <cc_root>/pipelines/<pipelineRunId>/ (wspólnym dla wszystkich kroków bash w danym uruchomieniu) i GITHUB_TOKEN w środowisku. Zapisuje też <stepId>_runDir do stanu. Kod wyjścia inny niż zero powoduje niepowodzenie kroku z końcówką stderr.
conversation.promptAgent
Dział zatytułowany „conversation.promptAgent”Ogólny węzeł promptu i wysyłki: renderuje prompt, wysyła agenta do rozmowy i wstrzymuje się do zakończenia uruchomienia. Ładunek submit_output agenta jest zbierany do stanu pod outputKey.
| Pole konfiguracji | Opis |
|---|---|
agentId |
UUID agenta z obszaru roboczego do wysłania (wymagany) |
prompt |
Tekst instrukcji; obsługuje podstawianie {{key}} (wymagany) |
outputKey |
Klucz stanu dla zebranego wyniku |
outputSchema |
JSON Schema, który musi spełniać wynik |
label |
Etykieta wyświetlana na kanwie |
extras.spaceId |
Istniejąca rozmowa, w której pracować (domyślnie: ukryta) |
extras.conversationTitle |
Nazwany strumień wewnątrz tej przestrzeni (opcjonalny) |
extras.mode |
Odczytywany na węźle, który otwiera pokój (messaging.createSpace), a nie tutaj. Domyślnie review. |
pipeline.condition
Dział zatytułowany „pipeline.condition”Ciało routera — odczytuje config.extras, ocenia warunek i zwraca StepResult.route(key). Warunki to nie wyrażenia JavaScript; obsługiwane są trzy sposoby ich tworzenia, w kolejności priorytetu:
- Drzewo predykatów (
extras.predicate) — drzewo logiczne, które kieruje do"true"/"false". Każdy węzeł to mapa z polemtype:{"type": "fileExists", "paths": [...], "baseKey": "repo_local_path", "negate": false, "recursive": false}— true, gdy dowolna z wymienionych ścieżek istnieje na dysku;negateodwraca wynik;recursiveprzeszukuje także podkatalogi (pomijając.git,node_modules,build,.dart_tool). Ścieżki względne są rozwiązywane względemstate[baseKey](domyślnierepo_local_path), a w przeciwnym razie względem katalogu obszaru roboczego danego uruchomienia.{"type": "comparison", "left": "{{score}}", "op": "gt", "right": 80}— operatoryequals,notEquals,contains,exists,notExists,gt,lt; odczytuje stan potoku, a nie system plików.{"type": "and" | "or", "of": [<predicate>, ...]}— grupy logiczne.{"type": "not", "of": <predicate>}— negacja.
- Przełącznik (
extras.switchKey) —{"switchKey": "pr_class", "cases": [...], "default": "standard"}; kieruje do pierwszego przypadku, który wartość stanu zawiera bez rozróżniania wielkości liter, a w przeciwnym razie dodefault. - Porównanie (starsze, najwyższego poziomu
extras.left/extras.op/extras.right) — równoważne predykatowicomparison, zachowane dla szablonów utworzonych przed wprowadzeniem drzewa.
Krawędzie wychodzące z węzła niosą routeKey, który musi się zgadzać ze zwróconym kluczem.
prReview.comment
Dział zatytułowany „prReview.comment”Zamieszcza skonsolidowane ustalenia jako komentarz recenzji PR przez klienta GitHub PR.
| Klucz stanu | Opis |
|---|---|
repo_full_name |
owner/repo (wymagany) |
pr_number |
Numer PR — liczba całkowita lub ciąg liczbowy (wymagany) |
consolidated_findings |
Treść komentarza (wymagana) |
Zwraca StepResult.terminal z comment_review_id i commented_at w stanie.
messaging.postSpace
Dział zatytułowany „messaging.postSpace”Wysyła wiadomość do przestrzeni komunikacyjnej przez MessagingPort.
| Klucz stanu | Opis |
|---|---|
space_id |
Przestrzeń docelowa (wymagana) |
content |
Treść wiadomości, odczytywana dosłownie ze stanu (wymagana) |
Zapisuje posted_space_id i posted_at do stanu.
team.dispatch
Dział zatytułowany „team.dispatch”Wysyła cały zespół zamiast pojedynczego agenta, a następnie wstrzymuje się do zakończenia uruchomień członków.
| Pole konfiguracji | Opis |
|---|---|
teamId |
Zespół z obszaru roboczego do wysłania (wymagany; wyklucza się z agentId) |
prompt |
Tekst celu; obsługuje podstawianie {{key}} (wymagany) |
dispatchMode |
allParallel (domyślnie — jedno zadanie na członka) lub manager (wysyła tylko lidera do koordynacji) |
outputKey |
Klucz stanu dla zebranych wyników członków |
outputSchema, label, extras.spaceId |
Jak w conversation.promptAgent |
human.gate
Dział zatytułowany „human.gate”Bramka zatwierdzenia. Wysyła agenta zatwierdzającego do rozmowy (tryb recenzji) z promptem bramki i kontraktem wyniku {decision, reason}, a następnie wstrzymuje się, aż zatwierdzający zgłosi wynik przez submit_output.
| Pole konfiguracji | Opis |
|---|---|
agentId |
Agent zatwierdzający (wymagany) |
prompt |
Prompt bramki (opcjonalny; w razie braku używany jest domyślny) |
outputKey |
Klucz stanu dla zebranego ładunku {decision, reason} |
outputSchema |
Domyślnie schemat zatwierdzenia (decision: approved / rejected, reason) |
label, extras.spaceId |
Jak w conversation.promptAgent |
Bramka jest rozwiązywana wyłącznie przez submit_output. BuiltInBodyKeys.humanGate, biblioteka typów węzłów oraz tekst promptu jednego z wbudowanych szablonów wymieniają narzędzia MCP approve_step i reject_step; żadne z tych narzędzi nie istnieje w żadnym pakiecie ani rejestrze.
repos.cleanup
Dział zatytułowany „repos.cleanup”Usuwa nieaktualne izolowane worktree; tryb wybiera na podstawie ładunku wyzwalacza. Respektuje flagę dryRun uruchomienia.
| Klucz stanu | Opis |
|---|---|
ticket_id |
Zwalnia worktree tego zgłoszenia |
repo_full_name + pr_number |
Zwalnia efemeryczny worktree edytora PR |
space_id |
Usuwa worktree i folder tej rozmowy |
| (żaden z powyższych) | Przeczesuje obszar roboczy: zniknięte katalogi, martwe przestrzenie, osierocone foldery rozmów |
outputKey (opcjonalny) otrzymuje ciąg podsumowania.
flow.forEach
Dział zatytułowany „flow.forEach”Mapowanie / fan-out po kolekcji ze stanu: renderuje prompt raz na element, a następnie wysyła agenta raz z tymi promptami połączonymi (separatory ---) i wstrzymuje się do zakończenia tego uruchomienia. Pusta kolekcja kończy się natychmiast z pustą listą. To nie jest jedno równoległe wysłanie na element.
| Pole konfiguracji | Opis |
|---|---|
agentId |
Agent do wysłania na element (wymagany) |
prompt |
Prompt dla pojedynczego elementu; obsługuje podstawianie {{key}} (wymagany) |
extras.iterableKey |
Klucz stanu przechowujący kolekcję (wymagany) |
extras.itemKey |
Klucz, do którego wiązany jest bieżący element w prompcie (domyślnie item) |
outputKey |
Klucz stanu dla zebranego wyniku (jedno wysłanie, nie lista) |
flow.callPipeline
Dział zatytułowany „flow.callPipeline”Uruchamia inny szablon potoku jako zagnieżdżony podkrok (podpotok działa jako uruchomienie potomne z ustawionym parentPipelineRunId). Krok wstrzymuje się, aż potomny osiągnie stan końcowy; następnie końcowy stan potomnego jest scalany pod outputKey.
| Pole konfiguracji | Opis |
|---|---|
extras.templateId |
Szablon potomny do uruchomienia (wymagany; potok nie może wywołać samego siebie) |
inputKeys |
Klucze stanu kopiowane do ładunku wyzwalacza potomnego |
outputKey |
Klucz stanu dla scalonego stanu końcowego potomnego |
code.index
Dział zatytułowany „code.index”Indeksowanie tree-sitter w tle: przechodzi repozytorium, wyodrębnia symbole/krawędzie w izolatach workerów i wprowadza je do grafu kodu obszaru roboczego. Strumieniuje postęp do wiersza uruchomienia kroku; obsługuje dry-run. Tree-sitter i jego gramatyki to wymagane natywne biblioteki — brakujący plik dylib oznacza, że serwer nigdy nie wystartował, a gramatyka, której nie da się rozwiązać w trakcie uruchomienia, powoduje niepowodzenie kroku, a nie jego pominięcie.
| Klucz stanu | Opis |
|---|---|
repo_id |
Repozytorium do zaindeksowania (wymagane) |
repo_local_path |
Ścieżka lokalna checkoutu (wymagana) |
meeting.diarize
Dział zatytułowany „meeting.diarize”Diaryzacja mówców w trybie offline. Odczytuje zachowane audio spotkania, grupuje je w mówców (Person 1, Person 2, …), zmienia etykiety segmentów transkryptu i nadpisuje stan transcript; emituje też diarization_spans dla równoległego kroku meeting.updateTranscript. Przepuszcza transkrypt bez zmian, gdy nie zachowano audio lub modele nie są zainstalowane.
| Klucz stanu | Opis |
|---|---|
meeting_id |
Spotkanie do diaryzacji (wymagane) |
meeting.saveNotes, meeting.addActionItems, meeting.addDecisions
Dział zatytułowany „meeting.saveNotes, meeting.addActionItems, meeting.addDecisions”Deterministyczne ciała utrwalania podsumowania spotkania. Każde odczytuje ze stanu meeting_id oraz ustrukturyzowany ładunek meeting_outcome kroku agenta ({summary, enhancedNotes, actionItems[], decisions[]}) i zapisuje swoją część do własnej tabeli — notatki, zadania albo decyzje. Wcześniej zapisane wiersze nigdy nie są czyszczone w zdegradowanym uruchomieniu.
Pozostałe wbudowane ciała
Dział zatytułowany „Pozostałe wbudowane ciała”| Klucz ciała | Opis |
|---|---|
meeting.identifySpeakers |
Rozpoznawanie mówców między spotkaniami: dopasowuje zdiatryzowanych mówców do zapisanych profili głosu i automatycznie stosuje pewne dopasowania. Puste ciało bez profili/osadzeń. |
meeting.updateTranscript |
Stosuje etykiety Person N z diarization_spans i scala fragmenty z poszczególnych okien w tury mówców, a następnie utrwala oczyszczony transkrypt. Puste ciało, gdy diaryzacja nic nie wygenerowała. |
meeting.assemblePlayback |
Składa zmiksowaną ścieżkę odtwarzania spotkania z zachowanych plików WAV poszczególnych kanałów. Puste ciało, gdy nie zachowano audio. |
orchestration.markPhase |
Na końcu DAG pracy zapisuje wartowniki niepowodzenia dla podzgłoszeń bez wyniku i przełącza orkiestrację na synthesizing. |
orchestration.persistDeliverable |
Zapisuje wynik syntezy do zgłoszenia nadrzędnego, kończy je, publikuje produkt końcowy i oznacza orkiestrację jako completed. |
orchestration.awaitApproval |
Bramka częściowego zatwierdzenia: kończy się natychmiast, gdy klucz węzła kroku znajduje się w zatwierdzonym zbiorze orkiestracji; w przeciwnym razie wstrzymuje się, aż wznowi ją późniejsze zatwierdzenie. To jedyne ciało, które ustawia extras.awaitApproval, co zwalnia je z 24-godzinnego limitu czasu wstrzymanego kroku. |
messaging.createSpace |
Otwiera rozmowę, w której pracuje reszta szablonu, publikowaną pod outputKey kroku (wbudowane używają pipeline_space_id). Pusta lista repoIds nie tworzy żadnego checkoutu; extras['allRepos'] tworzy checkouty wszystkich repozytoriów obszaru roboczego. extras['createConversation'] (domyślnie wyłączone) otwiera dodatkowo nazwany strumień. Krok agenta DOŁĄCZA do pokoju i nigdy go nie otwiera, więc fan-out bez tego węzła zawodzi przy wysyłce — a podłączenie go raz sprawia, że cały fan-out współdzieli jeden checkout, zamiast tworzyć osobny dla każdego. |
prReview.finalize |
Ostatni krok szablonu recenzji PR: sortuje zgromadzone węzły recenzji do koszyków konsensusu i oblicza werdykt dla danego PR-a. |
skills.analyze |
Uruchamia skan łańcucha dostaw skillów dla zainstalowanych skillów w obszarze roboczym i zapisuje każdy werdykt. |
Wyniki kroków
Dział zatytułowany „Wyniki kroków”Ciało kroku zwraca StepResult (packages/cc_domain/lib/features/pipelines/domain/entities/step_result.dart). Wszystkie fabryki przyjmują opcjonalną mapę mutatedState scalaną ze stanem potoku, z wyjątkiem failed.
| Fabryka | Sygnatura | Opis |
|---|---|---|
ok |
StepResult.ok({mutatedState}) |
Normalne zakończenie; dalsze kroki nasłuchujące są oceniane |
route |
StepResult.route(nextRouterKey, {mutatedState}) |
Zakończenie routera; wybiera dalszą gałąź, której routeKey się zgadza |
suspendUntilEvent |
StepResult.suspendUntilEvent(eventType, {mutatedState}) |
Wstrzymuje do czasu wystąpienia określonego typu zdarzenia domenowego |
suspendUntilTasksComplete |
StepResult.suspendUntilTasksComplete(taskIds, {mutatedState}) |
Wstrzymuje do czasu, gdy wszystkie wymienione zadania osiągną stan końcowy |
terminal |
StepResult.terminal({mutatedState}) |
Uruchomienie potoku jest zakończone — nie ma już kroków |
failed |
StepResult.failed(errorMessage) |
Krok zakończył się niepowodzeniem z komunikatem o błędzie |
StepRetryPolicy
Dział zatytułowany „StepRetryPolicy”Zachowanie ponawiania dla ciała węzła, które zawodzi (PipelineNodeConfig.retryPolicy; null wyłącza ponawianie).
| Pole | Typ | Opis |
|---|---|---|
maxAttempts |
int |
Łączna liczba prób wraz z pierwszą (3 = 1 próba + 2 ponowienia); musi być ≥ 1 |
backoff |
String |
linear lub exponential (domyślnie) |
initialDelayMs |
int |
Opóźnienie przed pierwszym ponowieniem w milisekundach; kolejne opóźnienia rosną zgodnie z backoff |
PipelineNodeConfig
Dział zatytułowany „PipelineNodeConfig”Konfiguracja pojedynczego węzła przenoszona wewnątrz definicji kroku (packages/cc_domain/lib/features/pipelines/domain/entities/pipeline_node_config.dart). Ten kształt mają zarówno węzły wbudowane, jak i niestandardowe; każde ciało odczytuje tylko te pola, które go dotyczą.
| Pole | Typ | Opis |
|---|---|---|
prompt |
String? |
Szablon promptu z podstawianiem {{key}} |
script |
String? |
Treść skryptu bash z podstawianiem {{key}} |
agentId |
String? |
UUID agenta z obszaru roboczego do wysłania |
inputKeys |
List<String> |
Klucze stanu, które ten węzeł pobiera jako dane wejściowe |
repoIds |
List<String> |
Repozytoria, które węzeł messaging.createSpace pobiera do checkoutu (<repoId> lub <repoId>@<branch>). Puste = żadne; extras['allRepos'] = wszystkie repozytoria obszaru roboczego. Ignorowane w krokach agenta. |
outputKey |
String? |
Klucz stanu, pod którym zapisywany jest wynik tego węzła |
label |
String? |
Etykieta wyświetlana na kanwie (domyślnie identyfikator kroku) |
outputSchema |
Map<String, dynamic>? |
JSON Schema (podzbiór), który musi spełniać wartość wyniku |
reducer |
String? |
Strategia scalania, gdy równoległe gałęzie zapisują ten sam outputKey: append, mergeLists, mergeMaps, sum lub override (domyślnie) |
retryPolicy |
StepRetryPolicy? |
Polityka ponawiania przy niepowodzeniu ciała; null = pojedyncza próba |
continueOnFail |
bool |
Gdy true, końcowe niepowodzenie nie powoduje niepowodzenia uruchomienia; błąd trafia do state['_stepErrors'][stepId] |
timeoutMs |
int? |
Limit czasu zegarowego dla ciała węzła w milisekundach; null = brak limitu |
teamId |
String? |
Zespół do wysłania (węzły team.dispatch); wyklucza się z agentId |
dispatchMode |
String? |
Tryb wykonania zespołu: allParallel lub manager |
extras |
Map<String, dynamic> |
Swobodna konfiguracja specyficzna dla ciała (np. iterableKey, templateId, drzewa warunków) |
Wyprowadzone akcesory: spaceId (extras['spaceId'] — istniejąca rozmowa, w której pracować), createsConversation (extras['createConversation']), conversationTitle (extras['conversationTitle']), modeName (extras['mode'] — chat / review / plan, odczytywany na węźle otwierającym przestrzeń) oraz runWhen (extras['runWhen'] — pomija węzeł, gdy warunek nie jest spełniony).