跳到內容

管線步驟

一個管線步驟有兩條彼此獨立的軸:

  • 種類(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

核准閘門。把核准者代理派送到一個對話中(審查模式),並帶上閘門提示與 {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 工具;這兩個工具都不存在於任何套件或登錄表中。

移除過期的隔離工作樹;從觸發承載資料挑選其模式。遵循該次執行的 dryRun 旗標。

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

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

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

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

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

設定欄位 說明
extras.templateId 要執行的子範本(必填;管線不能呼叫自己)
inputKeys 複製到子執行觸發承載資料中的狀態鍵
outputKey 子執行合併後最終狀態所存放的狀態鍵

背景 tree-sitter 索引:走訪一個儲存庫、在工作程序 isolate 中擷取符號/邊,並把它們納入工作區的程式碼圖。會把進度串流到步驟執行資料列;支援試執行。Tree-sitter 及其文法是必要的原生程式庫 —— 缺少動態函式庫代表伺服器從未開機成功,而文法在執行中途解析失敗會讓該步驟失敗,而不是略過它。

狀態鍵 說明
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'] —— 未滿足時略過該節點)。