跳至內容

管線步驟

一個管線步驟有兩條互相獨立的軸線:

  • 種類(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 步驟則沒有主體。

強制性 trigger 節點的空操作主體——立即完成,讓引擎向下游監聽器扇出。沒有設定。

無代理的 shell 步驟(bash -c)。指令碼中的 {{key}} 佔位符會以 shell 轉義後的值,從狀態加上觸發器載荷替換。

設定欄位 說明
script 要執行的 shell 指令(必填)
outputKey 結束碼為 0 時接收已修剪 stdout 的狀態鍵

執行時的 cwd 為 <cc_root>/pipelines/<pipelineRunId>/(同一次執行中的所有 bash 步驟共用),環境中帶有 GITHUB_TOKEN。它也會把 <stepId>_runDir 寫入狀態。非零結束碼會令該步驟失敗,並附上 stderr 結尾。

通用的提示並派發節點:渲染提示、把代理派發到一則對話,然後暫停直到該次執行完成。代理的 submit_output 載荷會收集到狀態中的 outputKey 之下。

設定欄位 說明
agentId 要派發的工作區範圍代理 UUID(必填)
prompt 指示文字;支援 {{key}} 替換(必填)
outputKey 收集所得輸出的狀態鍵
outputSchema 輸出必須符合的 JSON Schema
label 顯示在畫布上的人類可讀標籤
extras.spaceId 要在其中工作的現有對話(預設:一個隱藏對話)
extras.conversationTitle 該空間內具名的串流(選填)
extras.mode 在開啟該房間的節點(messaging.createSpace)上讀取,而不是這裡。預設 review。

路由器主體——讀取 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,它必須與傳回的鍵相符。

透過 GitHub PR 用戶端,把彙整後的發現以 PR 審查留言的形式發佈。

狀態鍵 說明
repo_full_name owner/repo(必填)
pr_number PR 編號——整數或數字字串(必填)
consolidated_findings 留言內文(必填)

傳回 StepResult.terminal,並在狀態中帶有 comment_review_id 與 commented_at。

透過 MessagingPort 向訊息空間張貼一則訊息。

狀態鍵 說明
space_id 目標空間(必填)
content 訊息內文,逐字從狀態讀取(必填)

把 posted_space_id 與 posted_at 寫入狀態。

派發整個團隊而非單一代理,然後暫停直到各成員的執行完成。

設定欄位 說明
teamId 要派發的工作區範圍團隊(必填;與 agentId 互斥)
prompt 目標文字;支援 {{key}} 替換(必填)
dispatchMode allParallel(預設——每個成員一個任務)或 manager(只派發領導者來協調)
outputKey 收集成員輸出的狀態鍵
outputSchema, label, extras.spaceId 同 conversation.promptAgent

核准閘門。把核准者代理派發到一則對話(review 模式),帶上閘門提示與 {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 工具;兩個工具在任何套件或註冊表中都不存在。

移除過期的隔離 worktree;它的模式取自觸發器載荷。會依從該次執行的 dryRun 旗標。

狀態鍵 說明
ticket_id 釋放該工單的 worktree
repo_full_name + pr_number 釋放暫時性的 PR 編輯器 worktree
space_id 拆除該對話的 worktree 與資料夾
(以上皆非) 掃掠整個工作區:已消失的目錄、已失效的空間、孤立的對話資料夾

outputKey(選填)會接收一段摘要字串。

對某個狀態集合做映射/扇出:為每個項目渲染一次提示,然後把這些提示串接(以 --- 分隔)後一次派發代理,並暫停直到該次執行完成。空集合會立即以空清單完成。它並不是為每個項目各做一次並行派發。

設定欄位 說明
agentId 每個項目要派發的代理(必填)
prompt 逐項提示;支援 {{key}} 替換(必填)
extras.iterableKey 存放該集合的狀態鍵(必填)
extras.itemKey 提示中目前項目所繫結的鍵(預設 item)
outputKey 收集所得輸出的狀態鍵(一次派發,不是清單)

以巢狀子步驟的形式執行另一個管線範本(子管線會以設定了 parentPipelineRunId 的子執行身分執行)。該步驟會暫停,直到子項達到終態,然後子項的最終狀態會合併到 outputKey 之下。

設定欄位 說明
extras.templateId 要執行的子範本(必填;管線不能呼叫自身)
inputKeys 複製到子項觸發器載荷的狀態鍵
outputKey 子項合併後最終狀態的狀態鍵

背景的 tree-sitter 索引:走訪一個儲存庫,在工作程序隔離區中擷取符號/邊,並把它們收進工作區程式碼圖。會把進度串流到步驟執行資料列;支援 dry-run。tree-sitter 及其語法是必要原生元件——缺少 dylib 代表伺服器從未啟動,而在執行途中無法解析的語法會令該步驟失敗,而不是略過它。

狀態鍵 說明
repo_id 要建立索引的儲存庫(必填)
repo_local_path 檢出的本機路徑(必填)

離線說話人分離。讀取會議保留的音訊,把它聚類成說話人(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) 該步驟因錯誤訊息而失敗

失敗節點主體的重試行為(PipelineNodeConfig.retryPolicy;null 會停用重試)。

欄位 類型 說明
maxAttempts int 包含第一次在內的總嘗試次數(3 = 1 次嘗試 + 2 次重試);必須 ≥ 1
backoff String linear 或 exponential(預設)
initialDelayMs int 第一次重試前的延遲,以毫秒計;之後的延遲會按 backoff 增長

節點定義內所帶的逐節點設定(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']——未滿足時跳過該節點)。