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:
13
src/index.ts
13
src/index.ts
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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
163
src/tools/delegations.ts
Normal 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;
|
||||
}
|
||||
@@ -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)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user