diff --git a/packages/agent-graph/src/canvas/draw-particles.ts b/packages/agent-graph/src/canvas/draw-particles.ts index 554c59cc..3118af0e 100644 --- a/packages/agent-graph/src/canvas/draw-particles.ts +++ b/packages/agent-graph/src/canvas/draw-particles.ts @@ -37,7 +37,11 @@ export function drawParticles( if (!source || !target) continue; if (source.x == null || source.y == null || target.x == null || target.y == null) continue; - const cp = computeControlPoints(source.x, source.y, target.x, target.y); + // Reverse: swap source/target for particles going in opposite direction + const from = p.reverse ? target : source; + const to = p.reverse ? source : target; + + const cp = computeControlPoints(from.x!, from.y!, to.x!, to.y!); const color = p.color || COLORS.message; const baseSize = (p.size ?? 1) * 3; // Differentiate visual by particle kind @@ -50,12 +54,12 @@ export function drawParticles( const phaseOffset = p.id.charCodeAt(Math.min(5, p.id.length - 1)) * 0.1; const wobbleAmp = BEAM.wobble.amp; - drawParticleTrail(ctx, source, target, cp, p.progress, color, size, wobbleAmp, phaseOffset, time, p.kind); - drawParticleCore(ctx, source, target, cp, p.progress, color, size, wobbleAmp, phaseOffset, time, p.kind); + drawParticleTrail(ctx, from, to, cp, p.progress, color, size, wobbleAmp, phaseOffset, time, p.kind); + drawParticleCore(ctx, from, to, cp, p.progress, color, size, wobbleAmp, phaseOffset, time, p.kind); // Label if (p.label && p.progress > PARTICLE_DRAW.labelMinT && p.progress < PARTICLE_DRAW.labelMaxT) { - const pos = getWobbledPosition(source, target, cp, p.progress, wobbleAmp, phaseOffset, time); + const pos = getWobbledPosition(from, to, cp, p.progress, wobbleAmp, phaseOffset, time); ctx.font = `${PARTICLE_DRAW.labelFontSize}px monospace`; ctx.textAlign = 'center'; ctx.fillStyle = hexWithAlpha(color, 0.56); diff --git a/packages/agent-graph/src/ports/types.ts b/packages/agent-graph/src/ports/types.ts index 8517e081..ea7378f7 100644 --- a/packages/agent-graph/src/ports/types.ts +++ b/packages/agent-graph/src/ports/types.ts @@ -135,6 +135,8 @@ export interface GraphParticle { size?: number; /** Short label near particle */ label?: string; + /** If true, particle travels from target → source (reverse direction) */ + reverse?: boolean; } // ─── Domain Reference (opaque back-pointer) ────────────────────────────────── diff --git a/src/renderer/features/agent-graph/adapters/TeamGraphAdapter.ts b/src/renderer/features/agent-graph/adapters/TeamGraphAdapter.ts index 97d0fdcf..755e50f3 100644 --- a/src/renderer/features/agent-graph/adapters/TeamGraphAdapter.ts +++ b/src/renderer/features/agent-graph/adapters/TeamGraphAdapter.ts @@ -494,12 +494,19 @@ export class TeamGraphAdapter { if (this.#seenMessageIds.has(msgKey)) continue; this.#seenMessageIds.add(msgKey); - // Skip system/noise messages (idle notifications, JSON blobs) - if (TeamGraphAdapter.#isSystemMessage(msg)) continue; - const edgeId = TeamGraphAdapter.#resolveMessageEdge(msg, teamName, leadId, leadName, edges); if (!edgeId) continue; + // Determine direction: messages FROM a teammate TO lead should reverse + // (edges are always lead→member, but message goes member→lead) + const fromId = TeamGraphAdapter.#resolveParticipantId( + msg.from ?? '', + teamName, + leadId, + leadName + ); + const isFromTeammate = fromId !== leadId; + particles.push({ id: `particle:msg:${teamName}:${msgKey}`, edgeId, @@ -507,6 +514,7 @@ export class TeamGraphAdapter { kind: 'inbox_message', color: msg.color ?? '#66ccff', label: TeamGraphAdapter.#buildParticleLabel(msg.summary ?? msg.text, 'inbox'), + reverse: isFromTeammate, }); } } @@ -694,18 +702,6 @@ export class TeamGraphAdapter { return `member:${teamName}:${name}`; } - /** Filter out system/noise messages that shouldn't show as particles */ - static #isSystemMessage(msg: InboxMessage): boolean { - const text = msg.text ?? ''; - // JSON system messages (idle_notification, shutdown, etc.) - if (text.startsWith('{"type":') || text.startsWith('{"type" :')) return true; - // Very short system messages - if (text.length < 3) return true; - // System notification source - if (msg.source === 'system_notification') return true; - return false; - } - static #buildParticleLabel( text: string | undefined, kind: 'inbox' | 'comment',