Skip to content

[Story 3.2c] Extension-side task-scoping guard #370

@edelauna

Description

@edelauna

Part of #358 (Epic 3: TaskScheduler + Subtask Fan-out).

Depends on: Story 3.2b (Fan-out). Story 3.2d (webview-side guard) depends on this and must follow.

Context

The webview is fundamentally single-task. messageUpdated events have no taskId field (Task.ts, line 1048), and ChatView renders whatever arrives without verifying it belongs to currentTaskId. If two tasks post to the webview simultaneously — which becomes possible when maxConcurrency > 1 via Story 3.2b — messages from a background task corrupt the displayed task's chat.

The primary fix lives in the extension host: non-focused tasks simply do not post. VS Code's WebviewView is a single instance per view ID (no multi-pane option), so the extension host must enforce focus gating. This story implements the extension-side half. Story 3.2d implements the webview-side defense-in-depth (rejection guard + per-task seq migration).

Developer Notes

In src/core/task/Task.ts:

  • Add taskId field to messageUpdated events: provider?.postMessageToWebview({ type: "messageUpdated", clineMessage: message, taskId: this.taskId }).
  • Gate postStateToWebviewWithoutTaskHistory() and postMessageToWebview({ type: "messageUpdated" }): only post if this.taskId === this.provider.taskRegistry.current?.taskId. Non-focused tasks accumulate messages in their own clineMessages array silently; the webview receives them when the user navigates to that task.

In src/core/webview/ClineProvider.ts:

  • Add runningTaskIds: string[] to the state shape returned by getStateToPostToWebview(), populated from taskRegistry.getRunning().map(t => t.taskId).
  • When showTaskWithId is handled and the target task is still running in TaskRegistry: call taskRegistry.setCurrent(taskId) (focus switch) and push the target task's current clineMessages to the webview. Do NOT pop/destroy the previous task — it continues running in the background.

In packages/types/src/vscode-extension-host.ts:

  • Add taskId?: string to ExtensionMessage for messageUpdated type.
  • Add clineMessagesTaskId?: string stub alongside clineMessagesSeq in state messages (consumed by Story 3.2d).

Files: src/core/task/Task.ts, src/core/webview/ClineProvider.ts, packages/types/src/vscode-extension-host.ts

Tests (src/core/task/__tests__/TaskWebviewGuard.spec.ts):

  • Non-focused task: postStateToWebviewWithoutTaskHistory() does NOT call postMessageToWebview. Assert via spy.
  • Focused task: postStateToWebviewWithoutTaskHistory() calls postMessageToWebview normally.
  • messageUpdated includes taskId field matching the emitting task.
  • Focus switch via taskRegistry.setCurrent(newTaskId): subsequent posts from old task are suppressed; new task's posts go through.
  • runningTaskIds in state reflects all running tasks from TaskRegistry.

Acceptance Criteria

  • messageUpdated events include taskIdgrep -n "messageUpdated" src/core/task/Task.ts shows taskId in every call.
  • Non-focused tasks do not post to the webview — verified by spy-based test.
  • runningTaskIds is present in webview state and reflects TaskRegistry.getRunning().
  • All existing single-task tests pass unchanged — the guard is transparent when only one task runs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions