diff --git a/docs/todo-system.md b/docs/todo-system.md index 8d86d7b53..69ce6008f 100644 --- a/docs/todo-system.md +++ b/docs/todo-system.md @@ -6,8 +6,8 @@ The LLxprt Code features a sophisticated task management system to help AI agent This built-in tool allows the AI model to read the current state of the todo list for the active session. -**Purpose**: To retrieve the list of tasks, their statuses, priorities, subtasks, and recent tool calls. -**Returns**: A markdown block that mirrors the Todo panel, including status icons (✔/○/→), priority badges, subtasks, and the five most recent tool calls per todo. +**Purpose**: To retrieve the list of tasks, their statuses, subtasks, and recent tool calls. +**Returns**: A markdown block that mirrors the Todo panel, including status icons (/○/→), subtasks, and the five most recent tool calls per todo. **Parameters**: None. ## `todo_write` Tool @@ -21,7 +21,6 @@ This built-in tool allows the AI model to create, update, or overwrite the entir - `id` (string, required): A unique identifier for the todo item. - `content` (string, required): A clear, descriptive task for the AI to perform. - `status` (string, enum: "pending", "in_progress", "completed", required): The current status of the task. - - `priority` (string, enum: "high", "medium", "low", required): The priority level of the task. **Behavior**: The tool completely replaces the current todo list with the one provided in the `todos` array. In non-interactive sessions it returns a simplified markdown view of the list to the AI. In interactive sessions the CLI renders the Todo panel by default, but if you disable the panel (see below) LLxprt synthesizes the same structured markdown that `todo_read` now emits so the entire list remains visible in scrollback. @@ -44,7 +43,7 @@ This built-in tool allows the AI model to pause its automatic workflow continuat Some users prefer all todo updates to remain in the scrollback instead of a separate Ink panel. Open `/settings` (or edit `.llxprt/settings.json`) and toggle **UI → Show Todo Panel**. When this setting is off: - The Todo panel is hidden immediately—no restart required. -- `todo_write` tool calls render the full structured todo list inline (status icons, priorities, subtasks, recent tool calls) instead of the `✦ Todo list updated` placeholder. +- `todo_write` tool calls render the full structured todo list inline (status icons, subtasks, recent tool calls) instead of the ` Todo list updated` placeholder. - `todo_read` outputs the same formatter, so both tools always share one canonical textual representation. Re-enable the toggle to restore the rich Ink panel without losing any history. diff --git a/integration-tests/todo-continuation.e2e.test.js b/integration-tests/todo-continuation.e2e.test.js index 82181159a..b59d0c221 100644 --- a/integration-tests/todo-continuation.e2e.test.js +++ b/integration-tests/todo-continuation.e2e.test.js @@ -30,7 +30,7 @@ test('basic todo continuation flow', { skip: skipTodoTests }, async () => { // First, create a todo list with an active task const _createResult = await rig.run( - 'Create a todo list with these tasks: 1. Implement auth system (in_progress, high), 2. Write tests (pending, medium)', + 'Create a todo list with these tasks: 1. Implement auth system (in_progress), 2. Write tests (pending)', ); // Wait for todo_write tool call @@ -161,7 +161,7 @@ test( }); const _createResult = await rig.run( - 'Create a todo list with one item: Build login form (in_progress, high)', + 'Create a todo list with one item: Build login form (in_progress)', ); const writeToolCall = await rig.waitForToolCall('todo_write'); assert.ok(writeToolCall, 'Expected to find a todo_write tool call'); @@ -393,25 +393,25 @@ test( /** * @requirement REQ-006 - * @scenario Multiple active todos continuation priority + * @scenario Multiple active todos continuation alphabetical sorting * @given Multiple in_progress todos * @when Continuation is triggered - * @then Continuation focuses on highest priority active todo + * @then Continuation focuses on first alphabetically sorted active todo */ test( - 'multiple active todos continuation priority', + 'multiple active todos continuation alphabetical sorting', { skip: skipTodoTests }, async () => { const rig = new TestRig(); - await rig.setup('multiple active todos priority', { + await rig.setup('multiple active todos alphabetical sorting', { settings: { 'todo-continuation': true, }, }); - // Create multiple active todos with different priorities + // Create multiple active todos - alphabetically "Fix critical bug" comes before "Update docs" const _createResult = await rig.run( - 'Create todos: 1. Fix critical bug (in_progress, high), 2. Update docs (in_progress, low), 3. Code review (pending, medium)', + 'Create todos: 1. Fix critical bug (in_progress), 2. Update docs (in_progress), 3. Code review (pending)', ); const writeToolCall = await rig.waitForToolCall('todo_write'); @@ -422,15 +422,16 @@ test( 'I need to step back and think about priorities', ); - // Should focus on the high priority task - const focusesOnHighPriority = + // Should focus on the first alphabetically sorted task + const focusesOnFirstAlphabetical = priorityResult.toLowerCase().includes('critical bug') || priorityResult.toLowerCase().includes('bug') || - priorityResult.toLowerCase().includes('critical'); + priorityResult.toLowerCase().includes('critical') || + priorityResult.toLowerCase().includes('fix'); - if (!focusesOnHighPriority) { + if (!focusesOnFirstAlphabetical) { printDebugInfo(rig, priorityResult, { - 'Expected high priority focus': true, + 'Expected alphabetically first focus': true, 'Mentions critical bug': priorityResult .toLowerCase() .includes('critical'), @@ -441,11 +442,11 @@ test( }); } - // The AI should reference the most important active task + // The AI should reference the alphabetically first active task validateModelOutput( priorityResult, ['bug', 'critical', 'fix'], - 'Priority continuation test', + 'Alphabetical sorting continuation test', ); await rig.cleanup(); diff --git a/packages/cli/src/integration-tests/todo-continuation.integration.test.ts b/packages/cli/src/integration-tests/todo-continuation.integration.test.ts index c4a27a3f4..90b41a2df 100644 --- a/packages/cli/src/integration-tests/todo-continuation.integration.test.ts +++ b/packages/cli/src/integration-tests/todo-continuation.integration.test.ts @@ -42,7 +42,6 @@ describe('Todo Continuation Integration Tests', () => { id, content, status, - priority: 'medium', }); beforeEach(async () => { @@ -619,20 +618,17 @@ describe('Todo Continuation Integration Tests', () => { id: '1', content: 'Valid todo', status: 'pending', - priority: 'medium', } as Todo, { id: '2', content: 'Todo with special chars: !@#$%^&*()', status: 'in_progress', - priority: 'high', } as Todo, { id: '3', content: 'Very long todo content that spans multiple lines and contains various characters', status: 'completed', - priority: 'low', } as Todo, ]; @@ -644,7 +640,7 @@ describe('Todo Continuation Integration Tests', () => { // Test that TodoStore validates malformed todos (expected to throw) const malformedTodos = [ - { id: '1', status: 'pending', priority: 'low' } as unknown as Todo, // Missing content + { id: '1', status: 'pending' } as unknown as Todo, // Missing content ]; // TodoStore should validate and throw for invalid data diff --git a/packages/cli/src/services/todo-continuation/todoContinuationService.spec.ts b/packages/cli/src/services/todo-continuation/todoContinuationService.spec.ts index 83dafad6b..6c25c11aa 100644 --- a/packages/cli/src/services/todo-continuation/todoContinuationService.spec.ts +++ b/packages/cli/src/services/todo-continuation/todoContinuationService.spec.ts @@ -33,12 +33,10 @@ describe('TodoContinuationService', () => { id: string, content: string, status: 'pending' | 'in_progress' | 'completed' = 'pending', - priority: 'high' | 'medium' | 'low' = 'medium', ): Todo => ({ id, content, status, - priority, }); const createConfig = ( @@ -366,22 +364,16 @@ describe('TodoContinuationService', () => { '1', 'Implement user authentication', 'pending', - 'high', - ); - const pendingTodo2 = createTodo( - '2', - 'Update documentation', - 'pending', - 'low', ); + const pendingTodo2 = createTodo('2', 'Update documentation', 'pending'); - const todos = [pendingTodo2, pendingTodo1]; // Lower priority first + const todos = [pendingTodo2, pendingTodo1]; // Second alphabetically const context = createContext({ todos, hadToolCalls: false }); const result = service.checkContinuationConditions(context); expect(result.shouldContinue).toBe(true); - expect(result.activeTodo).toEqual(pendingTodo1); // Should pick higher priority + expect(result.activeTodo).toEqual(pendingTodo1); // Should pick first alphabetically }); it('formats todo content into readable task description', () => { @@ -408,26 +400,16 @@ describe('TodoContinuationService', () => { expect(result.length).toBeGreaterThan(0); // Should provide fallback }); - it('prioritizes high-priority pending todos over low-priority ones', () => { - const lowPriorityTodo = createTodo( - '1', - 'Update README', - 'pending', - 'low', - ); - const highPriorityTodo = createTodo( - '2', - 'Fix security vulnerability', - 'pending', - 'high', - ); + it('sorts pending todos alphabetically', () => { + const todoZ = createTodo('1', 'Update README', 'pending'); + const todoF = createTodo('2', 'Fix security vulnerability', 'pending'); - const todos = [lowPriorityTodo, highPriorityTodo]; + const todos = [todoZ, todoF]; const context = createContext({ todos, hadToolCalls: false }); const result = service.checkContinuationConditions(context); - expect(result.activeTodo).toEqual(highPriorityTodo); + expect(result.activeTodo).toEqual(todoF); // 'Fix' comes before 'Update' alphabetically }); }); }); @@ -489,7 +471,6 @@ describe('TodoContinuationService', () => { id: '1', content: 'Minimal todo item', status: 'in_progress', - priority: 'medium', // subtasks and toolCalls are undefined }; diff --git a/packages/cli/src/services/todo-continuation/todoContinuationService.ts b/packages/cli/src/services/todo-continuation/todoContinuationService.ts index 90b08f83e..53d0fc373 100644 --- a/packages/cli/src/services/todo-continuation/todoContinuationService.ts +++ b/packages/cli/src/services/todo-continuation/todoContinuationService.ts @@ -534,7 +534,7 @@ export class TodoContinuationService { * @returns Best active todo or undefined */ private findBestActiveTodo(todos: readonly Todo[]): Todo | undefined { - // Priority 1: Find in_progress todos (should be max 1) + // First: Find in_progress todos (should be max 1) const inProgressTodos = todos.filter( (todo) => todo.status === 'in_progress', ); @@ -542,20 +542,11 @@ export class TodoContinuationService { return inProgressTodos[0]; } - // Priority 2: Find pending todos, prioritize by priority + // Second: Find pending todos, sort alphabetically by content const pendingTodos = todos.filter((todo) => todo.status === 'pending'); if (pendingTodos.length > 0) { - // Sort by priority: high > medium > low - const priorityOrder: Record = { - high: 3, - medium: 2, - low: 1, - }; - pendingTodos.sort((a, b) => { - const aPriority = priorityOrder[a.priority || 'medium'] || 2; - const bPriority = priorityOrder[b.priority || 'medium'] || 2; - return bPriority - aPriority; // Descending order (high to low) - }); + // Sort alphabetically by content + pendingTodos.sort((a, b) => a.content.localeCompare(b.content)); return pendingTodos[0]; } diff --git a/packages/cli/src/ui/components/TodoPanel.responsive.test.tsx b/packages/cli/src/ui/components/TodoPanel.responsive.test.tsx index 24dff5b4d..d475e4591 100644 --- a/packages/cli/src/ui/components/TodoPanel.responsive.test.tsx +++ b/packages/cli/src/ui/components/TodoPanel.responsive.test.tsx @@ -49,19 +49,16 @@ const testTodos: Todo[] = [ content: 'This is a very long todo item that should be truncated at different widths', status: 'completed', - priority: 'medium', }, { id: '2', content: 'Short task', status: 'in_progress', - priority: 'high', }, { id: '3', content: 'Another pending task with moderate length content', status: 'pending', - priority: 'low', }, ]; @@ -263,7 +260,6 @@ describe('TodoPanel Responsive Behavior', () => { content: 'This is a very long todo item that should use more width for better readability instead of being truncated too early', status: 'pending', - priority: 'medium', }, ]; @@ -306,7 +302,6 @@ describe('TodoPanel Responsive Behavior', () => { content: 'This extremely long todo item content should demonstrate the improved truncation behavior by showing much more text', status: 'pending', - priority: 'medium', }, ]; diff --git a/packages/cli/src/ui/components/TodoPanel.semantic.test.tsx b/packages/cli/src/ui/components/TodoPanel.semantic.test.tsx index b700503dd..39aa40f72 100644 --- a/packages/cli/src/ui/components/TodoPanel.semantic.test.tsx +++ b/packages/cli/src/ui/components/TodoPanel.semantic.test.tsx @@ -71,7 +71,6 @@ describe('TodoPanel Semantic Colors', () => { id: '1', content: 'Completed task', status: 'completed', - priority: 'medium', }; mockTodoContext.todos = [completedTodo]; @@ -98,7 +97,6 @@ describe('TodoPanel Semantic Colors', () => { id: '1', content: 'Current task', status: 'in_progress', - priority: 'high', }; mockTodoContext.todos = [inProgressTodo]; @@ -120,7 +118,6 @@ describe('TodoPanel Semantic Colors', () => { id: '1', content: 'Pending task', status: 'pending', - priority: 'low', }; mockTodoContext.todos = [pendingTodo]; @@ -142,7 +139,6 @@ describe('TodoPanel Semantic Colors', () => { id: '1', content: 'Test task', status: 'completed', - priority: 'medium', }; mockTodoContext.todos = [testTodo]; @@ -197,7 +193,6 @@ describe('TodoPanel Semantic Colors', () => { id: '1', content: 'Main task', status: 'in_progress', - priority: 'medium', subtasks: [ { id: '1-1', content: 'Subtask 1', toolCalls: [] }, { id: '1-2', content: 'Subtask 2', toolCalls: [] }, diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx index c5bc25eeb..f22e75ae0 100644 --- a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx @@ -106,7 +106,6 @@ describe('', () => { id: 'todo-1', content: 'Implement role-based access control', status: 'in_progress', - priority: 'high', subtasks: [ { id: 'sub-1', diff --git a/packages/cli/src/ui/hooks/useGeminiStream.integration.test.tsx b/packages/cli/src/ui/hooks/useGeminiStream.integration.test.tsx index 022e06338..b036b6469 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.integration.test.tsx +++ b/packages/cli/src/ui/hooks/useGeminiStream.integration.test.tsx @@ -310,13 +310,11 @@ describe('Todo Continuation Integration - useGeminiStream', () => { id: 'todo-1', content: 'Implement user auth', status: 'in_progress' as const, - priority: 'high' as const, }, { id: 'todo-2', content: 'Add validation', status: 'pending' as const, - priority: 'medium' as const, }, ]; @@ -692,13 +690,11 @@ describe('Todo Continuation Integration - useGeminiStream', () => { id: 'todo-1', content: 'Completed task', status: 'completed' as const, - priority: 'high' as const, }, { id: 'todo-2', content: 'Active pending task', status: 'pending' as const, - priority: 'medium' as const, }, ]; @@ -744,13 +740,11 @@ describe('Todo Continuation Integration - useGeminiStream', () => { id: 'todo-1', content: 'Pending task', status: 'pending' as const, - priority: 'high' as const, }, { id: 'todo-2', content: 'In progress task', status: 'in_progress' as const, - priority: 'low' as const, // Lower priority but in_progress should win }, ]; @@ -796,7 +790,6 @@ describe('Todo Continuation Integration - useGeminiStream', () => { id: 'todo-1', content: 'Done task', status: 'completed' as const, - priority: 'high' as const, }, ]; diff --git a/packages/cli/src/ui/hooks/useTodoContinuation.spec.ts b/packages/cli/src/ui/hooks/useTodoContinuation.spec.ts index 4c22d7ead..5d2516bab 100644 --- a/packages/cli/src/ui/hooks/useTodoContinuation.spec.ts +++ b/packages/cli/src/ui/hooks/useTodoContinuation.spec.ts @@ -68,7 +68,6 @@ describe('useTodoContinuation - Behavioral Tests', () => { id, content, status, - priority: 'medium', }); beforeEach(() => { @@ -524,14 +523,11 @@ describe('useTodoContinuation - Behavioral Tests', () => { id: '1', content: '', status: 'in_progress', - priority: 'medium', } as Todo, - // @ts-expect-error Testing malformed data { id: '2', status: 'pending', - priority: 'high', - }, + } as Todo, ]; mockConfig.getEphemeralSettings.mockReturnValue({ 'todo-continuation': true, diff --git a/packages/cli/src/zed-integration/schema.ts b/packages/cli/src/zed-integration/schema.ts index 9a0f52e98..e82ac8dba 100644 --- a/packages/cli/src/zed-integration/schema.ts +++ b/packages/cli/src/zed-integration/schema.ts @@ -231,7 +231,6 @@ export const toolCallLocationSchema = z.object({ export const planEntrySchema = z.object({ content: z.string(), - priority: z.union([z.literal('high'), z.literal('medium'), z.literal('low')]), status: z.union([ z.literal('pending'), z.literal('in_progress'), diff --git a/packages/cli/src/zed-integration/zedIntegration.ts b/packages/cli/src/zed-integration/zedIntegration.ts index 233a81196..4b4364926 100644 --- a/packages/cli/src/zed-integration/zedIntegration.ts +++ b/packages/cli/src/zed-integration/zedIntegration.ts @@ -1403,7 +1403,6 @@ class Session { const entries: acp.PlanEntry[] = todos.map((todo) => ({ content: todo.content, status: todo.status, - priority: todo.priority, })); // Send plan update to Zed via ACP protocol diff --git a/packages/core/src/core/client.test.ts b/packages/core/src/core/client.test.ts index 1b9287a8f..4511c0a79 100644 --- a/packages/core/src/core/client.test.ts +++ b/packages/core/src/core/client.test.ts @@ -1311,7 +1311,6 @@ describe('Gemini Client (client.ts)', () => { id: 'todo-1', content: 'Complete the task', status: 'pending', - priority: 'high', }, ]); @@ -1384,7 +1383,6 @@ describe('Gemini Client (client.ts)', () => { id: 'todo-1', content: 'Blocked task', status: 'pending', - priority: 'high', }, ]); diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 359ea31c1..f38048a0f 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -484,7 +484,6 @@ export class GeminiClient { id: `${todo.id ?? ''}`, status: (todo.status ?? 'pending').toLowerCase(), content: todo.content ?? '', - priority: todo.priority ?? 'medium', })) .sort((left, right) => left.id.localeCompare(right.id)); const normalizedA = normalize(a); @@ -1447,7 +1446,6 @@ export class GeminiClient { id: `${(todo as Todo).id ?? ''}`, content: (todo as Todo).content ?? '', status: (todo as Todo).status ?? 'pending', - priority: (todo as Todo).priority ?? 'medium', })); } } diff --git a/packages/core/src/core/subagent.test.ts b/packages/core/src/core/subagent.test.ts index 75341e57e..a84df84b1 100644 --- a/packages/core/src/core/subagent.test.ts +++ b/packages/core/src/core/subagent.test.ts @@ -1148,7 +1148,6 @@ describe('subagent.ts', () => { id: 'todo-1', content: 'Complete the technical report', status: 'in_progress', - priority: 'high', }, ]) .mockResolvedValueOnce([]) // agent-scoped store on second pass @@ -1157,7 +1156,6 @@ describe('subagent.ts', () => { id: 'todo-1', content: 'Complete the technical report', status: 'completed', - priority: 'high', }, ]); diff --git a/packages/core/src/parsers/TextToolCallParser.ts b/packages/core/src/parsers/TextToolCallParser.ts index c1acad4bb..c7c18d426 100644 --- a/packages/core/src/parsers/TextToolCallParser.ts +++ b/packages/core/src/parsers/TextToolCallParser.ts @@ -672,7 +672,6 @@ export class GemmaToolCallParser implements ITextToolCallParser { } normalized.status = this.normalizeTodoStatus(normalized.status); - normalized.priority = this.normalizeTodoPriority(normalized.priority); return normalized; } @@ -692,40 +691,6 @@ export class GemmaToolCallParser implements ITextToolCallParser { return 'pending'; } - private normalizeTodoPriority(value: unknown): 'high' | 'medium' | 'low' { - if (typeof value === 'string') { - const normalized = value.trim().toLowerCase(); - if ( - normalized === 'high' || - normalized === 'medium' || - normalized === 'low' - ) { - return normalized; - } - if (normalized === '1') { - return 'high'; - } - if (normalized === '2') { - return 'medium'; - } - if (normalized === '3') { - return 'low'; - } - } - - if (typeof value === 'number') { - if (value <= 1) { - return 'high'; - } - if (value >= 3) { - return 'low'; - } - return 'medium'; - } - - return 'high'; - } - private extractBalancedSegment( content: string, startIndex: number, diff --git a/packages/core/src/services/todo-reminder-service.ts b/packages/core/src/services/todo-reminder-service.ts index 554b86439..1c2d77196 100644 --- a/packages/core/src/services/todo-reminder-service.ts +++ b/packages/core/src/services/todo-reminder-service.ts @@ -40,7 +40,6 @@ export class TodoReminderService { stateChange.currentTodos.map((todo) => ({ content: todo.content, status: todo.status, - priority: todo.priority, id: todo.id, })), ); diff --git a/packages/core/src/todo/todoFormatter.ts b/packages/core/src/todo/todoFormatter.ts index 632351ee9..4f19724b2 100644 --- a/packages/core/src/todo/todoFormatter.ts +++ b/packages/core/src/todo/todoFormatter.ts @@ -21,13 +21,7 @@ export interface TodoFormatterOptions { const STATUS_ICONS: Record = { in_progress: '→', pending: '○', - completed: '✔', -}; - -const PRIORITY_LABELS: Record = { - high: 'HIGH', - medium: 'MEDIUM', - low: 'LOW', + completed: '', }; const STATUS_ORDER: Record = { @@ -36,12 +30,6 @@ const STATUS_ORDER: Record = { completed: 2, }; -const PRIORITY_ORDER: Record = { - high: 0, - medium: 1, - low: 2, -}; - const DEFAULT_HEADER = '## Todo Progress'; const DEFAULT_MAX_TOOL_CALLS = 5; @@ -150,9 +138,8 @@ const calculateStats = (todos: Todo[]) => ({ const formatTodoEntry = (todo: Todo): string => { const marker = STATUS_ICONS[todo.status]; - const priorityLabel = PRIORITY_LABELS[todo.priority]; const currentSuffix = todo.status === 'in_progress' ? ' ← current' : ''; - return `${marker} ${todo.content} [${priorityLabel}]${currentSuffix}`; + return `${marker} ${todo.content}${currentSuffix}`; }; const mergeToolCalls = ( @@ -221,17 +208,5 @@ export const formatTodoListForDisplay = ( return lines.join('\n').trimEnd(); }; const orderTodos = (todos: Todo[]): Todo[] => - [...todos].sort((a, b) => { - const statusDiff = STATUS_ORDER[a.status] - STATUS_ORDER[b.status]; - if (statusDiff !== 0) { - return statusDiff; - } - - const priorityDiff = - PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]; - if (priorityDiff !== 0) { - return priorityDiff; - } - - return a.content.localeCompare(b.content); - }); + // Sort by status only, preserving original array order within each status group + [...todos].sort((a, b) => STATUS_ORDER[a.status] - STATUS_ORDER[b.status]); diff --git a/packages/core/src/tools/ToolFormatter.test.ts b/packages/core/src/tools/ToolFormatter.test.ts index b26d88673..ac3ea200d 100644 --- a/packages/core/src/tools/ToolFormatter.test.ts +++ b/packages/core/src/tools/ToolFormatter.test.ts @@ -43,13 +43,8 @@ describe('ToolFormatter', () => { enum: ['pending', 'in_progress', 'completed'], description: 'Current status of the todo item', }, - priority: { - type: Type.STRING, - enum: ['high', 'medium', 'low'], - description: 'Priority level of the todo item', - }, }, - required: ['id', 'content', 'status', 'priority'], + required: ['id', 'content', 'status'], }, description: 'List of todo items', }, @@ -101,19 +96,12 @@ describe('ToolFormatter', () => { expect( tool.function.parameters.properties.todos.items.properties.status, ).toBeDefined(); - expect( - tool.function.parameters.properties.todos.items.properties.priority, - ).toBeDefined(); expect(tool.function.parameters.required).toEqual(['todos']); // Check that enums are preserved expect( tool.function.parameters.properties.todos.items.properties.status.enum, ).toEqual(['pending', 'in_progress', 'completed']); - expect( - tool.function.parameters.properties.todos.items.properties.priority - .enum, - ).toEqual(['high', 'medium', 'low']); // Check that minLength was converted from string to number expect( diff --git a/packages/core/src/tools/todo-read.test.ts b/packages/core/src/tools/todo-read.test.ts index 6be570761..dd8d76657 100644 --- a/packages/core/src/tools/todo-read.test.ts +++ b/packages/core/src/tools/todo-read.test.ts @@ -19,33 +19,28 @@ describe('TodoRead', () => { const sampleTodos: Todo[] = [ { id: '1', - content: 'High priority in progress task', + content: 'In progress task', status: 'in_progress', - priority: 'high', }, { id: '2', - content: 'Medium priority pending task', + content: 'Medium pending task', status: 'pending', - priority: 'medium', }, { id: '3', - content: 'Low priority completed task', + content: 'Completed task', status: 'completed', - priority: 'low', }, { id: '4', - content: 'High priority pending task', + content: 'Another pending task', status: 'pending', - priority: 'high', }, { id: '5', - content: 'High priority completed task', + content: 'Another completed task', status: 'completed', - priority: 'high', }, ]; @@ -72,15 +67,9 @@ describe('TodoRead', () => { const result = await tool.execute({}, abortSignal); expect(result.llmContent).toContain('Todo Progress'); - expect(result.llmContent).toContain( - '→ High priority in progress task [HIGH]', - ); - expect(result.llmContent).toContain( - '○ Medium priority pending task [MEDIUM]', - ); - expect(result.llmContent).toContain( - '✔ Low priority completed task [LOW]', - ); + expect(result.llmContent).toContain('→ In progress task'); + expect(result.llmContent).toContain('○ Medium pending task'); + expect(result.llmContent).toContain(' Completed task'); }); it('should sort todos by status (in_progress > pending > completed)', async () => { @@ -93,20 +82,20 @@ describe('TodoRead', () => { // Find indices of different status todos const inProgressIndex = lines.findIndex((line) => - line.includes('→ High priority in progress task'), + line.includes('→ In progress task'), ); const pendingIndex = lines.findIndex((line) => - line.includes('○ Medium priority pending task'), + line.includes('○ Medium pending task'), ); const completedIndex = lines.findIndex((line) => - line.includes('✔ Low priority completed task'), + line.includes(' Completed task'), ); expect(inProgressIndex).toBeLessThan(pendingIndex); expect(pendingIndex).toBeLessThan(completedIndex); }); - it('should sort by priority within same status', async () => { + it('should preserve original array order within same status', async () => { vi.mocked(TodoStore.prototype.readTodos).mockResolvedValue(sampleTodos); const result = await tool.execute({}, abortSignal); @@ -114,15 +103,15 @@ describe('TodoRead', () => { .split('\n') .filter((line: string) => line.trim()); - // High priority pending should come before medium priority pending - const highPendingIndex = lines.findIndex((line) => - line.includes('High priority pending task'), - ); + // "Medium pending task" should come before "Another pending task" (original array order) const mediumPendingIndex = lines.findIndex((line) => - line.includes('Medium priority pending task'), + line.includes('Medium pending task'), + ); + const anotherPendingIndex = lines.findIndex((line) => + line.includes('Another pending task'), ); - expect(highPendingIndex).toBeLessThan(mediumPendingIndex); + expect(mediumPendingIndex).toBeLessThan(anotherPendingIndex); }); it('should handle read errors gracefully', async () => { @@ -167,19 +156,9 @@ describe('TodoRead', () => { const result = await tool.execute({}, abortSignal); // Check for status indicators - expect(result.llmContent).toMatch(/→/); // in_progress - expect(result.llmContent).toMatch(/○/); // pending - expect(result.llmContent).toMatch(/✔/); // completed - }); - - it('should include priority levels in output', async () => { - vi.mocked(TodoStore.prototype.readTodos).mockResolvedValue(sampleTodos); - - const result = await tool.execute({}, abortSignal); - - expect(result.llmContent).toContain('[HIGH]'); - expect(result.llmContent).toContain('[MEDIUM]'); - expect(result.llmContent).toContain('[LOW]'); + expect(result.llmContent).toContain('→'); // in_progress + expect(result.llmContent).toContain('○'); // pending + expect(result.llmContent).toContain(''); // completed }); }); diff --git a/packages/core/src/tools/todo-read.ts b/packages/core/src/tools/todo-read.ts index aa81338f1..45d8c8a8a 100644 --- a/packages/core/src/tools/todo-read.ts +++ b/packages/core/src/tools/todo-read.ts @@ -22,7 +22,7 @@ export class TodoRead extends BaseTool { super( TodoRead.Name, 'TodoRead', - 'Read the current todo list for the session. Returns all todos with their status, priority, and content.', + 'Read the current todo list for the session. Returns all todos with their status and content.', Kind.Think, { type: Type.OBJECT, @@ -83,9 +83,6 @@ export class TodoRead extends BaseTool { inProgress: number; pending: number; completed: number; - highPriority: number; - mediumPriority: number; - lowPriority: number; total: number; } { return { @@ -93,9 +90,6 @@ export class TodoRead extends BaseTool { inProgress: todos.filter((t) => t.status === 'in_progress').length, pending: todos.filter((t) => t.status === 'pending').length, completed: todos.filter((t) => t.status === 'completed').length, - highPriority: todos.filter((t) => t.priority === 'high').length, - mediumPriority: todos.filter((t) => t.priority === 'medium').length, - lowPriority: todos.filter((t) => t.priority === 'low').length, }; } @@ -104,14 +98,10 @@ export class TodoRead extends BaseTool { taskId?: string; taskContent?: string; } { - // Check if any tasks are in progress + // Check if any tasks are in progress - return first one (preserves LLM order) const inProgressTasks = todos.filter((t) => t.status === 'in_progress'); if (inProgressTasks.length > 0) { - // Continue with highest priority in-progress task - const task = inProgressTasks.sort((a, b) => { - const priorityOrder = { high: 0, medium: 1, low: 2 }; - return priorityOrder[a.priority] - priorityOrder[b.priority]; - })[0]; + const task = inProgressTasks[0]; return { type: 'continue', taskId: task.id, @@ -119,14 +109,10 @@ export class TodoRead extends BaseTool { }; } - // Check if any tasks are pending + // Check if any tasks are pending - return first one (preserves LLM order) const pendingTasks = todos.filter((t) => t.status === 'pending'); if (pendingTasks.length > 0) { - // Start with highest priority pending task - const task = pendingTasks.sort((a, b) => { - const priorityOrder = { high: 0, medium: 1, low: 2 }; - return priorityOrder[a.priority] - priorityOrder[b.priority]; - })[0]; + const task = pendingTasks[0]; return { type: 'start', taskId: task.id, diff --git a/packages/core/src/tools/todo-schemas.test.ts b/packages/core/src/tools/todo-schemas.test.ts index 38b0a4694..f3d93d856 100644 --- a/packages/core/src/tools/todo-schemas.test.ts +++ b/packages/core/src/tools/todo-schemas.test.ts @@ -11,7 +11,6 @@ import { SubtaskSchema, TodoToolCallSchema, TodoStatus, - TodoPriority, } from './todo-schemas.js'; describe('TodoSchemas', () => { @@ -29,27 +28,12 @@ describe('TodoSchemas', () => { }); }); - describe('TodoPriority', () => { - it('should accept valid priority values', () => { - expect(() => TodoPriority.parse('high')).not.toThrow(); - expect(() => TodoPriority.parse('medium')).not.toThrow(); - expect(() => TodoPriority.parse('low')).not.toThrow(); - }); - - it('should reject invalid priority values', () => { - expect(() => TodoPriority.parse('critical')).toThrow(); - expect(() => TodoPriority.parse('')).toThrow(); - expect(() => TodoPriority.parse(undefined)).toThrow(); - }); - }); - describe('TodoSchema', () => { it('should accept valid todo object', () => { const validTodo = { id: 'test-1', content: 'Test task', status: 'pending', - priority: 'high', }; expect(() => TodoSchema.parse(validTodo)).not.toThrow(); }); @@ -59,7 +43,6 @@ describe('TodoSchemas', () => { id: 'test-1', content: '', status: 'pending', - priority: 'high', }; expect(() => TodoSchema.parse(invalidTodo)).toThrow(); }); @@ -68,30 +51,20 @@ describe('TodoSchemas', () => { const missingId = { content: 'Test task', status: 'pending', - priority: 'high', }; expect(() => TodoSchema.parse(missingId)).toThrow(); const missingContent = { id: 'test-1', status: 'pending', - priority: 'high', }; expect(() => TodoSchema.parse(missingContent)).toThrow(); const missingStatus = { id: 'test-1', content: 'Test task', - priority: 'high', }; expect(() => TodoSchema.parse(missingStatus)).toThrow(); - - const missingPriority = { - id: 'test-1', - content: 'Test task', - status: 'pending', - }; - expect(() => TodoSchema.parse(missingPriority)).toThrow(); }); it('should accept numeric IDs and coerce them to strings', () => { @@ -99,7 +72,6 @@ describe('TodoSchemas', () => { id: 123, // numeric ID should be coerced to string content: 'Test task', status: 'pending', - priority: 'high', }; const parsed = TodoSchema.parse(numericId); expect(parsed.id).toBe('123'); // Should be coerced to string @@ -111,7 +83,6 @@ describe('TodoSchemas', () => { id: 'test-1', content: 'Test task', status: 'pending', - priority: 'high', extra: 'field', }; // Strict mode would reject this @@ -131,19 +102,16 @@ describe('TodoSchemas', () => { id: 'test-1', content: 'First task', status: 'pending', - priority: 'high', }, { id: 'test-2', content: 'Second task', status: 'in_progress', - priority: 'medium', }, { id: 'test-3', content: 'Third task', status: 'completed', - priority: 'low', }, ]; expect(() => TodoArraySchema.parse(validTodos)).not.toThrow(); @@ -155,13 +123,11 @@ describe('TodoSchemas', () => { id: 'test-1', content: 'Valid task', status: 'pending', - priority: 'high', }, { id: 'test-2', content: '', // Invalid: empty content status: 'pending', - priority: 'high', }, ]; expect(() => TodoArraySchema.parse(invalidTodos)).toThrow(); @@ -278,7 +244,6 @@ describe('TodoSchemas', () => { id: 'task-1', content: 'Implement role-based access control', status: 'in_progress', - priority: 'high', }; expect(() => TodoSchema.parse(validTodo)).not.toThrow(); }); @@ -288,7 +253,6 @@ describe('TodoSchemas', () => { id: 'task-1', content: 'Implement role-based access control', status: 'in_progress', - priority: 'high', subtasks: [ { id: 'subtask-1', @@ -314,7 +278,6 @@ describe('TodoSchemas', () => { id: 'task-1', content: 'Test task', status: 'pending', - priority: 'high', subtasks: [ { id: 'subtask-1', @@ -337,7 +300,6 @@ describe('TodoSchemas', () => { id: 'task-1', content: 'Implement role-based access control', status: 'in_progress', - priority: 'high', subtasks: [ { id: 'subtask-1', @@ -359,7 +321,6 @@ describe('TodoSchemas', () => { id: 'task-2', content: 'Document security model', status: 'pending', - priority: 'medium', }, ]; expect(() => TodoArraySchema.parse(validTodos)).not.toThrow(); @@ -371,13 +332,11 @@ describe('TodoSchemas', () => { id: 'task-1', content: 'Valid task', status: 'pending', - priority: 'high', }, { id: 'task-2', content: '', // Invalid: empty content status: 'pending', - priority: 'high', }, ]; expect(() => TodoArraySchema.parse(invalidTodos)).toThrow(); diff --git a/packages/core/src/tools/todo-schemas.ts b/packages/core/src/tools/todo-schemas.ts index 4ff108f88..b493a7010 100644 --- a/packages/core/src/tools/todo-schemas.ts +++ b/packages/core/src/tools/todo-schemas.ts @@ -7,7 +7,6 @@ import { z } from 'zod'; export const TodoStatus = z.enum(['pending', 'in_progress', 'completed']); -export const TodoPriority = z.enum(['high', 'medium', 'low']); // Create a coercion schema for IDs that accepts both strings and numbers // This handles providers like GLM that send numeric IDs @@ -32,7 +31,6 @@ export const TodoSchema = z.object({ id: IdSchema, content: z.string().min(1), status: TodoStatus, - priority: TodoPriority, subtasks: z.array(SubtaskSchema).optional(), toolCalls: z.array(TodoToolCallSchema).optional(), }); @@ -43,4 +41,3 @@ export type TodoToolCall = z.infer; export type Subtask = z.infer; export type Todo = z.infer; export type TodoStatus = z.infer; -export type TodoPriority = z.infer; diff --git a/packages/core/src/tools/todo-store.test.ts b/packages/core/src/tools/todo-store.test.ts index 236b8fe13..cf163ba1d 100644 --- a/packages/core/src/tools/todo-store.test.ts +++ b/packages/core/src/tools/todo-store.test.ts @@ -23,19 +23,16 @@ describe('TodoStore', () => { id: '1', content: 'First task', status: 'pending', - priority: 'high', }, { id: '2', content: 'Second task', status: 'in_progress', - priority: 'medium', }, { id: '3', content: 'Third task', status: 'completed', - priority: 'low', }, ]; @@ -119,7 +116,6 @@ describe('TodoStore', () => { id: '99', content: 'New task', status: 'pending', - priority: 'high', }, ]; await store.writeTodos(newTodos); diff --git a/packages/core/src/tools/todo-write.test.ts b/packages/core/src/tools/todo-write.test.ts index 1e7dfa271..8f2b460d3 100644 --- a/packages/core/src/tools/todo-write.test.ts +++ b/packages/core/src/tools/todo-write.test.ts @@ -57,7 +57,6 @@ describe('TodoWrite tool', () => { id: '1', content: 'Copy README.md to ./tmp/README.jp.md using shell command', status: 'pending', - priority: 'high', }, ]); writeTodosMock.mockResolvedValue(undefined); @@ -85,14 +84,12 @@ describe('TodoWrite tool', () => { id: '1', content: 'Copy README.md to ./tmp/README.jp.md using shell command', status: 'in_progress', - priority: 'high', }, { id: '2', content: 'Edit ./tmp/README.jp.md to translate text to Japanese (keeping code blocks in English)', status: 'pending', - priority: 'high', }, ], }; @@ -128,7 +125,6 @@ describe('TodoWrite tool', () => { id: undefined, content: 'Create initial translation plan', status: 'pending', - priority: 'high', subtasks: [ { id: undefined, @@ -140,7 +136,6 @@ describe('TodoWrite tool', () => { id: 'existing-id', content: 'Verify translated document formatting', status: 'pending', - priority: 'medium', subtasks: [], }, ] as unknown as Todo[]; diff --git a/packages/core/src/tools/todo-write.ts b/packages/core/src/tools/todo-write.ts index 4d5eaa346..c9ac5a6a7 100644 --- a/packages/core/src/tools/todo-write.ts +++ b/packages/core/src/tools/todo-write.ts @@ -50,11 +50,6 @@ export class TodoWrite extends BaseTool { enum: ['pending', 'in_progress', 'completed'], description: 'Current status of the todo item', }, - priority: { - type: Type.STRING, - enum: ['high', 'medium', 'low'], - description: 'Priority level of the todo item', - }, subtasks: { type: Type.ARRAY, items: { @@ -98,7 +93,7 @@ export class TodoWrite extends BaseTool { description: 'Subtasks associated with this todo', }, }, - required: ['id', 'content', 'status', 'priority'], + required: ['id', 'content', 'status'], }, description: 'The updated todo list', }, @@ -236,18 +231,12 @@ export class TodoWrite extends BaseTool { inProgress: number; pending: number; completed: number; - highPriority: number; - mediumPriority: number; - lowPriority: number; } { return { total: todos.length, inProgress: todos.filter((t) => t.status === 'in_progress').length, pending: todos.filter((t) => t.status === 'pending').length, completed: todos.filter((t) => t.status === 'completed').length, - highPriority: todos.filter((t) => t.priority === 'high').length, - mediumPriority: todos.filter((t) => t.priority === 'medium').length, - lowPriority: todos.filter((t) => t.priority === 'low').length, }; } @@ -256,14 +245,10 @@ export class TodoWrite extends BaseTool { taskId?: string; taskContent?: string; } { - // Check if any tasks are in progress + // Check if any tasks are in progress - return first one (preserves LLM order) const inProgressTasks = todos.filter((t) => t.status === 'in_progress'); if (inProgressTasks.length > 0) { - // Continue with highest priority in-progress task - const task = inProgressTasks.sort((a, b) => { - const priorityOrder = { high: 0, medium: 1, low: 2 }; - return priorityOrder[a.priority] - priorityOrder[b.priority]; - })[0]; + const task = inProgressTasks[0]; return { type: 'continue', taskId: task.id, @@ -271,14 +256,10 @@ export class TodoWrite extends BaseTool { }; } - // Check if any tasks are pending + // Check if any tasks are pending - return first one (preserves LLM order) const pendingTasks = todos.filter((t) => t.status === 'pending'); if (pendingTasks.length > 0) { - // Start with highest priority pending task - const task = pendingTasks.sort((a, b) => { - const priorityOrder = { high: 0, medium: 1, low: 2 }; - return priorityOrder[a.priority] - priorityOrder[b.priority]; - })[0]; + const task = pendingTasks[0]; return { type: 'start', taskId: task.id, diff --git a/packages/core/src/utils/parameterCoercion.test.ts b/packages/core/src/utils/parameterCoercion.test.ts index 35db8b07b..7e8637b74 100644 --- a/packages/core/src/utils/parameterCoercion.test.ts +++ b/packages/core/src/utils/parameterCoercion.test.ts @@ -237,15 +237,13 @@ describe('coerceParametersToSchema', () => { id: { type: 'string' }, content: { type: 'string' }, status: { type: 'string' }, - priority: { type: 'string' }, }, }, }, }, }; const params = { - todos: - '[{"id": "1", "content": "Task one", "status": "pending", "priority": "high"}]', + todos: '[{"id": "1", "content": "Task one", "status": "pending"}]', }; const result = coerceParametersToSchema(params, schema); @@ -256,7 +254,6 @@ describe('coerceParametersToSchema', () => { id: '1', content: 'Task one', status: 'pending', - priority: 'high', }, ], });