130 lines
4.2 KiB
TypeScript
130 lines
4.2 KiB
TypeScript
/**
|
|
* Teammate Initialization Module
|
|
*
|
|
* Handles initialization for Claude Code instances running as teammates in a swarm.
|
|
* Registers a Stop hook to notify the team leader when the teammate becomes idle.
|
|
*/
|
|
|
|
import type { AppState } from '../../state/AppState.js'
|
|
import { logForDebugging } from '../debug.js'
|
|
import { addFunctionHook } from '../hooks/sessionHooks.js'
|
|
import { applyPermissionUpdate } from '../permissions/PermissionUpdate.js'
|
|
import { jsonStringify } from '../slowOperations.js'
|
|
import { getTeammateColor } from '../teammate.js'
|
|
import {
|
|
createIdleNotification,
|
|
getLastPeerDmSummary,
|
|
writeToMailbox,
|
|
} from '../teammateMailbox.js'
|
|
import { readTeamFile, setMemberActive } from './teamHelpers.js'
|
|
|
|
/**
|
|
* Initializes hooks for a teammate running in a swarm.
|
|
* Should be called early in session startup after AppState is available.
|
|
*
|
|
* Registers a Stop hook that sends an idle notification to the team leader
|
|
* when this teammate's session stops.
|
|
*/
|
|
export function initializeTeammateHooks(
|
|
setAppState: (updater: (prev: AppState) => AppState) => void,
|
|
sessionId: string,
|
|
teamInfo: { teamName: string; agentId: string; agentName: string },
|
|
): void {
|
|
const { teamName, agentId, agentName } = teamInfo
|
|
|
|
// Read team file to get leader ID
|
|
const teamFile = readTeamFile(teamName)
|
|
if (!teamFile) {
|
|
logForDebugging(`[TeammateInit] Team file not found for team: ${teamName}`)
|
|
return
|
|
}
|
|
|
|
const leadAgentId = teamFile.leadAgentId
|
|
|
|
// Apply team-wide allowed paths if any exist
|
|
if (teamFile.teamAllowedPaths && teamFile.teamAllowedPaths.length > 0) {
|
|
logForDebugging(
|
|
`[TeammateInit] Found ${teamFile.teamAllowedPaths.length} team-wide allowed path(s)`,
|
|
)
|
|
|
|
for (const allowedPath of teamFile.teamAllowedPaths) {
|
|
// For absolute paths (starting with /), prepend one / to create //path/** pattern
|
|
// For relative paths, just use path/**
|
|
const ruleContent = allowedPath.path.startsWith('/')
|
|
? `/${allowedPath.path}/**`
|
|
: `${allowedPath.path}/**`
|
|
|
|
logForDebugging(
|
|
`[TeammateInit] Applying team permission: ${allowedPath.toolName} allowed in ${allowedPath.path} (rule: ${ruleContent})`,
|
|
)
|
|
|
|
setAppState(prev => ({
|
|
...prev,
|
|
toolPermissionContext: applyPermissionUpdate(
|
|
prev.toolPermissionContext,
|
|
{
|
|
type: 'addRules',
|
|
rules: [
|
|
{
|
|
toolName: allowedPath.toolName,
|
|
ruleContent,
|
|
},
|
|
],
|
|
behavior: 'allow',
|
|
destination: 'session',
|
|
},
|
|
),
|
|
}))
|
|
}
|
|
}
|
|
|
|
// Find the leader's name from the members array
|
|
const leadMember = teamFile.members.find(m => m.agentId === leadAgentId)
|
|
const leadAgentName = leadMember?.name || 'team-lead'
|
|
|
|
// Don't register hook if this agent is the leader
|
|
if (agentId === leadAgentId) {
|
|
logForDebugging(
|
|
'[TeammateInit] This agent is the team leader - skipping idle notification hook',
|
|
)
|
|
return
|
|
}
|
|
|
|
logForDebugging(
|
|
`[TeammateInit] Registering Stop hook for teammate ${agentName} to notify leader ${leadAgentName}`,
|
|
)
|
|
|
|
// Register Stop hook to notify leader when this teammate stops
|
|
addFunctionHook(
|
|
setAppState,
|
|
sessionId,
|
|
'Stop',
|
|
'', // No matcher - applies to all Stop events
|
|
async (messages, _signal) => {
|
|
// Mark this teammate as idle in the team config (fire and forget)
|
|
void setMemberActive(teamName, agentName, false)
|
|
|
|
// Send idle notification to the team leader using agent name (not UUID)
|
|
// Must await to ensure the write completes before process shutdown
|
|
const notification = createIdleNotification(agentName, {
|
|
idleReason: 'available',
|
|
summary: getLastPeerDmSummary(messages),
|
|
})
|
|
await writeToMailbox(leadAgentName, {
|
|
from: agentName,
|
|
text: jsonStringify(notification),
|
|
timestamp: new Date().toISOString(),
|
|
color: getTeammateColor(),
|
|
})
|
|
logForDebugging(
|
|
`[TeammateInit] Sent idle notification to leader ${leadAgentName}`,
|
|
)
|
|
return true // Don't block the Stop
|
|
},
|
|
'Failed to send idle notification to team leader',
|
|
{
|
|
timeout: 10000,
|
|
},
|
|
)
|
|
}
|