snow-ai 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/anthropic.js +30 -1
- package/dist/api/chat.js +25 -0
- package/dist/api/gemini.js +26 -8
- package/dist/api/responses.js +32 -5
- 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/useFilePicker.d.ts +1 -1
- 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.d.ts +28 -17
- package/dist/mcp/filesystem.js +177 -28
- package/dist/mcp/types/filesystem.types.d.ts +75 -0
- package/dist/mcp/types/filesystem.types.js +24 -1
- package/dist/mcp/utils/filesystem/office-parser.utils.d.ts +43 -0
- package/dist/mcp/utils/filesystem/office-parser.utils.js +163 -0
- 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/dist/ui/pages/ChatScreen.js +4 -0
- package/dist/utils/toolExecutor.d.ts +2 -0
- package/dist/utils/toolExecutor.js +58 -1
- package/package.json +9 -4
package/dist/api/anthropic.js
CHANGED
|
@@ -97,13 +97,42 @@ function convertToAnthropicMessages(messages, includeBuiltinSystemPrompt = true)
|
|
|
97
97
|
continue;
|
|
98
98
|
}
|
|
99
99
|
if (msg.role === 'tool' && msg.tool_call_id) {
|
|
100
|
+
// Build tool_result content - can be text or array with images
|
|
101
|
+
let toolResultContent;
|
|
102
|
+
if (msg.images && msg.images.length > 0) {
|
|
103
|
+
// Multimodal tool result with images
|
|
104
|
+
const contentArray = [];
|
|
105
|
+
// Add text content first
|
|
106
|
+
if (msg.content) {
|
|
107
|
+
contentArray.push({
|
|
108
|
+
type: 'text',
|
|
109
|
+
text: msg.content,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// Add images
|
|
113
|
+
for (const image of msg.images) {
|
|
114
|
+
contentArray.push({
|
|
115
|
+
type: 'image',
|
|
116
|
+
source: {
|
|
117
|
+
type: 'base64',
|
|
118
|
+
media_type: image.mimeType,
|
|
119
|
+
data: image.data,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
toolResultContent = contentArray;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Text-only tool result
|
|
127
|
+
toolResultContent = msg.content;
|
|
128
|
+
}
|
|
100
129
|
anthropicMessages.push({
|
|
101
130
|
role: 'user',
|
|
102
131
|
content: [
|
|
103
132
|
{
|
|
104
133
|
type: 'tool_result',
|
|
105
134
|
tool_use_id: msg.tool_call_id,
|
|
106
|
-
content:
|
|
135
|
+
content: toolResultContent,
|
|
107
136
|
},
|
|
108
137
|
],
|
|
109
138
|
});
|
package/dist/api/chat.js
CHANGED
|
@@ -50,6 +50,31 @@ function convertToOpenAIMessages(messages, includeBuiltinSystemPrompt = true) {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
if (msg.role === 'tool' && msg.tool_call_id) {
|
|
53
|
+
// Handle multimodal tool results with images
|
|
54
|
+
if (msg.images && msg.images.length > 0) {
|
|
55
|
+
const content = [];
|
|
56
|
+
// Add text content
|
|
57
|
+
if (msg.content) {
|
|
58
|
+
content.push({
|
|
59
|
+
type: 'text',
|
|
60
|
+
text: msg.content,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// Add images as base64 data URLs
|
|
64
|
+
for (const image of msg.images) {
|
|
65
|
+
content.push({
|
|
66
|
+
type: 'image_url',
|
|
67
|
+
image_url: {
|
|
68
|
+
url: `data:${image.mimeType};base64,${image.data}`,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
role: 'tool',
|
|
74
|
+
content,
|
|
75
|
+
tool_call_id: msg.tool_call_id,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
53
78
|
return {
|
|
54
79
|
role: 'tool',
|
|
55
80
|
content: msg.content,
|
package/dist/api/gemini.js
CHANGED
|
@@ -91,6 +91,18 @@ function convertToGeminiMessages(messages, includeBuiltinSystemPrompt = true) {
|
|
|
91
91
|
// Tool response must be a valid object for Gemini API
|
|
92
92
|
// If content is a JSON string, parse it; otherwise wrap it in an object
|
|
93
93
|
let responseData;
|
|
94
|
+
const imageParts = [];
|
|
95
|
+
// Handle images from tool result
|
|
96
|
+
if (msg.images && msg.images.length > 0) {
|
|
97
|
+
for (const image of msg.images) {
|
|
98
|
+
imageParts.push({
|
|
99
|
+
inlineData: {
|
|
100
|
+
mimeType: image.mimeType,
|
|
101
|
+
data: image.data,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
94
106
|
if (!msg.content) {
|
|
95
107
|
responseData = {};
|
|
96
108
|
}
|
|
@@ -132,16 +144,22 @@ function convertToGeminiMessages(messages, includeBuiltinSystemPrompt = true) {
|
|
|
132
144
|
responseData = { content: contentToParse };
|
|
133
145
|
}
|
|
134
146
|
}
|
|
147
|
+
// Build parts array with functionResponse and optional images
|
|
148
|
+
const parts = [
|
|
149
|
+
{
|
|
150
|
+
functionResponse: {
|
|
151
|
+
name: functionName,
|
|
152
|
+
response: responseData,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
// Add images as inline data parts
|
|
157
|
+
if (imageParts.length > 0) {
|
|
158
|
+
parts.push(...imageParts);
|
|
159
|
+
}
|
|
135
160
|
contents.push({
|
|
136
161
|
role: 'user',
|
|
137
|
-
parts
|
|
138
|
-
{
|
|
139
|
-
functionResponse: {
|
|
140
|
-
name: functionName,
|
|
141
|
-
response: responseData,
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
],
|
|
162
|
+
parts,
|
|
145
163
|
});
|
|
146
164
|
continue;
|
|
147
165
|
}
|
package/dist/api/responses.js
CHANGED
|
@@ -163,11 +163,38 @@ function convertToResponseInput(messages, includeBuiltinSystemPrompt = true) {
|
|
|
163
163
|
}
|
|
164
164
|
// Tool 消息:转换为 function_call_output
|
|
165
165
|
if (msg.role === 'tool' && msg.tool_call_id) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
output
|
|
170
|
-
|
|
166
|
+
// Handle multimodal tool results with images
|
|
167
|
+
if (msg.images && msg.images.length > 0) {
|
|
168
|
+
// For Responses API, we need to include images in a structured way
|
|
169
|
+
// The output can be an array of content items
|
|
170
|
+
const outputContent = [];
|
|
171
|
+
// Add text content
|
|
172
|
+
if (msg.content) {
|
|
173
|
+
outputContent.push({
|
|
174
|
+
type: 'input_text',
|
|
175
|
+
text: msg.content,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
// Add images as base64 data URLs (Responses API format)
|
|
179
|
+
for (const image of msg.images) {
|
|
180
|
+
outputContent.push({
|
|
181
|
+
type: 'input_image',
|
|
182
|
+
image_url: `data:${image.mimeType};base64,${image.data}`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
result.push({
|
|
186
|
+
type: 'function_call_output',
|
|
187
|
+
call_id: msg.tool_call_id,
|
|
188
|
+
output: outputContent,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
result.push({
|
|
193
|
+
type: 'function_call_output',
|
|
194
|
+
call_id: msg.tool_call_id,
|
|
195
|
+
output: msg.content,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
171
198
|
continue;
|
|
172
199
|
}
|
|
173
200
|
}
|
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
|
+
- **Location Code**: Must First use a search tool to locate the line number of the code, then use \`filesystem-read\` to read the code content
|
|
83
|
+
- **Boundary verification**: MUST use \`filesystem-read\` to identify complete code boundaries before ANY edit. Never guess line numbers or code structure
|
|
84
|
+
- **Impact analysis**: Consider modification impact and conflicts with existing business logic
|
|
85
|
+
- **Optimal solution**: Avoid hardcoding/shortcuts unless explicitly requested
|
|
86
|
+
- **Avoid duplication**: Search for existing reusable functions before creating new ones
|
|
87
|
+
- **Compilable code**: No syntax errors - always verify complete syntactic units
|
|
86
88
|
|
|
87
89
|
### Smart Action Mode
|
|
88
90
|
**Principle: Understand enough to code correctly, but don't over-investigate**
|
|
89
91
|
|
|
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
|
|
92
|
+
**Examples:** "Fix timeout in parser.ts" → Read file + check imports → Fix → Done
|
|
94
93
|
|
|
95
94
|
PLACEHOLDER_FOR_WORKFLOW_SECTION
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
### TODO Management - USE ACTIVELY
|
|
97
|
+
|
|
98
|
+
**STRONGLY RECOMMENDED: Create TODO for ALL multi-step tasks (3+ steps)** - Prevents missing steps, ensures systematic execution
|
|
99
|
+
|
|
100
|
+
**When to use:** Multi-file changes, features, refactoring, bug fixes touching 2+ files
|
|
101
|
+
**Skip only:** Single-file trivial edits (1-2 lines)
|
|
102
|
+
|
|
103
|
+
**CRITICAL - PARALLEL CALLS ONLY:** ALWAYS call TODO tools WITH action tools in same function call block
|
|
104
|
+
- CORRECT: todo-create + filesystem-read | todo-update + filesystem-edit
|
|
105
|
+
- FORBIDDEN: NEVER call TODO tools alone then wait for result
|
|
106
|
+
|
|
107
|
+
**Lifecycle:** New task → todo-create + initial action | Major change → delete + recreate | Minor → todo-add/update
|
|
98
108
|
|
|
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!**
|
|
109
|
+
**Best practice:** Start every non-trivial task with todo-create in parallel with first action
|
|
143
110
|
|
|
144
111
|
## Available Tools
|
|
145
112
|
|
|
146
113
|
**Filesystem (SUPPORTS BATCH OPERATIONS):**
|
|
147
|
-
- Read first and then modify to avoid grammatical errors caused by boundary judgment errors**
|
|
148
114
|
|
|
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
|
|
115
|
+
**CRITICAL: BOUNDARY-FIRST EDITING**
|
|
154
116
|
|
|
155
|
-
**
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
- Per-file line ranges: \`filesystem-edit(filePath=[{path:"a.ts", startLine:10, endLine:20, newContent:"..."}, {path:"b.ts", startLine:50, endLine:60, newContent:"..."}])\`
|
|
117
|
+
**MANDATORY WORKFLOW:**
|
|
118
|
+
1. **READ & VERIFY** - Use \`filesystem-read\` to identify COMPLETE units (functions: opening to closing brace, markup: full tags, check indentation)
|
|
119
|
+
2. **COPY COMPLETE CODE** - Remove line numbers, preserve all content
|
|
120
|
+
3. **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
|
}
|
|
@@ -10,7 +10,7 @@ export declare function useFilePicker(buffer: TextBuffer, triggerUpdate: () => v
|
|
|
10
10
|
atSymbolPosition: number;
|
|
11
11
|
setAtSymbolPosition: (_pos: number) => void;
|
|
12
12
|
filteredFileCount: number;
|
|
13
|
-
searchMode: "
|
|
13
|
+
searchMode: "file" | "content";
|
|
14
14
|
updateFilePickerState: (_text: string, cursorPos: number) => void;
|
|
15
15
|
handleFileSelect: (filePath: string) => Promise<void>;
|
|
16
16
|
handleFilteredCountChange: (count: number) => void;
|
|
@@ -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
|