UUID refactoring: self-identification by member_id, mention filtering by ID instead of slug

This commit is contained in:
Markov 2026-03-02 09:03:09 +01:00
parent 7d7fe6df42
commit 9e8307d8bc
3 changed files with 31 additions and 13 deletions

View File

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

View File

@ -21,6 +21,8 @@ export class EventRouter {
private wsTransport: WsClientTransport | null = null;
/** chat_id → project_id mapping (populated from auth.ok) */
private chatToProject: Map<string, string> = 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<string, unknown>): Promise<void> {
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<string, unknown> | 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<Record<string, unknown>>;
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));

View File

@ -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<string, unknown>): void {
const data = (msg.data || msg.init || {}) as Record<string, unknown>;
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[]) || [];