跳到主要内容

teamHelpers.ts:团队协作工具函数集

🟡 进阶

文件定位

teamHelpers.ts(683 行)是 Swarm 系统的基础设施层,提供所有与团队配置文件操作相关的工具函数。上层模块(teamDiscovery.tspermissionSync.tsinProcessRunner.ts 等)都依赖它。

这个文件没有"算法",但有大量的状态管理逻辑:团队成员的增删改查、路径权限管理、成员状态更新等。

核心数据模型:TeamFile

export type TeamFile = {
name: string // 团队名称
description?: string // 团队描述
createdAt: number // 创建时间戳
leadAgentId: string // Leader 的 agentId
leadSessionId?: string // Leader 的 session UUID(用于发现)
hiddenPaneIds?: string[] // 被隐藏的面板 ID 列表
teamAllowedPaths?: TeamAllowedPath[] // 团队级别的权限路径
members: Array<{
agentId: string // "name@team"
name: string // 简短名称
agentType?: string // 角色类型
model?: string // 使用的模型
prompt?: string // 初始任务提示词
color?: string // UI 颜色
planModeRequired?: boolean // 是否需要 plan mode
joinedAt: number // 加入时间戳
tmuxPaneId: string // tmux 面板 ID 或虚拟 ID
cwd: string // 工作目录
worktreePath?: string // git worktree 路径
sessionId?: string // 实际 session UUID
subscriptions: string[] // 订阅的事件类型
backendType?: BackendType // 执行后端类型
isActive?: boolean // false = idle,undefined/true = running
mode?: PermissionMode // 当前权限模式
}>
}

路径与文件操作基础

名称清理

// 用于 tmux 窗口名、worktree 路径、文件路径:
// 将所有非字母数字字符替换为 "-",转为小写
export function sanitizeName(name: string): string {
return name.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase()
}

// 用于 agentId 格式(agentName@teamName):
// 只替换 @ 字符,防止格式歧义
export function sanitizeAgentName(name: string): string {
return name.replace(/@/g, '-')
}

路径计算

export function getTeamDir(teamName: string): string {
return join(getTeamsDir(), sanitizeName(teamName))
// ~/ .claude/teams/{sanitized-name}/
}

export function getTeamFilePath(teamName: string): string {
return join(getTeamDir(teamName), 'config.json')
}

读写文件(Sync + Async 双套 API)

// 同步读(React render 路径使用)
export function readTeamFile(teamName: string): TeamFile | null {
try {
return jsonParse(readFileSync(getTeamFilePath(teamName), 'utf-8')) as TeamFile
} catch (e) {
if (getErrnoCode(e) === 'ENOENT') return null
return null // 损坏文件也返回 null
}
}

// 异步读(工具处理器使用)
export async function readTeamFileAsync(teamName: string): Promise<TeamFile | null>

// 同步写(React render 路径使用)
function writeTeamFile(teamName: string, teamFile: TeamFile): void {
const teamDir = getTeamDir(teamName)
mkdirSync(teamDir, { recursive: true })
writeFileSync(getTeamFilePath(teamName), jsonStringify(teamFile, null, 2))
}

// 异步写(工具处理器使用)
export async function writeTeamFileAsync(teamName: string, teamFile: TeamFile): Promise<void>

成员管理函数

removeTeammateFromTeamFile() — 按 agentId 或 name 移除

export function removeTeammateFromTeamFile(
teamName: string,
identifier: { agentId?: string; name?: string },
): boolean {
const teamFile = readTeamFile(teamName)
if (!teamFile) return false

const originalLength = teamFile.members.length
teamFile.members = teamFile.members.filter(m => {
if (identifier.agentId && m.agentId === identifier.agentId) return false
if (identifier.name && m.name === identifier.name) return false
return true
})

if (teamFile.members.length === originalLength) return false // 没找到

writeTeamFile(teamName, teamFile)
return true
}

removeMemberFromTeam() — 按 tmuxPaneId 移除

export function removeMemberFromTeam(teamName: string, tmuxPaneId: string): boolean {
const teamFile = readTeamFile(teamName)
if (!teamFile) return false

const memberIndex = teamFile.members.findIndex(m => m.tmuxPaneId === tmuxPaneId)
if (memberIndex === -1) return false

teamFile.members.splice(memberIndex, 1)

// 同时从 hiddenPaneIds 移除
if (teamFile.hiddenPaneIds) {
const hiddenIndex = teamFile.hiddenPaneIds.indexOf(tmuxPaneId)
if (hiddenIndex !== -1) {
teamFile.hiddenPaneIds.splice(hiddenIndex, 1)
}
}

writeTeamFile(teamName, teamFile)
return true
}

removeMemberByAgentId() — 从 AppState 移除(in-process 专用)

export function removeMemberByAgentId(
agentId: string,
teamName: string,
setAppState: SetAppStateFn,
): void {
// 1. 从团队文件中移除
removeTeammateFromTeamFile(teamName, { agentId })

// 2. 从 AppState 中移除(触发 UI 更新)
setAppState(prev => ({
...prev,
tasks: Object.fromEntries(
Object.entries(prev.tasks).filter(([_, task]) => {
if (task.type !== 'in_process_teammate') return true
return task.identity.agentId !== agentId
})
)
}))
}

面板隐藏管理

// 隐藏面板(从视图中移除但保持运行)
export function addHiddenPaneId(teamName: string, paneId: string): boolean

// 重新显示面板
export function removeHiddenPaneId(teamName: string, paneId: string): boolean

面板隐藏是一种 UI 功能,Teammate 仍在运行,只是不在主 swarm 视图中显示。适合已完成主要任务的 Teammate 但需要保持活跃状态的场景。

团队允许路径管理

teamAllowedPaths 是一个团队级的权限快捷方式列表:

export type TeamAllowedPath = {
path: string // 绝对目录路径(如 "/Users/admin/project/src")
toolName: string // 工具名(如 "Edit"、"Write"、"Bash")
addedBy: string // 由哪个 agentName 添加
addedAt: number // 时间戳(毫秒)
}
// 添加允许路径
export async function addTeamAllowedPath(
teamName: string,
allowedPath: TeamAllowedPath,
): Promise<boolean> {
const teamFile = await readTeamFileAsync(teamName)
if (!teamFile) return false

teamFile.teamAllowedPaths ??= []
// 检查是否已存在相同的路径+工具组合
const exists = teamFile.teamAllowedPaths.some(
p => p.path === allowedPath.path && p.toolName === allowedPath.toolName
)
if (!exists) {
teamFile.teamAllowedPaths.push(allowedPath)
await writeTeamFileAsync(teamName, teamFile)
}
return true
}

操作模式快照(mode snapshot)

当 Teammate 切换权限模式(如从 default 切到 acceptEdits)时,这个状态需要持久化到团队文件,以便 Leader 的 UI 能正确显示每个 Teammate 的当前模式:

// 来自 backends/teammateModeSnapshot.ts
export function updateMemberMode(
teamName: string,
tmuxPaneId: string,
mode: PermissionMode,
): void {
const teamFile = readTeamFile(teamName)
if (!teamFile) return

const member = teamFile.members.find(m => m.tmuxPaneId === tmuxPaneId)
if (member) {
member.mode = mode
writeTeamFile(teamName, teamFile)
}
}

spawnTeam 输入 Schema

teamHelpers.ts 还定义了创建团队时的输入 Schema(用于 AI 工具调用):

export const inputSchema = lazySchema(() =>
z.strictObject({
operation: z.enum(['spawnTeam', 'cleanup']).describe(
'Operation: spawnTeam to create a team, cleanup to remove team and task directories.'
),
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.'
),
team_name: z.string().optional().describe(
'Name for the new team to create (required for spawnTeam).'
),
description: z.string().optional().describe(
'Team description/purpose (only used with spawnTeam).'
),
})
)

两种操作:

  • spawnTeam:创建新团队,写入 config.json,返回 SpawnTeamOutput
  • cleanup:清理团队目录和任务目录

Git Worktree 集成

teamHelpers.ts 还包含创建 git worktree 的工具函数,允许每个 Teammate 在独立的 git worktree 中工作,避免分支冲突:

// 为 teammate 创建 git worktree
export async function createWorktreeForTeammate(
repoPath: string,
teammateName: string,
teamName: string,
): Promise<{ path: string; branch: string } | null> {
const worktreeBranch = `swarm/${sanitizeName(teamName)}/${sanitizeName(teammateName)}`
const worktreePath = join(repoPath, '.claude', 'worktrees', sanitizeName(teamName), sanitizeName(teammateName))

const result = await execFileNoThrowWithCwd(repoPath, gitExe(), [
'worktree', 'add', '-b', worktreeBranch, worktreePath
])

if (result.code !== 0) return null
return { path: worktreePath, branch: worktreeBranch }
}

小结

teamHelpers.ts 是 Swarm 系统的"数据访问层":

功能类别主要函数
路径计算getTeamDir, getTeamFilePath
配置读写readTeamFile, writeTeamFile, readTeamFileAsync
成员管理removeTeammateFromTeamFile, removeMemberFromTeam
面板隐藏addHiddenPaneId, removeHiddenPaneId
路径权限addTeamAllowedPath
团队清理cleanupTeam
WorktreecreateWorktreeForTeammate

所有函数都遵循同一个原则:读取 → 修改 → 写回,确保配置文件始终是完整的、有效的 JSON。

📄source/src/utils/swarm/teamHelpers.tsL1-100查看源码 →
📄source/src/utils/swarm/teamHelpers.tsL183-310查看源码 →