From 9e8307d8bcf1cfa57d73d48bd08fa03bc84c6df7 Mon Sep 17 00:00:00 2001 From: Markov Date: Mon, 2 Mar 2026 09:03:09 +0100 Subject: [PATCH] UUID refactoring: self-identification by member_id, mention filtering by ID instead of slug --- src/index.ts | 5 ++++- src/router.ts | 36 ++++++++++++++++++++++++------------ src/transport/ws-client.ts | 3 +++ 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/index.ts b/src/index.ts index 0c88273..027805a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,7 +46,10 @@ async function startAgentWs(config: AgentConfig, client: TrackerClient): Promise await wsTransport.start(); logger.info('Connected to tracker via WebSocket'); - // Register project mappings for memory context + // Register member ID and project mappings from auth.ok + if (wsTransport.memberId) { + router.setMemberId(wsTransport.memberId); + } if (wsTransport.projects.length > 0) { router.setProjectMappings(wsTransport.projects); } diff --git a/src/router.ts b/src/router.ts index 5d374a7..da92400 100644 --- a/src/router.ts +++ b/src/router.ts @@ -21,6 +21,8 @@ export class EventRouter { private wsTransport: WsClientTransport | null = null; /** chat_id → project_id mapping (populated from auth.ok) */ private chatToProject: Map = new Map(); + /** Own member_id from auth.ok */ + private memberId: string | null = null; constructor( private config: AgentConfig, @@ -40,6 +42,12 @@ export class EventRouter { this.wsTransport = transport; } + /** Set own member_id from auth.ok */ + setMemberId(id: string): void { + this.memberId = id; + this.log.info('Member ID set: %s', id); + } + /** Register chat_id → project_id mappings from auth.ok */ setProjectMappings(projects: Array<{ id: string; chat_id?: string | null }>): void { for (const p of projects) { @@ -108,7 +116,6 @@ export class EventRouter { */ private async handleMessageNew(data: Record): Promise { const content = (data.content as string) || ''; - const authorSlug = (data.author_slug as string) || (data.sender_slug as string) || ''; const authorType = (data.author_type as string) || 'member'; const taskId = data.task_id as string | undefined; const chatId = data.chat_id as string | undefined; @@ -117,7 +124,14 @@ export class EventRouter { // Extract author info from nested author object if present const author = data.author as Record | undefined; - const resolvedAuthorSlug = authorSlug || (author?.slug as string) || ''; + const authorId = (data.author_id as string) || (author?.id as string) || ''; + const authorSlug = (data.author_slug as string) || (author?.slug as string) || ''; + + // Extract mention IDs from mentions array + const mentions = (data.mentions || []) as Array>; + const mentionIds = new Set(mentions.map(m => (m.id as string) || '').filter(Boolean)); + const isMentioned = (this.memberId && mentionIds.has(this.memberId)) || + content.includes(`@${this.config.slug}`); // text fallback if (!content) { this.log.warn({ data }, 'message.new event missing content'); @@ -125,25 +139,23 @@ export class EventRouter { } // Ignore own messages (agent talking to itself) - if (resolvedAuthorSlug === this.config.slug) { + if ((this.memberId && authorId === this.memberId) || authorSlug === this.config.slug) { this.log.info('│ Skipping own message'); return; } - // Ignore messages from other agents (unless explicitly @mentioned) - // Prevents infinite reply loops between agents + // Ignore messages from other agents (unless explicitly mentioned) if (authorType === 'agent') { - if (!content.includes(`@${this.config.slug}`)) { - this.log.info('│ Skipping agent message (not mentioned): @%s "%s"', resolvedAuthorSlug, content.slice(0, 80)); + if (!isMentioned) { + this.log.info('│ Skipping agent message (not mentioned): %s "%s"', authorSlug, content.slice(0, 80)); return; } - this.log.info('│ Processing agent message (mentioned): @%s', resolvedAuthorSlug); + this.log.info('│ Processing agent message (mentioned): %s', authorSlug); } - // System messages: only process if agent is explicitly mentioned (@slug) - // This lets assignment/mention notifications through, but ignores generic lifecycle events + // System messages: only process if agent is explicitly mentioned if (authorType === 'system') { - if (!content.includes(`@${this.config.slug}`)) { + if (!isMentioned) { this.log.info('│ Skipping system message (not mentioned): "%s"', content.slice(0, 100)); return; } @@ -152,7 +164,7 @@ export class EventRouter { // Build context-rich prompt for the agent const ctx = taskId ? `[задача ${taskKey || taskId}]` : chatId ? '[чат]' : ''; - const from = authorType === 'system' ? '[система]' : `@${resolvedAuthorSlug}`; + const from = authorType === 'system' ? '[система]' : `@${authorSlug}`; const prompt = `${ctx} ${from}: ${content}`; this.log.info('│ %s %s: "%s"', ctx, from, content.slice(0, 200)); diff --git a/src/transport/ws-client.ts b/src/transport/ws-client.ts index e69784a..9ea1654 100644 --- a/src/transport/ws-client.ts +++ b/src/transport/ws-client.ts @@ -36,6 +36,8 @@ export class WsClientTransport implements TaskTracker { projects: Array<{ id: string; slug: string; name: string; chat_id?: string }> = []; /** Online members from auth.ok */ online: string[] = []; + /** Own member_id from auth.ok */ + memberId: string | null = null; /** Remote agent config from auth.ok (if agent) */ remoteConfig: { model?: string; @@ -209,6 +211,7 @@ export class WsClientTransport implements TaskTracker { private onAuthOk(msg: Record): void { const data = (msg.data || msg.init || {}) as Record; + this.memberId = (data.member_id as string) || null; this.lobbyChatId = (data.lobby_chat_id as string) || null; this.projects = (data.projects as Array<{ id: string; slug: string; name: string; chat_id?: string }>) || []; this.online = (data.online as string[]) || (data.agents_online as string[]) || [];