管線步驟
一個管線步驟有兩條彼此獨立的軸:
- 種類(
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」的章節不派送代理的 shell 步驟(bash -c)。指令碼中的 {{key}} 預留位置會以經 shell 轉義的值,從狀態 + 觸發承載資料替換。
| 設定欄位 | 說明 |
|---|---|
script |
要執行的 shell 命令(必填) |
outputKey |
在結束碼為 0 時接收去除前後空白 stdout 的狀態鍵 |
執行時的 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}—— 當列出的任一路徑存在於磁碟上時為 true;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」的章節透過 GitHub PR 用戶端,把彙整後的發現張貼成一則 PR 審查留言。
| 狀態鍵 | 說明 |
|---|---|
repo_full_name |
owner/repo(必填) |
pr_number |
PR 編號 —— 整數或數字字串(必填) |
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、節點類型庫,以及某個內建範本的提示文字,都提到 approve_step 與 reject_step 這兩個 MCP 工具;這兩個工具都不存在於任何套件或登錄表中。
repos.cleanup
標題為「repos.cleanup」的章節移除過期的隔離工作樹;從觸發承載資料挑選其模式。遵循該次執行的 dryRun 旗標。
| 狀態鍵 | 說明 |
|---|---|
ticket_id |
釋放該工單的工作樹 |
repo_full_name + pr_number |
釋放短暫的 PR 編輯器工作樹 |
space_id |
拆除該對話的工作樹與資料夾 |
| (以上皆無) | 清掃工作區:消失的目錄、失效的空間、孤立的對話資料夾 |
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 索引:走訪一個儲存庫、在工作程序 isolate 中擷取符號/邊,並把它們納入工作區的程式碼圖。會把進度串流到步驟執行資料列;支援試執行。Tree-sitter 及其文法是必要的原生程式庫 —— 缺少動態函式庫代表伺服器從未開機成功,而文法在執行中途解析失敗會讓該步驟失敗,而不是略過它。
| 狀態鍵 | 說明 |
|---|---|
repo_id |
要建立索引的儲存庫(必填) |
repo_local_path |
該簽出的本機路徑(必填) |
meeting.diarize
標題為「meeting.diarize」的章節離線的語者分離。讀取會議保留下來的音訊、把它分群成一位位說話者(Person 1、Person 2、……)、重新標記逐字稿片段並改寫 transcript 狀態;也會為並行執行的 meeting.updateTranscript 步驟發出 diarization_spans。當沒有保留任何音訊,或模型未安裝時,會把逐字稿原樣傳遞過去。
| 狀態鍵 | 說明 |
|---|---|
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 |
套用來自 diarization_spans 的 Person N 標籤,並把各視窗的片段合併成各說話者的發言輪次,然後持久化清理後的逐字稿。語者分離沒有產生任何結果時為空操作。 |
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)。除了 failed 之外,所有工廠都接受一個選填的 mutatedState 映射,並合併到管線狀態中。
| 工廠 | 簽章 | 說明 |
|---|---|---|
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? |
支援 {{key}} 替換的 bash 指令碼主體 |
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'] —— 未滿足時略過該節點)。