diff --git a/src/main/ipc/teams.ts b/src/main/ipc/teams.ts index 93c95603..e3d0f353 100644 --- a/src/main/ipc/teams.ts +++ b/src/main/ipc/teams.ts @@ -1286,12 +1286,29 @@ async function handleAddMember( return { success: false, error: 'role must be a string' }; } - return wrapTeamHandler('addMember', () => - getTeamDataService().addMember(vTeam.value!, { - name: vName.value!, + return wrapTeamHandler('addMember', async () => { + const tn = vTeam.value!; + const memberName = vName.value!; + await getTeamDataService().addMember(tn, { + name: memberName, role: role, - }) - ); + }); + + // If team is alive, notify the lead to spawn the new teammate + const provisioning = getTeamProvisioningService(); + if (provisioning.isTeamAlive(tn)) { + const roleHint = typeof role === 'string' && role.trim() ? ` with role "${role.trim()}"` : ''; + const spawnMessage = + `A new teammate "${memberName}"${roleHint} has been added to the team. ` + + `Please spawn them immediately using the Task tool with team_name="${tn}" and name="${memberName}".`; + try { + await provisioning.sendMessageToTeam(tn, spawnMessage); + } catch { + // Best-effort: lead process may not be responsive + logger.warn(`Failed to notify lead about new member "${memberName}" in ${tn}`); + } + } + }); } async function handleRemoveMember( diff --git a/src/main/services/team/TeamMemberResolver.ts b/src/main/services/team/TeamMemberResolver.ts index a44ff571..3ebbd4c6 100644 --- a/src/main/services/team/TeamMemberResolver.ts +++ b/src/main/services/team/TeamMemberResolver.ts @@ -81,7 +81,7 @@ export class TeamMemberResolver { ) ?? null; const memberMessages = messages.filter((message) => message.from === name); const latestMessage = memberMessages[0] ?? null; - const status = this.resolveStatus(latestMessage); + const status = this.resolveStatus(latestMessage, currentTask !== null); const configMember = configMemberMap.get(name); const metaMember = metaMemberMap.get(name); members.push({ @@ -103,9 +103,11 @@ export class TeamMemberResolver { return members; } - private resolveStatus(message: InboxMessage | null): MemberStatus { + private resolveStatus(message: InboxMessage | null, hasActiveTask: boolean): MemberStatus { if (!message) { - return 'unknown'; + // Member exists in config but has no messages yet — + // if they own an in_progress task they're clearly active, otherwise idle + return hasActiveTask ? 'active' : 'idle'; } const structured = this.parseStructuredMessage(message.text); diff --git a/src/renderer/utils/memberHelpers.ts b/src/renderer/utils/memberHelpers.ts index 90596d3b..fd10157b 100644 --- a/src/renderer/utils/memberHelpers.ts +++ b/src/renderer/utils/memberHelpers.ts @@ -32,7 +32,7 @@ export function getPresenceLabel( if (member.status === 'terminated') return 'terminated'; if (isTeamProvisioning) return 'connecting'; if (isTeamAlive === false) return 'offline'; - if (member.status === 'unknown') return 'unknown'; + if (member.status === 'unknown') return 'idle'; return member.currentTaskId ? 'working' : 'idle'; }