import { z } from 'zod/v4'import { getSessionId } from '../../bootstrap/state.js'import { logEvent } from '../../services/analytics/index.js'import type { AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS } from '../../services/analytics/metadata.js'import type { Tool } from '../../Tool.js'import { buildTool, type ToolDef } from '../../Tool.js'import { formatAgentId } from '../../utils/agentId.js'import { isAgentSwarmsEnabled } from '../../utils/agentSwarmsEnabled.js'import { getCwd } from '../../utils/cwd.js'import { lazySchema } from '../../utils/lazySchema.js'import {getDefaultMainLoopModel,parseUserSpecifiedModel,} from '../../utils/model/model.js'import { jsonStringify } from '../../utils/slowOperations.js'import { getResolvedTeammateMode } from '../../utils/swarm/backends/registry.js'import { TEAM_LEAD_NAME } from '../../utils/swarm/constants.js'import type { TeamFile } from '../../utils/swarm/teamHelpers.js'import {getTeamFilePath,readTeamFile,registerTeamForSessionCleanup,sanitizeName,writeTeamFileAsync,} from '../../utils/swarm/teamHelpers.js'import { assignTeammateColor } from '../../utils/swarm/teammateLayoutManager.js'import {ensureTasksDir,resetTaskList,setLeaderTeamName,} from '../../utils/tasks.js'import { generateWordSlug } from '../../utils/words.js'import { TEAM_CREATE_TOOL_NAME } from './constants.js'import { getPrompt } from './prompt.js'import { renderToolUseMessage } from './UI.js'const inputSchema = lazySchema(() =>z.strictObject({team_name: z.string().describe('Name for the new team to create.'),description: z.string().optional().describe('Team description/purpose.'),agent_type: z.string().optional().describe('Type/role of the team lead (e.g., "researcher", "test-runner"). ' +'Used for team file and inter-agent coordination.',),}),)type InputSchema = ReturnType<typeof inputSchema>export type Output = {team_name: stringteam_file_path: stringlead_agent_id: string}export type Input = z.infer<InputSchema>/*** Generates a unique team name by checking if the provided name already exists.* If the name already exists, generates a new word slug.*/function generateUniqueTeamName(providedName: string): string {// If the team doesn't exist, use the provided nameif (!readTeamFile(providedName)) {return providedName}// Team exists, generate a new unique namereturn generateWordSlug()}export const TeamCreateTool: Tool<InputSchema, Output> = buildTool({name: TEAM_CREATE_TOOL_NAME,searchHint: 'create a multi-agent swarm team',maxResultSizeChars: 100_000,shouldDefer: true,userFacingName() {return ''},get inputSchema(): InputSchema {return inputSchema()},isEnabled() {return isAgentSwarmsEnabled()},toAutoClassifierInput(input) {return input.team_name},async validateInput(input, _context) {if (!input.team_name || input.team_name.trim().length === 0) {return {result: false,message: 'team_name is required for TeamCreate',errorCode: 9,}}return { result: true }},async description() {return 'Create a new team for coordinating multiple agents'},async prompt() {return getPrompt()},mapToolResultToToolResultBlockParam(data, toolUseID) {return {tool_use_id: toolUseID,type: 'tool_result' as const,content: [{type: 'text' as const,text: jsonStringify(data),},],}},async call(input, context) {const { setAppState, getAppState } = contextconst { team_name, description: _description, agent_type } = input// Check if already in a team - restrict to one team per leaderconst appState = getAppState()const existingTeam = appState.teamContext?.teamNameif (existingTeam) {throw new Error(`Already leading team "${existingTeam}". A leader can only manage one team at a time. Use TeamDelete to end the current team before creating a new one.`,)}// If team already exists, generate a unique name instead of failingconst finalTeamName = generateUniqueTeamName(team_name)// Generate a deterministic agent ID for the team leadconst leadAgentId = formatAgentId(TEAM_LEAD_NAME, finalTeamName)const leadAgentType = agent_type || TEAM_LEAD_NAME// Get the team lead's current model from AppState (handles session model, settings, CLI override)const leadModel = parseUserSpecifiedModel(appState.mainLoopModelForSession ??appState.mainLoopModel ??getDefaultMainLoopModel(),)const teamFilePath = getTeamFilePath(finalTeamName)const teamFile: TeamFile = {name: finalTeamName,description: _description,createdAt: Date.now(),leadAgentId,leadSessionId: getSessionId(), // Store actual session ID for team discoverymembers: [{agentId: leadAgentId,name: TEAM_LEAD_NAME,agentType: leadAgentType,model: leadModel,joinedAt: Date.now(),tmuxPaneId: '',cwd: getCwd(),subscriptions: [],},],}await writeTeamFileAsync(finalTeamName, teamFile)// Track for session-end cleanup — teams were left on disk forever// unless explicitly TeamDelete'd (gh-32730).registerTeamForSessionCleanup(finalTeamName)// Reset and create the corresponding task list directory (Team = Project = TaskList)// This ensures task numbering starts fresh at 1 for each new swarmconst taskListId = sanitizeName(finalTeamName)await resetTaskList(taskListId)await ensureTasksDir(taskListId)// Register the team name so getTaskListId() returns it for the leader.// Without this, the leader falls through to getSessionId() and writes tasks// to a different directory than tmux/iTerm2 teammates expect.setLeaderTeamName(sanitizeName(finalTeamName))// Update AppState with team contextsetAppState(prev => ({...prev,teamContext: {teamName: finalTeamName,teamFilePath,leadAgentId,teammates: {[leadAgentId]: {name: TEAM_LEAD_NAME,agentType: leadAgentType,color: assignTeammateColor(leadAgentId),tmuxSessionName: '',tmuxPaneId: '',cwd: getCwd(),spawnedAt: Date.now(),},},},}))logEvent('tengu_team_created', {team_name:finalTeamName as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,teammate_count: 1,lead_agent_type:leadAgentType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,teammate_mode:getResolvedTeammateMode() as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,})// Note: We intentionally don't set CLAUDE_CODE_AGENT_ID for the team lead because:// 1. The lead is not a "teammate" - isTeammate() should return false for them// 2. Their ID is deterministic (team-lead@teamName) and can be derived when needed// 3. Setting it would cause isTeammate() to return true, breaking inbox polling// Team name is stored in AppState.teamContext, not process.envreturn {data: {team_name: finalTeamName,team_file_path: teamFilePath,lead_agent_id: leadAgentId,},}},renderToolUseMessage,} satisfies ToolDef<InputSchema, Output>)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。