Кроки конвеєра
Крок конвеєра має дві незалежні осі:
- Тип (
StepKind) — як рушій планує крок відносно його тригерів. - Ключ тіла (
BuiltInBodyKeys) — яке зареєстроване замикання тіла виконує роботу кроку.
Типи кроків
Розділ із назвою «Типи кроків»Визначено в packages/cc_domain/lib/features/pipelines/domain/entities/step_kind.dart.
| Тип | Семантика планування |
|---|---|
trigger |
Обов’язкова точка входу — рівно один на шаблон, завжди перший вузол. Сам нічого не робить; те, що починає запуск (вручну / подія / розклад), відстежується окремо як рядки PipelineTrigger. Його тіло — порожнє pipeline.trigger. |
listen |
Спрацьовує, коли завершуються всі вихідні кроки (у StepTrigger.sourceStepIds). |
join |
Спрацьовує, коли всі кроки в waitForStepIds досягають кінцевого стану. |
router |
Умовне розгалуження — тіло повертає StepResult.route(key), що запускає лише те ребро нижче за потоком, чий routeKey збігається; невибрані гілки позначаються як пропущені. |
forEach |
Планується як listen (спрацьовує, коли завершуються всі sourceStepIds). Ітерує саме зареєстроване тіло flow.forEach; рушій не створює окремий дочірній крок на кожен елемент. |
terminal |
Сторожовий елемент планувальника без тіла. Запуск конвеєра завершується, коли завершується термінальний крок. |
Тіла кроків
Розділ із назвою «Тіла кроків»Зареєстровані замикання тіл, ключовані константами в BuiltInBodyKeys (packages/cc_domain/lib/features/pipelines/domain/templates/builtin_template_seeds.dart). Їх 25, усі наведені нижче. Конфігурацію читають з PipelineNodeConfig кроку (див. нижче), якщо не зазначено інше; «ключі стану» читаються зі стану конвеєра / корисного навантаження тригера під час виконання.
Крок, чий bodyKey не зареєстровано, видає попередження під час завантаження шаблону і потім ніколи не виконується — він не провалює запуск. Кроки trigger і terminal звільнені від цього попередження: тригер усе одно виконує порожнє тіло pipeline.trigger; крок terminal не має тіла.
pipeline.trigger
Розділ із назвою «pipeline.trigger»Порожнє тіло обов’язкового вузла trigger — завершується негайно, щоб рушій розійшовся на слухачів нижче за потоком. Без конфігурації.
pipeline.bashScript
Розділ із назвою «pipeline.bashScript»Крок оболонки без агента (bash -c). Плейсхолдери {{key}} у скрипті підставляються зі стану + корисного навантаження тригера зі значеннями, екранованими для оболонки.
| Поле конфігурації | Опис |
|---|---|
script |
Команда оболонки для виконання (обов’язково) |
outputKey |
Ключ стану, що отримує обрізаний stdout при виході 0 |
Виконується з cwd <cc_root>/pipelines/<pipelineRunId>/ (спільним для всіх bash-кроків у запуску) і GITHUB_TOKEN у середовищі. Також записує <stepId>_runDir до стану. Ненульовий код виходу провалює крок із хвостом stderr.
conversation.promptAgent
Розділ із назвою «conversation.promptAgent»Загальний вузол «промпт і запуск»: рендерить промпт, запускає агента в розмову й призупиняється, доки запуск не завершиться. Корисне навантаження submit_output агента збирається до стану під outputKey.
| Поле конфігурації | Опис |
|---|---|
agentId |
UUID агента в межах робочого простору для запуску (обов’язково) |
prompt |
Текст інструкції; підтримує підстановку {{key}} (обов’язково) |
outputKey |
Ключ стану для зібраного виводу |
outputSchema |
JSON Schema, якій має відповідати вивід |
label |
Зрозуміла людині назва, яка показується на полотні |
extras.spaceId |
Наявна розмова, у якій працювати (типово: прихована) |
extras.conversationTitle |
Іменований потік усередині цього простору (необов’язково) |
extras.mode |
Читається на вузлі, який відкриває кімнату (messaging.createSpace), а не тут. Типово review. |
pipeline.condition
Розділ із назвою «pipeline.condition»Тіло маршрутизатора — читає config.extras, обчислює умову й повертає StepResult.route(key). Умови — не вирази JavaScript; підтримуються три форми написання, у порядку пріоритету:
- Дерево предикатів (
extras.predicate) — булеве дерево, що спрямовує до"true"/"false". Кожен вузол — це мапа зtype:{"type": "fileExists", "paths": [...], "baseKey": "repo_local_path", "negate": false, "recursive": false}— істинний, коли будь-який із перелічених шляхів існує на диску;negateінвертує його;recursiveтакож шукає в підкаталогах (пропускаючи.git,node_modules,build,.dart_tool). Відносні шляхи розв’язуються відносноstate[baseKey](типовоrepo_local_path), інакше — каталогу робочого простору для цього запуску.{"type": "comparison", "left": "{{score}}", "op": "gt", "right": 80}— операториequals,notEquals,contains,exists,notExists,gt,lt; читає стан конвеєра, а не файлову систему.{"type": "and" | "or", "of": [<predicate>, ...]}— булеві групи.{"type": "not", "of": <predicate>}— заперечення.
- Перемикач (
extras.switchKey) —{"switchKey": "pr_class", "cases": [...], "default": "standard"}; спрямовує до першого випадку, який значення стану містить без урахування регістру, інакше — доdefault. - Порівняння (застарілі
extras.left/extras.op/extras.rightверхнього рівня) — еквівалент предикатаcomparison, збережений для шаблонів, написаних до появи дерева.
Ребра, що виходять із вузла, несуть routeKey, який має збігатися з повернутим ключем.
prReview.comment
Розділ із назвою «prReview.comment»Публікує зведені знахідки як коментар до рев’ю PR через клієнт PR GitHub.
| Ключ стану | Опис |
|---|---|
repo_full_name |
owner/repo (обов’язково) |
pr_number |
Номер PR — int або рядок із цифр (обов’язково) |
consolidated_findings |
Тіло коментаря (обов’язково) |
Повертає StepResult.terminal з comment_review_id і commented_at у стані.
messaging.postSpace
Розділ із назвою «messaging.postSpace»Публікує повідомлення до простору обміну повідомленнями через MessagingPort.
| Ключ стану | Опис |
|---|---|
space_id |
Цільовий простір (обов’язково) |
content |
Тіло повідомлення, що читається зі стану дослівно (обов’язково) |
Записує posted_space_id і posted_at до стану.
team.dispatch
Розділ із назвою «team.dispatch»Запускає цілу команду замість одного агента, а потім призупиняється, доки не завершаться запуски її учасників.
| Поле конфігурації | Опис |
|---|---|
teamId |
Команда в межах робочого простору для запуску (обов’язково; взаємовиключно з agentId) |
prompt |
Текст цілі; підтримує підстановку {{key}} (обов’язково) |
dispatchMode |
allParallel (типово — одне завдання на учасника) або manager (запускається лише лідер для координації) |
outputKey |
Ключ стану для зібраних виводів учасників |
outputSchema, label, extras.spaceId |
Як для conversation.promptAgent |
human.gate
Розділ із назвою «human.gate»Шлюз схвалення. Запускає агента-схвалювача в розмову (режим рев’ю) з промптом шлюзу й контрактом виводу {decision, reason}, а потім призупиняється, доки схвалювач не подасть результат через submit_output.
| Поле конфігурації | Опис |
|---|---|
agentId |
Агент-схвалювач (обов’язково) |
prompt |
Промпт шлюзу (необов’язково; за відсутності використовується типовий) |
outputKey |
Ключ стану для зібраного навантаження {decision, reason} |
outputSchema |
Типово — схема схвалення (decision: approved / rejected, reason) |
label, extras.spaceId |
Як для conversation.promptAgent |
Шлюз розв’язується лише через submit_output. BuiltInBodyKeys.humanGate, бібліотека типів вузлів і текст промпту одного з вбудованих шаблонів називають інструменти MCP approve_step і reject_step; жодного з цих інструментів не існує в жодному пакеті чи реєстрі.
repos.cleanup
Розділ із назвою «repos.cleanup»Прибирає застарілі ізольовані worktree; обирає свій режим із корисного навантаження тригера. Зважає на прапорець dryRun запуску.
| Ключ стану | Опис |
|---|---|
ticket_id |
Звільняє worktree цього тікета |
repo_full_name + pr_number |
Звільняє тимчасовий worktree редактора PR |
space_id |
Знищує worktree і теку цієї розмови |
| (нічого з переліченого) | Прибирає в робочому просторі: зниклі каталоги, мертві простори, осиротілі теки розмов |
outputKey (необов’язково) отримує рядок-підсумок.
flow.forEach
Розділ із назвою «flow.forEach»Відображення / розгалуження над колекцією зі стану: рендерить промпт по одному разу на елемент, потім запускає агента один раз із цими промптами, з’єднаними (--- як роздільники), і призупиняється, доки цей запуск не завершиться. Порожня колекція завершується негайно з порожнім списком. Це не окремий паралельний запуск на кожен елемент.
| Поле конфігурації | Опис |
|---|---|
agentId |
Агент для запуску на кожен елемент (обов’язково) |
prompt |
Промпт для кожного елемента; підтримує підстановку {{key}} (обов’язково) |
extras.iterableKey |
Ключ стану, що містить колекцію (обов’язково) |
extras.itemKey |
Ключ, до якого прив’язується поточний елемент у промпті (типово item) |
outputKey |
Ключ стану для зібраного виводу (один запуск, не список) |
flow.callPipeline
Розділ із назвою «flow.callPipeline»Запускає інший шаблон конвеєра як вкладений підкрок (підконвеєр виконується як дочірній запуск із заданим parentPipelineRunId). Крок призупиняється, доки дочірній запуск не досягне кінцевого стану, після чого фінальний стан дочірнього зливається під outputKey.
| Поле конфігурації | Опис |
|---|---|
extras.templateId |
Дочірній шаблон для запуску (обов’язково; конвеєр не може викликати сам себе) |
inputKeys |
Ключі стану, скопійовані в корисне навантаження тригера дочірнього |
outputKey |
Ключ стану для злитого фінального стану дочірнього |
code.index
Розділ із назвою «code.index»Фонове індексування tree-sitter: обходить репозиторій, видобуває символи/ребра в ізолятах воркерів і завантажує їх до графа коду робочого простору. Передає прогрес потоком у рядок запуску кроку; підтримує dry-run. Tree-sitter та його граматики — обов’язкові нативні залежності: відсутня dylib означає, що сервер узагалі не завантажився, а граматика, яку не вдається розв’язати посеред запуску, провалює крок, а не пропускає його.
| Ключ стану | Опис |
|---|---|
repo_id |
Репозиторій для індексування (обов’язково) |
repo_local_path |
Локальний шлях чекауту (обов’язково) |
meeting.diarize
Розділ із назвою «meeting.diarize»Офлайнова діаризація спікерів. Читає збережене аудіо зустрічі, кластеризує його на спікерів (Person 1, Person 2, …), перепозначає сегменти транскрипту й переписує стан transcript; також видає diarization_spans для паралельного кроку meeting.updateTranscript. Пропускає транскрипт без змін, коли аудіо не збереглося або моделі не встановлено.
| Ключ стану | Опис |
|---|---|
meeting_id |
Зустріч для діаризації (обов’язково) |
meeting.saveNotes, meeting.addActionItems, meeting.addDecisions
Розділ із назвою «meeting.saveNotes, meeting.addActionItems, meeting.addDecisions»Детерміновані тіла збереження підсумку зустрічі. Кожне читає зі стану meeting_id і структуроване навантаження meeting_outcome кроку-агента ({summary, enhancedNotes, actionItems[], decisions[]}) і записує свою частину до власної таблиці — нотатки, пункти дій або рішення. Раніше збережені рядки ніколи не стираються під час деградованого запуску.
Інші вбудовані тіла
Розділ із назвою «Інші вбудовані тіла»| Ключ тіла | Опис |
|---|---|
meeting.identifySpeakers |
Розпізнавання спікерів між зустрічами: зіставляє діаризованих спікерів зі збереженими голосовими профілями й автоматично застосовує впевнені збіги. Порожня дія без профілів/ембедингів. |
meeting.updateTranscript |
Застосовує позначки Person N з diarization_spans і зливає фрагменти за вікнами в репліки кожного спікера, після чого зберігає очищений транскрипт. Порожня дія, коли діаризація нічого не дала. |
meeting.assemblePlayback |
Збирає змішану доріжку відтворення зустрічі зі збережених поканальних WAV. Порожня дія, коли аудіо не збереглося. |
orchestration.markPhase |
Наприкінці DAG роботи записує сторожові позначки збою для підтікетів без виводу й переводить оркестрацію в synthesizing. |
orchestration.persistDeliverable |
Записує вивід синтезу до батьківського тікета, завершує його, публікує результат і позначає оркестрацію як completed. |
orchestration.awaitApproval |
Шлюз часткового схвалення: завершується негайно, коли ключ вузла кроку є у схваленій множині оркестрації, інакше призупиняється, доки пізніше схвалення його не відновить. Це єдине тіло, що встановлює extras.awaitApproval, а це звільняє його від 24-годинного тайм-ауту призупиненого кроку. |
messaging.createSpace |
Відкриває розмову, у якій працює решта шаблону, опубліковану під outputKey кроку (вбудовані використовують pipeline_space_id). Порожній список repoIds не створює жодного чекауту; extras['allRepos'] створює чекаут кожного репозиторію робочого простору. extras['createConversation'] (типово вимкнено) також відкриває іменований потік. Крок-агент ПРИЄДНУЄТЬСЯ до кімнати й ніколи її не відкриває, тож розгалуження без цього вузла падає під час запуску — а під’єднання його один раз означає, що все розгалуження ділить один чекаут, а не виділяє окремий кожному. |
prReview.finalize |
Останній крок шаблону рев’ю PR: розкладає накопичені вузли рев’ю по кошиках консенсусу й обчислює вердикт для кожного PR. |
skills.analyze |
Запускає сканування ланцюга постачання навичок для встановлених у робочому просторі навичок і фіксує кожен вердикт. |
Результати кроку
Розділ із назвою «Результати кроку»Тіло кроку повертає StepResult (packages/cc_domain/lib/features/pipelines/domain/entities/step_result.dart). Усі фабрики приймають необов’язкову мапу mutatedState, що зливається зі станом конвеєра, крім failed.
| Фабрика | Сигнатура | Опис |
|---|---|---|
ok |
StepResult.ok({mutatedState}) |
Звичайне завершення; слухачі нижче за потоком оцінюються |
route |
StepResult.route(nextRouterKey, {mutatedState}) |
Завершення маршрутизатора; обирає гілку нижче за потоком, чий routeKey збігається |
suspendUntilEvent |
StepResult.suspendUntilEvent(eventType, {mutatedState}) |
Пауза, доки не спрацює конкретний тип доменної події |
suspendUntilTasksComplete |
StepResult.suspendUntilTasksComplete(taskIds, {mutatedState}) |
Пауза, доки всі перелічені завдання не досягнуть кінцевого стану |
terminal |
StepResult.terminal({mutatedState}) |
Запуск конвеєра завершено — більше немає кроків |
failed |
StepResult.failed(errorMessage) |
Крок не вдався з повідомленням про помилку |
StepRetryPolicy
Розділ із назвою «StepRetryPolicy»Поведінка повторних спроб для тіла вузла, що падає (PipelineNodeConfig.retryPolicy; null вимикає повторні спроби).
| Поле | Тип | Опис |
|---|---|---|
maxAttempts |
int |
Загальна кількість спроб, включно з першою (3 = 1 спроба + 2 повтори); має бути ≥ 1 |
backoff |
String |
linear або exponential (типово) |
initialDelayMs |
int |
Затримка перед першим повтором, у мілісекундах; подальші затримки зростають відповідно до backoff |
PipelineNodeConfig
Розділ із назвою «PipelineNodeConfig»Конфігурація для кожного вузла, що переноситься всередині визначення кроку (packages/cc_domain/lib/features/pipelines/domain/entities/pipeline_node_config.dart). І вбудовані, і власні вузли мають цю форму; кожне тіло читає лише ті поля, які його цікавлять.
| Поле | Тип | Опис |
|---|---|---|
prompt |
String? |
Шаблон промпту з підстановкою {{key}} |
script |
String? |
Тіло bash-скрипту з підстановкою {{key}} |
agentId |
String? |
UUID агента в межах робочого простору для запуску |
inputKeys |
List<String> |
Ключі стану, які цей вузол споживає як вхід |
repoIds |
List<String> |
Репозиторії, для яких вузол messaging.createSpace створює чекаут (<repoId> або <repoId>@<branch>). Порожньо = жодного; extras['allRepos'] = кожен репозиторій робочого простору. Ігнорується на кроках-агентах. |
outputKey |
String? |
Ключ стану, під яким записується вивід цього вузла |
label |
String? |
Зрозуміла людині назва, яка показується на полотні (типово — ID кроку) |
outputSchema |
Map<String, dynamic>? |
JSON Schema (підмножина), якій має відповідати значення виводу |
reducer |
String? |
Стратегія злиття, коли паралельні гілки пишуть в один outputKey: append, mergeLists, mergeMaps, sum або override (типово) |
retryPolicy |
StepRetryPolicy? |
Політика повторних спроб при збої тіла; null = одна спроба |
continueOnFail |
bool |
Коли true, остаточний збій не провалює запуск; помилка зберігається під state['_stepErrors'][stepId] |
timeoutMs |
int? |
Тайм-аут реального часу для тіла вузла в мілісекундах; null = без тайм-ауту |
teamId |
String? |
Команда для запуску (вузли team.dispatch); взаємовиключно з agentId |
dispatchMode |
String? |
Режим виконання команди: allParallel або manager |
extras |
Map<String, dynamic> |
Довільна конфігурація, специфічна для тіла (наприклад, iterableKey, templateId, дерева умов) |
Похідні аксесори: spaceId (extras['spaceId'] — наявна розмова, у якій працювати), createsConversation (extras['createConversation']), conversationTitle (extras['conversationTitle']), modeName (extras['mode'] — chat / review / plan, читається на вузлі, що відкриває простір) і runWhen (extras['runWhen'] — пропустити вузол, коли умову не виконано).