init claude-code
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
import { z } from 'zod/v4'
|
||||
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 { isAgentSwarmsEnabled } from '../../utils/agentSwarmsEnabled.js'
|
||||
import { lazySchema } from '../../utils/lazySchema.js'
|
||||
import { jsonStringify } from '../../utils/slowOperations.js'
|
||||
import { TEAM_LEAD_NAME } from '../../utils/swarm/constants.js'
|
||||
import {
|
||||
cleanupTeamDirectories,
|
||||
readTeamFile,
|
||||
unregisterTeamForSessionCleanup,
|
||||
} from '../../utils/swarm/teamHelpers.js'
|
||||
import { clearTeammateColors } from '../../utils/swarm/teammateLayoutManager.js'
|
||||
import { clearLeaderTeamName } from '../../utils/tasks.js'
|
||||
import { TEAM_DELETE_TOOL_NAME } from './constants.js'
|
||||
import { getPrompt } from './prompt.js'
|
||||
import { renderToolResultMessage, renderToolUseMessage } from './UI.js'
|
||||
|
||||
const inputSchema = lazySchema(() => z.strictObject({}))
|
||||
type InputSchema = ReturnType<typeof inputSchema>
|
||||
|
||||
export type Output = {
|
||||
success: boolean
|
||||
message: string
|
||||
team_name?: string
|
||||
}
|
||||
|
||||
export type Input = z.infer<InputSchema>
|
||||
|
||||
export const TeamDeleteTool: Tool<InputSchema, Output> = buildTool({
|
||||
name: TEAM_DELETE_TOOL_NAME,
|
||||
searchHint: 'disband a swarm team and clean up',
|
||||
maxResultSizeChars: 100_000,
|
||||
shouldDefer: true,
|
||||
|
||||
userFacingName() {
|
||||
return ''
|
||||
},
|
||||
|
||||
get inputSchema(): InputSchema {
|
||||
return inputSchema()
|
||||
},
|
||||
|
||||
isEnabled() {
|
||||
return isAgentSwarmsEnabled()
|
||||
},
|
||||
|
||||
async description() {
|
||||
return 'Clean up team and task directories when the swarm is complete'
|
||||
},
|
||||
|
||||
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 appState = getAppState()
|
||||
const teamName = appState.teamContext?.teamName
|
||||
|
||||
if (teamName) {
|
||||
// Read team config to check for active members
|
||||
const teamFile = readTeamFile(teamName)
|
||||
if (teamFile) {
|
||||
// Filter out the team lead - only count non-lead members
|
||||
const nonLeadMembers = teamFile.members.filter(
|
||||
m => m.name !== TEAM_LEAD_NAME,
|
||||
)
|
||||
|
||||
// Separate truly active members from idle/dead ones
|
||||
// Members with isActive === false are idle (finished their turn or crashed)
|
||||
const activeMembers = nonLeadMembers.filter(m => m.isActive !== false)
|
||||
|
||||
if (activeMembers.length > 0) {
|
||||
const memberNames = activeMembers.map(m => m.name).join(', ')
|
||||
return {
|
||||
data: {
|
||||
success: false,
|
||||
message: `Cannot cleanup team with ${activeMembers.length} active member(s): ${memberNames}. Use requestShutdown to gracefully terminate teammates first.`,
|
||||
team_name: teamName,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await cleanupTeamDirectories(teamName)
|
||||
// Already cleaned — don't try again on gracefulShutdown.
|
||||
unregisterTeamForSessionCleanup(teamName)
|
||||
|
||||
// Clear color assignments so new teams start fresh
|
||||
clearTeammateColors()
|
||||
|
||||
// Clear leader team name so getTaskListId() falls back to session ID
|
||||
clearLeaderTeamName()
|
||||
|
||||
logEvent('tengu_team_deleted', {
|
||||
team_name:
|
||||
teamName as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
})
|
||||
}
|
||||
|
||||
// Clear team context and inbox from app state
|
||||
setAppState(prev => ({
|
||||
...prev,
|
||||
teamContext: undefined,
|
||||
inbox: {
|
||||
messages: [], // Clear any queued messages
|
||||
},
|
||||
}))
|
||||
|
||||
return {
|
||||
data: {
|
||||
success: true,
|
||||
message: teamName
|
||||
? `Cleaned up directories and worktrees for team "${teamName}"`
|
||||
: 'No team name found, nothing to clean up',
|
||||
team_name: teamName,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
renderToolUseMessage,
|
||||
renderToolResultMessage,
|
||||
} satisfies ToolDef<InputSchema, Output>)
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { jsonParse } from '../../utils/slowOperations.js';
|
||||
import type { Output } from './TeamDeleteTool.js';
|
||||
export function renderToolUseMessage(_input: Record<string, unknown>): React.ReactNode {
|
||||
return 'cleanup team: current';
|
||||
}
|
||||
export function renderToolResultMessage(content: Output | string, _progressMessages: unknown, {
|
||||
verbose: _verbose
|
||||
}: {
|
||||
verbose: boolean;
|
||||
}): React.ReactNode {
|
||||
const result: Output = typeof content === 'string' ? jsonParse(content) : content;
|
||||
|
||||
// Suppress cleanup result - the batched shutdown message covers this
|
||||
if ('success' in result && 'team_name' in result && 'message' in result) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsImpzb25QYXJzZSIsIk91dHB1dCIsInJlbmRlclRvb2xVc2VNZXNzYWdlIiwiX2lucHV0IiwiUmVjb3JkIiwiUmVhY3ROb2RlIiwicmVuZGVyVG9vbFJlc3VsdE1lc3NhZ2UiLCJjb250ZW50IiwiX3Byb2dyZXNzTWVzc2FnZXMiLCJ2ZXJib3NlIiwiX3ZlcmJvc2UiLCJyZXN1bHQiXSwic291cmNlcyI6WyJVSS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsganNvblBhcnNlIH0gZnJvbSAnLi4vLi4vdXRpbHMvc2xvd09wZXJhdGlvbnMuanMnXG5pbXBvcnQgdHlwZSB7IE91dHB1dCB9IGZyb20gJy4vVGVhbURlbGV0ZVRvb2wuanMnXG5cbmV4cG9ydCBmdW5jdGlvbiByZW5kZXJUb29sVXNlTWVzc2FnZShcbiAgX2lucHV0OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPixcbik6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIHJldHVybiAnY2xlYW51cCB0ZWFtOiBjdXJyZW50J1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcmVuZGVyVG9vbFJlc3VsdE1lc3NhZ2UoXG4gIGNvbnRlbnQ6IE91dHB1dCB8IHN0cmluZyxcbiAgX3Byb2dyZXNzTWVzc2FnZXM6IHVua25vd24sXG4gIHsgdmVyYm9zZTogX3ZlcmJvc2UgfTogeyB2ZXJib3NlOiBib29sZWFuIH0sXG4pOiBSZWFjdC5SZWFjdE5vZGUge1xuICBjb25zdCByZXN1bHQ6IE91dHB1dCA9XG4gICAgdHlwZW9mIGNvbnRlbnQgPT09ICdzdHJpbmcnID8ganNvblBhcnNlKGNvbnRlbnQpIDogY29udGVudFxuXG4gIC8vIFN1cHByZXNzIGNsZWFudXAgcmVzdWx0IC0gdGhlIGJhdGNoZWQgc2h1dGRvd24gbWVzc2FnZSBjb3ZlcnMgdGhpc1xuICBpZiAoJ3N1Y2Nlc3MnIGluIHJlc3VsdCAmJiAndGVhbV9uYW1lJyBpbiByZXN1bHQgJiYgJ21lc3NhZ2UnIGluIHJlc3VsdCkge1xuICAgIHJldHVybiBudWxsXG4gIH1cblxuICByZXR1cm4gbnVsbFxufVxuIl0sIm1hcHBpbmdzIjoiQUFBQSxPQUFPQSxLQUFLLE1BQU0sT0FBTztBQUN6QixTQUFTQyxTQUFTLFFBQVEsK0JBQStCO0FBQ3pELGNBQWNDLE1BQU0sUUFBUSxxQkFBcUI7QUFFakQsT0FBTyxTQUFTQyxvQkFBb0JBLENBQ2xDQyxNQUFNLEVBQUVDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQ2hDLEVBQUVMLEtBQUssQ0FBQ00sU0FBUyxDQUFDO0VBQ2pCLE9BQU8sdUJBQXVCO0FBQ2hDO0FBRUEsT0FBTyxTQUFTQyx1QkFBdUJBLENBQ3JDQyxPQUFPLEVBQUVOLE1BQU0sR0FBRyxNQUFNLEVBQ3hCTyxpQkFBaUIsRUFBRSxPQUFPLEVBQzFCO0VBQUVDLE9BQU8sRUFBRUM7QUFBK0IsQ0FBckIsRUFBRTtFQUFFRCxPQUFPLEVBQUUsT0FBTztBQUFDLENBQUMsQ0FDNUMsRUFBRVYsS0FBSyxDQUFDTSxTQUFTLENBQUM7RUFDakIsTUFBTU0sTUFBTSxFQUFFVixNQUFNLEdBQ2xCLE9BQU9NLE9BQU8sS0FBSyxRQUFRLEdBQUdQLFNBQVMsQ0FBQ08sT0FBTyxDQUFDLEdBQUdBLE9BQU87O0VBRTVEO0VBQ0EsSUFBSSxTQUFTLElBQUlJLE1BQU0sSUFBSSxXQUFXLElBQUlBLE1BQU0sSUFBSSxTQUFTLElBQUlBLE1BQU0sRUFBRTtJQUN2RSxPQUFPLElBQUk7RUFDYjtFQUVBLE9BQU8sSUFBSTtBQUNiIiwiaWdub3JlTGlzdCI6W119
|
||||
@@ -0,0 +1 @@
|
||||
export const TEAM_DELETE_TOOL_NAME = 'TeamDelete'
|
||||
@@ -0,0 +1,16 @@
|
||||
export function getPrompt(): string {
|
||||
return `
|
||||
# TeamDelete
|
||||
|
||||
Remove team and task directories when the swarm work is complete.
|
||||
|
||||
This operation:
|
||||
- Removes the team directory (\`~/.claude/teams/{team-name}/\`)
|
||||
- Removes the task directory (\`~/.claude/tasks/{team-name}/\`)
|
||||
- Clears team context from the current session
|
||||
|
||||
**IMPORTANT**: TeamDelete will fail if the team still has active members. Gracefully terminate teammates first, then call TeamDelete after all teammates have shut down.
|
||||
|
||||
Use this when all teammates have finished their work and you want to clean up the team resources. The team name is automatically determined from the current session's team context.
|
||||
`.trim()
|
||||
}
|
||||
Reference in New Issue
Block a user