diff --git a/src/index.ts b/src/index.ts index 025bc5d..aed5016 100644 --- a/src/index.ts +++ b/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}`); } diff --git a/src/tools/crud.ts b/src/tools/crud.ts index b7ffb9d..778b726 100644 --- a/src/tools/crud.ts +++ b/src/tools/crud.ts @@ -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 { } } + // Get recent delegations + const delegationHistory = await getRecentDelegations(id); + if (delegationHistory) { + output += delegationHistory; + } + return output; } diff --git a/src/tools/delegations.ts b/src/tools/delegations.ts new file mode 100644 index 0000000..a0f5125 --- /dev/null +++ b/src/tools/delegations.ts @@ -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 { + const { task_id } = args; + + const delegations = await query( + `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 { + 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( + `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 { + const delegations = await query( + `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; +} diff --git a/src/tools/index.ts b/src/tools/index.ts index ccb05e4..6f13828 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -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)' }, + }, + }, + }, ];