Aller au contenu

Étapes de pipeline

Une étape de pipeline comporte deux axes indépendants :

  • Type (StepKind) — la façon dont le moteur planifie l’étape par rapport à ses déclencheurs.
  • Clé de corps (BuiltInBodyKeys) — la fermeture de corps enregistrée qui 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 aucun travail en soi ; ce qui démarre l’exécution (manuel / événement / planification) est suivi séparément dans des lignes PipelineTrigger. Son corps est le corps sans effet pipeline.trigger.
listen Se déclenche lorsque toutes les étapes sources (StepTrigger.sourceStepIds) sont terminées.
join Se déclenche lorsque toutes les étapes de waitForStepIds atteignent un état terminal.
router Embranchement conditionnel — le corps retourne StepResult.route(key), ce qui ne déclenche que l’arête en aval dont la clé routeKey correspond ; les branches non sélectionnées sont marquées comme ignorées.
forEach Planifiée comme listen (se déclenche lorsque toutes les étapes de sourceStepIds sont terminées). C’est le corps enregistré flow.forEach 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.

Fermetures 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, tous répertoriés ci-dessous. La configuration est lue dans le PipelineNodeConfig de l’étape (voir ci-dessous) sauf indication contraire ; 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 sans effet pipeline.trigger ; une étape terminal n’a pas de corps.

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

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

Champ de configuration Description
script Commande shell à exécuter (obligatoire)
outputKey Clé d’état qui reçoit la sortie standard débarrassée des espaces de début et de fin en cas de sortie 0

S’exécute avec le répertoire de travail <cc_root>/pipelines/<pipelineRunId>/ (partagé par toutes les étapes bash d’une exécution) et 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 la sortie d’erreur (stderr).

Nœud générique de prompt et de lancement : rend le prompt, lance l’agent dans une conversation et suspend jusqu’à ce que l’exécution se termine. La charge utile submit_output de l’agent est recueillie dans l’état sous outputKey.

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

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

  • Arbre de prédicats (extras.predicate) — un arbre booléen qui achemine vers "true" ou "false". Chaque nœud est une map avec un type :
    • {"type": "fileExists", "paths": [...], "baseKey": "repo_local_path", "negate": false, "recursive": false} — vrai lorsqu’un des chemins listés existe sur le disque ; negate l’inverse ; recursive fouille 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.
  • Switch (extras.switchKey) — {"switchKey": "pr_class", "cases": [...], "default": "standard"} ; achemine vers le premier cas que la valeur d’état contient sans tenir compte de la casse, sinon vers default.
  • Comparaison (champs extras.left / extras.op / extras.right hérités du premier niveau) — équivalent à un prédicat comparison, conservé pour les modèles rédigés avant l’arbre.

Les arêtes qui sortent du nœud portent une clé routeKey qui doit correspondre à la clé retournée.

Publie les constats consolidés sous forme de commentaire de révision de PR par l’entremise du client de 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)

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

Publie un message dans un espace de messagerie par l’entremise de 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 jusqu’à ce que les exécutions des membres se terminent.

Champ de configuration Description
teamId Équipe propre à l’espace de travail à lancer (obligatoire ; mutuellement exclusive avec 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 pour coordonner)
outputKey Clé d’état des sorties recueillies des membres
outputSchema, label, extras.spaceId Comme pour conversation.promptAgent

Porte d’approbation. Lance l’agent approbateur dans une conversation (mode review) avec le prompt de la porte et un contrat de sortie {decision, reason}, puis suspend jusqu’à ce que l’approbateur soumette par submit_output.

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

La porte ne se résout 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é nomment tous les outils MCP approve_step et reject_step ; aucun de ces deux outils n’existe dans un paquet ou un registre.

Supprime les arbres de travail isolés périmés ; 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étruit 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 conversation orphelins

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

Application / déploiement 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 (séparateurs ---) et suspend jusqu’à ce que cette exécution se termine. Une collection vide se termine immédiatement avec une liste vide. Ce n’est pas 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 recueillie (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 exécution enfant avec parentPipelineRunId défini). L’étape suspend 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 isolats de workers et les intègre au 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 bibliothèques natives obligatoires — 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 plutôt que de l’ignorer.

Clé d’état Description
repo_id Dépôt à indexer (obligatoire)
repo_local_path Chemin local de la copie de dépôt (obligatoire)

Diarisation hors ligne des locuteurs. Lit l’audio conservé de la réunion, le regroupe par locuteur (Person 1, Person 2, …), réétiquette les segments de transcription et réécrit l’état transcript ; émet aussi diarization_spans pour l’étape parallèle meeting.updateTranscript. Laisse la transcription intacte 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 résumé 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 et écrit sa partie dans sa propre table — notes, tâches 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 : compare les locuteurs diarisés aux profils vocaux enregistrés et applique automatiquement les correspondances sûres. Sans effet en l’absence de profils ou d’embeddings.
meeting.updateTranscript Applique les étiquettes Person N issues de diarization_spans et fusionne les fragments par fenêtre en tours par locuteur, puis conserve 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 fichiers 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 Porte 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 suspend jusqu’à ce qu’une approbation ultérieure la 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 l’outputKey de l’étape (les modèles intégrés utilisent pipeline_space_id). Une liste repoIds vide n’extrait aucun dépôt ; extras['allRepos'] extrait tous les dépôts de l’espace de travail. extras['createConversation'] (désactivé par défaut) ouvre en plus un fil nommé. Une étape d’agent REJOINT un salon et n’en ouvre jamais un, de sorte qu’un éventail sans ce nœud échoue au lancement — et le câbler une fois fait en sorte que tout l’éventail partage une seule copie de dépôt au lieu d’en provisionner une par agent.
prReview.finalize La dernière étape du modèle de révision de PR : classe les nœuds de révision accumulés dans des catégories de consensus et calcule le verdict par PR.
skills.analyze Exécute l’analyse de la chaîne d’approvisionnement des skills sur les skills installés d’un espace de travail et consigne chaque verdict.

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

Fabrique Signature Description
ok StepResult.ok({mutatedState}) Achèvement normal ; les écouteurs en aval sont évalués
route StepResult.route(nextRouterKey, {mutatedState}) Achèvement du routeur ; sélectionne la branche en aval dont la clé 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 ; une valeur nulle 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 de l’étape (packages/cc_domain/lib/features/pipelines/domain/entities/pipeline_node_config.dart). Les nœuds intégrés et 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 d’agent propre à l’espace de travail à lancer
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 extrait (<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é lisible affiché sur le canevas (par défaut, l’ID 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 ; nulle = 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 ; nul = aucun délai d’expiration
teamId String? Équipe à lancer (nœuds team.dispatch) ; mutuellement exclusive avec agentId
dispatchMode String? Mode d’exécution de l’équipe : allParallel ou manager
extras Map<String, dynamic> Configuration libre propre au corps (p. 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 d’ouverture d’espace) et runWhen (extras['runWhen'] — ignore le nœud lorsque la condition n’est pas remplie).