Roo-Code/src/core/task/TaskContextBuilder.ts
Roo Code d5b45ff368 feat: add TaskContext and TaskPermissions for Phase 3a task isolation
Introduces the foundation for isolated task execution (Phase 3a of #12330):

- TaskContext: immutable snapshot of mode, API config, and workspace for
  each task, replacing runtime reads from shared ClineProvider state
- TaskPermissions: fine-grained permission boundaries (file patterns,
  command restrictions, read-only mode, tool allowlists) that the
  orchestrator can attach when spawning subtasks
- TaskContextBuilder: factory functions to build TaskContext from provider
  state and to derive child contexts with merged permissions
- Task constructor now accepts optional taskContext, using it for mode
  and API config initialization instead of provider.getState()
- delegateParentAndOpenChild builds and passes a TaskContext to child tasks
- Permission merging follows most-restrictive-wins semantics

This is a pure refactor with no behavioral change -- tasks still execute
sequentially, but they now carry their own isolated context. Enforcement
of permission boundaries is deferred to Phase 3b/3d.

Ref: #12330
2026-05-12 05:01:59 +00:00

63 lines
2.5 KiB
TypeScript

import { type TaskContext, type TaskPermissions, mergePermissions } from "@roo-code/types"
import { defaultModeSlug } from "../../shared/modes"
import type { ClineProvider } from "../webview/ClineProvider"
/**
* Build a TaskContext from the current provider state.
*
* This factory snapshots the provider's current mode and API config
* into an immutable TaskContext that a child task can carry independently.
* This is the key enabler for Phase 3a: tasks no longer need to reach
* back into the provider for their mode/config during execution.
*
* @param provider - The ClineProvider to snapshot state from
* @param overrides - Optional overrides (e.g., mode from new_task tool)
* @returns A TaskContext snapshot
*/
export async function buildTaskContext(
provider: ClineProvider,
overrides?: Partial<TaskContext>,
): Promise<TaskContext> {
const state = await provider.getState()
const context: TaskContext = {
mode: overrides?.mode ?? state?.mode ?? defaultModeSlug,
apiConfigName: overrides?.apiConfigName ?? state?.currentApiConfigName ?? "default",
permissions: overrides?.permissions,
inheritSkills: overrides?.inheritSkills ?? true,
skillOverrides: overrides?.skillOverrides,
workspacePath: overrides?.workspacePath,
parentTaskId: overrides?.parentTaskId,
rootTaskId: overrides?.rootTaskId,
}
return context
}
/**
* Build a TaskContext for a child task, inheriting from a parent context
* and applying any child-specific overrides.
*
* Permission merging follows the "most restrictive" principle:
* the child's effective permissions are the intersection of the parent's
* permissions and any child-specific permissions.
*
* @param parentContext - The parent task's context
* @param childOverrides - Child-specific overrides
* @returns A new TaskContext for the child task
*/
export function buildChildTaskContext(parentContext: TaskContext, childOverrides: Partial<TaskContext>): TaskContext {
const mergedPermissions = mergePermissions(parentContext.permissions, childOverrides.permissions)
return {
mode: childOverrides.mode ?? parentContext.mode,
apiConfigName: childOverrides.apiConfigName ?? parentContext.apiConfigName,
permissions: mergedPermissions,
inheritSkills: childOverrides.inheritSkills ?? parentContext.inheritSkills,
skillOverrides: childOverrides.skillOverrides ?? parentContext.skillOverrides,
workspacePath: childOverrides.workspacePath ?? parentContext.workspacePath,
parentTaskId: childOverrides.parentTaskId,
rootTaskId: childOverrides.rootTaskId ?? parentContext.rootTaskId,
}
}