import path from 'path'; import { logger } from './logger.js'; import { runAgent } from './agent.js'; import { TrackerClient } from './tracker/client.js'; import { createTrackerTools } from './tools/index.js'; import type { ToolDefinition } from '@mariozechner/pi-coding-agent'; import type { AgentConfig } from './config.js'; import type { TrackerEvent } from './tracker/types.js'; export interface TaskTracker { addTask(taskId: string): void; removeTask(taskId: string): void; } export class EventRouter { private log = logger.child({ component: 'event-router' }); private trackerTools: ToolDefinition[]; constructor( private config: AgentConfig, private client: TrackerClient, ) { this.trackerTools = createTrackerTools({ trackerClient: client, agentSlug: config.slug, selfAssignedTasks: new Set(), }); this.log.info({ toolCount: this.trackerTools.length }, 'Tracker tools registered'); } async handleEvent(event: TrackerEvent): Promise { this.log.info('┌── ROUTER: handling %s (id: %s)', event.event, event.id); switch (event.event) { case 'message.new': case 'chat.message': await this.handleMessageNew(event.data); break; case 'task.assigned': case 'task.created': case 'task.updated': case 'agent.status': case 'agent.online': case 'agent.offline': this.log.info({ event: event.event }, 'Informational event, skipping'); break; default: this.log.warn({ event: event.event }, 'Unknown event type, ignoring'); } } /** * message.new / chat.message — forward to agent session. * Agent uses send_message tool to reply when needed. Router posts nothing. */ 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; const taskKey = data.task_key as string | undefined; if (!content) { this.log.warn({ data }, 'message.new event missing content'); return; } // Build context-rich prompt for the agent const ctx = taskId ? `[задача ${taskKey || taskId}]` : chatId ? '[чат]' : ''; const from = authorType === 'system' ? '[система]' : `@${authorSlug}`; const prompt = `${ctx} ${from}: ${content}`; this.log.info('│ %s %s: "%s"', ctx, from, content.slice(0, 200)); await this.runAgent(prompt); this.log.info('└── MESSAGE handled'); } /** * Run agent session. Agent controls everything via tools (send_message, update_task, etc.) */ private async runAgent(prompt: string): Promise { for await (const msg of runAgent(prompt, { workDir: this.config.workDir, sessionId: this.config.sessionId, model: this.config.model, provider: this.config.provider, systemPrompt: this.config.prompt || undefined, // fallback if no AGENT.md skillsDir: this.config.agentHome, sessionDir: path.join(this.config.agentHome, 'sessions'), allowedPaths: this.config.allowedPaths.length > 0 ? this.config.allowedPaths : [this.config.agentHome], customTools: this.trackerTools, agentHome: this.config.agentHome, })) { if (msg.type === 'error') { this.log.error({ error: msg.content }, 'Agent error'); } } } }