feat: Add delegation tracking MCP tools

Session 374: Task-MCP and Delegation System Integration (Phase 4 & 6)

- Add task_delegations tool: query delegations for a specific task
- Add task_delegation_query tool: query across all tasks by status/backend
- Enhance taskShow() to display recent delegation history
- New delegations.ts module with getRecentDelegations helper

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-01-09 10:43:13 +02:00
parent df3e557c87
commit e21072ea54
4 changed files with 208 additions and 0 deletions

View File

@@ -22,6 +22,7 @@ import { taskAdd, taskList, taskShow, taskClose, taskUpdate } from './tools/crud
import { taskSimilar, taskContext } from './tools/search.js';
import { taskLink, checklistAdd, checklistToggle, taskResolveDuplicate } from './tools/relations.js';
import { epicAdd, epicList, epicShow, epicAssign } from './tools/epics.js';
import { taskDelegations, taskDelegationQuery } from './tools/delegations.js';
// Create MCP server
const server = new Server(
@@ -147,6 +148,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
});
break;
// Delegations
case 'task_delegations':
result = await taskDelegations({ task_id: a.task_id });
break;
case 'task_delegation_query':
result = await taskDelegationQuery({
status: a.status,
backend: a.backend,
limit: a.limit,
});
break;
default:
throw new Error(`Unknown tool: ${name}`);
}

View File

@@ -3,6 +3,7 @@
import { query, queryOne, execute, getNextTaskId, getProjectKey } from '../db.js';
import { getEmbedding, formatEmbedding } from '../embeddings.js';
import type { Task, ChecklistItem, TaskLink } from '../types.js';
import { getRecentDelegations } from './delegations.js';
interface TaskAddArgs {
title: string;
@@ -225,6 +226,12 @@ export async function taskShow(id: string): Promise<string> {
}
}
// Get recent delegations
const delegationHistory = await getRecentDelegations(id);
if (delegationHistory) {
output += delegationHistory;
}
return output;
}

163
src/tools/delegations.ts Normal file
View File

@@ -0,0 +1,163 @@
// Delegation tracking tools for task-mcp
// Session 374: Task-MCP and Delegation System Integration
import { query } from '../db.js';
interface TaskDelegation {
delegation_id: string;
backend: string;
quality_score: number | null;
status: string;
input_tokens: number;
output_tokens: number;
cost_usd: number;
started_at: string;
completed_at: string | null;
error_message: string | null;
}
interface TaskDelegationWithTask extends TaskDelegation {
task_id: string;
title: string;
}
interface DelegationListArgs {
task_id: string;
}
interface DelegationQueryArgs {
status?: string;
backend?: string;
limit?: number;
}
/**
* List delegations for a specific task
*/
export async function taskDelegations(args: DelegationListArgs): Promise<string> {
const { task_id } = args;
const delegations = await query<TaskDelegation>(
`SELECT delegation_id, backend, quality_score, status,
input_tokens, output_tokens, cost_usd,
to_char(started_at, 'YYYY-MM-DD HH24:MI') as started_at,
to_char(completed_at, 'YYYY-MM-DD HH24:MI') as completed_at,
error_message
FROM task_delegations
WHERE task_id = $1
ORDER BY started_at DESC
LIMIT 20`,
[task_id]
);
if (delegations.length === 0) {
return `No delegations found for ${task_id}`;
}
let output = `Delegations for ${task_id}:\n\n`;
for (const d of delegations) {
const icon = d.status === 'success' ? '✓' : d.status === 'failed' ? '✗' : '○';
const quality = d.quality_score !== null ? `Q:${d.quality_score}` : 'Q:-';
const tokens = d.input_tokens + d.output_tokens;
const cost = d.cost_usd > 0 ? ` $${d.cost_usd.toFixed(4)}` : '';
output += ` ${icon} ${d.delegation_id.substring(0, 8)} [${d.backend}] ${quality} (${d.started_at})`;
if (tokens > 0) output += ` ${tokens}tok`;
output += cost;
output += '\n';
if (d.error_message) {
output += ` Error: ${d.error_message.substring(0, 60)}\n`;
}
}
return output;
}
/**
* Query delegations across all tasks with filters
*/
export async function taskDelegationQuery(args: DelegationQueryArgs): Promise<string> {
const { status, backend, limit = 20 } = args;
let whereClause = 'WHERE 1=1';
const params: unknown[] = [];
let paramIndex = 1;
if (status) {
whereClause += ` AND d.status = $${paramIndex++}`;
params.push(status);
}
if (backend) {
whereClause += ` AND d.backend = $${paramIndex++}`;
params.push(backend);
}
params.push(limit);
const delegations = await query<TaskDelegationWithTask>(
`SELECT d.delegation_id, d.task_id, t.title, d.backend,
d.quality_score, d.status,
d.input_tokens, d.output_tokens, d.cost_usd,
to_char(d.started_at, 'YYYY-MM-DD HH24:MI') as started_at,
d.error_message
FROM task_delegations d
JOIN tasks t ON t.id = d.task_id
${whereClause}
ORDER BY d.started_at DESC
LIMIT $${paramIndex}`,
params
);
if (delegations.length === 0) {
return `No delegations found${status ? ` with status '${status}'` : ''}${backend ? ` for backend '${backend}'` : ''}`;
}
let output = `Delegations${status ? ` (${status})` : ''}${backend ? ` [${backend}]` : ''}:\n\n`;
for (const d of delegations) {
const icon = d.status === 'success' ? '✓' : d.status === 'failed' ? '✗' : '○';
const quality = d.quality_score !== null ? `Q:${d.quality_score}` : 'Q:-';
const titleShort = d.title.length > 30 ? d.title.substring(0, 27) + '...' : d.title;
output += `${icon} ${d.task_id}: ${titleShort}\n`;
output += ` ${d.delegation_id.substring(0, 8)} [${d.backend}] ${quality} (${d.started_at})\n`;
if (d.error_message) {
output += ` Error: ${d.error_message.substring(0, 50)}\n`;
}
}
return output;
}
/**
* Get recent delegations for a task (for embedding in taskShow)
* Returns formatted string or empty string if no delegations
*/
export async function getRecentDelegations(taskId: string): Promise<string> {
const delegations = await query<TaskDelegation>(
`SELECT delegation_id, backend, quality_score, status,
to_char(started_at, 'YYYY-MM-DD HH24:MI') as started_at
FROM task_delegations
WHERE task_id = $1
ORDER BY started_at DESC
LIMIT 5`,
[taskId]
);
if (delegations.length === 0) {
return '';
}
let output = '\n**Recent Delegations:**\n';
for (const d of delegations) {
const icon = d.status === 'success' ? '✓' : d.status === 'failed' ? '✗' : '○';
const quality = d.quality_score !== null ? `Q:${d.quality_score}` : 'Q:-';
output += ` ${icon} ${d.delegation_id.substring(0, 8)} [${d.backend}] ${quality} (${d.started_at})\n`;
}
return output;
}

View File

@@ -197,4 +197,29 @@ export const toolDefinitions = [
required: ['task_id', 'epic_id'],
},
},
// Delegation Tools
{
name: 'task_delegations',
description: 'List delegations for a specific task (quality scores, backends, status)',
inputSchema: {
type: 'object',
properties: {
task_id: { type: 'string', description: 'Task ID (e.g., ST-123)' },
},
required: ['task_id'],
},
},
{
name: 'task_delegation_query',
description: 'Query delegations across tasks (filter by status, backend)',
inputSchema: {
type: 'object',
properties: {
status: { type: 'string', enum: ['pending', 'success', 'failed', 'partial'], description: 'Filter by delegation status' },
backend: { type: 'string', description: 'Filter by backend (e.g., grok, haiku)' },
limit: { type: 'number', description: 'Max results (default: 20)' },
},
},
},
];