snow-ai 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +3 -3
- package/dist/hooks/useConversation.d.ts +1 -0
- package/dist/hooks/useConversation.js +9 -4
- package/dist/mcp/bash.js +2 -2
- package/dist/mcp/filesystem.d.ts +11 -6
- package/dist/mcp/filesystem.js +65 -23
- package/dist/mcp/todo.js +116 -14
- package/dist/ui/components/ChatInput.d.ts +2 -1
- package/dist/ui/components/ChatInput.js +16 -5
- package/dist/ui/components/FileRollbackConfirmation.d.ts +7 -0
- package/dist/ui/components/FileRollbackConfirmation.js +49 -0
- package/dist/ui/components/MessageList.js +1 -1
- package/dist/ui/components/SessionListPanel.d.ts +7 -0
- package/dist/ui/components/SessionListPanel.js +171 -0
- package/dist/ui/pages/ChatScreen.js +125 -17
- package/dist/utils/checkpointManager.d.ts +74 -0
- package/dist/utils/checkpointManager.js +181 -0
- package/dist/utils/commandExecutor.d.ts +1 -1
- package/dist/utils/commands/mcp.js +3 -3
- package/dist/utils/commands/resume.js +3 -3
- package/dist/utils/incrementalSnapshot.d.ts +87 -0
- package/dist/utils/incrementalSnapshot.js +250 -0
- package/dist/utils/workspaceSnapshot.d.ts +63 -0
- package/dist/utils/workspaceSnapshot.js +299 -0
- package/package.json +2 -1
package/dist/app.js
CHANGED
|
@@ -51,8 +51,8 @@ export default function App({ version }) {
|
|
|
51
51
|
return (React.createElement(WelcomeScreen, { version: version, onMenuSelect: handleMenuSelect }));
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
|
-
return (React.createElement(Box, { flexDirection: "column" },
|
|
55
|
-
renderView(),
|
|
56
|
-
exitNotification.show && (React.createElement(Box, { paddingX: 1 },
|
|
54
|
+
return (React.createElement(Box, { flexDirection: "column", height: "100%" },
|
|
55
|
+
React.createElement(Box, { flexGrow: 1, flexShrink: 1, minHeight: 0 }, renderView()),
|
|
56
|
+
exitNotification.show && (React.createElement(Box, { paddingX: 1, flexShrink: 0 },
|
|
57
57
|
React.createElement(Alert, { variant: "warning" }, exitNotification.message)))));
|
|
58
58
|
}
|
|
@@ -25,6 +25,7 @@ export type ConversationHandlerOptions = {
|
|
|
25
25
|
useBasicModel?: boolean;
|
|
26
26
|
getPendingMessages?: () => string[];
|
|
27
27
|
clearPendingMessages?: () => void;
|
|
28
|
+
setIsStreaming?: React.Dispatch<React.SetStateAction<boolean>>;
|
|
28
29
|
};
|
|
29
30
|
/**
|
|
30
31
|
* Handle conversation with streaming and tool calls
|
|
@@ -223,13 +223,18 @@ export async function handleConversationWithTools(options) {
|
|
|
223
223
|
// Use first tool for confirmation UI, but apply result to all
|
|
224
224
|
const confirmation = await requestToolConfirmation(firstTool, toolNames);
|
|
225
225
|
if (confirmation === 'reject') {
|
|
226
|
+
// Remove pending tool messages
|
|
227
|
+
setMessages(prev => prev.filter(msg => !msg.toolPending));
|
|
226
228
|
// User rejected - end conversation
|
|
227
229
|
setMessages(prev => [...prev, {
|
|
228
230
|
role: 'assistant',
|
|
229
231
|
content: 'Tool call rejected, session ended',
|
|
230
232
|
streaming: false
|
|
231
233
|
}]);
|
|
232
|
-
// End streaming
|
|
234
|
+
// End streaming immediately
|
|
235
|
+
if (options.setIsStreaming) {
|
|
236
|
+
options.setIsStreaming(false);
|
|
237
|
+
}
|
|
233
238
|
encoder.free();
|
|
234
239
|
return; // Exit the conversation loop
|
|
235
240
|
}
|
|
@@ -247,8 +252,8 @@ export async function handleConversationWithTools(options) {
|
|
|
247
252
|
// Execute approved tools
|
|
248
253
|
const toolResults = await executeToolCalls(approvedTools);
|
|
249
254
|
// Check if there are TODO related tool calls, if yes refresh TODO list
|
|
250
|
-
// Only show TODO panel for todo-
|
|
251
|
-
const shouldShowTodoPanel = approvedTools.some(t => t.function.name === 'todo-
|
|
255
|
+
// Only show TODO panel for todo-update, not for other todo operations
|
|
256
|
+
const shouldShowTodoPanel = approvedTools.some(t => t.function.name === 'todo-update');
|
|
252
257
|
const hasTodoTools = approvedTools.some(t => t.function.name.startsWith('todo-'));
|
|
253
258
|
if (hasTodoTools) {
|
|
254
259
|
const session = sessionManager.getCurrentSession();
|
|
@@ -256,7 +261,7 @@ export async function handleConversationWithTools(options) {
|
|
|
256
261
|
const updatedTodoList = await todoService.getTodoList(session.id);
|
|
257
262
|
if (updatedTodoList) {
|
|
258
263
|
setCurrentTodos(updatedTodoList.todos);
|
|
259
|
-
// Only show TODO panel for
|
|
264
|
+
// Only show TODO panel for update operations
|
|
260
265
|
if (shouldShowTodoPanel) {
|
|
261
266
|
// Remove any existing TODO tree messages and add a new one
|
|
262
267
|
setMessages(prev => {
|
package/dist/mcp/bash.js
CHANGED
|
@@ -117,13 +117,13 @@ export const terminalService = new TerminalCommandService();
|
|
|
117
117
|
export const mcpTools = [
|
|
118
118
|
{
|
|
119
119
|
name: 'terminal_execute',
|
|
120
|
-
description: '
|
|
120
|
+
description: 'Execute terminal commands like npm, git, build scripts, etc. BEST PRACTICE: For file modifications, prefer filesystem_edit/filesystem_create tools first - they are more reliable and provide better error handling. Terminal commands (sed, awk, echo >file, cat <<EOF) can be used for file editing, but only as a fallback option when filesystem tools are not suitable. Primary use cases: (1) Running build/test/lint scripts, (2) Version control operations, (3) Package management, (4) System utilities, (5) Fallback file editing when needed.',
|
|
121
121
|
inputSchema: {
|
|
122
122
|
type: 'object',
|
|
123
123
|
properties: {
|
|
124
124
|
command: {
|
|
125
125
|
type: 'string',
|
|
126
|
-
description: 'Terminal command to execute.
|
|
126
|
+
description: 'Terminal command to execute. For file editing, filesystem tools are generally preferred.'
|
|
127
127
|
},
|
|
128
128
|
timeout: {
|
|
129
129
|
type: 'number',
|
package/dist/mcp/filesystem.d.ts
CHANGED
|
@@ -76,21 +76,26 @@ export declare class FilesystemMCPService {
|
|
|
76
76
|
}>;
|
|
77
77
|
/**
|
|
78
78
|
* Edit a file by replacing lines within a specified range
|
|
79
|
+
* IMPORTANT: This tool enforces small, precise edits (max 15 lines per edit) to ensure accuracy.
|
|
80
|
+
* For larger changes, make multiple sequential edits instead of one large edit.
|
|
81
|
+
*
|
|
79
82
|
* @param filePath - Path to the file to edit
|
|
80
|
-
* @param startLine - Starting line number (1-indexed, inclusive)
|
|
81
|
-
* @param endLine - Ending line number (1-indexed, inclusive)
|
|
82
|
-
* @param newContent - New content to replace the specified lines
|
|
83
|
-
* @param contextLines - Number of context lines to return before and after the edit (default:
|
|
84
|
-
* @returns Object containing success message,
|
|
85
|
-
* @throws Error if file editing fails
|
|
83
|
+
* @param startLine - Starting line number (1-indexed, inclusive) - get from filesystem_read output
|
|
84
|
+
* @param endLine - Ending line number (1-indexed, inclusive) - get from filesystem_read output
|
|
85
|
+
* @param newContent - New content to replace the specified lines (WITHOUT line numbers)
|
|
86
|
+
* @param contextLines - Number of context lines to return before and after the edit (default: 8)
|
|
87
|
+
* @returns Object containing success message, precise before/after comparison, and diagnostics
|
|
88
|
+
* @throws Error if file editing fails or if the edit range is too large
|
|
86
89
|
*/
|
|
87
90
|
editFile(filePath: string, startLine: number, endLine: number, newContent: string, contextLines?: number): Promise<{
|
|
88
91
|
message: string;
|
|
89
92
|
oldContent: string;
|
|
90
93
|
newContent: string;
|
|
94
|
+
replacedLines: string;
|
|
91
95
|
contextStartLine: number;
|
|
92
96
|
contextEndLine: number;
|
|
93
97
|
totalLines: number;
|
|
98
|
+
linesModified: number;
|
|
94
99
|
diagnostics?: Diagnostic[];
|
|
95
100
|
}>;
|
|
96
101
|
/**
|
package/dist/mcp/filesystem.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { vscodeConnection } from '../utils/vscodeConnection.js';
|
|
4
|
+
import { incrementalSnapshotManager } from '../utils/incrementalSnapshot.js';
|
|
4
5
|
const { resolve, dirname, isAbsolute } = path;
|
|
5
6
|
/**
|
|
6
7
|
* Filesystem MCP Service
|
|
@@ -104,6 +105,8 @@ export class FilesystemMCPService {
|
|
|
104
105
|
throw error;
|
|
105
106
|
}
|
|
106
107
|
}
|
|
108
|
+
// Backup file before creation
|
|
109
|
+
await incrementalSnapshotManager.backupFile(fullPath);
|
|
107
110
|
// Create parent directories if needed
|
|
108
111
|
if (createDirectories) {
|
|
109
112
|
const dir = dirname(fullPath);
|
|
@@ -130,6 +133,8 @@ export class FilesystemMCPService {
|
|
|
130
133
|
if (!stats.isFile()) {
|
|
131
134
|
throw new Error(`Path is not a file: ${filePath}`);
|
|
132
135
|
}
|
|
136
|
+
// Backup file before deletion
|
|
137
|
+
await incrementalSnapshotManager.backupFile(fullPath);
|
|
133
138
|
await fs.unlink(fullPath);
|
|
134
139
|
return `File deleted successfully: ${filePath}`;
|
|
135
140
|
}
|
|
@@ -201,15 +206,18 @@ export class FilesystemMCPService {
|
|
|
201
206
|
}
|
|
202
207
|
/**
|
|
203
208
|
* Edit a file by replacing lines within a specified range
|
|
209
|
+
* IMPORTANT: This tool enforces small, precise edits (max 15 lines per edit) to ensure accuracy.
|
|
210
|
+
* For larger changes, make multiple sequential edits instead of one large edit.
|
|
211
|
+
*
|
|
204
212
|
* @param filePath - Path to the file to edit
|
|
205
|
-
* @param startLine - Starting line number (1-indexed, inclusive)
|
|
206
|
-
* @param endLine - Ending line number (1-indexed, inclusive)
|
|
207
|
-
* @param newContent - New content to replace the specified lines
|
|
208
|
-
* @param contextLines - Number of context lines to return before and after the edit (default:
|
|
209
|
-
* @returns Object containing success message,
|
|
210
|
-
* @throws Error if file editing fails
|
|
213
|
+
* @param startLine - Starting line number (1-indexed, inclusive) - get from filesystem_read output
|
|
214
|
+
* @param endLine - Ending line number (1-indexed, inclusive) - get from filesystem_read output
|
|
215
|
+
* @param newContent - New content to replace the specified lines (WITHOUT line numbers)
|
|
216
|
+
* @param contextLines - Number of context lines to return before and after the edit (default: 8)
|
|
217
|
+
* @returns Object containing success message, precise before/after comparison, and diagnostics
|
|
218
|
+
* @throws Error if file editing fails or if the edit range is too large
|
|
211
219
|
*/
|
|
212
|
-
async editFile(filePath, startLine, endLine, newContent, contextLines =
|
|
220
|
+
async editFile(filePath, startLine, endLine, newContent, contextLines = 8) {
|
|
213
221
|
try {
|
|
214
222
|
const fullPath = this.resolvePath(filePath);
|
|
215
223
|
// For absolute paths, skip validation to allow access outside base path
|
|
@@ -232,12 +240,37 @@ export class FilesystemMCPService {
|
|
|
232
240
|
}
|
|
233
241
|
// Adjust endLine if it exceeds file length
|
|
234
242
|
const adjustedEndLine = Math.min(endLine, totalLines);
|
|
235
|
-
//
|
|
243
|
+
// ENFORCE SMALL EDITS: Limit to max 15 lines per edit for precision
|
|
244
|
+
const linesToModify = adjustedEndLine - startLine + 1;
|
|
245
|
+
const MAX_LINES_PER_EDIT = 15;
|
|
246
|
+
if (linesToModify > MAX_LINES_PER_EDIT) {
|
|
247
|
+
throw new Error(`❌ Edit range too large (${linesToModify} lines). Maximum allowed: ${MAX_LINES_PER_EDIT} lines.\n\n` +
|
|
248
|
+
`💡 Best Practice: Make SMALL, PRECISE edits instead of large changes.\n` +
|
|
249
|
+
` - Break your changes into multiple sequential edits\n` +
|
|
250
|
+
` - Each edit should modify at most ${MAX_LINES_PER_EDIT} lines\n` +
|
|
251
|
+
` - This ensures accuracy and prevents syntax errors\n\n` +
|
|
252
|
+
`Current request: lines ${startLine}-${adjustedEndLine} (${linesToModify} lines)\n` +
|
|
253
|
+
`Suggested approach: Split into ${Math.ceil(linesToModify / MAX_LINES_PER_EDIT)} smaller edits`);
|
|
254
|
+
}
|
|
255
|
+
// Backup file before editing
|
|
256
|
+
await incrementalSnapshotManager.backupFile(fullPath);
|
|
257
|
+
// Extract the lines that will be replaced (for comparison)
|
|
258
|
+
const replacedLines = lines.slice(startLine - 1, adjustedEndLine);
|
|
259
|
+
const replacedContent = replacedLines.map((line, idx) => {
|
|
260
|
+
const lineNum = startLine + idx;
|
|
261
|
+
const paddedNum = String(lineNum).padStart(String(adjustedEndLine).length, ' ');
|
|
262
|
+
return `${paddedNum}→${line}`;
|
|
263
|
+
}).join('\n');
|
|
264
|
+
// Calculate context range (smaller context for focused edits)
|
|
236
265
|
const contextStart = Math.max(1, startLine - contextLines);
|
|
237
|
-
const contextEnd = Math.min(totalLines,
|
|
266
|
+
const contextEnd = Math.min(totalLines, adjustedEndLine + contextLines);
|
|
238
267
|
// Extract old content for context (including the lines to be replaced)
|
|
239
268
|
const oldContextLines = lines.slice(contextStart - 1, contextEnd);
|
|
240
|
-
const oldContent = oldContextLines.
|
|
269
|
+
const oldContent = oldContextLines.map((line, idx) => {
|
|
270
|
+
const lineNum = contextStart + idx;
|
|
271
|
+
const paddedNum = String(lineNum).padStart(String(contextEnd).length, ' ');
|
|
272
|
+
return `${paddedNum}→${line}`;
|
|
273
|
+
}).join('\n');
|
|
241
274
|
// Replace the specified lines
|
|
242
275
|
const newContentLines = newContent.split('\n');
|
|
243
276
|
const beforeLines = lines.slice(0, startLine - 1);
|
|
@@ -247,9 +280,13 @@ export class FilesystemMCPService {
|
|
|
247
280
|
const newTotalLines = modifiedLines.length;
|
|
248
281
|
const lineDifference = newContentLines.length - (adjustedEndLine - startLine + 1);
|
|
249
282
|
const newContextEnd = Math.min(newTotalLines, contextEnd + lineDifference);
|
|
250
|
-
// Extract new content for context
|
|
283
|
+
// Extract new content for context with line numbers
|
|
251
284
|
const newContextLines = modifiedLines.slice(contextStart - 1, newContextEnd);
|
|
252
|
-
const newContextContent = newContextLines.
|
|
285
|
+
const newContextContent = newContextLines.map((line, idx) => {
|
|
286
|
+
const lineNum = contextStart + idx;
|
|
287
|
+
const paddedNum = String(lineNum).padStart(String(newContextEnd).length, ' ');
|
|
288
|
+
return `${paddedNum}→${line}`;
|
|
289
|
+
}).join('\n');
|
|
253
290
|
// Write the modified content back to file
|
|
254
291
|
await fs.writeFile(fullPath, modifiedLines.join('\n'), 'utf-8');
|
|
255
292
|
// Try to get diagnostics from VS Code after editing
|
|
@@ -263,12 +300,16 @@ export class FilesystemMCPService {
|
|
|
263
300
|
// Ignore diagnostics errors, they are optional
|
|
264
301
|
}
|
|
265
302
|
const result = {
|
|
266
|
-
message:
|
|
303
|
+
message: `✅ File edited successfully: ${filePath}\n` +
|
|
304
|
+
` Replaced: lines ${startLine}-${adjustedEndLine} (${linesToModify} lines)\n` +
|
|
305
|
+
` Result: ${newContentLines.length} new lines`,
|
|
267
306
|
oldContent,
|
|
268
307
|
newContent: newContextContent,
|
|
308
|
+
replacedLines: replacedContent,
|
|
269
309
|
contextStartLine: contextStart,
|
|
270
310
|
contextEndLine: newContextEnd,
|
|
271
|
-
totalLines: newTotalLines
|
|
311
|
+
totalLines: newTotalLines,
|
|
312
|
+
linesModified: linesToModify
|
|
272
313
|
};
|
|
273
314
|
// Add diagnostics if any were found
|
|
274
315
|
if (diagnostics.length > 0) {
|
|
@@ -276,7 +317,8 @@ export class FilesystemMCPService {
|
|
|
276
317
|
const errorCount = diagnostics.filter(d => d.severity === 'error').length;
|
|
277
318
|
const warningCount = diagnostics.filter(d => d.severity === 'warning').length;
|
|
278
319
|
if (errorCount > 0 || warningCount > 0) {
|
|
279
|
-
result.message += `\n\n⚠️ Diagnostics detected: ${errorCount} error(s), ${warningCount} warning(s)
|
|
320
|
+
result.message += `\n\n⚠️ Diagnostics detected: ${errorCount} error(s), ${warningCount} warning(s)\n` +
|
|
321
|
+
` ⚡ TIP: Check the diagnostics and make another small edit to fix issues`;
|
|
280
322
|
}
|
|
281
323
|
}
|
|
282
324
|
return result;
|
|
@@ -421,7 +463,7 @@ export const mcpTools = [
|
|
|
421
463
|
},
|
|
422
464
|
{
|
|
423
465
|
name: 'filesystem_create',
|
|
424
|
-
description: 'Create a new file with specified content',
|
|
466
|
+
description: 'PREFERRED tool for file creation: Create a new file with specified content. More reliable than terminal commands like echo/cat with redirects. Automatically creates parent directories if needed. Terminal commands can be used as a fallback if needed.',
|
|
425
467
|
inputSchema: {
|
|
426
468
|
type: 'object',
|
|
427
469
|
properties: {
|
|
@@ -472,30 +514,30 @@ export const mcpTools = [
|
|
|
472
514
|
},
|
|
473
515
|
{
|
|
474
516
|
name: 'filesystem_edit',
|
|
475
|
-
description: '
|
|
517
|
+
description: '🎯 PREFERRED tool for precise file editing. **CRITICAL CONSTRAINTS**: (1) Maximum 15 lines per edit - larger changes will be REJECTED, (2) Must use exact line numbers from filesystem_read output. **BEST PRACTICES**: Use SMALL, INCREMENTAL edits instead of large changes. Multiple small edits are SAFER and MORE ACCURATE than one large edit. This prevents syntax errors and bracket mismatches. **WORKFLOW**: (1) Read target section with filesystem_read to get exact line numbers, (2) Edit SMALL sections (≤15 lines), (3) Verify with diagnostics, (4) Make next small edit if needed. Returns precise before/after comparison with line numbers and VS Code diagnostics.',
|
|
476
518
|
inputSchema: {
|
|
477
519
|
type: 'object',
|
|
478
520
|
properties: {
|
|
479
521
|
filePath: {
|
|
480
522
|
type: 'string',
|
|
481
|
-
description: 'Path to the file to edit'
|
|
523
|
+
description: 'Path to the file to edit (absolute or relative)'
|
|
482
524
|
},
|
|
483
525
|
startLine: {
|
|
484
526
|
type: 'number',
|
|
485
|
-
description: 'Starting line number (1-indexed, inclusive).
|
|
527
|
+
description: '⚠️ CRITICAL: Starting line number (1-indexed, inclusive). MUST match exact line number from filesystem_read output. Double-check this value!'
|
|
486
528
|
},
|
|
487
529
|
endLine: {
|
|
488
530
|
type: 'number',
|
|
489
|
-
description: 'Ending line number (1-indexed, inclusive).
|
|
531
|
+
description: '⚠️ CRITICAL: Ending line number (1-indexed, inclusive). MUST match exact line number from filesystem_read output. Range CANNOT exceed 15 lines (endLine - startLine + 1 ≤ 15).'
|
|
490
532
|
},
|
|
491
533
|
newContent: {
|
|
492
534
|
type: 'string',
|
|
493
|
-
description: 'New content to replace
|
|
535
|
+
description: 'New content to replace specified lines. ⚠️ Do NOT include line numbers. ⚠️ Ensure proper indentation and bracket closure. Keep changes MINIMAL and FOCUSED.'
|
|
494
536
|
},
|
|
495
537
|
contextLines: {
|
|
496
538
|
type: 'number',
|
|
497
|
-
description: 'Number of context lines to
|
|
498
|
-
default:
|
|
539
|
+
description: 'Number of context lines to show before/after edit for verification (default: 8)',
|
|
540
|
+
default: 8
|
|
499
541
|
}
|
|
500
542
|
},
|
|
501
543
|
required: ['filePath', 'startLine', 'endLine', 'newContent']
|
package/dist/mcp/todo.js
CHANGED
|
@@ -117,7 +117,36 @@ export class TodoService {
|
|
|
117
117
|
return [
|
|
118
118
|
{
|
|
119
119
|
name: 'todo-create',
|
|
120
|
-
description:
|
|
120
|
+
description: `Create a TODO list for complex multi-step tasks (optional planning tool).
|
|
121
|
+
|
|
122
|
+
## CORE PRINCIPLE - FOCUS ON EXECUTION:
|
|
123
|
+
TODO lists are OPTIONAL helpers for complex tasks. Your PRIMARY goal is COMPLETING THE WORK, not maintaining perfect TODO lists. Use this tool only when it genuinely helps organize complex work - don't let TODO management slow you down.
|
|
124
|
+
|
|
125
|
+
## WHEN TO USE (Optional):
|
|
126
|
+
- Complex tasks with 5+ distinct steps that benefit from tracking
|
|
127
|
+
- Long-running tasks where progress visibility helps the user
|
|
128
|
+
- Tasks with multiple dependencies that need careful ordering
|
|
129
|
+
- User explicitly requests a TODO list
|
|
130
|
+
|
|
131
|
+
## WHEN TO SKIP:
|
|
132
|
+
- Simple 1-3 step tasks (just do the work directly)
|
|
133
|
+
- Straightforward file edits or single-function changes
|
|
134
|
+
- Quick fixes or minor modifications
|
|
135
|
+
- When TODO creation takes longer than just doing the task
|
|
136
|
+
|
|
137
|
+
## LIFECYCLE MANAGEMENT:
|
|
138
|
+
1. **NEW REQUEST = NEW TODO LIST**: Completely new requirement? Delete old todos first, then create new list.
|
|
139
|
+
2. **INCREMENTAL REQUEST = USE TODO-ADD**: Adding to existing requirement? Use "todo-add" instead.
|
|
140
|
+
3. Use this tool ONLY when starting fresh (new session or new requirement after cleanup).
|
|
141
|
+
|
|
142
|
+
## CREATION GUIDELINES:
|
|
143
|
+
- Keep it simple and actionable
|
|
144
|
+
- 3-7 main tasks is usually sufficient (don't over-plan)
|
|
145
|
+
- Include verification steps only if critical
|
|
146
|
+
- Order by dependencies
|
|
147
|
+
|
|
148
|
+
## WARNING:
|
|
149
|
+
This REPLACES the entire TODO list. Never use it to "add more tasks" - use "todo-add" instead.`,
|
|
121
150
|
inputSchema: {
|
|
122
151
|
type: 'object',
|
|
123
152
|
properties: {
|
|
@@ -128,16 +157,16 @@ export class TodoService {
|
|
|
128
157
|
properties: {
|
|
129
158
|
content: {
|
|
130
159
|
type: 'string',
|
|
131
|
-
description: 'TODO item description
|
|
160
|
+
description: 'TODO item description - must be specific, actionable, and technically precise (e.g., "Modify handleSubmit function in ChatInput.tsx to validate user input before processing" NOT "fix input validation")',
|
|
132
161
|
},
|
|
133
162
|
parentId: {
|
|
134
163
|
type: 'string',
|
|
135
|
-
description: 'Parent TODO ID (optional, for creating subtasks)',
|
|
164
|
+
description: 'Parent TODO ID (optional, for creating subtasks in hierarchical structure)',
|
|
136
165
|
},
|
|
137
166
|
},
|
|
138
167
|
required: ['content'],
|
|
139
168
|
},
|
|
140
|
-
description: '
|
|
169
|
+
description: 'Complete list of TODO items. Each item must represent a discrete, verifiable unit of work. For programming tasks, typical structure: analyze code → implement changes → test functionality → verify build → commit (if requested).',
|
|
141
170
|
},
|
|
142
171
|
},
|
|
143
172
|
required: ['todos'],
|
|
@@ -145,7 +174,15 @@ export class TodoService {
|
|
|
145
174
|
},
|
|
146
175
|
{
|
|
147
176
|
name: 'todo-get',
|
|
148
|
-
description:
|
|
177
|
+
description: `Get the current TODO list for this session.
|
|
178
|
+
|
|
179
|
+
## WHEN TO USE:
|
|
180
|
+
- Before making any updates to check current task status and IDs
|
|
181
|
+
- To verify what tasks exist before deciding to add/delete/update
|
|
182
|
+
- To inspect the TODO structure before planning next steps
|
|
183
|
+
|
|
184
|
+
## RETURNS:
|
|
185
|
+
Complete TODO list with all task IDs, content, status, and hierarchy.`,
|
|
149
186
|
inputSchema: {
|
|
150
187
|
type: 'object',
|
|
151
188
|
properties: {},
|
|
@@ -153,22 +190,50 @@ export class TodoService {
|
|
|
153
190
|
},
|
|
154
191
|
{
|
|
155
192
|
name: 'todo-update',
|
|
156
|
-
description:
|
|
193
|
+
description: `Update TODO status - USE SPARINGLY, focus on actual work instead.
|
|
194
|
+
|
|
195
|
+
## CORE PRINCIPLE - WORK FIRST, UPDATES SECOND:
|
|
196
|
+
Prioritize COMPLETING TASKS over updating TODO status. Update TODO only at natural breakpoints, not constantly. It's better to finish 3 tasks and update once than to update TODO 10 times while making slow progress.
|
|
197
|
+
|
|
198
|
+
## WHEN TO UPDATE (Sparingly):
|
|
199
|
+
1. **Starting a major task**: Mark "in_progress" when beginning significant work (not for every micro-step)
|
|
200
|
+
2. **Completing a major task**: Mark "completed" ONLY when task is 100% done and verified
|
|
201
|
+
3. **Natural breakpoints**: When you finish a logical chunk of work
|
|
202
|
+
|
|
203
|
+
## WHEN NOT TO UPDATE:
|
|
204
|
+
- ❌ Every few minutes during work (you're wasting time on bookkeeping)
|
|
205
|
+
- ❌ Multiple updates for sub-steps of one task (just finish the task first)
|
|
206
|
+
- ❌ Immediately after starting (start working first, update later if it's a long task)
|
|
207
|
+
- ❌ Before verifying the work is actually complete
|
|
208
|
+
|
|
209
|
+
## SIMPLE RULES:
|
|
210
|
+
- Keep MAXIMUM one task as "in_progress" (don't over-track)
|
|
211
|
+
- Mark "completed" only when 100% done (no partial credit)
|
|
212
|
+
- If you're spending more time updating TODO than working, you're doing it wrong
|
|
213
|
+
|
|
214
|
+
## HONEST COMPLETION CRITERIA:
|
|
215
|
+
Only mark complete if:
|
|
216
|
+
- Task is 100% finished (no partial work)
|
|
217
|
+
- Tests/builds passed (if applicable)
|
|
218
|
+
- No errors or blockers
|
|
219
|
+
- You've actually verified it works
|
|
220
|
+
|
|
221
|
+
If blocked, keep as "in_progress" and report the blocker (don't create elaborate sub-TODO structures).`,
|
|
157
222
|
inputSchema: {
|
|
158
223
|
type: 'object',
|
|
159
224
|
properties: {
|
|
160
225
|
todoId: {
|
|
161
226
|
type: 'string',
|
|
162
|
-
description: 'TODO item ID to update',
|
|
227
|
+
description: 'TODO item ID to update (get exact ID from todo-get)',
|
|
163
228
|
},
|
|
164
229
|
status: {
|
|
165
230
|
type: 'string',
|
|
166
231
|
enum: ['pending', 'in_progress', 'completed'],
|
|
167
|
-
description: 'New status: "in_progress" when starting, "completed" when done, "pending"
|
|
232
|
+
description: 'New status - follow strict rules: "in_progress" when starting, "completed" when 100% done and verified, "pending" if not started',
|
|
168
233
|
},
|
|
169
234
|
content: {
|
|
170
235
|
type: 'string',
|
|
171
|
-
description: 'Updated TODO content (optional)',
|
|
236
|
+
description: 'Updated TODO content (optional, only if task description needs refinement)',
|
|
172
237
|
},
|
|
173
238
|
},
|
|
174
239
|
required: ['todoId'],
|
|
@@ -176,17 +241,34 @@ export class TodoService {
|
|
|
176
241
|
},
|
|
177
242
|
{
|
|
178
243
|
name: 'todo-add',
|
|
179
|
-
description:
|
|
244
|
+
description: `Add tasks to existing TODO list (use sparingly).
|
|
245
|
+
|
|
246
|
+
## CORE PRINCIPLE - AVOID TODO BLOAT:
|
|
247
|
+
Don't constantly add TODO items while working. If you discover small steps during execution, JUST DO THEM instead of creating TODO items. Only add to TODO if it's genuinely complex or user-requested.
|
|
248
|
+
|
|
249
|
+
## WHEN TO USE (Rare):
|
|
250
|
+
1. **User Adds Requirements**: User explicitly requests additional tasks
|
|
251
|
+
2. **Major Discovery**: You find a significant, complex step that wasn't initially planned
|
|
252
|
+
3. **Blocking Issue**: You discover a prerequisite that requires substantial separate work
|
|
253
|
+
|
|
254
|
+
## WHEN NOT TO USE (Common):
|
|
255
|
+
- ❌ Discovered a small 5-minute task while working (just do it, don't track it)
|
|
256
|
+
- ❌ Breaking down an existing task into micro-steps (over-planning)
|
|
257
|
+
- ❌ "Organizing" or "clarifying" existing tasks (maintain original structure)
|
|
258
|
+
- ❌ New unrelated requirement (use todo-delete + todo-create instead)
|
|
259
|
+
|
|
260
|
+
## GUIDELINE:
|
|
261
|
+
If a task takes less than 10 minutes, just do it instead of adding it to TODO. The goal is progress, not perfect tracking.`,
|
|
180
262
|
inputSchema: {
|
|
181
263
|
type: 'object',
|
|
182
264
|
properties: {
|
|
183
265
|
content: {
|
|
184
266
|
type: 'string',
|
|
185
|
-
description: 'TODO item description
|
|
267
|
+
description: 'TODO item description - must be specific, actionable, and technically precise',
|
|
186
268
|
},
|
|
187
269
|
parentId: {
|
|
188
270
|
type: 'string',
|
|
189
|
-
description: 'Parent TODO ID (optional
|
|
271
|
+
description: 'Parent TODO ID to create a subtask (optional). Get valid IDs from todo-get.',
|
|
190
272
|
},
|
|
191
273
|
},
|
|
192
274
|
required: ['content'],
|
|
@@ -194,13 +276,33 @@ export class TodoService {
|
|
|
194
276
|
},
|
|
195
277
|
{
|
|
196
278
|
name: 'todo-delete',
|
|
197
|
-
description:
|
|
279
|
+
description: `Delete TODO items from the current session.
|
|
280
|
+
|
|
281
|
+
## WHEN TO USE:
|
|
282
|
+
1. **Task No Longer Needed**: Requirement changed, task became irrelevant
|
|
283
|
+
2. **Mistake Correction**: Task was added by error or duplicated
|
|
284
|
+
3. **Clearing for New Requirement**: User provides COMPLETELY NEW requirement - delete all old todos first, then create new list
|
|
285
|
+
4. **Cascade Deletion**: Delete parent task with subtasks (automatically removes children)
|
|
286
|
+
|
|
287
|
+
## LIFECYCLE PATTERN FOR NEW REQUIREMENTS:
|
|
288
|
+
When user asks for something completely different:
|
|
289
|
+
1. Use todo-get to see current list
|
|
290
|
+
2. Use todo-delete on root items (children auto-delete via parentId cascade)
|
|
291
|
+
3. Use todo-create for the new requirement
|
|
292
|
+
|
|
293
|
+
## WHEN NOT TO USE:
|
|
294
|
+
- Do NOT delete completed tasks just for "cleanup" (keep as history)
|
|
295
|
+
- Do NOT delete in-progress tasks unless requirement truly changed
|
|
296
|
+
- Do NOT use for "reorganizing" (maintain original structure)
|
|
297
|
+
|
|
298
|
+
## CASCADE BEHAVIOR:
|
|
299
|
+
Deleting a parent task automatically deletes all its subtasks (parentId relationship).`,
|
|
198
300
|
inputSchema: {
|
|
199
301
|
type: 'object',
|
|
200
302
|
properties: {
|
|
201
303
|
todoId: {
|
|
202
304
|
type: 'string',
|
|
203
|
-
description: 'TODO item ID to delete',
|
|
305
|
+
description: 'TODO item ID to delete. Deleting a parent will cascade delete all its children. Get exact ID from todo-get.',
|
|
204
306
|
},
|
|
205
307
|
},
|
|
206
308
|
required: ['todoId'],
|
|
@@ -17,6 +17,7 @@ type Props = {
|
|
|
17
17
|
inputTokens: number;
|
|
18
18
|
maxContextTokens: number;
|
|
19
19
|
};
|
|
20
|
+
snapshotFileCount?: Map<number, number>;
|
|
20
21
|
};
|
|
21
|
-
export default function ChatInput({ onSubmit, onCommand, placeholder, disabled, chatHistory, onHistorySelect, yoloMode, contextUsage }: Props): React.JSX.Element;
|
|
22
|
+
export default function ChatInput({ onSubmit, onCommand, placeholder, disabled, chatHistory, onHistorySelect, yoloMode, contextUsage, snapshotFileCount }: Props): React.JSX.Element;
|
|
22
23
|
export {};
|
|
@@ -16,7 +16,7 @@ const commands = [
|
|
|
16
16
|
{ name: 'ide', description: 'Connect to VSCode editor and sync context' },
|
|
17
17
|
{ name: 'compact', description: 'Compress conversation history using compact model' }
|
|
18
18
|
];
|
|
19
|
-
export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type your message...', disabled = false, chatHistory = [], onHistorySelect, yoloMode = false, contextUsage }) {
|
|
19
|
+
export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type your message...', disabled = false, chatHistory = [], onHistorySelect, yoloMode = false, contextUsage, snapshotFileCount }) {
|
|
20
20
|
const { stdout } = useStdout();
|
|
21
21
|
const terminalWidth = stdout?.columns || 80;
|
|
22
22
|
const uiOverhead = 8;
|
|
@@ -557,10 +557,21 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
|
|
|
557
557
|
const userMessages = getUserMessages();
|
|
558
558
|
const maxHeight = 8;
|
|
559
559
|
const visibleMessages = userMessages.slice(0, maxHeight);
|
|
560
|
-
return visibleMessages.map((message, index) =>
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
560
|
+
return visibleMessages.map((message, index) => {
|
|
561
|
+
const messageIndex = parseInt(message.value, 10);
|
|
562
|
+
const fileCount = snapshotFileCount?.get(messageIndex) || 0;
|
|
563
|
+
return (React.createElement(Box, { key: message.value },
|
|
564
|
+
React.createElement(Text, { color: index === historySelectedIndex ? 'green' : 'white', bold: true },
|
|
565
|
+
index === historySelectedIndex ? '➣ ' : ' ',
|
|
566
|
+
message.label),
|
|
567
|
+
fileCount > 0 && (React.createElement(Text, { color: "yellow", dimColor: true },
|
|
568
|
+
' ',
|
|
569
|
+
"(",
|
|
570
|
+
fileCount,
|
|
571
|
+
" file",
|
|
572
|
+
fileCount > 1 ? 's' : '',
|
|
573
|
+
")"))));
|
|
574
|
+
});
|
|
564
575
|
})(),
|
|
565
576
|
getUserMessages().length > 8 && (React.createElement(Box, null,
|
|
566
577
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
export default function FileRollbackConfirmation({ fileCount, onConfirm }) {
|
|
4
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
5
|
+
const options = [
|
|
6
|
+
{ label: 'Yes, rollback files and conversation', value: true },
|
|
7
|
+
{ label: 'No, rollback conversation only', value: false }
|
|
8
|
+
];
|
|
9
|
+
useInput((_, key) => {
|
|
10
|
+
// Up arrow
|
|
11
|
+
if (key.upArrow) {
|
|
12
|
+
setSelectedIndex(prev => Math.max(0, prev - 1));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// Down arrow
|
|
16
|
+
if (key.downArrow) {
|
|
17
|
+
setSelectedIndex(prev => Math.min(options.length - 1, prev + 1));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
// Enter - confirm selection
|
|
21
|
+
if (key.return) {
|
|
22
|
+
onConfirm(options[selectedIndex]?.value ?? false);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// ESC - cancel rollback (select "No")
|
|
26
|
+
if (key.escape) {
|
|
27
|
+
onConfirm(false);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return (React.createElement(Box, { flexDirection: "column", marginX: 1, marginBottom: 1, borderStyle: "round", borderColor: "yellow", padding: 1 },
|
|
32
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
33
|
+
React.createElement(Text, { color: "yellow", bold: true }, "\u26A0 File Rollback Confirmation")),
|
|
34
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
35
|
+
React.createElement(Text, { color: "white" },
|
|
36
|
+
"This checkpoint has ",
|
|
37
|
+
fileCount,
|
|
38
|
+
" file",
|
|
39
|
+
fileCount > 1 ? 's' : '',
|
|
40
|
+
" that will be rolled back.")),
|
|
41
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
42
|
+
React.createElement(Text, { color: "gray", dimColor: true }, "Do you want to rollback the files as well?")),
|
|
43
|
+
React.createElement(Box, { flexDirection: "column" }, options.map((option, index) => (React.createElement(Box, { key: index },
|
|
44
|
+
React.createElement(Text, { color: index === selectedIndex ? 'green' : 'white', bold: index === selectedIndex },
|
|
45
|
+
index === selectedIndex ? '➣ ' : ' ',
|
|
46
|
+
option.label))))),
|
|
47
|
+
React.createElement(Box, { marginTop: 1 },
|
|
48
|
+
React.createElement(Text, { color: "gray", dimColor: true }, "Use \u2191\u2193 to select, Enter to confirm, ESC to cancel"))));
|
|
49
|
+
}
|
|
@@ -6,7 +6,7 @@ const MessageList = memo(({ messages, animationFrame, maxMessages = 6 }) => {
|
|
|
6
6
|
if (messages.length === 0) {
|
|
7
7
|
return null;
|
|
8
8
|
}
|
|
9
|
-
return (React.createElement(Box, { marginBottom: 1, flexDirection: "column" }, messages.slice(-maxMessages).map((message, index) => {
|
|
9
|
+
return (React.createElement(Box, { marginBottom: 1, flexDirection: "column", overflow: "hidden" }, messages.slice(-maxMessages).map((message, index) => {
|
|
10
10
|
const iconColor = message.role === 'user'
|
|
11
11
|
? 'green'
|
|
12
12
|
: message.role === 'command'
|