Skip to content

[ENHANCEMENT] Task Tree 統一子任務管理:詳細設計同 PR 拆分計劃(upstream #856) #9

Description

@easonLiangWorldedtech

Task Tree 統一子任務管理:詳細設計同 PR 拆分計劃

上游參考: Zoo-Code-Org/Zoo-Code#856(Task Tree Abstraction design spec;Zoo-Code-Org#849 / Zoo-Code-Org#850 / Zoo-Code-Org#854 已於 2026-07-07 標記為 duplicate 關閉)

本 issue 係 Zoo-Code-Org#856 喺呢個 fork 嘅實施計劃:將 spec 對照 main(e064cf059)實際代碼驗證過,修正同現實唔符嘅部分,並拆成每個 ≤1000 行改動(連測試) 嘅 PR。

一、已驗證現狀(main @ e064cf0

事實 位置
Task 只有單向鏈:readonly parentTaskId? + childTaskId?冇 depth 欄位(全倉 grep 零匹配) src/core/task/Task.ts:171-172, 505-506
HistoryItem 已有 rootTaskId / parentTaskId / status("active"|"completed"|"delegated"|"interrupted") / delegatedToId / childIds[] / awaitingChildId / completedByChildId / completionResultSummary packages/types/src/history.ts:10-29
委派流程強制 single-open invariant:flush tool results → removeClineFromStack() → provider 級 handleModeSwitch()(全局 mode)→ createTask(startTask:false)atomicReadAndUpdate 寫 parent 後設資料(含 rollback)→ scheduleTask(child) src/core/webview/ClineProvider.ts:3644-3810
new_task 委派前彈 askApproval("tool", ...) 批准框 src/core/tools/NewTaskTool.ts:~107
子任務完成:AttemptCompletionTool.delegateToParent()askFinishSubTaskApproval()(實現在 presentAssistantMessage.ts:553)→ reopenParentFromDelegation(),返回 "delegated"|"denied"|"continue" src/core/tools/AttemptCompletionTool.ts:219-240
持久化:每 task 一個目錄(taskMetadata()),HistoryItem 經帶鎖 atomicReadAndUpdate 寫 globalState src/core/task-persistence/taskMetadata.ts
webview 已有按 parentTaskId 分組嘅歷史視圖 hook webview-ui/src/components/history/useGroupedTasks.ts

關鍵結論:spec 講「數據模型已支持樹」只係譜系層面對;真正瓶頸係 single-open invariant + provider 級全局 mode,呢兩樣先係平行執行嘅架構障礙。

二、範圍決定

Phase 內容 風險
P1 depth 追蹤 + maxNestingDepth / autoFlattenOnLimit 設定 + auto-flatten inline 模式
P2 樹形持久化加固(depth 回填、循環保護)+ UI 樹視圖擴充
P3 平行子任務 — 本計劃唔排期:阻塞於 per-task mode 隔離 spike(handleModeSwitch 係 provider 級全局狀態,兩個 Task 無法同時各持一個 mode)。spike 完成前所有設計按 sequential-only
P4 會話級 checkpoint(只做手動觸發)

明確剷走

  • cross-model context analysis — spec 嘅 confidenceScore 假設 LLM 輸出校準過嘅置信度,實際上唔成立;仲要設計多 provider 路由同成本估算,spec 完全冇覆蓋 → 另開實驗性 issue
  • AI-driven inline escalation 啟發式countDistinctGoalsdetectedParallelNeeds 等)— 冇任何機制令子任務向 runtime 發出「我變複雜咗」訊號;改做用戶手動觸發(P2 UI 按鈕,單向、唔可逆)
  • checkpoint 自動觸發(depth-based / change detection / stuck detection)— spec 嗰套閾值純拍頭,先做手動

三、詳細設計

P1.1 數據模型

packages/types/src/history.ts

// 新增可選欄位 — zod .optional() 令舊資料自動兼容
depth: z.number().int().min(0).optional(), // 巢狀層級,root = 0;建立時寫入 parent.depth + 1
  • 唔加 siblingIds(可由 parent 嘅 childIds 反查,存兩份只會造成同步負擔)
  • inline 狀態唔持久化:只係 Task 實例內存欄位。理由:inline 子任務冇獨立 Task 實例、冇獨立 historyItem,重啟後會話自然當普通 parent task 繼續 — 冇孤兒狀態要修復

src/core/task/Task.ts(line ~171-172, ~505):

depth: number // constructor: historyItem?.depth ?? (parentTask ? parentTask.depth + 1 : 0)
inlineSubtask?: { message: string; todos: TodoItem[] } // 純內存,唔寫入任何持久層
  • CreateTaskOptionspackages/types/src/task.ts:86)加 depth?: number 透傳
  • depth 回填:舊任務 depth=undefined → 按 parentTaskId 鏈回溯補算一次,下次 atomicReadAndUpdate 時順帶寫入;唔需要一次性遷移腳本

P1.2 設定 round-trip(跟 AGENTS.md「Persisted Setting Checklist」)

// packages/types/src/global-settings.ts
maxNestingDepth: number      // default 2,範圍 0–5;0 = 禁止委派(全部 inline)
autoFlattenOnLimit: boolean  // default true;false 時超深直接拒絕 new_task

完整 round-trip:global-settings.tsExtensionStatevscode-extension-host.ts)→ SettingsView 綁定 local cachedState(唔好綁 live useExtensionState(),會 race)→ handleSubmit() payload → webviewMessageHandlercontextProxy.setValue()ClineProvider.getState() + getStateToPostToWebview() 兩邊都要加。測試覆蓋 true / false / 未設定三種情況。

P1.3 Auto-flatten inline 模式(核心,重新設計)

決策:inline = 同一個 Task 實例 + 階段標記,唔建子 Task。

Spec 講「virtual child」但冇定義 runtime 點樣區分子任務 turn 同 parent turn。本倉庫有三個硬約束逼出唯一乾淨方案:

  1. single-open invariant(removeClineFromStack)— parent 離開 stack 先開到子 tab
  2. handleModeSwitch 係 provider 級全局狀態,兩個 Task 無法同時各持一個 mode
  3. inline 若建真 Task 實例但「唔開啟」,直接違反不變量一、二

所以 inline 子任務 = parent Task 自己嘅一個執行階段。new_task 被 flatten 時唔彈批准框、唔建 Task、唔開 tab — tool_result 本身就係 inline prompt

模型調用 new_task(message, mode)
        │
NewTaskTool.execute():
  childDepth = task.depth + 1
  if (childDepth > maxNestingDepth && autoFlattenOnLimit) {
      task.inlineSubtask = { message, todos }   // 設階段標記
      pushToolResult(
`[auto-flattened: nesting limit ${maxNestingDepth} reached — executing inline]
You are now executing this subtask INLINE in the current conversation.
Subtask instruction: ${message}
Todos: ${todos}
Execute it with your available tools. When done, call attempt_completion
with a summary of what you did.`)
      return   // 唔彈 askApproval,唔建 child
  }
  ...現有流程不變(askApproval → delegateParentAndOpenChild)

好處:

  • 零合成訊息:parent 自己嘅會話就係 bounded context,唔使 spec 嗰套「截取最後 15 條 + extractParentContext」prompt 組裝
  • resumeAfterDelegation():parent 從未離開 task loop,唔需要任何狀態重置 — 比 spec「inline 同 tab 共用 resume 邏輯」簡單一個量級
  • 取消語義免費解決:用戶中途 cancel → 清走 task.inlineSubtask 標記,會話繼續當普通 parent。冇孤兒 child、冇 cancellation cascade(spec 列為 PR-stage 未決問題,呢度直接消失)

attempt_completion 喺 inline 階段(AttemptCompletionTool.handle()delegateToParent 之前加分支):

if (task.inlineSubtask) {
    task.inlineSubtask = undefined   // 清標記
    pushToolResult(`[inline subtask completed]\n${result}\nThe parent conversation continues.`)
    return                            // loop 自然進入下一次 API call
}
// ...現有 askFinishSubTaskApproval → reopenParentFromDelegation 流程

即 inline 完成唔彈 askFinishSubTaskApproval(用戶已經喺同一會話睇到全部過程,再彈框係雙重摩擦 — spec 呢點判斷正確)。presentAssistantMessage.ts:553 嗰個 approval callback 完全唔使改。

嵌套規則:inline 階段期間模型再調 new_task → 直接拒絕(pushToolResult error),P1 明確禁止。Escalation 留 P2 做用戶手動觸發。

autoFlattenOnLimit: false:超深 → pushToolResult error「Nesting limit reached — continue working directly」,模型自己繼續做。

四、PR 拆分計劃(每個 ≤1000 行改動,連測試)

全部喺 main-task-tree base branch(= origin/main @ e064cf0)順序開發;每個 PR 由 main-task-tree 開出,merge 後 rebase 繼續。

PR-1:depth 追蹤 + schema(~450 行)

檔案 改動
packages/types/src/history.ts +depth: z.number().int().min(0).optional()
packages/types/src/task.ts CreateTaskOptions.depth?
src/core/task/Task.ts constructor depth 計算;inlineSubtask 欄位(先加,PR-3 用);depth 回填邏輯
src/core/webview/ClineProvider.ts createTask 透傳 depth + 寫入 historyItem
循環保護 helper 沿 parentTaskId 祖先鏈檢查
測試 unit:depth 計算 / 回填 / 循環保護(~250 行)

依賴:無。風險:低(全部 additive,舊資料兼容)。

PR-2:設定 round-trip(~480 行)

檔案 改動
packages/types/src/global-settings.ts +maxNestingDepth(default 2, 0–5)、+autoFlattenOnLimit(default true)
packages/types/src/vscode-extension-host.ts ExtensionState 加兩欄位
webview-ui/.../SettingsView cachedState 綁定 + control + handleSubmit payload(唔好綁 live useExtensionState()
src/core/webview/webviewMessageHandler.ts 設定正規化
src/core/webview/ClineProvider.ts getState() + getStateToPostToWebview() 兩邊加
測試 UI binding/save spec、persistence/normalization、round-trip(true / false / 未設定)(~300 行)

依賴:PR-1。風險:低。

PR-3:auto-flatten inline 模式(~780 行)

檔案 改動
src/core/tools/NewTaskTool.ts depth check 喺 askApproval 之前;flatten 分支設 task.inlineSubtask + pushToolResult directive;inline 階段中再 new_task → 拒絕;autoFlattenOnLimit=false → error result
src/core/tools/AttemptCompletionTool.ts inline 完成分支(清標記、無 approval、loop 繼續)
src/core/task/Task.ts cancel / abort 路徑清走 inlineSubtask
測試 unit:flatten 決策純函數;integration:擴充 provider-delegation.spec.ts / nested-delegation-resume.spec.ts(超深 → 無 child Task、無 approval ask、tool_result 含 directive;inline attempt_completion → 標記清除、loop 繼續、 askFinishSubTaskApproval 調用)(~450 行)

依賴:PR-1 + PR-2。風險:中(核心行為變更,但 tab / sequential 現有流程完全唔變)。

PR-4:樹形持久化加固 + UI(~520 行)

檔案 改動
src/core/webview/ClineProvider.ts webview message 帶 inlineSubtaskActive;取消級聯(cancel parent → children interrupted,接入 abandonSubtask 路徑)
webview-ui/src/components/history/useGroupedTasks.ts depth badge + 狀態指示 + inline 標記
測試 useGroupedTasks spec、取消級聯 integration(~280 行)

依賴:PR-3。風險:中低。

PR-5:手動會話 checkpoint(~760 行)

檔案 改動
src/core/checkpoints/(新子模組 conversation-checkpoint.ts) task 目錄下 checkpoints/<timestamp>.json 存儲 + 載入
create_checkpoint tool 手動觸發,保存 fullHistory + summary
UI:CheckpointMenu 擴充或獨立節點按鈕 顯示 checkpoint 列表、restore 會話
測試 storage round-trip、tool 行為、UI(~380 行)

依賴:PR-1。風險:中。可同 PR-4 並行。

唔喺本計劃內

  • P3 平行模式:先開獨立 spike issue 做 per-task mode 隔離(handleModeSwitch / initializeTaskMode 層重構);spike 完成前所有設計按 sequential-only,schema 唔預留 awaitingChildIds
  • cross-model analysis、AI escalation 啟發式、checkpoint 自動觸發 — 見「範圍決定」

五、測試計劃總覽(跟 AGENTS.md test placement)

位置 場景
unit src/__tests__/(同 new-task-delegation.spec.ts 同層) depth 計算 / 回填;flatten 決策純函數(depth+1 > max、autoFlatten on/off、inline 階段中再委派被拒);循環保護
integration 擴充 provider-delegation.spec.ts / nested-delegation-resume.spec.ts flatten 路徑全鏈;inline attempt_completion;舊任務 depth=undefined 載入回填
webview-ui webview-ui/src/components/history/__tests__/useGroupedTasks.spec.ts + SettingsView spec depth badge、inline 標記渲染;設定 binding/save/round-trip
e2e apps/vscode-e2e/src/suite/subtasks.test.ts(已有委派 fixture) smoke:depth=2 時再委派 → 同 tab 內完成,tab 數量唔增加

六、對上游 spec 嘅明確修正清單

  1. 「數據模型已支持樹」→ 只係譜系層面;執行層要動 single-open invariant(P3)
  2. inline「virtual child + resumeAfterDelegation 兼容證明」→ 改為同實例階段標記,連 resumeAfterDelegation 都唔使碰
  3. AI-driven escalation 啟發式 → 剷走,改用戶手動(PR-4)
  4. cross-model analysis → 剷出核心範圍
  5. checkpoint change/stuck detection → 延後,先做手動觸發
  6. zooCode.* VSCode settings → 改用本倉庫 globalSettings + contextProxy round-trip(AGENTS.md checklist)
  7. 「取代 tab 嘅樹視圖」→ 並存,擴充現有 useGroupedTasks

七、分支策略

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions