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 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-get and todo-update, not for todo-create or todo-add
251
- const shouldShowTodoPanel = approvedTools.some(t => t.function.name === 'todo-get' || t.function.name === 'todo-update');
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 get/update operations
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: 'Run terminal commands. Pass commands exactly as typed in terminal. Examples: "npm -v", "git status", "node index.js"',
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. Examples: "npm -v", "git status", "ls -la"'
126
+ description: 'Terminal command to execute. For file editing, filesystem tools are generally preferred.'
127
127
  },
128
128
  timeout: {
129
129
  type: 'number',
@@ -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: 50)
84
- * @returns Object containing success message, old content, new content, and context
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
  /**
@@ -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: 50)
209
- * @returns Object containing success message, old content, new content, and context
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 = 50) {
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
- // Calculate context range
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, endLine + contextLines);
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.join('\n');
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.join('\n');
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: `File edited successfully: ${filePath} (lines ${startLine}-${adjustedEndLine} replaced)`,
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: 'Edit a file by replacing lines within a specified range. CRITICAL: You MUST use filesystem_read first to see the exact line numbers and current content before editing. This ensures precise line-based editing without errors. Returns context around the edited region for verification.',
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). Get this from filesystem_read output.'
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). Get this from filesystem_read output.'
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 the specified lines. Do NOT include line numbers in this content.'
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 return before and after the edit (default: 50)',
498
- default: 50
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: 'Create a TODO list for current session. CRITICAL FOR PROGRAMMING TASKS: You MUST use this tool at the start of ANY programming task (bug fixes, new features, refactoring, optimization, etc.) to break down the work into trackable steps. This ensures systematic execution and prevents missing critical steps. The TODO list helps maintain context and provides clear progress visibility to the user. WARNING: This tool creates/replaces the entire TODO list for the session - use it ONLY ONCE at the beginning. To add more tasks later, use "todo-add" instead.',
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 (be specific and actionable)',
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: 'List of TODO items to create. For programming tasks, include steps like: analyze requirements, implement changes, test functionality, verify build, etc. The list can contain multiple tasks that will be tracked and executed sequentially.',
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: 'Get the TODO list for current session. Use this to check existing tasks and their status before making updates.',
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: 'Update TODO item status or content. IMPORTANT: Mark task as "in_progress" BEFORE starting work, and "completed" IMMEDIATELY after finishing. Keep exactly ONE task in "in_progress" status at any time. This provides real-time progress feedback to the user.',
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" for not started',
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: 'Add a new TODO item to current session. Use this when you discover additional steps during execution, or when breaking down complex tasks into smaller subtasks.',
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 (be specific and actionable)',
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, for creating subtasks)',
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: 'Delete a TODO item from current session. Use this to remove tasks that are no longer relevant or were created by mistake.',
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) => (React.createElement(Box, { key: message.value },
561
- React.createElement(Text, { color: index === historySelectedIndex ? 'green' : 'white', bold: true },
562
- index === historySelectedIndex ? '➣ ' : ' ',
563
- message.label))));
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,7 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ fileCount: number;
4
+ onConfirm: (rollbackFiles: boolean) => void;
5
+ };
6
+ export default function FileRollbackConfirmation({ fileCount, onConfirm }: Props): React.JSX.Element;
7
+ export {};
@@ -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'
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ onSelectSession: (sessionId: string) => void;
4
+ onClose: () => void;
5
+ };
6
+ export default function SessionListPanel({ onSelectSession, onClose }: Props): React.JSX.Element;
7
+ export {};