fix(team): dedup duplicate SendMessage entries and show lead online during provisioning
- Add content-based dedup in handleGetData merge: when two directed messages have identical from+to+text within a 5-second window, keep only the first. Fixes duplicate display caused by both CLI and our persistInboxMessage writing to the same inbox file. - Reorder checks in getMemberDotClass/getPresenceLabel: check leadActivity before isTeamProvisioning so the lead shows green dot when its process is already running during team setup.
This commit is contained in:
parent
26e21251d0
commit
935128e26b
2 changed files with 26 additions and 4 deletions
|
|
@ -557,6 +557,12 @@ async function handleGetData(
|
|||
// messageIds inside the same session (e.g. lead-turn-* re-emits).
|
||||
const leadProcessTextFingerprints = new Set<string>();
|
||||
|
||||
// Content-based dedup for SendMessage captures: Claude Code CLI and our
|
||||
// persistInboxMessage both write to inboxes/{member}.json, producing two entries
|
||||
// with identical content but different messageIds. Track content fingerprints
|
||||
// (from+to+text) with timestamps to collapse them within a 5-second window.
|
||||
const contentSeen = new Map<string, number>(); // fingerprint → timestamp ms
|
||||
|
||||
const merged: typeof data.messages = [];
|
||||
const seen = new Set<string>();
|
||||
for (const msg of [...data.messages, ...live]) {
|
||||
|
|
@ -572,6 +578,19 @@ async function handleGetData(
|
|||
}
|
||||
leadProcessTextFingerprints.add(fp);
|
||||
}
|
||||
|
||||
// Content dedup for directed messages (SendMessage captures):
|
||||
// same from+to+text within 5 seconds = duplicate from CLI + our persist.
|
||||
if (typeof msg.to === 'string' && msg.to.trim().length > 0) {
|
||||
const contentFp = `${msg.from}\0${msg.to}\0${(msg.text ?? '').replace(/\s+/g, ' ').slice(0, 100)}`;
|
||||
const msgMs = Date.parse(msg.timestamp);
|
||||
const existingMs = contentSeen.get(contentFp);
|
||||
if (existingMs !== undefined && Math.abs(msgMs - existingMs) <= 5000) {
|
||||
continue; // duplicate within 5s window — skip
|
||||
}
|
||||
contentSeen.set(contentFp, msgMs);
|
||||
}
|
||||
|
||||
const key = keyFor(msg);
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
|
|
|
|||
|
|
@ -42,13 +42,15 @@ export function getMemberDotClass(
|
|||
): string {
|
||||
if (member.status === 'terminated') return STATUS_DOT_COLORS.terminated;
|
||||
if (member.removedAt) return STATUS_DOT_COLORS.terminated;
|
||||
if (isTeamProvisioning) return STATUS_DOT_COLORS.unknown;
|
||||
if (isTeamAlive === false) return STATUS_DOT_COLORS.terminated;
|
||||
// Lead activity check BEFORE provisioning fallback — when the lead process
|
||||
// is running (CLI logs present), show green even during provisioning.
|
||||
if (leadActivity && isLeadMember(member)) {
|
||||
return leadActivity === 'active'
|
||||
? `${STATUS_DOT_COLORS.active} animate-pulse`
|
||||
: STATUS_DOT_COLORS.active;
|
||||
}
|
||||
if (isTeamProvisioning) return STATUS_DOT_COLORS.unknown;
|
||||
if (isTeamAlive === false) return STATUS_DOT_COLORS.terminated;
|
||||
// When team is alive, all non-terminated members are online
|
||||
if (isTeamAlive) {
|
||||
if (member.currentTaskId) return `${STATUS_DOT_COLORS.active} animate-pulse`;
|
||||
|
|
@ -67,8 +69,7 @@ export function getPresenceLabel(
|
|||
leadContextPercent?: number
|
||||
): string {
|
||||
if (member.status === 'terminated') return 'terminated';
|
||||
if (isTeamProvisioning) return 'connecting';
|
||||
if (isTeamAlive === false) return 'offline';
|
||||
// Lead activity check before provisioning fallback (mirrors getMemberDotClass order).
|
||||
if (leadActivity && isLeadMember(member)) {
|
||||
if (leadActivity === 'active') {
|
||||
return leadContextPercent != null && leadContextPercent > 0
|
||||
|
|
@ -77,6 +78,8 @@ export function getPresenceLabel(
|
|||
}
|
||||
return 'ready';
|
||||
}
|
||||
if (isTeamProvisioning) return 'connecting';
|
||||
if (isTeamAlive === false) return 'offline';
|
||||
if (member.status === 'unknown') return 'idle';
|
||||
return member.currentTaskId ? 'working' : 'idle';
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue