feat(tracker): return TodoList display for tracker tools (#22060)

This commit is contained in:
anj-s
2026-03-13 11:18:33 -07:00
committed by GitHub
parent d368997ca3
commit dd8d4c98b3
4 changed files with 164 additions and 17 deletions
+69 -16
View File
@@ -23,11 +23,69 @@ import {
TRACKER_UPDATE_TASK_TOOL_NAME,
TRACKER_VISUALIZE_TOOL_NAME,
} from './tool-names.js';
import type { ToolResult } from './tools.js';
import type { ToolResult, TodoList } from './tools.js';
import { BaseDeclarativeTool, BaseToolInvocation, Kind } from './tools.js';
import { ToolErrorType } from './tool-error.js';
import type { TrackerTask, TaskType } from '../services/trackerTypes.js';
import { TaskStatus } from '../services/trackerTypes.js';
import { TaskStatus, TASK_TYPE_LABELS } from '../services/trackerTypes.js';
import type { TrackerService } from '../services/trackerService.js';
export async function buildTodosReturnDisplay(
service: TrackerService,
): Promise<TodoList> {
const tasks = await service.listTasks();
const childrenMap = new Map<string, TrackerTask[]>();
const roots: TrackerTask[] = [];
for (const task of tasks) {
if (task.parentId) {
if (!childrenMap.has(task.parentId)) {
childrenMap.set(task.parentId, []);
}
childrenMap.get(task.parentId)!.push(task);
} else {
roots.push(task);
}
}
const todos: TodoList['todos'] = [];
const addTask = (task: TrackerTask, depth: number, visited: Set<string>) => {
if (visited.has(task.id)) {
todos.push({
description: `${' '.repeat(depth)}[CYCLE DETECTED: ${task.id}]`,
status: 'cancelled',
});
return;
}
visited.add(task.id);
let status: 'pending' | 'in_progress' | 'completed' | 'cancelled' =
'pending';
if (task.status === TaskStatus.IN_PROGRESS) {
status = 'in_progress';
} else if (task.status === TaskStatus.CLOSED) {
status = 'completed';
}
const indent = ' '.repeat(depth);
const description = `${indent}[${task.id}] ${TASK_TYPE_LABELS[task.type]} ${task.title}`;
todos.push({ description, status });
const children = childrenMap.get(task.id) ?? [];
for (const child of children) {
addTask(child, depth + 1, visited);
}
visited.delete(task.id);
};
for (const root of roots) {
addTask(root, 0, new Set());
}
return { todos };
}
// --- tracker_create_task ---
@@ -71,7 +129,7 @@ class TrackerCreateTaskInvocation extends BaseToolInvocation<
});
return {
llmContent: `Created task ${task.id}: ${task.title}`,
returnDisplay: `Created task ${task.id}.`,
returnDisplay: await buildTodosReturnDisplay(this.service),
};
} catch (error) {
const errorMessage =
@@ -155,7 +213,7 @@ class TrackerUpdateTaskInvocation extends BaseToolInvocation<
const task = await this.service.updateTask(id, updates);
return {
llmContent: `Updated task ${task.id}. Status: ${task.status}`,
returnDisplay: `Updated task ${task.id}.`,
returnDisplay: await buildTodosReturnDisplay(this.service),
};
} catch (error) {
const errorMessage =
@@ -239,7 +297,7 @@ class TrackerGetTaskInvocation extends BaseToolInvocation<
}
return {
llmContent: JSON.stringify(task, null, 2),
returnDisplay: `Retrieved task ${task.id}.`,
returnDisplay: await buildTodosReturnDisplay(this.service),
};
}
}
@@ -327,7 +385,7 @@ class TrackerListTasksInvocation extends BaseToolInvocation<
.join('\n');
return {
llmContent: content,
returnDisplay: `Listed ${tasks.length} tasks.`,
returnDisplay: await buildTodosReturnDisplay(this.service),
};
}
}
@@ -427,7 +485,7 @@ class TrackerAddDependencyInvocation extends BaseToolInvocation<
await this.service.updateTask(task.id, { dependencies: newDeps });
return {
llmContent: `Linked ${task.id} -> ${dep.id}.`,
returnDisplay: 'Dependency added.',
returnDisplay: await buildTodosReturnDisplay(this.service),
};
} catch (error) {
const errorMessage =
@@ -516,12 +574,6 @@ class TrackerVisualizeInvocation extends BaseToolInvocation<
closed: '✅',
};
const typeLabels: Record<TaskType, string> = {
epic: '[EPIC]',
task: '[TASK]',
bug: '[BUG]',
};
const childrenMap = new Map<string, TrackerTask[]>();
const roots: TrackerTask[] = [];
@@ -550,14 +602,15 @@ class TrackerVisualizeInvocation extends BaseToolInvocation<
visited.add(task.id);
const indent = ' '.repeat(depth);
output += `${indent}${statusEmojis[task.status]} ${task.id} ${typeLabels[task.type]} ${task.title}\n`;
output += `${indent}${statusEmojis[task.status]} ${task.id} ${TASK_TYPE_LABELS[task.type]} ${task.title}\n`;
if (task.dependencies.length > 0) {
output += `${indent} └─ Depends on: ${task.dependencies.join(', ')}\n`;
}
const children = childrenMap.get(task.id) ?? [];
for (const child of children) {
renderTask(child, depth + 1, new Set(visited));
renderTask(child, depth + 1, visited);
}
visited.delete(task.id);
};
for (const root of roots) {
@@ -566,7 +619,7 @@ class TrackerVisualizeInvocation extends BaseToolInvocation<
return {
llmContent: output,
returnDisplay: output,
returnDisplay: await buildTodosReturnDisplay(this.service),
};
}
}