Saltar para o conteúdo

Passos de pipeline

Um passo de pipeline tem dois eixos independentes:

  • Tipo (StepKind) — como o motor agenda o passo em relação aos respetivos disparadores.
  • Chave de corpo (BuiltInBodyKeys) — que closure de corpo registada executa o trabalho do passo.

Definido em packages/cc_domain/lib/features/pipelines/domain/entities/step_kind.dart.

Tipo Semântica de agendamento
trigger Ponto de entrada obrigatório — exatamente um por modelo, sempre o primeiro nó. Não faz trabalho próprio; o que inicia a execução (manual / evento / agendamento) é registado em separado como linhas PipelineTrigger. O seu corpo é o no-op pipeline.trigger.
listen Dispara quando todos os passos de origem (em StepTrigger.sourceStepIds) terminam.
join Dispara quando todos os passos em waitForStepIds atingem o estado terminal.
router Ramificação condicional — o corpo devolve StepResult.route(key), que dispara apenas a aresta a jusante cuja routeKey corresponde; os ramos não selecionados são marcados como ignorados.
forEach Agendado como listen (dispara quando todos os sourceStepIds terminam). É o corpo registado flow.forEach que itera; o motor não cria um passo filho por item.
terminal Sentinela do agendador sem corpo. A execução do pipeline conclui-se quando um passo terminal termina.

Closures de corpo registadas, indexadas pelas constantes em BuiltInBodyKeys (packages/cc_domain/lib/features/pipelines/domain/templates/builtin_template_seeds.dart). São 25, todas listadas abaixo. A configuração é lida a partir do PipelineNodeConfig do passo (ver abaixo), salvo indicação em contrário; as «chaves de estado» são lidas do estado do pipeline / da carga útil do disparador no momento da execução.

Um passo cujo bodyKey não esteja registado produz um aviso na carga do modelo e depois nunca é executado — não faz falhar a execução. Os passos trigger e terminal estão isentos desse aviso: o disparador continua a executar o corpo no-op pipeline.trigger; um passo terminal não tem corpo.

Corpo no-op do nó trigger obrigatório — conclui-se de imediato para que o motor se distribua em leque pelos ouvintes a jusante. Sem configuração.

Passo de shell sem agente (bash -c). Os marcadores {{key}} no script são substituídos a partir do estado + da carga útil do disparador, com valores escapados para shell.

Campo de configuração Descrição
script Comando de shell a executar (obrigatório)
outputKey Chave de estado que recebe o stdout sem espaços em branco na saída com código 0

É executado com o cwd <cc_root>/pipelines/<pipelineRunId>/ (partilhado por todos os passos bash de uma execução) e GITHUB_TOKEN no ambiente. Escreve também <stepId>_runDir no estado. Uma saída com código diferente de zero faz falhar o passo com a parte final do stderr.

Nó genérico de prompt e despacho: processa o prompt, despacha o agente para uma conversa e suspende-se até a execução terminar. A carga útil de submit_output do agente é recolhida para o estado sob outputKey.

Campo de configuração Descrição
agentId UUID do agente com âmbito de espaço de trabalho a despachar (obrigatório)
prompt Texto de instrução; suporta substituição {{key}} (obrigatório)
outputKey Chave de estado da saída recolhida
outputSchema JSON Schema que a saída tem de satisfazer
label Etiqueta apresentada no canvas
extras.spaceId Conversa existente onde trabalhar (predefinição: uma conversa oculta)
extras.conversationTitle Fluxo com nome dentro desse espaço (opcional)
extras.mode Lido no nó que abre a sala (messaging.createSpace), não aqui. Predefinição review.

Corpo de router — lê config.extras, avalia uma condição e devolve StepResult.route(key). As condições não são expressões JavaScript; são suportadas três formas de escrita, por ordem de prioridade:

  • Árvore de predicados (extras.predicate) — uma árvore booleana que encaminha para "true" / "false". Cada nó é um mapa com um type:
    • {"type": "fileExists", "paths": [...], "baseKey": "repo_local_path", "negate": false, "recursive": false} — verdadeiro quando qualquer dos caminhos listados existe no disco; negate inverte-o; recursive procura também em subdiretórios (ignorando .git, node_modules, build, .dart_tool). Os caminhos relativos são resolvidos em relação a state[baseKey] (predefinição repo_local_path) ou, na sua ausência, ao diretório do espaço de trabalho de cada execução.
    • {"type": "comparison", "left": "{{score}}", "op": "gt", "right": 80} — operadores equals, notEquals, contains, exists, notExists, gt, lt; lê o estado do pipeline, não o sistema de ficheiros.
    • {"type": "and" | "or", "of": [<predicate>, ...]} — grupos booleanos.
    • {"type": "not", "of": <predicate>} — negação.
  • Switch (extras.switchKey) — {"switchKey": "pr_class", "cases": [...], "default": "standard"}; encaminha para o primeiro caso que o valor de estado contenha sem distinção de maiúsculas e minúsculas ou, caso contrário, para default.
  • Comparação (as chaves de nível superior antigas extras.left / extras.op / extras.right) — equivalente a um predicado comparison, mantida para os modelos escritos antes da árvore.

As arestas que saem do nó transportam uma routeKey que tem de corresponder à chave devolvida.

Publica as constatações consolidadas como um comentário de revisão da PR através do cliente de PR do GitHub.

Chave de estado Descrição
repo_full_name owner/repo (obrigatório)
pr_number Número da PR — int ou cadeia numérica (obrigatório)
consolidated_findings Corpo do comentário (obrigatório)

Devolve StepResult.terminal com comment_review_id e commented_at no estado.

Publica uma mensagem num espaço de mensagens através de MessagingPort.

Chave de estado Descrição
space_id Espaço de destino (obrigatório)
content Corpo da mensagem, lido literalmente do estado (obrigatório)

Escreve posted_space_id e posted_at no estado.

Despacha uma equipa inteira em vez de um único agente e depois suspende-se até as execuções dos membros terminarem.

Campo de configuração Descrição
teamId Equipa com âmbito de espaço de trabalho a despachar (obrigatório; mutuamente exclusivo com agentId)
prompt Texto do objetivo; suporta substituição {{key}} (obrigatório)
dispatchMode allParallel (predefinição — uma tarefa por membro) ou manager (despachar apenas o líder para coordenar)
outputKey Chave de estado das saídas recolhidas dos membros
outputSchema, label, extras.spaceId Como em conversation.promptAgent

Portão de aprovação. Despacha o agente aprovador para uma conversa (modo de revisão) com o prompt do portão e um contrato de saída {decision, reason} e depois suspende-se até o aprovador submeter através de submit_output.

Campo de configuração Descrição
agentId O agente aprovador (obrigatório)
prompt Prompt do portão (opcional; na sua ausência é usado um predefinido)
outputKey Chave de estado da carga útil {decision, reason} recolhida
outputSchema Toma por predefinição um esquema de aprovação (decision: approved / rejected, reason)
label, extras.spaceId Como em conversation.promptAgent

O portão só resolve através de submit_output. BuiltInBodyKeys.humanGate, a biblioteca de tipos de nó e o texto do prompt de um dos modelos incorporados referem as ferramentas MCP approve_step e reject_step; nenhuma das ferramentas existe em qualquer pacote ou registo.

Remove worktrees isoladas obsoletas; escolhe o modo a partir da carga útil do disparador. Respeita o sinalizador dryRun da execução.

Chave de estado Descrição
ticket_id Liberta as worktrees desse ticket
repo_full_name + pr_number Liberta a worktree efémera do editor de PR
space_id Desmantela as worktrees e a pasta dessa conversa
(nenhuma das anteriores) Varre o espaço de trabalho: diretórios desaparecidos, espaços mortos, pastas de conversa órfãs

outputKey (opcional) recebe uma cadeia de resumo.

Map / fan-out sobre uma coleção de estado: processa o prompt uma vez por item e depois despacha o agente uma única vez com esses prompts concatenados (separadores ---) e suspende-se até essa execução terminar. Uma coleção vazia conclui-se de imediato com uma lista vazia. Não é um despacho paralelo por item.

Campo de configuração Descrição
agentId Agente a despachar por item (obrigatório)
prompt Prompt por item; suporta substituição {{key}} (obrigatório)
extras.iterableKey Chave de estado que contém a coleção (obrigatório)
extras.itemKey Chave à qual o item atual é associado no prompt (predefinição item)
outputKey Chave de estado da saída recolhida (um despacho, não uma lista)

Executa outro modelo de pipeline como subpasso aninhado (o subpipeline corre como uma execução filha com parentPipelineRunId definido). O passo suspende-se até o filho atingir um estado terminal e depois o estado final do filho é integrado sob outputKey.

Campo de configuração Descrição
extras.templateId Modelo filho a executar (obrigatório; um pipeline não pode chamar-se a si próprio)
inputKeys Chaves de estado copiadas para a carga útil do disparador do filho
outputKey Chave de estado do estado final integrado do filho

Indexação tree-sitter em segundo plano: percorre um repo, extrai símbolos/arestas em isolates de worker e ingere-os no grafo de código do espaço de trabalho. Transmite o progresso para a linha da execução do passo; suporta execução a seco. O tree-sitter e as respetivas gramáticas são nativos obrigatórios — uma dylib em falta significa que o servidor nunca arrancou, e uma gramática que não se resolva a meio da execução faz falhar o passo em vez de o ignorar.

Chave de estado Descrição
repo_id Repo a indexar (obrigatório)
repo_local_path Caminho local do checkout (obrigatório)

Diarização de falantes offline. Lê o áudio retido da reunião, agrupa-o em falantes (Person 1, Person 2, …), reetiqueta os segmentos da transcrição e reescreve o estado transcript; emite também diarization_spans para o passo paralelo meeting.updateTranscript. Deixa passar a transcrição inalterada quando não foi retido áudio ou os modelos não estão instalados.

Chave de estado Descrição
meeting_id Reunião a diarizar (obrigatório)

meeting.saveNotes, meeting.addActionItems, meeting.addDecisions

Secção intitulada “meeting.saveNotes, meeting.addActionItems, meeting.addDecisions”

Corpos determinísticos de persistência do resumo da reunião. Cada um lê meeting_id e a carga útil estruturada meeting_outcome do passo do agente ({summary, enhancedNotes, actionItems[], decisions[]}) a partir do estado e escreve a sua parte na sua própria tabela — notas, itens de ação ou decisões. As linhas guardadas anteriormente nunca são eliminadas numa execução degradada.

Chave do corpo Descrição
meeting.identifySpeakers Reconhecimento de falantes entre reuniões: compara os falantes diarizados com perfis de voz guardados e aplica automaticamente as correspondências com confiança. No-op sem perfis/embeddings.
meeting.updateTranscript Aplica as etiquetas Person N de diarization_spans e funde fragmentos por janela em turnos por falante, e depois persiste a transcrição limpa. No-op quando a diarização não produziu nada.
meeting.assemblePlayback Monta a faixa de reprodução misturada da reunião a partir dos WAV por canal retidos. No-op quando não foi retido áudio.
orchestration.markPhase No fim do DAG de trabalho, escreve sentinelas de falha para sub-tickets sem saída e muda a orquestração para synthesizing.
orchestration.persistDeliverable Escreve a saída da síntese no ticket principal, conclui-o, publica a entrega e marca a orquestração como completed.
orchestration.awaitApproval Portão de aprovação parcial: conclui-se de imediato quando a chave do nó do passo está no conjunto aprovado da orquestração; caso contrário, suspende-se até uma aprovação posterior o retomar. É o único corpo que define extras.awaitApproval, o que o isenta do tempo limite de 24 horas para passos suspensos.
messaging.createSpace Abre a conversa em que o resto do modelo trabalha, publicada sob o outputKey do passo (os incorporados usam pipeline_space_id). Uma lista repoIds vazia não faz nenhum checkout; extras['allRepos'] faz o checkout de todos os repos do espaço de trabalho. extras['createConversation'] (desativado por predefinição) abre também um fluxo com nome. Um passo de agente JUNTA-SE a uma sala e nunca abre nenhuma, pelo que um fan-out sem este nó falha no despacho — e ligá-lo uma vez significa que todo o fan-out partilha um único checkout em vez de aprovisionar um para cada.
prReview.finalize O último passo do modelo de revisão de PR: ordena os nós de revisão acumulados em agrupamentos de consenso e calcula o veredicto da PR.
skills.analyze Executa a análise à cadeia de abastecimento das skills instaladas num espaço de trabalho e regista cada veredicto.

O corpo de um passo devolve um StepResult (packages/cc_domain/lib/features/pipelines/domain/entities/step_result.dart). Todos os construtores aceitam um mapa opcional mutatedState integrado no estado do pipeline, exceto failed.

Construtor Assinatura Descrição
ok StepResult.ok({mutatedState}) Conclusão normal; os ouvintes a jusante são avaliados
route StepResult.route(nextRouterKey, {mutatedState}) Conclusão do router; seleciona o ramo a jusante cuja routeKey corresponde
suspendUntilEvent StepResult.suspendUntilEvent(eventType, {mutatedState}) Pausa até um tipo específico de evento de domínio disparar
suspendUntilTasksComplete StepResult.suspendUntilTasksComplete(taskIds, {mutatedState}) Pausa até todas as tarefas listadas atingirem o estado terminal
terminal StepResult.terminal({mutatedState}) A execução do pipeline terminou — não há mais passos
failed StepResult.failed(errorMessage) O passo falhou com uma mensagem de erro

Comportamento de nova tentativa para o corpo de um nó que falha (PipelineNodeConfig.retryPolicy; null desativa as novas tentativas).

Campo Tipo Descrição
maxAttempts int Total de tentativas, incluindo a primeira (3 = 1 tentativa + 2 novas tentativas); tem de ser ≥ 1
backoff String linear ou exponential (predefinição)
initialDelayMs int Atraso antes da primeira nova tentativa, em milissegundos; os atrasos seguintes crescem segundo backoff

Configuração por nó transportada dentro da definição do passo (packages/cc_domain/lib/features/pipelines/domain/entities/pipeline_node_config.dart). Tanto os nós incorporados como os personalizados partilham esta forma; cada corpo lê apenas os campos que lhe interessam.

Campo Tipo Descrição
prompt String? Modelo de prompt com substituição {{key}}
script String? Corpo de script bash com substituição {{key}}
agentId String? UUID do agente com âmbito de espaço de trabalho a despachar
inputKeys List<String> Chaves de estado que este nó consome como entrada
repoIds List<String> Repos que o nó messaging.createSpace faz checkout (<repoId> ou <repoId>@<branch>). Vazio = nenhum; extras['allRepos'] = todos os repos do espaço de trabalho. Ignorado nos passos de agente.
outputKey String? Chave de estado sob a qual é escrita a saída deste nó
label String? Etiqueta apresentada no canvas (predefinição: o ID do passo)
outputSchema Map<String, dynamic>? JSON Schema (subconjunto) que o valor de saída tem de satisfazer
reducer String? Estratégia de integração quando ramos paralelos escrevem o mesmo outputKey: append, mergeLists, mergeMaps, sum ou override (predefinição)
retryPolicy StepRetryPolicy? Política de novas tentativas em caso de falha do corpo; null = uma única tentativa
continueOnFail bool Quando verdadeiro, uma falha terminal não faz falhar a execução; o erro fica guardado em state['_stepErrors'][stepId]
timeoutMs int? Tempo limite em tempo real para o corpo do nó, em milissegundos; null = sem tempo limite
teamId String? Equipa a despachar (nós team.dispatch); mutuamente exclusivo com agentId
dispatchMode String? Modo de execução da equipa: allParallel ou manager
extras Map<String, dynamic> Configuração específica do corpo, de forma livre (por exemplo, iterableKey, templateId, árvores de condições)

Acessores derivados: spaceId (extras['spaceId'] — conversa existente onde trabalhar), createsConversation (extras['createConversation']), conversationTitle (extras['conversationTitle']), modeName (extras['mode'] — chat / review / plan, lido no nó que abre o espaço) e runWhen (extras['runWhen'] — ignora o nó quando não se verifica).