diff --git a/src/index.ts b/src/index.ts index 264c135..a19f39c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ import type { IncomingMessage } from './transport/types.js'; async function startAgentHttp(config: AgentConfig, client: TrackerClient): Promise { const registration = new TrackerRegistration(config); - const router = new EventRouter(config, client, registration); + const router = new EventRouter(config, client); const http = new HttpTransport(config.listenPort); http.onEvent((event) => router.handleEvent(event)); @@ -38,7 +38,7 @@ async function startAgentHttp(config: AgentConfig, client: TrackerClient): Promi async function startAgentWs(config: AgentConfig, client: TrackerClient): Promise { const wsTransport = new WsClientTransport(config); - const router = new EventRouter(config, client, wsTransport); + const router = new EventRouter(config, client); wsTransport.onEvent((event) => router.handleEvent(event)); diff --git a/src/router.ts b/src/router.ts index a293c95..343fa44 100644 --- a/src/router.ts +++ b/src/router.ts @@ -5,7 +5,7 @@ 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, TrackerTask } from './tracker/types.js'; +import type { TrackerEvent } from './tracker/types.js'; export interface TaskTracker { addTask(taskId: string): void; @@ -19,12 +19,11 @@ export class EventRouter { constructor( private config: AgentConfig, private client: TrackerClient, - private taskTracker: TaskTracker, ) { this.trackerTools = createTrackerTools({ trackerClient: client, agentSlug: config.slug, - selfAssignedTasks: new Set(), // kept for ToolContext compat, no longer used in router + selfAssignedTasks: new Set(), }); this.log.info({ toolCount: this.trackerTools.length }, 'Tracker tools registered'); } @@ -33,13 +32,11 @@ export class EventRouter { this.log.info('┌── ROUTER: handling %s (id: %s)', event.event, event.id); switch (event.event) { - case 'task.assigned': - await this.handleTaskAssigned(event.data); - break; case 'message.new': case 'chat.message': await this.handleMessageNew(event.data); break; + case 'task.assigned': case 'task.created': case 'task.updated': case 'agent.status': @@ -53,39 +50,16 @@ export class EventRouter { } /** - * task.assigned — notify agent via session, no side effects. - * Agent decides what to do (change status, start work, etc.) via tools. - */ - private async handleTaskAssigned(data: Record): Promise { - const task = (data.task as TrackerTask) || (data as unknown as TrackerTask); - if (!task?.id) { - this.log.error({ data }, 'task.assigned event missing task data'); - return; - } - - this.log.info('│ TASK ASSIGNED: %s — %s', task.key || task.id, task.title); - - // Build human-readable prompt — agent decides what to do - const prompt = [ - `Тебе назначена задача: ${task.key || ''} — ${task.title}`, - task.description ? `\nОписание: ${task.description}` : '', - task.priority ? `Приоритет: ${task.priority}` : '', - '', - 'Ознакомься с задачей. Если готов — возьми в работу (обнови статус через update_task). Если нужна информация — спроси.', - ].filter(Boolean).join('\n'); - - await this.runAndReply(prompt, task.id ? { task_id: task.id } : undefined); - this.log.info('└── TASK ASSIGNED handled: %s', task.key || task.id); - } - - /** - * message.new / chat.message — forward to agent session, reply to same context. + * 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; // Don't respond to own messages if (authorSlug === this.config.slug) { @@ -98,23 +72,21 @@ export class EventRouter { return; } - this.log.info('│ MESSAGE from @%s: "%s"', authorSlug, content.slice(0, 200)); + // Build context-rich prompt for the agent + const ctx = taskId ? `[задача ${taskKey || taskId}]` : chatId ? '[чат]' : ''; + const from = authorType === 'system' ? '[система]' : `@${authorSlug}`; + const prompt = `${ctx} ${from}: ${content}`; - const replyCtx = taskId ? { task_id: taskId } : chatId ? { chat_id: chatId } : undefined; - await this.runAndReply(content, replyCtx); + this.log.info('│ %s %s: "%s"', ctx, from, content.slice(0, 200)); + + await this.runAgent(prompt); this.log.info('└── MESSAGE handled'); } /** - * Run agent with prompt and send reply to the appropriate context. - * No side effects — agent controls everything via tools. + * Run agent session. Agent controls everything via tools (send_message, update_task, etc.) */ - private async runAndReply( - prompt: string, - replyCtx?: { task_id?: string; chat_id?: string }, - ): Promise { - let collectedText = ''; - + private async runAgent(prompt: string): Promise { for await (const msg of runAgent(prompt, { workDir: this.config.workDir, sessionId: this.config.sessionId, @@ -126,23 +98,9 @@ export class EventRouter { allowedPaths: this.config.allowedPaths, customTools: this.trackerTools, })) { - if (msg.type === 'text') { - collectedText += msg.content; - } else if (msg.type === 'error') { + if (msg.type === 'error') { this.log.error({ error: msg.content }, 'Agent error'); } } - - if (collectedText.trim() && replyCtx) { - const payload = { - content: collectedText.trim(), - task_id: replyCtx.task_id, - chat_id: replyCtx.chat_id, - }; - - await this.client.sendMessage(payload, this.config.slug).catch((err) => { - this.log.error({ err, replyCtx }, 'Failed to send reply'); - }); - } } }