fix: resolve new member status and notify lead to spawn teammate

- Fix member status resolution: members without messages now show as
  idle/active instead of unknown based on task ownership
- Notify running team lead via stdin when a new member is added so
  the lead can spawn the teammate process
- Map unknown presence label to idle for better UX clarity
This commit is contained in:
iliya 2026-02-24 14:58:23 +02:00 committed by Илия
parent 34ef846a5b
commit e090f172ec
3 changed files with 28 additions and 9 deletions

View file

@ -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(

View file

@ -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);

View file

@ -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';
}