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.
@@ -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: msg.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,
@@ -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
  }
@@ -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
- result.push({
167
- type: 'function_call_output',
168
- call_id: msg.tool_call_id,
169
- output: msg.content,
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
  }
@@ -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
+ - **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
- **Golden Rule: Read what you need to write correct code, nothing more.**
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
- ### 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!**
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
- **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
115
+ **CRITICAL: BOUNDARY-FIRST EDITING**
154
116
 
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:"..."}])\`
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
- **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
  }
@@ -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: "content" | "file";
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
- 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