chatTodoListService.ts ×12

Frontier kind: Code frontier

unlabeled · c_89cd646a92f9

41 tests · 18599 LOC · 111 files · introduces 0 tests · 195 LOC · 2 files

Introduces — evidence that enters the hierarchy at this concept

Code
23 ranges195 lines · 2 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
2306 ranges18599 lines · 111 files · Browse complete extent
All tests (intent)
41 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

2 files ranked by introduced lines: 195 introduced LOC across 23 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/contrib/chat/common/tools/builtinTools/manageTodoListTool.ts 126 introduced LOC · 11 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- manageTodoListTool.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CancellationToken } from '../../../../../../base/common/cancellation.js';
7 > import { Disposable } from '../../../../../../base/common/lifecycle.js';
8 > import { Codicon } from '../../../../../../base/common/codicons.js';
9 > import { IJSONSchema, IJSONSchemaMap } from '../../../../../../base/common/jsonSchema.js';
10 > import { ThemeIcon } from '../../../../../../base/common/themables.js';
11 > import {
12 > IToolData,
13 > IToolImpl,
14 > IToolInvocation,
15 > IToolResult,
16 > ToolDataSource,
17 > IToolInvocationPreparationContext,
18 > IPreparedToolInvocation,
19 > ToolInvocationPresentation
20 > } from '../languageModelToolsService.js';
21 > import { ILogService } from '../../../../../../platform/log/common/log.js';
22 > import { ITelemetryService } from '../../../../../../platform/telemetry/common/telemetry.js';
23 > import { IChatTodo, IChatTodoListService } from '../chatTodoListService.js';
24 > import { localize } from '../../../../../../nls.js';
25 > import { MarkdownString } from '../../../../../../base/common/htmlContent.js';
26 > import { URI } from '../../../../../../base/common/uri.js';
27 >
28 > export const ManageTodoListToolToolId = 'manage_todo_list';
29 >
30 > export function createManageTodoListToolData(): IToolData {
31 > const inputSchema: IJSONSchema & { properties: IJSONSchemaMap } = {
32 > type: 'object',
33 > properties: {
34 > todoList: {
35 > type: 'array',
36 > description: 'Complete array of all todo items. Must include ALL items - both existing and new.',
37 > items: {
38 > type: 'object',
39 > properties: {
40 > id: {
41 > type: 'number',
42 > description: 'Unique identifier for the todo. Use sequential numbers starting from 1.'
43 > },
44 > title: {
45 > type: 'string',
46 > description: 'Concise action-oriented todo label (3-7 words). Displayed in UI.'
47 > },
48 > status: {
49 > type: 'string',
50 > enum: ['not-started', 'in-progress', 'completed'],
51 > description: 'not-started: Not begun | in-progress: Currently working (max 1) | completed: Fully finished with no blockers'
52 > },
53 > },
54 > required: ['id', 'title', 'status']
55 > }
56 > }
57 > },
58 > required: ['todoList']
59 > };
60 >
61 > return {
62 > id: ManageTodoListToolToolId,
63 > toolReferenceName: 'todo',
64 > legacyToolReferenceFullNames: ['todos'],
65 > canBeReferencedInPrompt: true,
66 > icon: ThemeIcon.fromId(Codicon.checklist.id),
67 > displayName: localize('tool.manageTodoList.displayName', 'Manage and track todo items for task planning'),
68 > userDescription: localize('tool.manageTodoList.userDescription', 'Manage and track todo items for task planning'),
69 > modelDescription: 'Manage a structured todo list to track progress and plan tasks throughout your coding session. Use this tool VERY frequently to ensure task visibility and proper planning.\n\nWhen to use this tool:\n- Complex multi-step work requiring planning and tracking\n- When user provides multiple tasks or requests (numbered/comma-separated)\n- After receiving new instructions that require multiple steps\n- BEFORE starting work on any todo (mark as in-progress)\n- IMMEDIATELY after completing each todo (mark completed individually)\n- When breaking down larger tasks into smaller actionable steps\n- To give users visibility into your progress and planning\n\nWhen NOT to use:\n- Single, trivial tasks that can be completed in one step\n- Purely conversational/informational requests\n- When just reading files or performing simple searches\n\nCRITICAL workflow:\n1. Plan tasks by writing todo list with specific, actionable items\n2. Mark ONE todo as in-progress before starting work\n3. Complete the work for that specific todo\n4. Mark that todo as completed IMMEDIATELY\n5. Move to next todo and repeat\n\nTodo states:\n- not-started: Todo not yet begun\n- in-progress: Currently working (limit ONE at a time)\n- completed: Finished successfully\n\nIMPORTANT: Mark todos completed as soon as they are done. Do not batch completions.',
70 > source: ToolDataSource.Internal,
71 > inputSchema: inputSchema
72 > };
73 > }
74 >
75 > export const ManageTodoListToolData: IToolData = createManageTodoListToolData();
76 >
77 > interface IManageTodoListToolInputParams {
78 > operation?: 'write' | 'read'; // Optional, defaults to 'write'
79 > todoList: Array<{
80 > id: number;
81 > title: string;
82 > status: 'not-started' | 'in-progress' | 'completed';
83 > }>;
84 > // used for todo read only
85 > chatSessionResource?: string;
86 > }
87 >
88 > export class ManageTodoListTool extends Disposable implements IToolImpl {
89 >
90 > constructor(
91 @IChatTodoListService private readonly chatTodoListService: IChatTodoListService,
92 @ILogService private readonly logService: ILogService,
95 super();
96 }
98 > // eslint-disable-next-line @typescript-eslint/no-explicit-any
99 > async invoke(invocation: IToolInvocation, _countTokens: any, _progress: any, _token: CancellationToken): Promise<IToolResult> {
100 const args = invocation.parameters as IManageTodoListToolInputParams;
101 let chatSessionResource = invocation.context?.sessionResource;
135 }
136 }
138 > async prepareToolInvocation(context: IToolInvocationPreparationContext, _token: CancellationToken): Promise<IPreparedToolInvocation | undefined> {
139 const args = context.parameters as IManageTodoListToolInputParams;
140 const chatSessionResource = context.chatSessionResource;
172 };
173 }
175 > private generatePastTenseMessage(currentTodos: IChatTodo[], newTodos: IManageTodoListToolInputParams['todoList']): string {
176 // If no current todos and we're adding new ones, this is creating new ones.
177 // When both lists are empty (a no-op write), fall through to the default
223 return localize('todo.updated', "Updated todo list");
224 }
226 > private handleRead(todoItems: IChatTodo[], sessionResource: URI): string {
227 if (todoItems.length === 0) {
228 return 'No todo list found.';
232 return `# Todo List\n\n${markdownTaskList}`;
233 }
235 > private handleReadOperation(chatSessionResource: URI): IToolResult {
236 const todoItems = this.chatTodoListService.getTodos(chatSessionResource);
237 const readResult = this.handleRead(todoItems, chatSessionResource);
255 };
256 }
258 > private handleWriteOperation(args: IManageTodoListToolInputParams, chatSessionResource: URI): IToolResult {
259 if (!args.todoList) {
260 return {
311 };
312 }
314 > private calculateStatusCounts(todos: IChatTodo[]): { notStartedCount: number; inProgressCount: number; completedCount: number } {
315 const notStartedCount = todos.filter(todo => todo.status === 'not-started').length;
316 const inProgressCount = todos.filter(todo => todo.status === 'in-progress').length;
318 return { notStartedCount, inProgressCount, completedCount };
319 }
321 > private formatTodoListAsMarkdownTaskList(todoList: IChatTodo[]): string {
322 if (todoList.length === 0) {
323 return '';
344 }).join('\n');
345 }
347 > private calculateTodoChanges(oldList: IChatTodo[], newList: IChatTodo[]): number {
348 // Assume arrays are equivalent in order; compare index-by-index
349 let modified = 0;
362 return totalChanges;
363 }
365 >
366 > type TodoListToolInvokedEvent = {
367 > operation: 'read' | 'write';
368 > notStartedCount: number;
369 > inProgressCount: number;
370 > completedCount: number;
371 > };
372 >
373 > type TodoListToolInvokedClassification = {
374 > operation: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The operation performed on the todo list (read or write).' };
375 > notStartedCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of tasks with not-started status.' };
376 > inProgressCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of tasks with in-progress status.' };
377 > completedCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of tasks with completed status.' };
378 > owner: 'bhavyaus';
379 > comment: 'Provides insight into the usage of the todo list tool including detailed task status distribution.';
380 > };
src/vs/workbench/contrib/chat/common/tools/chatTodoListService.ts 69 introduced LOC · 12 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- chatTodoListService.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { Emitter, Event } from '../../../../../base/common/event.js';
7 > import { Disposable } from '../../../../../base/common/lifecycle.js';
8 > import { URI } from '../../../../../base/common/uri.js';
9 > import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';
10 > import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
11 > import { Memento } from '../../../../common/memento.js';
12 > import { chatSessionResourceToId } from '../model/chatUri.js';
13 >
14 > export interface IChatTodo {
15 > id: number;
16 > title: string;
17 > status: 'not-started' | 'in-progress' | 'completed';
18 > }
19 >
20 > export interface IChatTodoListStorage {
21 > getTodoList(sessionResource: URI): IChatTodo[];
22 > setTodoList(sessionResource: URI, todoList: IChatTodo[]): void;
23 > migrateTodoList(oldSessionResource: URI, newSessionResource: URI): void;
24 > }
25 >
26 > export const IChatTodoListService = createDecorator<IChatTodoListService>('chatTodoListService');
27 >
28 > export interface IChatTodoListService {
29 > readonly _serviceBrand: undefined;
30 > readonly onDidUpdateTodos: Event<URI>;
31 > getTodos(sessionResource: URI): IChatTodo[];
32 > setTodos(sessionResource: URI, todos: IChatTodo[]): void;
33 > migrateTodos(oldSessionResource: URI, newSessionResource: URI): void;
34 > }
35 >
36 > export class ChatTodoListStorage implements IChatTodoListStorage {
37 > private memento: Memento<Record<string, IChatTodo[]>>;
38 >
39 > constructor(@IStorageService storageService: IStorageService) {
40 this.memento = new Memento('chat-todo-list', storageService);
41 }
43 > private getSessionData(sessionResource: URI): IChatTodo[] {
44 const storage = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE);
45 return storage[this.toKey(sessionResource)] || [];
46 }
48 > private setSessionData(sessionResource: URI, todoList: IChatTodo[]): void {
49 const storage = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE);
50 storage[this.toKey(sessionResource)] = todoList;
51 this.memento.saveMemento();
52 }
54 > getTodoList(sessionResource: URI): IChatTodo[] {
55 return this.getSessionData(sessionResource);
56 }
58 > setTodoList(sessionResource: URI, todoList: IChatTodo[]): void {
59 this.setSessionData(sessionResource, todoList);
60 }
62 > migrateTodoList(oldSessionResource: URI, newSessionResource: URI): void {
63 const todos = this.getSessionData(oldSessionResource);
64 if (todos.length > 0) {
70 }
71 }
73 > private toKey(sessionResource: URI): string {
74 return chatSessionResourceToId(sessionResource);
75 }
77 >
78 > export class ChatTodoListService extends Disposable implements IChatTodoListService {
79 > declare readonly _serviceBrand: undefined;
80 >
81 > private readonly _onDidUpdateTodos = this._register(new Emitter<URI>());
82 > readonly onDidUpdateTodos = this._onDidUpdateTodos.event;
83 >
84 > private todoListStorage: IChatTodoListStorage;
85 >
86 > constructor(@IStorageService storageService: IStorageService) {
87 super();
88 this.todoListStorage = new ChatTodoListStorage(storageService);
89 }
91 > getTodos(sessionResource: URI): IChatTodo[] {
92 return this.todoListStorage.getTodoList(sessionResource);
93 }
95 > setTodos(sessionResource: URI, todos: IChatTodo[]): void {
96 this.todoListStorage.setTodoList(sessionResource, todos);
97 this._onDidUpdateTodos.fire(sessionResource);
98 }
100 > migrateTodos(oldSessionResource: URI, newSessionResource: URI): void {
101 this.todoListStorage.migrateTodoList(oldSessionResource, newSessionResource);
102 this._onDidUpdateTodos.fire(newSessionResource);
103 }