feat(06-02): enhance conflict bounce-back with agent messaging

Adds MessageRepository to DefaultCoordinationManager for conflict notification:
- Creates info message to agent when merge conflict occurs
- Message includes conflicting file list and resolution instructions
- System-generated messages use senderType='user' with null senderId
- Refactors conflict description into cleaner array.join format
This commit is contained in:
Lukas May
2026-01-30 21:07:46 +01:00
parent 6d7894bde4
commit 4535707bc8

View File

@@ -18,6 +18,7 @@ import type {
import type { WorktreeManager } from '../git/types.js';
import type { TaskRepository } from '../db/repositories/task-repository.js';
import type { AgentRepository } from '../db/repositories/agent-repository.js';
import type { MessageRepository } from '../db/repositories/message-repository.js';
import type { CoordinationManager, MergeQueueItem, MergeResult } from './types.js';
// =============================================================================
@@ -55,6 +56,7 @@ export class DefaultCoordinationManager implements CoordinationManager {
private worktreeManager?: WorktreeManager,
private taskRepository?: TaskRepository,
private agentRepository?: AgentRepository,
private messageRepository?: MessageRepository,
private eventBus?: EventBus
) {}
@@ -257,24 +259,45 @@ export class DefaultCoordinationManager implements CoordinationManager {
/**
* Handle a merge conflict.
* Creates a conflict-resolution task and assigns back to the agent.
* Creates a conflict-resolution task and notifies the agent via message.
*/
async handleConflict(taskId: string, conflicts: string[]): Promise<void> {
if (!this.taskRepository) {
throw new Error('TaskRepository not configured');
}
if (!this.agentRepository) {
throw new Error('AgentRepository not configured');
}
// Get original task for context
const originalTask = await this.taskRepository.findById(taskId);
if (!originalTask) {
throw new Error(`Original task not found: ${taskId}`);
}
// Get agent that was working on the task
const agent = await this.agentRepository.findByTaskId(taskId);
if (!agent) {
throw new Error(`No agent found for task: ${taskId}`);
}
// Build conflict description
const conflictDescription = [
'Merge conflicts detected. Resolve conflicts in the following files:',
'',
...conflicts.map((f) => `- ${f}`),
'',
`Original task: ${originalTask.name}`,
'',
'Instructions: Resolve merge conflicts in the listed files, then mark task complete.',
].join('\n');
// Create new conflict-resolution task
const conflictTask = await this.taskRepository.create({
planId: originalTask.planId,
name: `Resolve conflicts: ${originalTask.name}`,
description: `Merge conflicts detected. Resolve conflicts in the following files:\n\n${conflicts.map((f) => `- ${f}`).join('\n')}\n\nOriginal task: ${originalTask.name}\n\nInstructions: Resolve merge conflicts in the listed files, then mark task complete.`,
description: conflictDescription,
type: 'auto',
priority: 'high', // Conflicts should be resolved quickly
status: 'pending',
@@ -284,6 +307,30 @@ export class DefaultCoordinationManager implements CoordinationManager {
// Update original task status to blocked
await this.taskRepository.update(taskId, { status: 'blocked' });
// Create message to agent if messageRepository is configured
if (this.messageRepository) {
const messageContent = [
`Merge conflict detected for task: ${originalTask.name}`,
'',
'Conflicting files:',
...conflicts.map((f) => `- ${f}`),
'',
`A new task has been created to resolve these conflicts: ${conflictTask.name}`,
'',
'Please resolve the merge conflicts in the listed files and mark the resolution task as complete.',
].join('\n');
await this.messageRepository.create({
senderType: 'user', // System-generated messages appear as from user
senderId: null,
recipientType: 'agent',
recipientId: agent.id,
type: 'info',
content: messageContent,
requiresResponse: false,
});
}
// Emit TaskQueuedEvent for the new conflict-resolution task
const event: TaskQueuedEvent = {
type: 'task:queued',