snow-ai 0.4.5 → 0.4.6

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.
@@ -78,88 +78,48 @@ const SYSTEM_PROMPT_TEMPLATE = `You are Snow AI CLI, an intelligent command-line
78
78
 
79
79
  ## Execution Strategy - BALANCE ACTION & ANALYSIS
80
80
 
81
- ## Rigorous coding habits
82
- - In any programming language or business logic, which is usually accompanied by many-to-many references to files, you also need to think about the impact of the modification and whether it will conflict with the user's original business.
83
- - Using the optimal solution principle, you cannot choose risk scenarios such as hardcoding, logic simplification, etc., unless the user asks you to do so.
84
- - Avoid duplication, users may have encapsulated some reusable functions, and you should try to find them instead of creating a new function right away.
85
- - Compilable principle, you should not have low-level errors such as syntax errors, use tools to check for syntax errors, non-compilable code is meaningless.
81
+ ### Rigorous Coding Habits
82
+ - **Boundary verification**: MUST use \`filesystem-read\` to identify complete code boundaries before ANY edit. Never guess line numbers or code structure
83
+ - **Impact analysis**: Consider modification impact and conflicts with existing business logic
84
+ - **Optimal solution**: Avoid hardcoding/shortcuts unless explicitly requested
85
+ - **Avoid duplication**: Search for existing reusable functions before creating new ones
86
+ - **Compilable code**: No syntax errors - always verify complete syntactic units
86
87
 
87
88
  ### Smart Action Mode
88
89
  **Principle: Understand enough to code correctly, but don't over-investigate**
89
90
 
90
- **Examples:**
91
- - "Fix timeout in parser.ts" → Read file + check imports if needed → Fix → Done
92
- - "Add validation to form" → Read form component + related validation utils → Add code → Done
93
- - "Refactor error handling" → Read error handler + callers → Refactor → Done
91
+ **Examples:** "Fix timeout in parser.ts" → Read file + check imports → Fix → Done
94
92
 
95
93
  PLACEHOLDER_FOR_WORKFLOW_SECTION
96
94
 
97
- **Golden Rule: Read what you need to write correct code, nothing more.**
95
+ ### TODO Management - USE ACTIVELY
96
+
97
+ **STRONGLY RECOMMENDED: Create TODO for ALL multi-step tasks (3+ steps)** - Prevents missing steps, ensures systematic execution
98
+
99
+ **When to use:** Multi-file changes, features, refactoring, bug fixes touching 2+ files
100
+ **Skip only:** Single-file trivial edits (1-2 lines)
101
+
102
+ **CRITICAL - PARALLEL CALLS ONLY:** ALWAYS call TODO tools WITH action tools in same function call block
103
+ - CORRECT: todo-create + filesystem-read | todo-update + filesystem-edit
104
+ - FORBIDDEN: NEVER call TODO tools alone then wait for result
105
+
106
+ **Lifecycle:** New task → todo-create + initial action | Major change → delete + recreate | Minor → todo-add/update
98
107
 
99
- ### TODO Management - STRONGLY RECOMMENDED for Better Results
100
-
101
- **DEFAULT BEHAVIOR: Use TODO for ALL multi-step tasks (3+ steps)**
102
-
103
- **WHY TODO IS ESSENTIAL:**
104
- - **Track progress** - Never lose your place in complex work
105
- - **Ensure completeness** - Verify all steps are done
106
- - **Stay focused** - Clear roadmap prevents confusion
107
- - **Build confidence** - Users see structured progress
108
- - **Better quality** - Systematic approach reduces errors
109
-
110
- **WHEN TO USE TODO (Default for most tasks):**
111
- - **ANY multi-file modification** (always use)
112
- - **ANY feature implementation** (always use)
113
- - **ANY refactoring task** (always use)
114
- - **Bug fixes touching 2+ files** (recommended)
115
- - **User requests with multiple requirements** (always use)
116
- - **Unfamiliar codebase changes** (recommended)
117
- - **SKIP ONLY for**: Single-file trivial edits (1-2 lines)
118
-
119
- **USAGE RULES (Critical):**
120
- 1. **PARALLEL CALLS ONLY**: ALWAYS call TODO tools with action tools in the SAME function call block
121
- 2. **Immediate updates**: Mark completed while performing work (not after)
122
- 3. **Right sizing**: 3-7 main tasks, add subtasks if needed
123
- 4. **Lifecycle Management**:
124
- - New task = Create TODO at start
125
- - Major requirement change = Delete old + create new
126
- - Minor adjustment = Use todo-add or todo-update
127
- - **CRITICAL**: Keep using TODO throughout the entire conversation!
128
-
129
- **CORRECT PATTERNS (Do this):**
130
- - todo-create + filesystem-read → Plan while gathering info
131
- - todo-update(completed) + filesystem-edit → Update as you work
132
- - todo-get + filesystem-read → Check status while reading
133
- - todo-add + filesystem-edit → Add new task while working
134
-
135
- **FORBIDDEN PATTERNS (NEVER do this - WILL FAIL):**
136
- - todo-create alone, wait for result, then work → VIOLATION! Call together!
137
- - todo-update alone, wait, then continue → VIOLATION! Update while working!
138
- - todo-get alone just to check → VIOLATION! Call with other tools!
139
- - Skipping TODO for multi-file tasks → VIOLATION! Always use TODO!
140
- - **Abandoning TODO mid-conversation** → VIOLATION! Keep using throughout dialogue!
141
-
142
- **BEST PRACTICE: Start every non-trivial task with todo-create + initial action in parallel!**
108
+ **Best practice:** Start every non-trivial task with todo-create in parallel with first action
143
109
 
144
110
  ## Available Tools
145
111
 
146
112
  **Filesystem (SUPPORTS BATCH OPERATIONS):**
147
- - Read first and then modify to avoid grammatical errors caused by boundary judgment errors**
148
113
 
149
- **BATCH EDITING WORKFLOW - HIGH EFFICIENCY:**
150
- When modifying multiple files (extremely common in real projects):
151
- 1. Use filesystem-read with array of files to read them ALL at once
152
- 2. Use filesystem-edit or filesystem-edit_search with array config to modify ALL at once
153
- 3. This saves multiple round trips and dramatically improves efficiency
114
+ **CRITICAL: BOUNDARY-FIRST EDITING**
154
115
 
155
- **BATCH EXAMPLES:**
156
- - Read multiple: \`filesystem-read(filePath=["a.ts", "b.ts", "c.ts"])\`
157
- - Edit multiple with same change: \`filesystem-edit_search(filePath=["a.ts", "b.ts"], searchContent="old", replaceContent="new")\`
158
- - Edit multiple with different changes: \`filesystem-edit_search(filePath=[{path:"a.ts", searchContent:"old1", replaceContent:"new1"}, {path:"b.ts", searchContent:"old2", replaceContent:"new2"}])\`
159
- - Per-file line ranges: \`filesystem-edit(filePath=[{path:"a.ts", startLine:10, endLine:20, newContent:"..."}, {path:"b.ts", startLine:50, endLine:60, newContent:"..."}])\`
116
+ **MANDATORY WORKFLOW:**
117
+ 1. **LOCATE** - \`ace-semantic_search\` / \`ace-text_search\` / \`ace-find_definition\`
118
+ 2. **READ & VERIFY** - Use \`filesystem-read\` to identify COMPLETE units (functions: opening to closing brace, markup: full tags, check indentation)
119
+ 3. **COPY COMPLETE CODE** - Remove line numbers, preserve all content
120
+ 4. **EDIT** - \`filesystem-edit_search\` (fuzzy match, safer) or \`filesystem-edit\` (line-based, for add/delete)
160
121
 
161
- **CRITICAL EFFICIENCY RULE:**
162
- When you need to modify 2+ files, ALWAYS use batch operations instead of calling tools multiple times. This is faster, cleaner, and more reliable.
122
+ **BATCH OPERATIONS:** Modify 2+ files? Use batch: \`filesystem-read(filePath=["a.ts","b.ts"])\` or \`filesystem-edit_search(filePath=[{path:"a.ts",...},{path:"b.ts",...}])\`
163
123
 
164
124
  **Code Search:**
165
125
  PLACEHOLDER_FOR_CODE_SEARCH_SECTION
@@ -333,7 +293,7 @@ What type of task?
333
293
  ## Quality Assurance
334
294
 
335
295
  Guidance and recommendations:
336
- 1. Run build: \`npm run build\` or \`tsc\`
296
+ 1. Run build
337
297
  2. Fix any errors immediately
338
298
  3. Never leave broken code
339
299
 
@@ -365,20 +325,16 @@ function isCodebaseEnabled() {
365
325
  function getWorkflowSection(hasCodebase) {
366
326
  if (hasCodebase) {
367
327
  return `**Your workflow:**
368
- 1. **START WITH SEMANTIC SEARCH** - Use \\\`codebase-search\\\` as your PRIMARY exploration tool
369
- - ALWAYS try \\\`codebase-search\\\` FIRST for ANY code understanding task
370
- - Examples: "authentication logic", "error handling", "user validation", "database queries"
371
- - Dramatically faster than reading multiple files manually
372
- - Returns relevant code snippets with context - read results to understand the codebase
373
- 2. Read the primary file(s) mentioned (or files found by codebase search)
328
+ 1. **START WITH \`codebase-search\`** - Your PRIMARY tool for code exploration (use for 90% of understanding tasks)
329
+ - Query by intent: "authentication logic", "error handling", "validation patterns"
330
+ - Returns relevant code with full context - dramatically faster than manual file reading
331
+ 2. Read specific files found by codebase-search or mentioned by user
374
332
  3. Check dependencies/imports that directly impact the change
375
- 4. For precise symbol lookup AFTER understanding context, use \\\`ace-search_symbols\\\`, \\\`ace-find_definition\\\`, or \\\`ace-find_references\\\`
376
- 5. Read related files ONLY if they're critical to understanding the task
377
- 6. Write/modify code with proper context
378
- 7. Verify with build
379
- 8. NO excessive exploration beyond what's needed
380
- 9. NO reading entire modules "for reference"
381
- 10. NO over-planning multi-step workflows for simple tasks`;
333
+ 4. Use ACE tools ONLY when needed: \`ace-find_definition\` (exact symbol), \`ace-find_references\` (usage tracking)
334
+ 5. Write/modify code with proper context
335
+ 6. Verify with build
336
+
337
+ **Key principle:** codebase-search first, ACE tools for precision only`;
382
338
  }
383
339
  else {
384
340
  return `**Your workflow:**
@@ -406,28 +362,22 @@ When dealing with 2+ files, ALWAYS prefer batch operations:
406
362
  */
407
363
  function getCodeSearchSection(hasCodebase) {
408
364
  if (hasCodebase) {
409
- // When codebase tool is available, prioritize it
365
+ // When codebase tool is available, prioritize it heavily
410
366
  return `**Code Search Strategy:**
411
367
 
412
- **Priority Order (use in this sequence):**
413
-
414
- 1. **Semantic Search First** (\`codebase-search\`):
415
- - **ALWAYS START HERE** for code understanding and exploration tasks
416
- - Query by MEANING: "how is auth handled", "error patterns", "validation logic", "where is X implemented"
417
- - Returns semantically relevant code with context
418
- - **CRITICAL**: Primary tool for code understanding - do NOT skip this!
419
- - Example: "find authentication logic" → use codebase-search first
420
-
421
- 2. **Precise Lookup Second** (ACE tools - AFTER understanding context):
422
- - \`ace-semantic_search\` - Symbol search with context (fuzzy matching + symbol type filtering)
423
- - \`ace-find_definition\` - Go to single definition of a known symbol
424
- - \`ace-find_references\` - Find all usages of a known symbol
425
- - Use for: Known exact symbol names, reference tracking after exploration
426
-
427
- 3. **Literal Text Search Last** (\`ace-text_search\`):
428
- - ONLY for literal string matching (TODOs, log messages, error strings, constants)
429
- - NOT for code understanding or exploration
430
- - Use AFTER semantic search when you need exact string patterns`;
368
+ **PRIMARY TOOL - \`codebase-search\` (Semantic Search):**
369
+ - **USE THIS FIRST for 90% of code exploration tasks**
370
+ - Query by MEANING and intent: "authentication logic", "error handling patterns", "validation flow"
371
+ - Returns relevant code with full context across entire codebase
372
+ - **Why it's superior**: Understands semantic relationships, not just exact matches
373
+ - Examples: "how users are authenticated", "where database queries happen", "error handling approach"
374
+
375
+ **Fallback tools (use ONLY when codebase-search insufficient):**
376
+ - \`ace-find_definition\` - Jump to exact symbol definition (when you know the exact name)
377
+ - \`ace-find_references\` - Find all usages of a known symbol (for impact analysis)
378
+ - \`ace-text_search\` - Literal string search (TODOs, log messages, exact error strings)
379
+
380
+ **Golden rule:** Try codebase-search first, use ACE tools only for precise symbol lookup`;
431
381
  }
432
382
  else {
433
383
  // When codebase tool is NOT available, only show ACE
@@ -6,5 +6,9 @@ export declare function useAgentPicker(buffer: TextBuffer, triggerUpdate: () =>
6
6
  agentSelectedIndex: number;
7
7
  setAgentSelectedIndex: import("react").Dispatch<import("react").SetStateAction<number>>;
8
8
  agents: SubAgent[];
9
+ agentQuery: string;
10
+ hashSymbolPosition: number;
11
+ updateAgentPickerState: (_text: string, cursorPos: number) => void;
12
+ getFilteredAgents: () => SubAgent[];
9
13
  handleAgentSelect: (agent: SubAgent) => void;
10
14
  };
@@ -4,6 +4,8 @@ export function useAgentPicker(buffer, triggerUpdate) {
4
4
  const [showAgentPicker, setShowAgentPicker] = useState(false);
5
5
  const [agentSelectedIndex, setAgentSelectedIndex] = useState(0);
6
6
  const [agents, setAgents] = useState([]);
7
+ const [agentQuery, setAgentQuery] = useState('');
8
+ const [hashSymbolPosition, setHashSymbolPosition] = useState(-1);
7
9
  // Load agents when picker is shown
8
10
  useEffect(() => {
9
11
  if (showAgentPicker) {
@@ -12,21 +14,106 @@ export function useAgentPicker(buffer, triggerUpdate) {
12
14
  setAgentSelectedIndex(0);
13
15
  }
14
16
  }, [showAgentPicker]);
17
+ // Update agent picker state based on # symbol
18
+ const updateAgentPickerState = useCallback((_text, cursorPos) => {
19
+ // Use display text (with placeholders) instead of full text (expanded)
20
+ const displayText = buffer.text;
21
+ // Find the last '#' symbol before the cursor
22
+ const beforeCursor = displayText.slice(0, cursorPos);
23
+ let position = -1;
24
+ let query = '';
25
+ // Search backwards from cursor to find #
26
+ for (let i = beforeCursor.length - 1; i >= 0; i--) {
27
+ if (beforeCursor[i] === '#') {
28
+ position = i;
29
+ const afterHash = beforeCursor.slice(i + 1);
30
+ // Only activate if no space/newline after #
31
+ if (!afterHash.includes(' ') && !afterHash.includes('\n')) {
32
+ query = afterHash;
33
+ break;
34
+ }
35
+ else {
36
+ // Has space after #, not valid
37
+ position = -1;
38
+ break;
39
+ }
40
+ }
41
+ }
42
+ if (position !== -1) {
43
+ // Found valid # context
44
+ if (!showAgentPicker ||
45
+ agentQuery !== query ||
46
+ hashSymbolPosition !== position) {
47
+ setShowAgentPicker(true);
48
+ setAgentQuery(query);
49
+ setHashSymbolPosition(position);
50
+ setAgentSelectedIndex(0);
51
+ }
52
+ }
53
+ else {
54
+ // Hide agent picker if no valid # context found and it was triggered by #
55
+ if (showAgentPicker && hashSymbolPosition !== -1) {
56
+ setShowAgentPicker(false);
57
+ setHashSymbolPosition(-1);
58
+ setAgentQuery('');
59
+ }
60
+ }
61
+ }, [buffer, showAgentPicker, agentQuery, hashSymbolPosition]);
62
+ // Get filtered agents based on query
63
+ const getFilteredAgents = useCallback(() => {
64
+ if (!agentQuery) {
65
+ return agents;
66
+ }
67
+ const query = agentQuery.toLowerCase();
68
+ return agents.filter(agent => agent.id.toLowerCase().includes(query) ||
69
+ agent.name.toLowerCase().includes(query) ||
70
+ agent.description.toLowerCase().includes(query));
71
+ }, [agents, agentQuery]);
15
72
  // Handle agent selection
16
73
  const handleAgentSelect = useCallback((agent) => {
17
- // Clear buffer and insert agent reference
18
- buffer.setText('');
19
- buffer.insert(`#${agent.id} `);
74
+ if (hashSymbolPosition !== -1) {
75
+ // Triggered by # symbol - replace inline
76
+ const displayText = buffer.text;
77
+ const cursorPos = buffer.getCursorPosition();
78
+ // Replace query with selected agent ID
79
+ const beforeHash = displayText.slice(0, hashSymbolPosition);
80
+ const afterCursor = displayText.slice(cursorPos);
81
+ // Construct the replacement: #agent_id
82
+ const newText = beforeHash + '#' + agent.id + ' ' + afterCursor;
83
+ // Set the new text and position cursor after the inserted agent ID + space
84
+ buffer.setText(newText);
85
+ // Calculate cursor position after the inserted text
86
+ // # length (1) + agent ID length + space (1)
87
+ const insertedLength = 1 + agent.id.length + 1;
88
+ const targetPos = hashSymbolPosition + insertedLength;
89
+ // Reset cursor to beginning, then move to correct position
90
+ for (let i = 0; i < targetPos; i++) {
91
+ if (i < buffer.text.length) {
92
+ buffer.moveRight();
93
+ }
94
+ }
95
+ setHashSymbolPosition(-1);
96
+ setAgentQuery('');
97
+ }
98
+ else {
99
+ // Triggered by command - clear buffer and insert
100
+ buffer.setText('');
101
+ buffer.insert(`#${agent.id} `);
102
+ }
20
103
  setShowAgentPicker(false);
21
104
  setAgentSelectedIndex(0);
22
105
  triggerUpdate();
23
- }, [buffer, triggerUpdate]);
106
+ }, [hashSymbolPosition, buffer, triggerUpdate]);
24
107
  return {
25
108
  showAgentPicker,
26
109
  setShowAgentPicker,
27
110
  agentSelectedIndex,
28
111
  setAgentSelectedIndex,
29
112
  agents,
113
+ agentQuery,
114
+ hashSymbolPosition,
115
+ updateAgentPickerState,
116
+ getFilteredAgents,
30
117
  handleAgentSelect,
31
118
  };
32
119
  }
@@ -57,7 +57,8 @@ type KeyboardInputOptions = {
57
57
  setShowAgentPicker: (show: boolean) => void;
58
58
  agentSelectedIndex: number;
59
59
  setAgentSelectedIndex: (index: number | ((prev: number) => number)) => void;
60
- agents: SubAgent[];
60
+ updateAgentPickerState: (text: string, cursorPos: number) => void;
61
+ getFilteredAgents: () => SubAgent[];
61
62
  handleAgentSelect: (agent: SubAgent) => void;
62
63
  showTodoPicker: boolean;
63
64
  setShowTodoPicker: (show: boolean) => void;
@@ -2,7 +2,7 @@ import { useRef, useEffect } from 'react';
2
2
  import { useInput } from 'ink';
3
3
  import { executeCommand } from '../utils/commandExecutor.js';
4
4
  export function useKeyboardInput(options) {
5
- const { buffer, disabled, triggerUpdate, forceUpdate, showCommands, setShowCommands, commandSelectedIndex, setCommandSelectedIndex, getFilteredCommands, updateCommandPanelState, onCommand, showFilePicker, setShowFilePicker, fileSelectedIndex, setFileSelectedIndex, setFileQuery, setAtSymbolPosition, filteredFileCount, updateFilePickerState, handleFileSelect, fileListRef, showHistoryMenu, setShowHistoryMenu, historySelectedIndex, setHistorySelectedIndex, escapeKeyCount, setEscapeKeyCount, escapeKeyTimer, getUserMessages, handleHistorySelect, currentHistoryIndex, navigateHistoryUp, navigateHistoryDown, resetHistoryNavigation, saveToHistory, pasteFromClipboard, onSubmit, ensureFocus, showAgentPicker, setShowAgentPicker, agentSelectedIndex, setAgentSelectedIndex, agents, handleAgentSelect, showTodoPicker, setShowTodoPicker, todoSelectedIndex, setTodoSelectedIndex, todos, selectedTodos, toggleTodoSelection, confirmTodoSelection, todoSearchQuery, setTodoSearchQuery, } = options;
5
+ const { buffer, disabled, triggerUpdate, forceUpdate, showCommands, setShowCommands, commandSelectedIndex, setCommandSelectedIndex, getFilteredCommands, updateCommandPanelState, onCommand, showFilePicker, setShowFilePicker, fileSelectedIndex, setFileSelectedIndex, setFileQuery, setAtSymbolPosition, filteredFileCount, updateFilePickerState, handleFileSelect, fileListRef, showHistoryMenu, setShowHistoryMenu, historySelectedIndex, setHistorySelectedIndex, escapeKeyCount, setEscapeKeyCount, escapeKeyTimer, getUserMessages, handleHistorySelect, currentHistoryIndex, navigateHistoryUp, navigateHistoryDown, resetHistoryNavigation, saveToHistory, pasteFromClipboard, onSubmit, ensureFocus, showAgentPicker, setShowAgentPicker, agentSelectedIndex, setAgentSelectedIndex, updateAgentPickerState, getFilteredAgents, handleAgentSelect, showTodoPicker, setShowTodoPicker, todoSelectedIndex, setTodoSelectedIndex, todos, selectedTodos, toggleTodoSelection, confirmTodoSelection, todoSearchQuery, setTodoSearchQuery, } = options;
6
6
  // Mark variables as used (they are used in useInput closure below)
7
7
  void todoSelectedIndex;
8
8
  void selectedTodos;
@@ -24,6 +24,7 @@ export function useKeyboardInput(options) {
24
24
  const text = buffer.getFullText();
25
25
  const cursorPos = buffer.getCursorPosition();
26
26
  updateFilePickerState(text, cursorPos);
27
+ updateAgentPickerState(text, cursorPos);
27
28
  updateCommandPanelState(text);
28
29
  forceUpdate({});
29
30
  };
@@ -165,6 +166,7 @@ export function useKeyboardInput(options) {
165
166
  }
166
167
  // Handle agent picker navigation
167
168
  if (showAgentPicker) {
169
+ const filteredAgents = getFilteredAgents();
168
170
  // Up arrow in agent picker
169
171
  if (key.upArrow) {
170
172
  setAgentSelectedIndex(prev => Math.max(0, prev - 1));
@@ -172,14 +174,14 @@ export function useKeyboardInput(options) {
172
174
  }
173
175
  // Down arrow in agent picker
174
176
  if (key.downArrow) {
175
- const maxIndex = Math.max(0, agents.length - 1);
177
+ const maxIndex = Math.max(0, filteredAgents.length - 1);
176
178
  setAgentSelectedIndex(prev => Math.min(maxIndex, prev + 1));
177
179
  return;
178
180
  }
179
181
  // Enter - select agent
180
182
  if (key.return) {
181
- if (agents.length > 0 && agentSelectedIndex < agents.length) {
182
- const selectedAgent = agents[agentSelectedIndex];
183
+ if (filteredAgents.length > 0 && agentSelectedIndex < filteredAgents.length) {
184
+ const selectedAgent = filteredAgents[agentSelectedIndex];
183
185
  if (selectedAgent) {
184
186
  handleAgentSelect(selectedAgent);
185
187
  setShowAgentPicker(false);
@@ -188,8 +190,9 @@ export function useKeyboardInput(options) {
188
190
  }
189
191
  return;
190
192
  }
191
- // For any other key in agent picker, just return to prevent interference
192
- return;
193
+ // Allow typing to filter - don't block regular input
194
+ // The input will be processed below and updateAgentPickerState will be called
195
+ // which will update the filter automatically
193
196
  }
194
197
  // Handle history menu navigation
195
198
  if (showHistoryMenu) {
@@ -395,6 +398,7 @@ export function useKeyboardInput(options) {
395
398
  const text = buffer.getFullText();
396
399
  const cursorPos = buffer.getCursorPosition();
397
400
  updateFilePickerState(text, cursorPos);
401
+ updateAgentPickerState(text, cursorPos);
398
402
  triggerUpdate();
399
403
  return;
400
404
  }
@@ -419,6 +423,7 @@ export function useKeyboardInput(options) {
419
423
  const text = buffer.getFullText();
420
424
  const cursorPos = buffer.getCursorPosition();
421
425
  updateFilePickerState(text, cursorPos);
426
+ updateAgentPickerState(text, cursorPos);
422
427
  triggerUpdate();
423
428
  return;
424
429
  }
@@ -452,6 +457,7 @@ export function useKeyboardInput(options) {
452
457
  const navigated = navigateHistoryUp();
453
458
  if (navigated) {
454
459
  updateFilePickerState(buffer.getFullText(), buffer.getCursorPosition());
460
+ updateAgentPickerState(buffer.getFullText(), buffer.getCursorPosition());
455
461
  triggerUpdate();
456
462
  return;
457
463
  }
@@ -459,6 +465,7 @@ export function useKeyboardInput(options) {
459
465
  // Normal cursor movement
460
466
  buffer.moveUp();
461
467
  updateFilePickerState(buffer.getFullText(), buffer.getCursorPosition());
468
+ updateAgentPickerState(buffer.getFullText(), buffer.getCursorPosition());
462
469
  triggerUpdate();
463
470
  return;
464
471
  }
@@ -492,6 +499,7 @@ export function useKeyboardInput(options) {
492
499
  const navigated = navigateHistoryDown();
493
500
  if (navigated) {
494
501
  updateFilePickerState(buffer.getFullText(), buffer.getCursorPosition());
502
+ updateAgentPickerState(buffer.getFullText(), buffer.getCursorPosition());
495
503
  triggerUpdate();
496
504
  return;
497
505
  }
@@ -499,6 +507,7 @@ export function useKeyboardInput(options) {
499
507
  // Normal cursor movement
500
508
  buffer.moveDown();
501
509
  updateFilePickerState(buffer.getFullText(), buffer.getCursorPosition());
510
+ updateAgentPickerState(buffer.getFullText(), buffer.getCursorPosition());
502
511
  triggerUpdate();
503
512
  return;
504
513
  }
@@ -522,6 +531,7 @@ export function useKeyboardInput(options) {
522
531
  const cursorPos = buffer.getCursorPosition();
523
532
  updateCommandPanelState(text);
524
533
  updateFilePickerState(text, cursorPos);
534
+ updateAgentPickerState(text, cursorPos);
525
535
  triggerUpdate();
526
536
  }
527
537
  else {
@@ -551,6 +561,7 @@ export function useKeyboardInput(options) {
551
561
  const cursorPos = buffer.getCursorPosition();
552
562
  updateCommandPanelState(text);
553
563
  updateFilePickerState(text, cursorPos);
564
+ updateAgentPickerState(text, cursorPos);
554
565
  triggerUpdate();
555
566
  }
556
567
  // Set timer to process accumulated input
@@ -587,6 +598,7 @@ export function useKeyboardInput(options) {
587
598
  const cursorPos = buffer.getCursorPosition();
588
599
  updateCommandPanelState(text);
589
600
  updateFilePickerState(text, cursorPos);
601
+ updateAgentPickerState(text, cursorPos);
590
602
  triggerUpdate();
591
603
  }
592
604
  }, timeoutDelay); // Extended delay for large pastes to ensure complete accumulation
@@ -283,6 +283,7 @@ export const en = {
283
283
  quickAccessTitle: '🔍 Quick Access:',
284
284
  insertFiles: '@ - Insert files from project',
285
285
  searchContent: '@@ - Search file content',
286
+ selectAgent: '# - Select sub-agent for task execution',
286
287
  showCommands: '/ - Show available commands',
287
288
  navigationTitle: '📋 Navigation:',
288
289
  navigateHistory: '↑/↓ - Navigate command/message history',
@@ -329,7 +330,7 @@ export const en = {
329
330
  headerExplanations: 'Ask for code explanations and debugging help',
330
331
  headerInterrupt: 'Press ESC during response to interrupt',
331
332
  headerYolo: 'Press Shift+Tab: toggle YOLO',
332
- headerShortcuts: "Shortcuts: Ctrl+L (delete to start) • Ctrl+R (delete to end) • {pasteKey} (paste images) • '@' (files) • '@@' (search content) • '/' (commands)",
333
+ headerShortcuts: "Shortcuts: Ctrl+L (delete to start) • Ctrl+R (delete to end) • {pasteKey} (paste images) • '@' (files) • '@@' (search content) • '#' (sub-agents) • '/' (commands)",
333
334
  headerWorkingDirectory: 'Working directory: {directory}',
334
335
  // Status messages
335
336
  statusThinking: 'Thinking...',
@@ -283,6 +283,7 @@ export const es = {
283
283
  quickAccessTitle: '🔍 Acceso Rápido:',
284
284
  insertFiles: '@ - Insertar archivos del proyecto',
285
285
  searchContent: '@@ - Buscar contenido de archivos',
286
+ selectAgent: '# - Seleccionar sub-agente para ejecutar tarea',
286
287
  showCommands: '/ - Mostrar comandos disponibles',
287
288
  navigationTitle: '📋 Navegación:',
288
289
  navigateHistory: '↑/↓ - Navegar por el historial de comandos/mensajes',
@@ -329,7 +330,7 @@ export const es = {
329
330
  headerExplanations: 'Solicita explicaciones de código y ayuda de depuración',
330
331
  headerInterrupt: 'Presiona ESC durante la respuesta para interrumpir',
331
332
  headerYolo: 'Presiona Shift+Tab: Alternar YOLO',
332
- headerShortcuts: "Atajos: Ctrl+L (eliminar hasta inicio) • Ctrl+R (eliminar hasta final) • {pasteKey} (pegar imagen) • '@' (archivo) • '@@' (buscar contenido) • '/' (comando)",
333
+ headerShortcuts: "Atajos: Ctrl+L (eliminar hasta inicio) • Ctrl+R (eliminar hasta final) • {pasteKey} (pegar imagen) • '@' (archivo) • '@@' (buscar contenido) • '#' (sub-agentes) • '/' (comando)",
333
334
  headerWorkingDirectory: 'Directorio de Trabajo: {directory}',
334
335
  // Status messages
335
336
  statusThinking: 'Pensando...',
@@ -283,6 +283,7 @@ export const ja = {
283
283
  quickAccessTitle: '🔍 クイックアクセス:',
284
284
  insertFiles: '@ - プロジェクトからファイルを挿入',
285
285
  searchContent: '@@ - ファイル内容を検索',
286
+ selectAgent: '# - サブエージェントを選択してタスクを実行',
286
287
  showCommands: '/ - 利用可能なコマンドを表示',
287
288
  navigationTitle: '📋 ナビゲーション:',
288
289
  navigateHistory: '↑/↓ - コマンド/メッセージ履歴を移動',
@@ -329,7 +330,7 @@ export const ja = {
329
330
  headerExplanations: 'コードの説明とデバッグヘルプを問い合わせ',
330
331
  headerInterrupt: '応答中にESCで中断',
331
332
  headerYolo: 'Shift+Tabを押す: YOLOを切替',
332
- headerShortcuts: "ショートカット: Ctrl+L (先頭まで削除) • Ctrl+R (末尾まで削除) • {pasteKey} (画像貼付) • '@' (ファイル) • '@@' (内容検索) • '/' (コマンド)",
333
+ headerShortcuts: "ショートカット: Ctrl+L (先頭まで削除) • Ctrl+R (末尾まで削除) • {pasteKey} (画像貼付) • '@' (ファイル) • '@@' (内容検索) • '#' (サブエージェント) • '/' (コマンド)",
333
334
  headerWorkingDirectory: '作業ディレクトリ: {directory}',
334
335
  // Status messages
335
336
  statusThinking: '思考中...',
@@ -283,6 +283,7 @@ export const ko = {
283
283
  quickAccessTitle: '🔍 빠른 접근:',
284
284
  insertFiles: '@ - 프로젝트에서 파일 삽입',
285
285
  searchContent: '@@ - 파일 내용 검색',
286
+ selectAgent: '# - 작업 실행을 위한 하위 에이전트 선택',
286
287
  showCommands: '/ - 사용 가능한 명령 표시',
287
288
  navigationTitle: '📋 탐색:',
288
289
  navigateHistory: '↑/↓ - 명령/메시지 기록 탐색',
@@ -329,7 +330,7 @@ export const ko = {
329
330
  headerExplanations: '코드 설명 및 디버그 도움 요청',
330
331
  headerInterrupt: '응답 중 ESC로 중단',
331
332
  headerYolo: 'Shift+Tab 누르기: YOLO 토글',
332
- headerShortcuts: "단축키: Ctrl+L (시작까지 삭제) • Ctrl+R (끝까지 삭제) • {pasteKey} (이미지 붙여넣기) • '@' (파일) • '@@' (내용 검색) • '/' (명령)",
333
+ headerShortcuts: "단축키: Ctrl+L (시작까지 삭제) • Ctrl+R (끝까지 삭제) • {pasteKey} (이미지 붙여넣기) • '@' (파일) • '@@' (내용 검색) • '#' (하위 에이전트) • '/' (명령)",
333
334
  headerWorkingDirectory: '작업 디렉토리: {directory}',
334
335
  // Status messages
335
336
  statusThinking: '생각 중...',
@@ -283,6 +283,7 @@ export const zhTW = {
283
283
  quickAccessTitle: '🔍 快速存取:',
284
284
  insertFiles: '@ - 從專案插入檔案',
285
285
  searchContent: '@@ - 搜尋檔案內容',
286
+ selectAgent: '# - 選擇子代理執行任務',
286
287
  showCommands: '/ - 顯示可用命令',
287
288
  navigationTitle: '📋 導航:',
288
289
  navigateHistory: '↑/↓ - 導航命令/訊息歷史',
@@ -329,7 +330,7 @@ export const zhTW = {
329
330
  headerExplanations: '詢問程式碼說明和偵錯協助',
330
331
  headerInterrupt: '在回應期間按 ESC 中斷',
331
332
  headerYolo: '按 Shift+Tab: 切換 YOLO',
332
- headerShortcuts: "快捷鍵: Ctrl+L (刪除至開頭) • Ctrl+R (刪除至末尾) • {pasteKey} (貼上圖片) • '@' (檔案) • '@@' (搜尋內容) • '/' (命令)",
333
+ headerShortcuts: "快捷鍵: Ctrl+L (刪除至開頭) • Ctrl+R (刪除至末尾) • {pasteKey} (貼上圖片) • '@' (檔案) • '@@' (搜尋內容) • '#' (子代理) • '/' (命令)",
333
334
  headerWorkingDirectory: '工作目錄: {directory}',
334
335
  // Status messages
335
336
  statusThinking: '思考中...',
@@ -283,6 +283,7 @@ export const zh = {
283
283
  quickAccessTitle: '🔍 快速访问:',
284
284
  insertFiles: '@ - 从项目插入文件',
285
285
  searchContent: '@@ - 搜索文件内容',
286
+ selectAgent: '# - 选择子代理执行任务',
286
287
  showCommands: '/ - 显示可用命令',
287
288
  navigationTitle: '📋 导航:',
288
289
  navigateHistory: '↑/↓ - 导航命令/消息历史',
@@ -329,7 +330,7 @@ export const zh = {
329
330
  headerExplanations: '询问代码说明和调试帮助',
330
331
  headerInterrupt: '在响应期间按 ESC 中断',
331
332
  headerYolo: '按 Shift+Tab: 切换 YOLO',
332
- headerShortcuts: "快捷键: Ctrl+L (删除至开头) • Ctrl+R (删除至末尾) • {pasteKey} (粘贴图片) • '@' (文件) • '@@' (搜索内容) • '/' (命令)",
333
+ headerShortcuts: "快捷键: Ctrl+L (删除至开头) • Ctrl+R (删除至末尾) • {pasteKey} (粘贴图片) • '@' (文件) • '@@' (搜索内容) • '#' (子代理) • '/' (命令)",
333
334
  headerWorkingDirectory: '工作目录: {directory}',
334
335
  // Status messages
335
336
  statusThinking: '思考中...',
@@ -280,6 +280,7 @@ export type TranslationKeys = {
280
280
  quickAccessTitle: string;
281
281
  insertFiles: string;
282
282
  searchContent: string;
283
+ selectAgent: string;
283
284
  showCommands: string;
284
285
  navigationTitle: string;
285
286
  navigateHistory: string;
@@ -10,6 +10,7 @@ import { calculateSimilarity, normalizeForDisplay, } from './utils/filesystem/si
10
10
  import { analyzeCodeStructure, findSmartContextBoundaries, } from './utils/filesystem/code-analysis.utils.js';
11
11
  import { findClosestMatches, generateDiffMessage, } from './utils/filesystem/match-finder.utils.js';
12
12
  import { parseEditBySearchParams, parseEditByLineParams, executeBatchOperation, } from './utils/filesystem/batch-operations.utils.js';
13
+ import { tryFixPath } from './utils/filesystem/path-fixer.utils.js';
13
14
  // ACE Code Search utilities for symbol parsing
14
15
  import { parseFileSymbols } from './utils/aceCodeSearch/symbol.utils.js';
15
16
  // Notebook utilities for automatic note retrieval
@@ -217,10 +218,8 @@ export class FilesystemMCPService {
217
218
  if (actualEndLine < actualStartLine) {
218
219
  throw new Error(`End line must be greater than or equal to start line for ${file}`);
219
220
  }
220
- if (actualStartLine > totalLines) {
221
- throw new Error(`Start line ${actualStartLine} exceeds file length ${totalLines} for ${file}`);
222
- }
223
- const start = actualStartLine;
221
+ // Auto-adjust if startLine exceeds file length
222
+ const start = Math.min(actualStartLine, totalLines);
224
223
  const end = Math.min(totalLines, actualEndLine);
225
224
  // Extract specified lines
226
225
  const selectedLines = lines.slice(start - 1, end);
@@ -302,10 +301,8 @@ export class FilesystemMCPService {
302
301
  if (actualEndLine < actualStartLine) {
303
302
  throw new Error('End line must be greater than or equal to start line');
304
303
  }
305
- if (actualStartLine > totalLines) {
306
- throw new Error(`Start line ${actualStartLine} exceeds file length ${totalLines}`);
307
- }
308
- const start = actualStartLine;
304
+ // Auto-adjust if startLine exceeds file length
305
+ const start = Math.min(actualStartLine, totalLines);
309
306
  const end = Math.min(totalLines, actualEndLine);
310
307
  // Extract specified lines (convert to 0-indexed) and add line numbers
311
308
  const selectedLines = lines.slice(start - 1, end);
@@ -340,6 +337,24 @@ export class FilesystemMCPService {
340
337
  };
341
338
  }
342
339
  catch (error) {
340
+ // Try to fix common path issues if it's a file not found error
341
+ if (error instanceof Error &&
342
+ error.message.includes('ENOENT') &&
343
+ typeof filePath === 'string') {
344
+ const fixedPath = await tryFixPath(filePath, this.basePath);
345
+ if (fixedPath && fixedPath !== filePath) {
346
+ // Verify the fixed path actually exists before suggesting
347
+ const fixedFullPath = this.resolvePath(fixedPath);
348
+ try {
349
+ await fs.access(fixedFullPath);
350
+ // File exists, provide helpful suggestion to AI
351
+ throw new Error(`Failed to read file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}\n💡 Tip: File not found. Did you mean "${fixedPath}"? Please use the correct path.`);
352
+ }
353
+ catch {
354
+ // Fixed path also doesn't work, just throw original error
355
+ }
356
+ }
357
+ }
343
358
  throw new Error(`Failed to read file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
344
359
  }
345
360
  }
@@ -855,25 +870,23 @@ export class FilesystemMCPService {
855
870
  if (startLine > endLine) {
856
871
  throw new Error('Start line must be less than or equal to end line');
857
872
  }
858
- if (startLine > totalLines) {
859
- throw new Error(`Start line ${startLine} exceeds file length ${totalLines}`);
860
- }
861
- // Adjust endLine if it exceeds file length
873
+ // Adjust startLine and endLine if they exceed file length
874
+ const adjustedStartLine = Math.min(startLine, totalLines);
862
875
  const adjustedEndLine = Math.min(endLine, totalLines);
863
- const linesToModify = adjustedEndLine - startLine + 1;
876
+ const linesToModify = adjustedEndLine - adjustedStartLine + 1;
864
877
  // Backup file before editing
865
878
  await incrementalSnapshotManager.backupFile(fullPath);
866
879
  // Extract the lines that will be replaced (for comparison)
867
880
  // Compress whitespace for display readability
868
- const replacedLines = lines.slice(startLine - 1, adjustedEndLine);
881
+ const replacedLines = lines.slice(adjustedStartLine - 1, adjustedEndLine);
869
882
  const replacedContent = replacedLines
870
883
  .map((line, idx) => {
871
- const lineNum = startLine + idx;
884
+ const lineNum = adjustedStartLine + idx;
872
885
  return `${lineNum}→${normalizeForDisplay(line)}`;
873
886
  })
874
887
  .join('\n');
875
888
  // Calculate context range using smart boundary detection
876
- const smartBoundaries = findSmartContextBoundaries(lines, startLine, adjustedEndLine, contextLines);
889
+ const smartBoundaries = findSmartContextBoundaries(lines, adjustedStartLine, adjustedEndLine, contextLines);
877
890
  const contextStart = smartBoundaries.start;
878
891
  const contextEnd = smartBoundaries.end;
879
892
  // Extract old content for context (compress whitespace for readability)
@@ -886,12 +899,12 @@ export class FilesystemMCPService {
886
899
  .join('\n');
887
900
  // Replace the specified lines
888
901
  const newContentLines = newContent.split('\n');
889
- const beforeLines = lines.slice(0, startLine - 1);
902
+ const beforeLines = lines.slice(0, adjustedStartLine - 1);
890
903
  const afterLines = lines.slice(adjustedEndLine);
891
904
  const modifiedLines = [...beforeLines, ...newContentLines, ...afterLines];
892
905
  // Calculate new context range
893
906
  const newTotalLines = modifiedLines.length;
894
- const lineDifference = newContentLines.length - (adjustedEndLine - startLine + 1);
907
+ const lineDifference = newContentLines.length - (adjustedEndLine - adjustedStartLine + 1);
895
908
  const newContextEnd = Math.min(newTotalLines, contextEnd + lineDifference);
896
909
  // Extract new content for context with line numbers (compress whitespace)
897
910
  const newContextLines = modifiedLines.slice(contextStart - 1, newContextEnd);
@@ -941,7 +954,7 @@ export class FilesystemMCPService {
941
954
  }
942
955
  }
943
956
  // Analyze code structure of the edited content (using formatted content if available)
944
- const editedContentLines = finalLines.slice(startLine - 1, startLine - 1 + newContentLines.length);
957
+ const editedContentLines = finalLines.slice(adjustedStartLine - 1, adjustedStartLine - 1 + newContentLines.length);
945
958
  const structureAnalysis = analyzeCodeStructure(finalLines.join('\n'), filePath, editedContentLines);
946
959
  // Try to get diagnostics from IDE (VSCode or JetBrains) after editing (non-blocking)
947
960
  let diagnostics = [];
@@ -958,7 +971,7 @@ export class FilesystemMCPService {
958
971
  }
959
972
  const result = {
960
973
  message: `✅ File edited successfully,Please check the edit results and pay attention to code boundary issues to avoid syntax errors caused by missing closed parts: ${filePath}\n` +
961
- ` Replaced: lines ${startLine}-${adjustedEndLine} (${linesToModify} lines)\n` +
974
+ ` Replaced: lines ${adjustedStartLine}-${adjustedEndLine} (${linesToModify} lines)\n` +
962
975
  ` Result: ${newContentLines.length} new lines` +
963
976
  (smartBoundaries.extended
964
977
  ? `\n 📍 Context auto-extended to show complete code block (lines ${contextStart}-${finalContextEnd})`
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Attempt to fix common path issues when file is not found
3
+ * @param originalPath - The original path that failed
4
+ * @param basePath - Base path for resolving relative paths
5
+ * @returns Fixed path or null if cannot be fixed
6
+ */
7
+ export declare function tryFixPath(originalPath: string, basePath: string): Promise<string | null>;
@@ -0,0 +1,60 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ /**
4
+ * Attempt to fix common path issues when file is not found
5
+ * @param originalPath - The original path that failed
6
+ * @param basePath - Base path for resolving relative paths
7
+ * @returns Fixed path or null if cannot be fixed
8
+ */
9
+ export async function tryFixPath(originalPath, basePath) {
10
+ try {
11
+ // Common pattern: "source/mcp/utils/filesystem.ts" should be "source/mcp/filesystem.ts"
12
+ // Remove unnecessary intermediate directories
13
+ const segments = originalPath.split('/');
14
+ // Try removing 'utils' directory if present
15
+ if (segments.includes('utils')) {
16
+ const withoutUtils = segments.filter(s => s !== 'utils').join('/');
17
+ const fixedPath = resolve(basePath, withoutUtils);
18
+ try {
19
+ await fs.access(fixedPath);
20
+ return withoutUtils;
21
+ }
22
+ catch {
23
+ // Continue to next attempt
24
+ }
25
+ }
26
+ // Try parent directories
27
+ for (let i = 0; i < segments.length - 1; i++) {
28
+ const reducedPath = [
29
+ ...segments.slice(0, i),
30
+ segments[segments.length - 1],
31
+ ].join('/');
32
+ const fixedPath = resolve(basePath, reducedPath);
33
+ try {
34
+ await fs.access(fixedPath);
35
+ return reducedPath;
36
+ }
37
+ catch {
38
+ // Continue to next attempt
39
+ }
40
+ }
41
+ // Try searching for the file by name in common directories
42
+ const fileName = segments[segments.length - 1];
43
+ const commonDirs = ['source', 'src', 'lib', 'dist'];
44
+ for (const dir of commonDirs) {
45
+ const searchPath = `${dir}/${fileName}`;
46
+ const fixedPath = resolve(basePath, searchPath);
47
+ try {
48
+ await fs.access(fixedPath);
49
+ return searchPath;
50
+ }
51
+ catch {
52
+ // Continue to next attempt
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
@@ -1,8 +1,10 @@
1
1
  import React from 'react';
2
+ import type { SubAgent } from '../../utils/subAgentConfig.js';
2
3
  interface Props {
4
+ agents: SubAgent[];
3
5
  selectedIndex: number;
4
6
  visible: boolean;
5
7
  maxHeight?: number;
6
8
  }
7
- declare const AgentPickerPanel: React.MemoExoticComponent<({ selectedIndex, visible, maxHeight }: Props) => React.JSX.Element | null>;
9
+ declare const AgentPickerPanel: React.MemoExoticComponent<({ agents, selectedIndex, visible, maxHeight }: Props) => React.JSX.Element | null>;
8
10
  export default AgentPickerPanel;
@@ -1,15 +1,12 @@
1
1
  import React, { memo, useMemo } from 'react';
2
2
  import { Box, Text } from 'ink';
3
3
  import { Alert } from '@inkjs/ui';
4
- import { getSubAgents } from '../../utils/subAgentConfig.js';
5
- const AgentPickerPanel = memo(({ selectedIndex, visible, maxHeight }) => {
4
+ const AgentPickerPanel = memo(({ agents, selectedIndex, visible, maxHeight }) => {
6
5
  // Fixed maximum display items to prevent rendering issues
7
6
  const MAX_DISPLAY_ITEMS = 5;
8
7
  const effectiveMaxItems = maxHeight
9
8
  ? Math.min(maxHeight, MAX_DISPLAY_ITEMS)
10
9
  : MAX_DISPLAY_ITEMS;
11
- // Load sub-agents
12
- const agents = useMemo(() => getSubAgents(), []);
13
10
  // Limit displayed agents
14
11
  const displayedAgents = useMemo(() => {
15
12
  if (agents.length <= effectiveMaxItems) {
@@ -54,7 +51,8 @@ const AgentPickerPanel = memo(({ selectedIndex, visible, maxHeight }) => {
54
51
  "Select Sub-Agent",
55
52
  ' ',
56
53
  agents.length > effectiveMaxItems &&
57
- `(${selectedIndex + 1}/${agents.length})`)),
54
+ `(${selectedIndex + 1}/${agents.length})`),
55
+ React.createElement(Text, { color: "gray", dimColor: true }, "(Press ESC to close)")),
58
56
  displayedAgents.map((agent, index) => (React.createElement(Box, { key: agent.id, flexDirection: "column", width: "100%" },
59
57
  React.createElement(Text, { color: index === displayedSelectedIndex ? 'green' : 'gray', bold: true },
60
58
  index === displayedSelectedIndex ? '❯ ' : ' ',
@@ -57,7 +57,7 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
57
57
  // Use history navigation hook
58
58
  const { showHistoryMenu, setShowHistoryMenu, historySelectedIndex, setHistorySelectedIndex, escapeKeyCount, setEscapeKeyCount, escapeKeyTimer, getUserMessages, handleHistorySelect, currentHistoryIndex, navigateHistoryUp, navigateHistoryDown, resetHistoryNavigation, saveToHistory, } = useHistoryNavigation(buffer, triggerUpdate, chatHistory, onHistorySelect);
59
59
  // Use agent picker hook
60
- const { showAgentPicker, setShowAgentPicker, agentSelectedIndex, setAgentSelectedIndex, agents, handleAgentSelect, } = useAgentPicker(buffer, triggerUpdate);
60
+ const { showAgentPicker, setShowAgentPicker, agentSelectedIndex, setAgentSelectedIndex, updateAgentPickerState, getFilteredAgents, handleAgentSelect, } = useAgentPicker(buffer, triggerUpdate);
61
61
  // Use todo picker hook
62
62
  const { showTodoPicker, setShowTodoPicker, todoSelectedIndex, setTodoSelectedIndex, todos, selectedTodos, toggleTodoSelection, confirmTodoSelection, isLoading: todoIsLoading, searchQuery: todoSearchQuery, setSearchQuery: setTodoSearchQuery, totalTodoCount, } = useTodoPicker(buffer, triggerUpdate, process.cwd());
63
63
  // Use clipboard hook
@@ -108,7 +108,8 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
108
108
  setShowAgentPicker,
109
109
  agentSelectedIndex,
110
110
  setAgentSelectedIndex,
111
- agents,
111
+ updateAgentPickerState,
112
+ getFilteredAgents,
112
113
  handleAgentSelect,
113
114
  showTodoPicker,
114
115
  setShowTodoPicker,
@@ -309,7 +310,7 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
309
310
  React.createElement(CommandPanel, { commands: getFilteredCommands(), selectedIndex: commandSelectedIndex, query: buffer.getFullText().slice(1), visible: showCommands, isProcessing: commandPanelIsProcessing }),
310
311
  React.createElement(Box, null,
311
312
  React.createElement(FileList, { ref: fileListRef, query: fileQuery, selectedIndex: fileSelectedIndex, visible: showFilePicker, maxItems: 10, rootPath: process.cwd(), onFilteredCountChange: handleFilteredCountChange, searchMode: searchMode })),
312
- React.createElement(AgentPickerPanel, { selectedIndex: agentSelectedIndex, visible: showAgentPicker, maxHeight: 5 }),
313
+ React.createElement(AgentPickerPanel, { agents: getFilteredAgents(), selectedIndex: agentSelectedIndex, visible: showAgentPicker, maxHeight: 5 }),
313
314
  React.createElement(TodoPickerPanel, { todos: todos, selectedIndex: todoSelectedIndex, selectedTodos: selectedTodos, visible: showTodoPicker, maxHeight: 5, isLoading: todoIsLoading, searchQuery: todoSearchQuery, totalCount: totalTodoCount }),
314
315
  yoloMode && (React.createElement(Box, { marginTop: 1 },
315
316
  React.createElement(Text, { color: "yellow", dimColor: true }, t.chatScreen.yoloModeActive))),
@@ -31,6 +31,9 @@ export default function HelpPanel() {
31
31
  React.createElement(Text, null,
32
32
  " \u2022 ",
33
33
  t.helpPanel.searchContent),
34
+ React.createElement(Text, null,
35
+ " \u2022 ",
36
+ t.helpPanel.selectAgent),
34
37
  React.createElement(Text, null,
35
38
  " \u2022 ",
36
39
  t.helpPanel.showCommands)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-ai",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {