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.
- package/dist/api/systemPrompt.js +51 -101
- package/dist/hooks/useAgentPicker.d.ts +4 -0
- package/dist/hooks/useAgentPicker.js +91 -4
- package/dist/hooks/useKeyboardInput.d.ts +2 -1
- package/dist/hooks/useKeyboardInput.js +18 -6
- package/dist/i18n/lang/en.js +2 -1
- package/dist/i18n/lang/es.js +2 -1
- package/dist/i18n/lang/ja.js +2 -1
- package/dist/i18n/lang/ko.js +2 -1
- package/dist/i18n/lang/zh-TW.js +2 -1
- package/dist/i18n/lang/zh.js +2 -1
- package/dist/i18n/types.d.ts +1 -0
- package/dist/mcp/filesystem.js +33 -20
- package/dist/mcp/utils/filesystem/path-fixer.utils.d.ts +7 -0
- package/dist/mcp/utils/filesystem/path-fixer.utils.js +60 -0
- package/dist/ui/components/AgentPickerPanel.d.ts +3 -1
- package/dist/ui/components/AgentPickerPanel.js +3 -5
- package/dist/ui/components/ChatInput.js +4 -3
- package/dist/ui/components/HelpPanel.js +3 -0
- package/package.json +1 -1
package/dist/api/systemPrompt.js
CHANGED
|
@@ -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
|
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
**
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
**
|
|
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
|
|
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
|
|
369
|
-
-
|
|
370
|
-
-
|
|
371
|
-
|
|
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.
|
|
376
|
-
5.
|
|
377
|
-
6.
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
**
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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 (
|
|
182
|
-
const selectedAgent =
|
|
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
|
-
//
|
|
192
|
-
|
|
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
|
package/dist/i18n/lang/en.js
CHANGED
|
@@ -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...',
|
package/dist/i18n/lang/es.js
CHANGED
|
@@ -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...',
|
package/dist/i18n/lang/ja.js
CHANGED
|
@@ -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: '思考中...',
|
package/dist/i18n/lang/ko.js
CHANGED
|
@@ -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: '생각 중...',
|
package/dist/i18n/lang/zh-TW.js
CHANGED
|
@@ -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: '思考中...',
|
package/dist/i18n/lang/zh.js
CHANGED
|
@@ -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: '思考中...',
|
package/dist/i18n/types.d.ts
CHANGED
package/dist/mcp/filesystem.js
CHANGED
|
@@ -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
|
|
221
|
-
|
|
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
|
|
306
|
-
|
|
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
|
-
|
|
859
|
-
|
|
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 -
|
|
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(
|
|
881
|
+
const replacedLines = lines.slice(adjustedStartLine - 1, adjustedEndLine);
|
|
869
882
|
const replacedContent = replacedLines
|
|
870
883
|
.map((line, idx) => {
|
|
871
|
-
const lineNum =
|
|
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,
|
|
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,
|
|
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 -
|
|
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(
|
|
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 ${
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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)),
|