|
| 1 | +# Phase 6: Background Task Visibility and Interaction |
| 2 | + |
| 3 | +> Architectural design document for Issue #12330 |
| 4 | +> Phase 6 of "Support parallel execution of specialized agents and improve context handoff between modes" |
| 5 | +
|
| 6 | +## 1. Context |
| 7 | + |
| 8 | +Phase 5 (Background Tasks Panel UI) is complete. This document proposes the scope, priority, and architecture for Phase 6, which focuses on enabling better visibility and interaction with background tasks. |
| 9 | + |
| 10 | +## 2. Current Architecture |
| 11 | + |
| 12 | +### Task Lifecycle |
| 13 | + |
| 14 | +`ClineProvider` maintains a `clineStack: Task[]` (LIFO). Only the top-of-stack task is "current" -- all state updates, webview messages, and user interactions route through `getCurrentTask()`. |
| 15 | + |
| 16 | +``` |
| 17 | +ClineProvider |
| 18 | +├── clineStack: Task[] # LIFO stack, sequential execution |
| 19 | +├── taskHistoryStore # Per-task file persistence |
| 20 | +├── getCurrentTask() # Returns top of stack |
| 21 | +├── addClineToStack(task) # Push new task |
| 22 | +└── removeClineFromStack() # Pop completed task |
| 23 | +``` |
| 24 | + |
| 25 | +### Task Persistence |
| 26 | + |
| 27 | +| Layer | File | Purpose | |
| 28 | +|-------|------|---------| |
| 29 | +| Messages | `taskMessages.ts` | Save/load `ClineMessage[]` per task | |
| 30 | +| API History | `apiMessages.ts` | Save/load API conversation history | |
| 31 | +| History Items | `TaskHistoryStore.ts` | Per-task metadata files with in-memory cache | |
| 32 | +| Metadata | `taskMetadata.ts` | Task metadata helpers | |
| 33 | + |
| 34 | +### Webview Communication |
| 35 | + |
| 36 | +The extension sends typed `ExtensionMessage` objects to the webview. Key message types: |
| 37 | + |
| 38 | +- `state` -- Full state snapshot (includes `clineMessages`, `currentTaskId`) |
| 39 | +- `taskHistoryUpdated` -- Full history list refresh |
| 40 | +- `taskHistoryItemUpdated` -- Single history item update |
| 41 | + |
| 42 | +Currently, `postStateToWebviewWithoutTaskHistory()` sends state for only the current task. There is no mechanism to send updates for background tasks. |
| 43 | + |
| 44 | +### Subtask Support |
| 45 | + |
| 46 | +Parent-child relationships exist via `parentTaskId` and `childIds` on `HistoryItem`. The `new_task` tool creates subtasks that push onto the stack. When a subtask completes, it pops and returns control to the parent. |
| 47 | + |
| 48 | +## 3. Agreed Scope for Phase 6 |
| 49 | + |
| 50 | +**In scope (Items 1-3):** |
| 51 | +1. Full conversation replay for completed background tasks |
| 52 | +2. Tab switching / multi-task view |
| 53 | +3. Real-time progress streaming for active background tasks |
| 54 | + |
| 55 | +**Deferred to Phase 7 (Items 4-5):** |
| 56 | +4. Write-capable background tasks + basic file locking |
| 57 | +5. Persistent background task history across sessions |
| 58 | + |
| 59 | +## 4. Feasibility Analysis |
| 60 | + |
| 61 | +### Item 1: Full Conversation Replay |
| 62 | + |
| 63 | +**Complexity: Medium | Risk: Low** |
| 64 | + |
| 65 | +`readTaskMessages(taskId, globalStoragePath)` already loads the full `ClineMessage[]` array from disk for any task. The existing `ChatView` component renders these messages. The main work is creating a read-only wrapper that: |
| 66 | + |
| 67 | +- Accepts a `taskId` prop instead of reading from global state |
| 68 | +- Loads messages on mount via a new webview message |
| 69 | +- Hides input controls (chat box, approval buttons) |
| 70 | +- Renders tool calls, outputs, and assistant responses in the same format |
| 71 | + |
| 72 | +**Why it's low risk:** No changes to task execution, persistence, or the foreground task flow. Purely additive UI + a new message handler. |
| 73 | + |
| 74 | +### Item 2: Tab Switching / Multi-task View |
| 75 | + |
| 76 | +**Complexity: Medium-High | Risk: Medium** |
| 77 | + |
| 78 | +The webview already has a tab system in `App.tsx` (`tab === "history"`, `tab === "settings"`, `tab === "chat"`). Adding a background tasks view requires: |
| 79 | + |
| 80 | +- A new tab or panel within the chat view |
| 81 | +- A list of active/completed background tasks with status indicators |
| 82 | +- Navigation to open a task's replay view or live view |
| 83 | +- State management to track which background task is currently being viewed |
| 84 | + |
| 85 | +**Key challenge:** The webview currently receives state for only one task. Viewing a background task must not disrupt the foreground task's state. This requires either: |
| 86 | + - (a) A separate message channel for background task data, or |
| 87 | + - (b) A secondary state context in the webview that can hold background task data alongside the primary task state |
| 88 | + |
| 89 | +Option (a) is cleaner and avoids polluting the existing state management. |
| 90 | + |
| 91 | +### Item 3: Real-time Progress Streaming |
| 92 | + |
| 93 | +**Complexity: High | Risk: Medium-High** |
| 94 | + |
| 95 | +Currently, `Task.ts` calls `provider.postStateToWebviewWithoutTaskHistory()` to update the UI. This method sends the full state for the current task only. For background tasks to stream progress: |
| 96 | + |
| 97 | +1. `Task.ts` must emit incremental updates even when it is not the "current" task |
| 98 | +2. A new message type (`backgroundTaskProgress`) must carry task-scoped updates |
| 99 | +3. The webview must handle concurrent update streams without degrading performance |
| 100 | +4. Throttling/batching is needed to prevent excessive re-renders |
| 101 | + |
| 102 | +**Why it's harder:** Requires changes to the core task execution loop (`Task.ts`), not just additive UI. The task currently assumes it IS the visible task when posting updates. |
| 103 | + |
| 104 | +## 5. Recommended Priority Order |
| 105 | + |
| 106 | +``` |
| 107 | +Phase 6a: Conversation Replay (Foundation -- standalone value) |
| 108 | + │ |
| 109 | + ▼ |
| 110 | +Phase 6b: Tab/Panel Switching (Navigation framework, depends on 6a) |
| 111 | + │ |
| 112 | + ▼ |
| 113 | +Phase 6c: Real-time Progress Streaming (Highest complexity, builds on 6b) |
| 114 | +``` |
| 115 | + |
| 116 | +Each sub-phase is independently shippable and testable. |
| 117 | + |
| 118 | +## 6. Detailed Design |
| 119 | + |
| 120 | +### 6a. Conversation Replay |
| 121 | + |
| 122 | +#### New Message Types |
| 123 | + |
| 124 | +```typescript |
| 125 | +// Webview → Extension |
| 126 | +interface RequestBackgroundTaskMessages { |
| 127 | + type: "requestBackgroundTaskMessages" |
| 128 | + taskId: string |
| 129 | +} |
| 130 | + |
| 131 | +// Extension → Webview |
| 132 | +interface BackgroundTaskMessages { |
| 133 | + type: "backgroundTaskMessages" |
| 134 | + taskId: string |
| 135 | + messages: ClineMessage[] |
| 136 | +} |
| 137 | +``` |
| 138 | + |
| 139 | +#### Extension Handler (webviewMessageHandler.ts) |
| 140 | + |
| 141 | +```typescript |
| 142 | +case "requestBackgroundTaskMessages": { |
| 143 | + const taskId = message.taskId |
| 144 | + const globalStoragePath = provider.contextProxy.globalStorageUri.fsPath |
| 145 | + const messages = await readTaskMessages(taskId, globalStoragePath) |
| 146 | + provider.postMessageToWebview({ |
| 147 | + type: "backgroundTaskMessages", |
| 148 | + taskId, |
| 149 | + messages: messages ?? [], |
| 150 | + }) |
| 151 | + break |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +#### Webview Component |
| 156 | + |
| 157 | +``` |
| 158 | +BackgroundTaskReplayView |
| 159 | +├── Props: { taskId: string, onClose: () => void } |
| 160 | +├── State: messages (ClineMessage[]), loading (boolean) |
| 161 | +├── On mount: sends requestBackgroundTaskMessages |
| 162 | +├── On message: receives backgroundTaskMessages, filters by taskId |
| 163 | +├── Renders: read-only message list (reuses ChatRow components) |
| 164 | +└── No input controls, no approval buttons |
| 165 | +``` |
| 166 | + |
| 167 | +### 6b. Tab/Panel Switching |
| 168 | + |
| 169 | +#### UI Structure |
| 170 | + |
| 171 | +``` |
| 172 | +App.tsx |
| 173 | +├── tab === "chat" → ChatView (foreground task) |
| 174 | +├── tab === "history" → HistoryView |
| 175 | +├── tab === "settings" → SettingsView |
| 176 | +└── tab === "bgTask" → BackgroundTaskView |
| 177 | + ├── BackgroundTasksList (sidebar/panel) |
| 178 | + │ ├── Active tasks with status badges |
| 179 | + │ └── Completed tasks |
| 180 | + └── BackgroundTaskReplayView (from 6a) OR BackgroundTaskLiveView (from 6c) |
| 181 | +``` |
| 182 | + |
| 183 | +#### State Management |
| 184 | + |
| 185 | +```typescript |
| 186 | +// New webview state (in App.tsx or dedicated context) |
| 187 | +interface BackgroundTaskViewState { |
| 188 | + selectedTaskId: string | null |
| 189 | + viewMode: "replay" | "live" |
| 190 | +} |
| 191 | +``` |
| 192 | + |
| 193 | +#### Navigation Flow |
| 194 | + |
| 195 | +1. User clicks background task icon/tab |
| 196 | +2. App switches to `tab === "bgTask"` |
| 197 | +3. BackgroundTasksList shows available tasks |
| 198 | +4. User clicks a task → sets `selectedTaskId` |
| 199 | +5. If task is completed → opens BackgroundTaskReplayView |
| 200 | +6. If task is active → opens BackgroundTaskLiveView (Phase 6c) |
| 201 | + |
| 202 | +### 6c. Real-time Progress Streaming |
| 203 | + |
| 204 | +#### New Message Types |
| 205 | + |
| 206 | +```typescript |
| 207 | +// Extension → Webview (incremental updates) |
| 208 | +interface BackgroundTaskProgress { |
| 209 | + type: "backgroundTaskProgress" |
| 210 | + taskId: string |
| 211 | + update: BackgroundTaskUpdate |
| 212 | +} |
| 213 | + |
| 214 | +interface BackgroundTaskUpdate { |
| 215 | + kind: "tool_call" | "tool_result" | "assistant_text" | "status_change" | "error" |
| 216 | + timestamp: number |
| 217 | + data: any // Typed per kind |
| 218 | +} |
| 219 | +``` |
| 220 | + |
| 221 | +#### Task.ts Changes |
| 222 | + |
| 223 | +Add a method that emits progress regardless of whether the task is "current": |
| 224 | + |
| 225 | +```typescript |
| 226 | +// In Task.ts |
| 227 | +private emitBackgroundProgress(update: BackgroundTaskUpdate) { |
| 228 | + const provider = this.providerRef.deref() |
| 229 | + if (!provider) return |
| 230 | + |
| 231 | + // Only emit background updates when this task is NOT the current task |
| 232 | + if (provider.getCurrentTask()?.taskId === this.taskId) return |
| 233 | + |
| 234 | + provider.postMessageToWebview({ |
| 235 | + type: "backgroundTaskProgress", |
| 236 | + taskId: this.taskId, |
| 237 | + update, |
| 238 | + }) |
| 239 | +} |
| 240 | +``` |
| 241 | + |
| 242 | +#### Throttling Strategy |
| 243 | + |
| 244 | +- Batch updates in 200ms windows |
| 245 | +- Cap at 10 updates per batch per task |
| 246 | +- Drop older updates if buffer exceeds threshold |
| 247 | +- Priority: status_change > error > tool_result > tool_call > assistant_text |
| 248 | + |
| 249 | +#### Webview: BackgroundTaskLiveView |
| 250 | + |
| 251 | +``` |
| 252 | +BackgroundTaskLiveView |
| 253 | +├── Props: { taskId: string } |
| 254 | +├── State: updates (BackgroundTaskUpdate[]), status |
| 255 | +├── Subscribes to backgroundTaskProgress messages filtered by taskId |
| 256 | +├── Renders: streaming list of tool calls and results |
| 257 | +├── Auto-scrolls to latest update |
| 258 | +└── Shows task status badge (running, paused, completed, errored) |
| 259 | +``` |
| 260 | + |
| 261 | +## 7. Testing Strategy |
| 262 | + |
| 263 | +| Area | Test Type | Key Scenarios | |
| 264 | +|------|-----------|---------------| |
| 265 | +| Message handler | Unit (vitest) | Request/response for task messages, missing task, corrupt data | |
| 266 | +| BackgroundTaskReplayView | Component (vitest + RTL) | Loading state, message rendering, empty state | |
| 267 | +| Tab switching | Component (vitest + RTL) | Tab navigation, state preservation, back to foreground | |
| 268 | +| Progress streaming | Unit (vitest) | Throttling, batching, concurrent tasks | |
| 269 | +| Integration | E2E (if feasible) | Full flow: start bg task → view progress → replay after completion | |
| 270 | + |
| 271 | +## 8. Open Questions |
| 272 | + |
| 273 | +1. **Should the background task list be a sidebar panel or a tab?** A sidebar panel (like the existing Phase 5 panel) keeps the foreground task visible. A tab replaces the view entirely but is simpler. |
| 274 | + |
| 275 | +2. **Message size limits for replay:** Completed tasks can have thousands of messages. Should we paginate or lazy-load? Initial recommendation: load all at once (same as current ChatView behavior), optimize if performance becomes an issue. |
| 276 | + |
| 277 | +3. **Progress streaming granularity:** Should we stream every tool call parameter, or just tool names + status? Start with names + status, add detail incrementally. |
0 commit comments