mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-25 05:53:57 +00:00
feat(teams): add run_team_phase tool and TeamsManager for phased multi-agent workflows
Implements the Atlas-Agent-Teams pattern for Roo-Code: - TeamConfig JSON schema in packages/types (slug, phases, agents, conventions) - TeamsManager scans .roo/teams/*.json and caches configs - RunTeamPhaseTool executes one phase: concurrent via spawnConcurrentChildren, sequential single via delegateParentAndOpenChild, sequential multiple via queue drain - LLM schema (run_team_phase.ts) with orchestrator loop description - Wired into ClineProvider (init, getTeamConfig, dispose), TOOL_GROUPS.modes, TOOL_DISPLAY_NAMES, NativeToolArgs, toolParamNames, and presentAssistantMessage - Sample .roo/teams/fullstack.json: discovery (concurrent) → implementation → review Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5aecb08211
commit
c2a549a1dc
11 changed files with 460 additions and 1 deletions
66
.roo/teams/fullstack.json
Normal file
66
.roo/teams/fullstack.json
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"slug": "fullstack",
|
||||
"name": "Full-Stack Feature Team",
|
||||
"description": "Three-phase team for building full-stack features: discovery → implementation → review",
|
||||
"orchestratorMode": "orchestrator",
|
||||
"conventions": ".roo/teams/conventions/fullstack.md",
|
||||
"phases": [
|
||||
{
|
||||
"name": "discovery",
|
||||
"label": "Discovery & Planning",
|
||||
"concurrent": true,
|
||||
"requireApproval": true,
|
||||
"agents": [
|
||||
{
|
||||
"mode": "architect",
|
||||
"role": "Backend Architect",
|
||||
"instruction": "Analyze the backend requirements for the following task and produce a detailed technical spec.\n\nTask: {{task}}\n\nDeliverables:\n- API endpoints needed (method, path, request/response shapes)\n- Database schema changes (if any)\n- Key implementation risks or unknowns\n\nOutput as structured markdown."
|
||||
},
|
||||
{
|
||||
"mode": "architect",
|
||||
"role": "Frontend Architect",
|
||||
"instruction": "Analyze the frontend requirements for the following task and produce a detailed technical spec.\n\nTask: {{task}}\n\nDeliverables:\n- Component tree and data flow\n- State management approach\n- API integration points\n- UX edge cases to handle\n\nOutput as structured markdown."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "implementation",
|
||||
"label": "Implementation",
|
||||
"concurrent": false,
|
||||
"requireApproval": false,
|
||||
"abortOnChildFailure": true,
|
||||
"agents": [
|
||||
{
|
||||
"mode": "code",
|
||||
"role": "Backend Engineer",
|
||||
"instruction": "Implement the backend changes for this task.\n\nTask: {{task}}\n\nDiscovery results:\n{{context}}\n\nWrite production-quality code. Run tests after each significant change. Do not leave TODOs.",
|
||||
"worktree": "feat/{{team}}-backend"
|
||||
},
|
||||
{
|
||||
"mode": "code",
|
||||
"role": "Frontend Engineer",
|
||||
"instruction": "Implement the frontend changes for this task.\n\nTask: {{task}}\n\nDiscovery results:\n{{context}}\n\nWrite production-quality code. Ensure the UI handles loading, error, and empty states. Do not leave TODOs.",
|
||||
"worktree": "feat/{{team}}-frontend"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "review",
|
||||
"label": "Code Review",
|
||||
"concurrent": true,
|
||||
"requireApproval": false,
|
||||
"agents": [
|
||||
{
|
||||
"mode": "code",
|
||||
"role": "Security Reviewer",
|
||||
"instruction": "Review the implementation for security issues.\n\nTask: {{task}}\n\nImplementation summary:\n{{context}}\n\nFocus on: input validation, auth/authz, injection risks, sensitive data exposure. Output a list of findings (severity: critical/high/medium/low) with suggested fixes."
|
||||
},
|
||||
{
|
||||
"mode": "code",
|
||||
"role": "QA Engineer",
|
||||
"instruction": "Review the implementation for correctness and test coverage.\n\nTask: {{task}}\n\nImplementation summary:\n{{context}}\n\nFocus on: missing test cases, edge cases not handled, regression risks. Output a list of findings with suggested test additions."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ export * from "./provider-settings.js"
|
|||
export * from "./task.js"
|
||||
export * from "./todo.js"
|
||||
export * from "./skills.js"
|
||||
export * from "./team.js"
|
||||
export * from "./telemetry.js"
|
||||
export * from "./terminal.js"
|
||||
export * from "./tool.js"
|
||||
|
|
|
|||
67
packages/types/src/team.ts
Normal file
67
packages/types/src/team.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Team configuration — defines a named, phased multi-agent workflow.
|
||||
* Team configs live in .roo/teams/<slug>.json inside the workspace.
|
||||
*/
|
||||
|
||||
export interface TeamAgentSpec {
|
||||
/** Mode slug for this agent (e.g., "code", "architect") */
|
||||
mode: string
|
||||
/** Role label shown in results (e.g., "frontend", "explorer"). Optional, for readability. */
|
||||
role?: string
|
||||
/**
|
||||
* Instruction template for this agent.
|
||||
* Supports: {{task}}, {{context}}, {{phase}}, {{team}}
|
||||
*/
|
||||
instruction: string
|
||||
/**
|
||||
* Optional git worktree isolation.
|
||||
* "auto" creates a new branch+worktree; any other string is used as the branch name.
|
||||
*/
|
||||
worktree?: string
|
||||
}
|
||||
|
||||
export interface TeamPhase {
|
||||
/** Phase identifier used when calling run_team_phase (e.g., "discovery", "execution") */
|
||||
name: string
|
||||
/** Human-readable label for UI display. Defaults to name if omitted. */
|
||||
label?: string
|
||||
/** When true, all agents in this phase run concurrently. Default: false (sequential). */
|
||||
concurrent?: boolean
|
||||
/**
|
||||
* When true, the orchestrator should ask for user approval before this phase starts.
|
||||
* The run_team_phase tool itself does not enforce this — it is a signal for the
|
||||
* orchestrator mode to call ask_followup_question first.
|
||||
*/
|
||||
requireApproval?: boolean
|
||||
/**
|
||||
* When true and concurrent is also true, abort all remaining sibling agents as soon as
|
||||
* one fails. Has no effect in sequential mode.
|
||||
*/
|
||||
abortOnChildFailure?: boolean
|
||||
/** Agents to run in this phase. Must contain at least one entry. */
|
||||
agents: TeamAgentSpec[]
|
||||
}
|
||||
|
||||
export interface TeamConfig {
|
||||
/** Auto-populated with the source file path at load time. Not set by user. */
|
||||
$source?: string
|
||||
/** Unique identifier used in tool calls and skill registration (e.g., "fullstack"). */
|
||||
slug: string
|
||||
/** Human-readable team name (e.g., "Full-Stack Dev Team"). */
|
||||
name: string
|
||||
/** Short description of what this team does. Shown in team listings. */
|
||||
description?: string
|
||||
/** Ordered list of phases. The orchestrator runs them in this order. */
|
||||
phases: TeamPhase[]
|
||||
/**
|
||||
* Path to a Markdown file containing shared conventions for all agents.
|
||||
* Relative to the workspace root. Content is injected into every agent's message
|
||||
* inside a <team_conventions> block.
|
||||
*/
|
||||
conventions?: string
|
||||
/**
|
||||
* Mode slug for the orchestrating task. Defaults to "architect".
|
||||
* This is informational — the skill / invocation mechanism uses it to set the initial mode.
|
||||
*/
|
||||
orchestratorMode?: string
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ export const toolNames = [
|
|||
"switch_mode",
|
||||
"new_task",
|
||||
"spawn_parallel_tasks",
|
||||
"run_team_phase",
|
||||
"codebase_search",
|
||||
"update_todo_list",
|
||||
"run_slash_command",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { switchModeTool } from "../tools/SwitchModeTool"
|
|||
import { attemptCompletionTool, AttemptCompletionCallbacks } from "../tools/AttemptCompletionTool"
|
||||
import { newTaskTool } from "../tools/NewTaskTool"
|
||||
import { spawnParallelTasksTool } from "../tools/SpawnParallelTasksTool"
|
||||
import { runTeamPhaseTool } from "../tools/RunTeamPhaseTool"
|
||||
import { updateTodoListTool } from "../tools/UpdateTodoListTool"
|
||||
import { runSlashCommandTool } from "../tools/RunSlashCommandTool"
|
||||
import { skillTool } from "../tools/SkillTool"
|
||||
|
|
@ -827,6 +828,15 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
toolCallId: block.id,
|
||||
})
|
||||
break
|
||||
case "run_team_phase":
|
||||
await checkpointSaveAndMark(cline)
|
||||
await runTeamPhaseTool.handle(cline, block as ToolUse<"run_team_phase">, {
|
||||
askApproval,
|
||||
handleError,
|
||||
pushToolResult,
|
||||
toolCallId: block.id,
|
||||
})
|
||||
break
|
||||
case "attempt_completion": {
|
||||
const completionCallbacks: AttemptCompletionCallbacks = {
|
||||
askApproval,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import executeCommand from "./execute_command"
|
|||
import generateImage from "./generate_image"
|
||||
import listFiles from "./list_files"
|
||||
import newTask from "./new_task"
|
||||
import runTeamPhase from "./run_team_phase"
|
||||
import spawnParallelTasks from "./spawn_parallel_tasks"
|
||||
import readCommandOutput from "./read_command_output"
|
||||
import { createReadFileTool, type ReadFileToolOptions } from "./read_file"
|
||||
|
|
@ -58,6 +59,7 @@ export function getNativeTools(options: NativeToolsOptions = {}): OpenAI.Chat.Ch
|
|||
generateImage,
|
||||
listFiles,
|
||||
newTask,
|
||||
runTeamPhase,
|
||||
spawnParallelTasks,
|
||||
readCommandOutput,
|
||||
createReadFileTool(readFileOptions),
|
||||
|
|
|
|||
56
src/core/prompts/tools/native-tools/run_team_phase.ts
Normal file
56
src/core/prompts/tools/native-tools/run_team_phase.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import type OpenAI from "openai"
|
||||
|
||||
const RUN_TEAM_PHASE_DESCRIPTION = `Run one phase of a pre-configured team workflow defined in .roo/teams/<team_slug>.json.
|
||||
|
||||
A team config describes an ordered list of phases (e.g., discovery → execution → review). Each phase runs one or more specialist agents. This tool executes exactly ONE phase and returns its aggregated results.
|
||||
|
||||
**Typical orchestrator loop:**
|
||||
1. Read .roo/teams/<slug>.json (with read_file) to learn the phase names and requireApproval flags.
|
||||
2. For each phase in order:
|
||||
a. If requireApproval is true, call ask_followup_question to get user sign-off.
|
||||
b. Call run_team_phase with the phase name, the original task, and accumulated context from prior phases.
|
||||
3. After the last phase, call attempt_completion with the final summary.
|
||||
|
||||
**Execution modes (set in the team config per-phase):**
|
||||
- concurrent: false (default) — agents in the phase run one at a time; you receive results when all finish.
|
||||
- concurrent: true — all agents start simultaneously; you receive results when all finish.
|
||||
|
||||
Pass prior phase results as JSON in the \`context\` parameter so later phases (e.g., review agents) have full context.
|
||||
|
||||
CRITICAL: This tool MUST be called alone. Do NOT call it alongside other tools in the same turn.`
|
||||
|
||||
export default {
|
||||
type: "function",
|
||||
function: {
|
||||
name: "run_team_phase",
|
||||
description: RUN_TEAM_PHASE_DESCRIPTION,
|
||||
strict: true,
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
team_slug: {
|
||||
type: "string",
|
||||
description:
|
||||
'Slug of the team to run (matches the "slug" field in the team config, e.g., "fullstack")',
|
||||
},
|
||||
phase_name: {
|
||||
type: "string",
|
||||
description:
|
||||
'Name of the phase to execute (matches the "name" field in a phase entry, e.g., "discovery")',
|
||||
},
|
||||
task: {
|
||||
type: "string",
|
||||
description:
|
||||
"The original user task description. Injected into agent instructions via {{task}}. Pass the same value for every phase.",
|
||||
},
|
||||
context: {
|
||||
type: ["string", "null"],
|
||||
description:
|
||||
"JSON string of accumulated results from previous phases. Injected into agent instructions via {{context}}. Omit or pass null for the first phase.",
|
||||
},
|
||||
},
|
||||
required: ["team_slug", "phase_name", "task", "context"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
} satisfies OpenAI.Chat.ChatCompletionTool
|
||||
172
src/core/tools/RunTeamPhaseTool.ts
Normal file
172
src/core/tools/RunTeamPhaseTool.ts
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
|
||||
import type { TeamConfig } from "@roo-code/types"
|
||||
|
||||
import { Task } from "../task/Task"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface RunTeamPhaseParams {
|
||||
team_slug: string
|
||||
phase_name: string
|
||||
task: string
|
||||
context?: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal provider surface needed by RunTeamPhaseTool.
|
||||
* Cast from providerRef.deref() using `provider as TeamPhaseProvider`.
|
||||
*/
|
||||
interface TeamPhaseProvider {
|
||||
getTeamConfig(slug: string): TeamConfig | undefined
|
||||
delegateParentAndOpenChild(params: {
|
||||
parentTaskId: string
|
||||
message: string
|
||||
initialTodos: []
|
||||
mode: string
|
||||
worktree?: string
|
||||
parallelQueue?: Array<{ mode: string; message: string; worktree?: string }>
|
||||
}): Promise<Task>
|
||||
spawnConcurrentChildren(params: {
|
||||
parentTaskId: string
|
||||
tasks: Array<{ mode: string; message: string; worktree?: string }>
|
||||
abortOnChildFailure?: boolean
|
||||
}): Promise<Array<{ taskId: string; summary: string; error?: string }>>
|
||||
}
|
||||
|
||||
export class RunTeamPhaseTool extends BaseTool<"run_team_phase"> {
|
||||
readonly name = "run_team_phase" as const
|
||||
|
||||
async execute(params: RunTeamPhaseParams, task: Task, callbacks: ToolCallbacks): Promise<void> {
|
||||
const { team_slug, phase_name, task: taskDesc, context } = params
|
||||
const { askApproval, handleError, pushToolResult } = callbacks
|
||||
|
||||
try {
|
||||
const provider = task.providerRef.deref() as TeamPhaseProvider | undefined
|
||||
if (!provider) {
|
||||
pushToolResult(formatResponse.toolError("Provider reference lost"))
|
||||
return
|
||||
}
|
||||
|
||||
// 1) Look up team config
|
||||
const teamConfig = provider.getTeamConfig(team_slug)
|
||||
if (!teamConfig) {
|
||||
pushToolResult(
|
||||
formatResponse.toolError(
|
||||
`Team "${team_slug}" not found. Make sure .roo/teams/${team_slug}.json exists in the workspace.`,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// 2) Find the phase
|
||||
const phase = teamConfig.phases.find((p) => p.name === phase_name)
|
||||
if (!phase) {
|
||||
const available = teamConfig.phases.map((p) => p.name).join(", ")
|
||||
pushToolResult(
|
||||
formatResponse.toolError(
|
||||
`Phase "${phase_name}" not found in team "${team_slug}". Available phases: ${available}`,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (!phase.agents || phase.agents.length === 0) {
|
||||
pushToolResult(formatResponse.toolError(`Phase "${phase_name}" has no agents defined.`))
|
||||
return
|
||||
}
|
||||
|
||||
// 3) Load shared conventions (non-fatal if missing)
|
||||
let conventionsPrefix = ""
|
||||
if (teamConfig.conventions) {
|
||||
try {
|
||||
const conventionsPath = path.resolve(task.workspacePath, teamConfig.conventions)
|
||||
const content = await fs.readFile(conventionsPath, "utf-8")
|
||||
if (content.trim()) {
|
||||
conventionsPrefix = `<team_conventions>\n${content.trim()}\n</team_conventions>\n\n`
|
||||
}
|
||||
} catch {
|
||||
// Conventions file not found — proceed without it
|
||||
}
|
||||
}
|
||||
|
||||
// 4) Interpolate {{task}}, {{context}}, {{phase}}, {{team}} in instructions
|
||||
const interpolate = (template: string) =>
|
||||
template
|
||||
.replace(/\{\{task\}\}/g, taskDesc)
|
||||
.replace(/\{\{context\}\}/g, context ?? "")
|
||||
.replace(/\{\{phase\}\}/g, phase_name)
|
||||
.replace(/\{\{team\}\}/g, teamConfig.name)
|
||||
|
||||
const agentSpecs = phase.agents.map((agent) => ({
|
||||
mode: agent.mode,
|
||||
role: agent.role,
|
||||
message: conventionsPrefix + interpolate(agent.instruction),
|
||||
worktree: agent.worktree,
|
||||
}))
|
||||
|
||||
// 5) Ask approval
|
||||
const toolMessage = JSON.stringify({
|
||||
tool: "runTeamPhase",
|
||||
team: teamConfig.name,
|
||||
phase: phase.label ?? phase_name,
|
||||
agentCount: agentSpecs.length,
|
||||
concurrent: phase.concurrent ?? false,
|
||||
agents: agentSpecs.map((s) => ({ role: s.role ?? s.mode, mode: s.mode })),
|
||||
})
|
||||
|
||||
const didApprove = await askApproval("tool", toolMessage)
|
||||
if (!didApprove) return
|
||||
|
||||
task.consecutiveMistakeCount = 0
|
||||
|
||||
// 6) Execute the phase
|
||||
if (phase.concurrent) {
|
||||
// Concurrent: all agents start simultaneously; parent stays alive.
|
||||
const results = await provider.spawnConcurrentChildren({
|
||||
parentTaskId: task.taskId,
|
||||
tasks: agentSpecs.map(({ mode, message, worktree }) => ({ mode, message, worktree })),
|
||||
abortOnChildFailure: phase.abortOnChildFailure ?? false,
|
||||
})
|
||||
pushToolResult(JSON.stringify(results, null, 2))
|
||||
} else if (agentSpecs.length === 1) {
|
||||
// Single sequential agent — simple delegation (like new_task)
|
||||
await provider.delegateParentAndOpenChild({
|
||||
parentTaskId: task.taskId,
|
||||
message: agentSpecs[0].message,
|
||||
initialTodos: [],
|
||||
mode: agentSpecs[0].mode,
|
||||
worktree: agentSpecs[0].worktree,
|
||||
})
|
||||
// Parent is now suspended; this line is reached only if delegation didn't proceed.
|
||||
pushToolResult(`Phase "${phase_name}" agent started. Awaiting completion...`)
|
||||
} else {
|
||||
// Multiple sequential agents — queue drain (first runs, rest queued)
|
||||
const [first, ...rest] = agentSpecs
|
||||
await provider.delegateParentAndOpenChild({
|
||||
parentTaskId: task.taskId,
|
||||
message: first.message,
|
||||
initialTodos: [],
|
||||
mode: first.mode,
|
||||
worktree: first.worktree,
|
||||
parallelQueue: rest.map(({ mode, message, worktree }) => ({ mode, message, worktree })),
|
||||
})
|
||||
// Parent is now suspended; this line is reached only if delegation didn't proceed.
|
||||
pushToolResult(
|
||||
`Phase "${phase_name}" started with ${agentSpecs.length} sequential agents. Awaiting all results...`,
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("running team phase", error as Error)
|
||||
}
|
||||
}
|
||||
|
||||
override async handlePartial(task: Task, block: ToolUse<"run_team_phase">): Promise<void> {
|
||||
const partialMessage = JSON.stringify({ tool: "runTeamPhase" })
|
||||
await task.ask("tool", partialMessage, block.partial).catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
export const runTeamPhaseTool = new RunTeamPhaseTool()
|
||||
|
|
@ -77,6 +77,7 @@ import { CodeIndexManager } from "../../services/code-index/manager"
|
|||
import type { IndexProgressUpdate } from "../../services/code-index/interfaces/manager"
|
||||
import { MdmService } from "../../services/mdm/MdmService"
|
||||
import { SkillsManager } from "../../services/skills/SkillsManager"
|
||||
import { TeamsManager } from "../../services/teams/TeamsManager"
|
||||
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { setTtsEnabled, setTtsSpeed } from "../../utils/tts"
|
||||
|
|
@ -152,6 +153,7 @@ export class ClineProvider
|
|||
private _workspaceTracker?: WorkspaceTracker // workSpaceTracker read-only for access outside this class
|
||||
protected mcpHub?: McpHub // Change from private to protected
|
||||
protected skillsManager?: SkillsManager
|
||||
protected teamsManager?: TeamsManager
|
||||
private marketplaceManager: MarketplaceManager
|
||||
private mdmService?: MdmService
|
||||
private taskCreationCallback: (task: Task) => void
|
||||
|
|
@ -241,6 +243,12 @@ export class ClineProvider
|
|||
this.log(`Failed to initialize Skills Manager: ${error}`)
|
||||
})
|
||||
|
||||
// Initialize Teams Manager for team workflow discovery
|
||||
this.teamsManager = new TeamsManager(this)
|
||||
this.teamsManager.initialize().catch((error) => {
|
||||
this.log(`Failed to initialize Teams Manager: ${error}`)
|
||||
})
|
||||
|
||||
this.marketplaceManager = new MarketplaceManager(this.context, this.customModesManager)
|
||||
|
||||
// Forward <most> task events to the provider.
|
||||
|
|
@ -732,6 +740,7 @@ export class ClineProvider
|
|||
this.mcpHub = undefined
|
||||
await this.skillsManager?.dispose()
|
||||
this.skillsManager = undefined
|
||||
this.teamsManager = undefined
|
||||
this.marketplaceManager?.cleanup()
|
||||
this.customModesManager?.dispose()
|
||||
this.taskHistoryStore.dispose()
|
||||
|
|
@ -2771,6 +2780,10 @@ export class ClineProvider
|
|||
return this.skillsManager
|
||||
}
|
||||
|
||||
public getTeamConfig(slug: string): import("@roo-code/types").TeamConfig | undefined {
|
||||
return this.teamsManager?.getTeamConfig(slug)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current state is compliant with MDM policy
|
||||
* @returns true if compliant or no MDM policy exists, false if MDM policy exists and user is non-compliant
|
||||
|
|
|
|||
66
src/services/teams/TeamsManager.ts
Normal file
66
src/services/teams/TeamsManager.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
|
||||
import type { TeamConfig } from "@roo-code/types"
|
||||
import type { ClineProvider } from "../../core/webview/ClineProvider"
|
||||
|
||||
export class TeamsManager {
|
||||
private teams: Map<string, TeamConfig> = new Map()
|
||||
private providerRef: WeakRef<ClineProvider>
|
||||
|
||||
constructor(provider: ClineProvider) {
|
||||
this.providerRef = new WeakRef(provider)
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
await this.discoverTeams()
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan .roo/teams/*.json in the current workspace and cache all valid TeamConfig objects.
|
||||
* Non-fatal: missing directory or malformed files are silently skipped.
|
||||
*/
|
||||
async discoverTeams(): Promise<void> {
|
||||
this.teams.clear()
|
||||
const provider = this.providerRef.deref()
|
||||
if (!provider) return
|
||||
|
||||
const teamsDir = path.join(provider.cwd, ".roo", "teams")
|
||||
|
||||
let entries: import("fs").Dirent[]
|
||||
try {
|
||||
entries = await fs.readdir(teamsDir, { withFileTypes: true, encoding: "utf-8" })
|
||||
} catch {
|
||||
// Directory doesn't exist — normal for projects without teams
|
||||
return
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile() || !entry.name.endsWith(".json")) continue
|
||||
const filePath = path.join(teamsDir, entry.name)
|
||||
try {
|
||||
const raw = await fs.readFile(filePath, "utf-8")
|
||||
const config = JSON.parse(raw) as TeamConfig
|
||||
if (typeof config.slug === "string" && config.slug && Array.isArray(config.phases)) {
|
||||
config.$source = filePath
|
||||
this.teams.set(config.slug, config)
|
||||
}
|
||||
} catch {
|
||||
// Malformed JSON or missing required fields — skip silently
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTeamConfig(slug: string): TeamConfig | undefined {
|
||||
return this.teams.get(slug)
|
||||
}
|
||||
|
||||
listTeams(): TeamConfig[] {
|
||||
return Array.from(this.teams.values())
|
||||
}
|
||||
|
||||
/** Reload all team configs from disk (e.g., after a file-system change). */
|
||||
async refresh(): Promise<void> {
|
||||
await this.discoverTeams()
|
||||
}
|
||||
}
|
||||
|
|
@ -81,6 +81,9 @@ export const toolParamNames = [
|
|||
// read_file legacy format parameter (backward compatibility)
|
||||
"files",
|
||||
"line_ranges",
|
||||
// run_team_phase parameters
|
||||
"team_slug",
|
||||
"phase_name",
|
||||
] as const
|
||||
|
||||
export type ToolParamName = (typeof toolParamNames)[number]
|
||||
|
|
@ -116,6 +119,7 @@ export type NativeToolArgs = {
|
|||
update_todo_list: { todos: string }
|
||||
use_mcp_tool: { server_name: string; tool_name: string; arguments?: Record<string, unknown> }
|
||||
write_to_file: { path: string; content: string }
|
||||
run_team_phase: { team_slug: string; phase_name: string; task: string; context?: string | null }
|
||||
// Add more tools as they are migrated to native protocol
|
||||
}
|
||||
|
||||
|
|
@ -285,6 +289,7 @@ export const TOOL_DISPLAY_NAMES: Record<ToolName, string> = {
|
|||
switch_mode: "switch modes",
|
||||
new_task: "create new task",
|
||||
spawn_parallel_tasks: "spawn parallel tasks",
|
||||
run_team_phase: "run team phase",
|
||||
codebase_search: "codebase search",
|
||||
update_todo_list: "update todo list",
|
||||
run_slash_command: "run slash command",
|
||||
|
|
@ -309,7 +314,7 @@ export const TOOL_GROUPS: Record<ToolGroup, ToolGroupConfig> = {
|
|||
tools: ["use_mcp_tool", "access_mcp_resource"],
|
||||
},
|
||||
modes: {
|
||||
tools: ["switch_mode", "new_task"],
|
||||
tools: ["switch_mode", "new_task", "run_team_phase"],
|
||||
alwaysAvailable: true,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue