Aller au contenu

Étapes de pipeline

Une étape de pipeline possède deux axes indépendants :

  • Type (StepKind) — comment le moteur ordonnance l’étape par rapport à ses déclencheurs.
  • Clé de corps (BuiltInBodyKeys) — quelle closure de corps enregistrée exécute le travail de l’étape.

Définis dans packages/cc_domain/lib/features/pipelines/domain/entities/step_kind.dart.

Type Sémantique d’ordonnancement
trigger Point d’entrée obligatoire — exactement un par modèle, toujours le premier nœud. Ne fait lui-même aucun travail ; ce qui démarre l’exécution (manuel / événement / planification) est suivi séparément par les lignes PipelineTrigger. Son corps est le pipeline.trigger sans effet.
listen Se déclenche lorsque toutes les étapes sources (dans StepTrigger.sourceStepIds) sont terminées.
join Se déclenche lorsque toutes les étapes de waitForStepIds atteignent un état terminal.
router Branchement conditionnel — le corps renvoie StepResult.route(key), ce qui ne déclenche que l’arête en aval dont le routeKey correspond ; les branches non sélectionnées sont marquées comme ignorées.
forEach Ordonnancé comme listen (se déclenche lorsque tous les sourceStepIds sont terminés). C’est le corps flow.forEach enregistré qui itère ; le moteur ne crée pas une étape enfant par élément.
terminal Sentinelle de l’ordonnanceur, sans corps. L’exécution du pipeline se termine lorsqu’une étape terminale se termine.

Closures de corps enregistrées, indexées par les constantes de BuiltInBodyKeys (packages/cc_domain/lib/features/pipelines/domain/templates/builtin_template_seeds.dart). Il y en a 25, toutes listées ci-dessous. Sauf mention contraire, la configuration est lue dans le PipelineNodeConfig de l’étape (voir ci-dessous) ; les « clés d’état » sont lues dans l’état du pipeline ou la charge utile du déclencheur au moment de l’exécution.

Une étape dont le bodyKey n’est pas enregistré produit un avertissement au chargement du modèle, puis ne s’exécute jamais — elle ne fait pas échouer l’exécution. Les étapes trigger et terminal sont exemptées de cet avertissement : le déclencheur exécute quand même le corps pipeline.trigger sans effet ; une étape terminal n’a pas de corps.

Corps sans effet du nœud trigger obligatoire — se termine immédiatement pour que le moteur déclenche les écouteurs en aval. Aucune configuration.

Étape shell sans agent (bash -c). Les espaces réservés {{key}} du script sont remplacés par des valeurs échappées pour le shell, issues de l’état et de la charge utile du déclencheur.

Champ de configuration Description
script Commande shell à exécuter (obligatoire)
outputKey Clé d’état qui reçoit la sortie standard élaguée lorsque le code de sortie est 0

S’exécute avec pour répertoire courant <cc_root>/pipelines/<pipelineRunId>/ (partagé par toutes les étapes bash d’une même exécution) et avec GITHUB_TOKEN dans l’environnement. Écrit aussi <stepId>_runDir dans l’état. Un code de sortie non nul fait échouer l’étape, avec la fin de stderr comme message d’erreur.

Nœud générique de prompt et de lancement : il rend le prompt, lance l’agent dans une conversation et suspend l’étape jusqu’à la fin de l’exécution. La charge utile submit_output de l’agent est récupérée dans l’état sous outputKey.

Champ de configuration Description
agentId UUID de l’agent à lancer, limité à l’espace de travail (obligatoire)
prompt Texte d’instruction ; prend en charge la substitution {{key}} (obligatoire)
outputKey Clé d’état de la sortie récupérée
outputSchema Schéma JSON auquel la sortie doit satisfaire
label Libellé destiné aux humains, affiché sur le canevas
extras.spaceId Conversation existante dans laquelle travailler (par défaut : une conversation masquée)
extras.conversationTitle Flux nommé à l’intérieur de cet espace (facultatif)
extras.mode Lu sur le nœud qui ouvre la salle (messaging.createSpace), pas ici. Valeur par défaut review.

Corps de routeur — lit config.extras, évalue une condition et renvoie StepResult.route(key). Les conditions ne sont pas des expressions JavaScript ; trois formes d’écriture sont prises en charge, par ordre de priorité :

  • Arbre de prédicats (extras.predicate) — un arbre booléen qui aiguille vers "true" / "false". Chaque nœud est une map dotée d’un type :
    • {"type": "fileExists", "paths": [...], "baseKey": "repo_local_path", "negate": false, "recursive": false} — vrai lorsqu’au moins un des chemins listés existe sur le disque ; negate inverse le résultat ; recursive explore aussi les sous-répertoires (en ignorant .git, node_modules, build, .dart_tool). Les chemins relatifs sont résolus par rapport à state[baseKey] (par défaut repo_local_path), sinon par rapport au répertoire d’espace de travail propre à l’exécution.
    • {"type": "comparison", "left": "{{score}}", "op": "gt", "right": 80} — opérateurs equals, notEquals, contains, exists, notExists, gt, lt ; lit l’état du pipeline, pas le système de fichiers.
    • {"type": "and" | "or", "of": [<predicate>, ...]} — groupes booléens.
    • {"type": "not", "of": <predicate>} — négation.
  • Aiguillage (extras.switchKey) — {"switchKey": "pr_class", "cases": [...], "default": "standard"} ; aiguille vers le premier cas que la valeur d’état contient sans tenir compte de la casse, sinon vers default.
  • Comparaison (anciennes clés de premier niveau extras.left / extras.op / extras.right) — équivalent à un prédicat comparison, conservé pour les modèles écrits avant l’arbre.

Les arêtes sortantes du nœud portent un routeKey qui doit correspondre à la clé renvoyée.

Publie les remarques consolidées sous forme de commentaire de revue de PR via le client PR GitHub.

Clé d’état Description
repo_full_name owner/repo (obligatoire)
pr_number Numéro de PR — entier ou chaîne numérique (obligatoire)
consolidated_findings Corps du commentaire (obligatoire)

Renvoie StepResult.terminal, avec comment_review_id et commented_at dans l’état.

Publie un message dans un espace de messagerie via MessagingPort.

Clé d’état Description
space_id Espace cible (obligatoire)
content Corps du message, lu tel quel dans l’état (obligatoire)

Écrit posted_space_id et posted_at dans l’état.

Lance une équipe entière plutôt qu’un seul agent, puis suspend l’étape jusqu’à la fin des exécutions des membres.

Champ de configuration Description
teamId Équipe à lancer, limitée à l’espace de travail (obligatoire ; exclusive de agentId)
prompt Texte de l’objectif ; prend en charge la substitution {{key}} (obligatoire)
dispatchMode allParallel (par défaut — une tâche par membre) ou manager (ne lancer que le chef de file pour coordonner)
outputKey Clé d’état des sorties récupérées des membres
outputSchema, label, extras.spaceId Comme pour conversation.promptAgent

Portail d’approbation. Lance l’agent approbateur dans une conversation (mode review) avec le prompt du portail et un contrat de sortie {decision, reason}, puis suspend l’étape jusqu’à ce que l’approbateur soumette sa décision via submit_output.

Champ de configuration Description
agentId L’agent approbateur (obligatoire)
prompt Prompt du portail (facultatif ; une valeur par défaut est utilisée en son absence)
outputKey Clé d’état de la charge utile {decision, reason} récupérée
outputSchema Par défaut, un schéma d’approbation (decision : approved / rejected, reason)
label, extras.spaceId Comme pour conversation.promptAgent

Le portail ne se dénoue que par submit_output. BuiltInBodyKeys.humanGate, la bibliothèque de types de nœuds et le texte de prompt d’un modèle intégré citent tous les outils MCP approve_step et reject_step ; aucun de ces deux outils n’existe dans le moindre paquet ni registre.

Supprime les arbres de travail isolés obsolètes ; choisit son mode à partir de la charge utile du déclencheur. Respecte l’indicateur dryRun de l’exécution.

Clé d’état Description
ticket_id Libère les arbres de travail de ce ticket
repo_full_name + pr_number Libère l’arbre de travail éphémère de l’éditeur de PR
space_id Démonte les arbres de travail et le dossier de cette conversation
(aucune des clés ci-dessus) Balaye l’espace de travail : répertoires disparus, espaces morts, dossiers de conversations orphelines

outputKey (facultatif) reçoit une chaîne de résumé.

Map / éclatement en éventail sur une collection d’état : rend le prompt une fois par élément, puis lance l’agent une seule fois avec ces prompts concaténés (--- comme séparateurs) et suspend l’étape jusqu’à la fin de cette exécution. Une collection vide se termine immédiatement avec une liste vide. Il ne s’agit pas d’un lancement parallèle par élément.

Champ de configuration Description
agentId Agent à lancer pour chaque élément (obligatoire)
prompt Prompt par élément ; prend en charge la substitution {{key}} (obligatoire)
extras.iterableKey Clé d’état contenant la collection (obligatoire)
extras.itemKey Clé à laquelle l’élément courant est lié dans le prompt (par défaut item)
outputKey Clé d’état de la sortie récupérée (un seul lancement, pas une liste)

Exécute un autre modèle de pipeline comme sous-étape imbriquée (le sous-pipeline s’exécute comme une exécution enfant, avec parentPipelineRunId défini). L’étape est suspendue jusqu’à ce que l’enfant atteigne un état terminal, puis l’état final de l’enfant est fusionné sous outputKey.

Champ de configuration Description
extras.templateId Modèle enfant à exécuter (obligatoire ; un pipeline ne peut pas s’appeler lui-même)
inputKeys Clés d’état copiées dans la charge utile du déclencheur de l’enfant
outputKey Clé d’état de l’état final fusionné de l’enfant

Indexation tree-sitter en arrière-plan : parcourt un dépôt, extrait les symboles et les arêtes dans des isolates de worker et les ingère dans le graphe de code de l’espace de travail. Diffuse la progression dans la ligne d’exécution de l’étape ; prend en charge l’exécution à blanc. Tree-sitter et ses grammaires sont des natifs requis — une dylib manquante signifie que le serveur n’a jamais démarré, et une grammaire qui ne se résout pas en cours d’exécution fait échouer l’étape au lieu d’être ignorée.

Clé d’état Description
repo_id Dépôt à indexer (obligatoire)
repo_local_path Chemin local du checkout (obligatoire)

Diarisation hors ligne des locuteurs. Lit l’audio conservé de la réunion, le regroupe en locuteurs (Person 1, Person 2, …), réétiquette les segments de la transcription et réécrit l’état transcript ; émet aussi diarization_spans pour l’étape parallèle meeting.updateTranscript. Laisse la transcription inchangée lorsqu’aucun audio n’a été conservé ou que les modèles ne sont pas installés.

Clé d’état Description
meeting_id Réunion à diariser (obligatoire)

meeting.saveNotes, meeting.addActionItems, meeting.addDecisions

Section intitulée « meeting.saveNotes, meeting.addActionItems, meeting.addDecisions »

Corps déterministes de persistance du compte rendu de réunion. Chacun lit meeting_id et la charge utile structurée meeting_outcome de l’étape d’agent ({summary, enhancedNotes, actionItems[], decisions[]}) dans l’état, puis écrit sa partie dans sa propre table — notes, actions à mener ou décisions. Les lignes déjà enregistrées ne sont jamais effacées lors d’une exécution dégradée.

Clé de corps Description
meeting.identifySpeakers Reconnaissance des locuteurs entre réunions : met en correspondance les locuteurs diarisés avec les profils vocaux enregistrés et applique automatiquement les correspondances fiables. Sans effet en l’absence de profils ou d’empreintes vocales.
meeting.updateTranscript Applique les libellés Person N issus de diarization_spans et fusionne les fragments par fenêtre en tours par locuteur, puis enregistre la transcription nettoyée. Sans effet lorsque la diarisation n’a rien produit.
meeting.assemblePlayback Assemble la piste de lecture mixée de la réunion à partir des WAV par canal conservés. Sans effet lorsqu’aucun audio n’a été conservé.
orchestration.markPhase À la fin du DAG de travail, écrit des sentinelles d’échec pour les sous-tickets sans sortie et fait passer l’orchestration à synthesizing.
orchestration.persistDeliverable Écrit la sortie de synthèse dans le ticket parent, le termine, publie le livrable et marque l’orchestration comme completed.
orchestration.awaitApproval Portail d’approbation partielle : se termine immédiatement lorsque la clé de nœud de l’étape figure dans l’ensemble approuvé de l’orchestration, sinon reste suspendu jusqu’à ce qu’une approbation ultérieure le reprenne. C’est le seul corps qui définit extras.awaitApproval, ce qui l’exempte du délai d’expiration de 24 heures des étapes suspendues.
messaging.createSpace Ouvre la conversation dans laquelle travaille le reste du modèle, publiée sous la clé outputKey de l’étape (les corps intégrés utilisent pipeline_space_id). Une liste repoIds vide ne récupère aucun dépôt ; extras['allRepos'] récupère tous les dépôts de l’espace de travail. extras['createConversation'] (désactivé par défaut) ouvre aussi un flux nommé. Une étape d’agent REJOINT une salle et n’en ouvre jamais, donc un éclatement en éventail sans ce nœud échoue au lancement — et le câbler une fois signifie que tout l’éclatement partage un seul checkout au lieu d’en provisionner un pour chacun.
prReview.finalize Dernière étape du modèle de revue de PR : trie les nœuds de revue accumulés en catégories de consensus et calcule le verdict de chaque PR.
skills.analyze Exécute l’analyse de chaîne d’approvisionnement des skills sur les skills installés d’un espace de travail et enregistre chaque verdict.

Un corps d’étape renvoie un StepResult (packages/cc_domain/lib/features/pipelines/domain/entities/step_result.dart). Toutes les fabriques acceptent une map mutatedState facultative fusionnée dans l’état du pipeline, sauf failed.

Fabrique Signature Description
ok StepResult.ok({mutatedState}) Terminaison normale ; les écouteurs en aval sont évalués
route StepResult.route(nextRouterKey, {mutatedState}) Terminaison du routeur ; sélectionne la branche en aval dont le routeKey correspond
suspendUntilEvent StepResult.suspendUntilEvent(eventType, {mutatedState}) Met en pause jusqu’à ce qu’un type d’événement de domaine précis se produise
suspendUntilTasksComplete StepResult.suspendUntilTasksComplete(taskIds, {mutatedState}) Met en pause jusqu’à ce que toutes les tâches listées atteignent un état terminal
terminal StepResult.terminal({mutatedState}) L’exécution du pipeline est terminée — plus aucune étape
failed StepResult.failed(errorMessage) L’étape a échoué avec un message d’erreur

Comportement de nouvelle tentative pour un corps de nœud en échec (PipelineNodeConfig.retryPolicy ; null désactive les nouvelles tentatives).

Champ Type Description
maxAttempts int Nombre total de tentatives, la première comprise (3 = 1 essai + 2 nouvelles tentatives) ; doit être ≥ 1
backoff String linear ou exponential (par défaut)
initialDelayMs int Délai avant la première nouvelle tentative, en millisecondes ; les délais suivants augmentent selon backoff

Configuration propre à chaque nœud, portée à l’intérieur de la définition d’étape (packages/cc_domain/lib/features/pipelines/domain/entities/pipeline_node_config.dart). Les nœuds intégrés comme les nœuds personnalisés partagent cette forme ; chaque corps ne lit que les champs qui le concernent.

Champ Type Description
prompt String? Modèle de prompt avec substitution {{key}}
script String? Corps de script bash avec substitution {{key}}
agentId String? UUID de l’agent à lancer, limité à l’espace de travail
inputKeys List<String> Clés d’état que ce nœud consomme en entrée
repoIds List<String> Dépôts que le nœud messaging.createSpace récupère (<repoId> ou <repoId>@<branch>). Vide = aucun ; extras['allRepos'] = tous les dépôts de l’espace de travail. Ignoré sur les étapes d’agent.
outputKey String? Clé d’état sous laquelle la sortie de ce nœud est écrite
label String? Libellé destiné aux humains, affiché sur le canevas (par défaut, l’identifiant de l’étape)
outputSchema Map<String, dynamic>? Schéma JSON (sous-ensemble) auquel la valeur de sortie doit satisfaire
reducer String? Stratégie de fusion lorsque des branches parallèles écrivent la même outputKey : append, mergeLists, mergeMaps, sum ou override (par défaut)
retryPolicy StepRetryPolicy? Politique de nouvelle tentative en cas d’échec du corps ; null = une seule tentative
continueOnFail bool Lorsque vrai, un échec terminal ne fait pas échouer l’exécution ; l’erreur est rangée sous state['_stepErrors'][stepId]
timeoutMs int? Délai d’expiration en temps réel du corps du nœud, en millisecondes ; null = aucun délai
teamId String? Équipe à lancer (nœuds team.dispatch) ; exclusive de agentId
dispatchMode String? Mode d’exécution de l’équipe : allParallel ou manager
extras Map<String, dynamic> Configuration propre au corps, de forme libre (par ex. iterableKey, templateId, arbres de conditions)

Accesseurs dérivés : spaceId (extras['spaceId'] — conversation existante dans laquelle travailler), createsConversation (extras['createConversation']), conversationTitle (extras['conversationTitle']), modeName (extras['mode'] — chat / review / plan, lu sur le nœud qui ouvre l’espace) et runWhen (extras['runWhen'] — ignore le nœud lorsque la condition n’est pas remplie).