Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
7.7 kB · 240 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241import { 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: string team_file_path: string lead_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 name if (!readTeamFile(providedName)) { return providedName }
// Team exists, generate a new unique name return 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 } = context const { team_name, description: _description, agent_type } = input
// Check if already in a team - restrict to one team per leader const appState = getAppState() const existingTeam = appState.teamContext?.teamName
if (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 failing const finalTeamName = generateUniqueTeamName(team_name)
// Generate a deterministic agent ID for the team lead const 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 discovery members: [ { 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 swarm const 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 context setAppState(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.env
return { data: { team_name: finalTeamName, team_file_path: teamFilePath, lead_agent_id: leadAgentId, }, } },
renderToolUseMessage,} satisfies ToolDef<InputSchema, Output>)