feat: enhance team provisioning process with pre-creation of configuration
- Updated the TeamProvisioningService to pre-create team configuration and member metadata before spawning the CLI, ensuring persistence even in case of failures. - Modified return messages to reflect the new provisioning flow, indicating that the team configuration has been pre-created. - Adjusted the steps in the provisioning process to streamline the creation and provisioning of teams, improving overall user experience.
This commit is contained in:
parent
f01defc32c
commit
01a1472e4c
1 changed files with 49 additions and 7 deletions
|
|
@ -908,21 +908,21 @@ ${buildMemberSpawnPrompt(m, displayName, request.teamName, leadName)
|
||||||
members: request.members,
|
members: request.members,
|
||||||
});
|
});
|
||||||
|
|
||||||
return `agent_teams_ui [Agent Team: “${request.teamName}” | Project: “${projectName}” | Lead: “${leadName}”] — team does NOT exist yet. You must create it.
|
return `agent_teams_ui [Agent Team: “${request.teamName}” | Project: “${projectName}” | Lead: “${leadName}”] — team config has been pre-created. Proceed with provisioning.
|
||||||
|
|
||||||
You are running in a non-interactive CLI session. Do not ask questions. Do everything in a single turn.
|
You are running in a non-interactive CLI session. Do not ask questions. Do everything in a single turn.
|
||||||
CRITICAL: Execute ALL steps directly yourself in sequence. Do NOT delegate any step to a sub-agent via the Agent tool. The ONLY valid use of the Agent tool is spawning individual teammates in step 2.
|
CRITICAL: Execute ALL steps directly yourself in sequence. Do NOT delegate any step to a sub-agent via the Agent tool. The ONLY valid use of the Agent tool is spawning individual teammates in step 2.
|
||||||
CRITICAL: For step 1, use the BUILT-IN TeamCreate tool — NOT any mcp__agent-teams__* MCP tool. Do NOT call mcp__agent-teams__team_launch, mcp__agent-teams__team_stop, or any other mcp__agent-teams__ runtime tool during provisioning. MCP board tools (task_create, task_set_status, etc.) are allowed only in step 3.
|
Do NOT call mcp__agent-teams__team_launch, mcp__agent-teams__team_stop, or any other mcp__agent-teams__ runtime tool during provisioning. MCP board tools (task_create, task_set_status, etc.) are allowed only in step 3.
|
||||||
You are “${leadName}”, the team lead.
|
You are “${leadName}”, the team lead.
|
||||||
|
|
||||||
Goal: Create and provision a NEW Claude Code agent team${request.members.length === 0 ? ' (solo — lead only)' : ' with live teammates'}.
|
Goal: Provision Claude Code agent team${request.members.length === 0 ? ' (solo — lead only)' : ' with live teammates'}.
|
||||||
The team does NOT exist yet — no config, no state, nothing. Step 1 is MANDATORY.
|
The team config has been pre-created (config.json, members metadata). Proceed with member provisioning.
|
||||||
${userPromptBlock}
|
${userPromptBlock}
|
||||||
${persistentContext}
|
${persistentContext}
|
||||||
|
|
||||||
Steps (execute in this exact order — do NOT skip any step):
|
Steps (execute in this exact order — do NOT skip any step):
|
||||||
|
|
||||||
1) MANDATORY FIRST STEP: Call the BUILT-IN TeamCreate tool (not any MCP tool) with team_name=”${request.teamName}”. This creates the team config and in-memory state. Without this step, teammate spawns will FAIL. Do NOT assume the team already exists.
|
1) The team config has been pre-created. Proceed directly to step 2.
|
||||||
|
|
||||||
${step2Block}
|
${step2Block}
|
||||||
|
|
||||||
|
|
@ -2846,7 +2846,7 @@ export class TeamProvisioningService {
|
||||||
waitingTasksSince: null,
|
waitingTasksSince: null,
|
||||||
provisioningComplete: false,
|
provisioningComplete: false,
|
||||||
isLaunch: false,
|
isLaunch: false,
|
||||||
fsPhase: 'waiting_config',
|
fsPhase: 'waiting_members',
|
||||||
leadRelayCapture: null,
|
leadRelayCapture: null,
|
||||||
activeCrossTeamReplyHints: [],
|
activeCrossTeamReplyHints: [],
|
||||||
leadMsgSeq: 0,
|
leadMsgSeq: 0,
|
||||||
|
|
@ -2905,7 +2905,7 @@ export class TeamProvisioningService {
|
||||||
'--mcp-config',
|
'--mcp-config',
|
||||||
mcpConfigPath,
|
mcpConfigPath,
|
||||||
'--disallowedTools',
|
'--disallowedTools',
|
||||||
'TeamDelete,TodoWrite',
|
'TeamDelete,TodoWrite,TeamCreate',
|
||||||
// Explicit --permission-mode overrides user's defaultMode in ~/.claude/settings.json
|
// Explicit --permission-mode overrides user's defaultMode in ~/.claude/settings.json
|
||||||
// (e.g. "acceptEdits") which otherwise takes precedence over CLI flags
|
// (e.g. "acceptEdits") which otherwise takes precedence over CLI flags
|
||||||
...(request.skipPermissions !== false
|
...(request.skipPermissions !== false
|
||||||
|
|
@ -2917,12 +2917,18 @@ export class TeamProvisioningService {
|
||||||
...parseCliArgs(request.extraCliArgs),
|
...parseCliArgs(request.extraCliArgs),
|
||||||
];
|
];
|
||||||
try {
|
try {
|
||||||
|
await this.preCreateConfig(request);
|
||||||
child = spawnCli(claudePath, spawnArgs, {
|
child = spawnCli(claudePath, spawnArgs, {
|
||||||
cwd: request.cwd,
|
cwd: request.cwd,
|
||||||
env: { ...shellEnv },
|
env: { ...shellEnv },
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Clean up pre-saved files (may not exist if preCreateConfig failed partway)
|
||||||
|
const teamDir = path.join(getTeamsBasePath(), request.teamName);
|
||||||
|
const tasksDir = path.join(getTasksBasePath(), request.teamName);
|
||||||
|
await fs.promises.rm(teamDir, { recursive: true, force: true }).catch(() => {});
|
||||||
|
await fs.promises.rm(tasksDir, { recursive: true, force: true }).catch(() => {});
|
||||||
this.runs.delete(runId);
|
this.runs.delete(runId);
|
||||||
this.provisioningRunByTeam.delete(request.teamName);
|
this.provisioningRunByTeam.delete(request.teamName);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -2930,6 +2936,7 @@ export class TeamProvisioningService {
|
||||||
|
|
||||||
updateProgress(run, 'spawning', 'Starting Claude CLI process', {
|
updateProgress(run, 'spawning', 'Starting Claude CLI process', {
|
||||||
pid: child.pid ?? undefined,
|
pid: child.pid ?? undefined,
|
||||||
|
configReady: true,
|
||||||
});
|
});
|
||||||
run.onProgress(run.progress);
|
run.onProgress(run.progress);
|
||||||
run.child = child;
|
run.child = child;
|
||||||
|
|
@ -7192,6 +7199,41 @@ export class TeamProvisioningService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-saves team config.json + members-meta.json + tasks/ dir to disk
|
||||||
|
* BEFORE spawning CLI. This guarantees the team persists even if CLI
|
||||||
|
* crashes (e.g. 429 rate limit) before it can call TeamCreate.
|
||||||
|
*/
|
||||||
|
private async preCreateConfig(request: TeamCreateRequest): Promise<void> {
|
||||||
|
const teamDir = path.join(getTeamsBasePath(), request.teamName);
|
||||||
|
const tasksDir = path.join(getTasksBasePath(), request.teamName);
|
||||||
|
await fs.promises.mkdir(teamDir, { recursive: true });
|
||||||
|
await fs.promises.mkdir(tasksDir, { recursive: true });
|
||||||
|
|
||||||
|
const config: Record<string, unknown> = {
|
||||||
|
name: request.displayName?.trim() || request.teamName,
|
||||||
|
description: request.description?.trim() || undefined,
|
||||||
|
color: request.color?.trim() || undefined,
|
||||||
|
};
|
||||||
|
if (request.cwd?.trim()) {
|
||||||
|
config.projectPath = request.cwd.trim();
|
||||||
|
config.projectPathHistory = [request.cwd.trim()];
|
||||||
|
}
|
||||||
|
await atomicWriteAsync(path.join(teamDir, 'config.json'), JSON.stringify(config, null, 2));
|
||||||
|
|
||||||
|
const joinedAt = Date.now();
|
||||||
|
await this.membersMetaStore.writeMembers(
|
||||||
|
request.teamName,
|
||||||
|
request.members.map((m) => ({
|
||||||
|
name: m.name.trim(),
|
||||||
|
role: m.role?.trim() || undefined,
|
||||||
|
agentType: 'general-purpose' as const,
|
||||||
|
color: getMemberColorByName(m.name.trim()),
|
||||||
|
joinedAt,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private async persistMembersMeta(teamName: string, request: TeamCreateRequest): Promise<void> {
|
private async persistMembersMeta(teamName: string, request: TeamCreateRequest): Promise<void> {
|
||||||
const teammateMembers = request.members.filter((member) => {
|
const teammateMembers = request.members.filter((member) => {
|
||||||
const trimmed = member.name.trim();
|
const trimmed = member.name.trim();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue