snow-ai 0.3.17 → 0.3.19
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 +5 -3
- package/dist/cli.js +3 -0
- package/dist/hooks/useAgentPicker.d.ts +10 -0
- package/dist/hooks/useAgentPicker.js +32 -0
- package/dist/hooks/useCommandPanel.js +8 -0
- package/dist/hooks/useFilePicker.d.ts +1 -0
- package/dist/hooks/useFilePicker.js +102 -23
- package/dist/hooks/useKeyboardInput.d.ts +22 -0
- package/dist/hooks/useKeyboardInput.js +109 -1
- package/dist/hooks/useStreamingState.js +16 -10
- package/dist/hooks/useTodoPicker.d.ts +16 -0
- package/dist/hooks/useTodoPicker.js +94 -0
- package/dist/mcp/aceCodeSearch.js +9 -1
- package/dist/mcp/bash.js +32 -4
- package/dist/ui/components/AgentPickerPanel.d.ts +8 -0
- package/dist/ui/components/AgentPickerPanel.js +74 -0
- package/dist/ui/components/ChatInput.js +32 -4
- package/dist/ui/components/FileList.d.ts +1 -0
- package/dist/ui/components/FileList.js +181 -32
- package/dist/ui/components/TodoPickerPanel.d.ts +14 -0
- package/dist/ui/components/TodoPickerPanel.js +117 -0
- package/dist/ui/pages/ChatScreen.d.ts +2 -0
- package/dist/ui/pages/ChatScreen.js +2 -0
- package/dist/utils/commandExecutor.d.ts +1 -1
- package/dist/utils/commands/agent.d.ts +2 -0
- package/dist/utils/commands/agent.js +12 -0
- package/dist/utils/commands/todoPicker.d.ts +2 -0
- package/dist/utils/commands/todoPicker.js +12 -0
- package/dist/utils/processManager.d.ts +27 -0
- package/dist/utils/processManager.js +75 -0
- package/dist/utils/subAgentExecutor.js +3 -12
- package/dist/utils/todoScanner.d.ts +8 -0
- package/dist/utils/todoScanner.js +148 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export interface CommandResult {
|
|
2
2
|
success: boolean;
|
|
3
3
|
message?: string;
|
|
4
|
-
action?: 'clear' | 'resume' | 'info' | 'showMcpInfo' | 'toggleYolo' | 'initProject' | 'compact' | 'showSessionPanel' | 'showMcpPanel' | 'showUsagePanel' | 'home' | 'review' | 'exportChat';
|
|
4
|
+
action?: 'clear' | 'resume' | 'info' | 'showMcpInfo' | 'toggleYolo' | 'initProject' | 'compact' | 'showSessionPanel' | 'showMcpPanel' | 'showUsagePanel' | 'home' | 'review' | 'exportChat' | 'showAgentPicker' | 'showTodoPicker';
|
|
5
5
|
prompt?: string;
|
|
6
6
|
alreadyConnected?: boolean;
|
|
7
7
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { registerCommand } from '../commandExecutor.js';
|
|
2
|
+
// Agent picker command handler - shows agent selection panel
|
|
3
|
+
registerCommand('agent-', {
|
|
4
|
+
execute: () => {
|
|
5
|
+
return {
|
|
6
|
+
success: true,
|
|
7
|
+
action: 'showAgentPicker',
|
|
8
|
+
message: 'Showing sub-agent selection panel'
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
export default {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { registerCommand } from '../commandExecutor.js';
|
|
2
|
+
// Todo picker command handler - shows todo selection panel
|
|
3
|
+
registerCommand('todo-', {
|
|
4
|
+
execute: () => {
|
|
5
|
+
return {
|
|
6
|
+
success: true,
|
|
7
|
+
action: 'showTodoPicker',
|
|
8
|
+
message: 'Showing TODO comment selection panel',
|
|
9
|
+
};
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
export default {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ChildProcess } from 'child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Process Manager
|
|
4
|
+
* Tracks and manages all child processes to ensure proper cleanup
|
|
5
|
+
*/
|
|
6
|
+
declare class ProcessManager {
|
|
7
|
+
private processes;
|
|
8
|
+
private isShuttingDown;
|
|
9
|
+
/**
|
|
10
|
+
* Register a child process for tracking
|
|
11
|
+
*/
|
|
12
|
+
register(process: ChildProcess): void;
|
|
13
|
+
/**
|
|
14
|
+
* Kill a specific process gracefully
|
|
15
|
+
*/
|
|
16
|
+
private killProcess;
|
|
17
|
+
/**
|
|
18
|
+
* Kill all tracked processes
|
|
19
|
+
*/
|
|
20
|
+
killAll(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Get count of active processes
|
|
23
|
+
*/
|
|
24
|
+
getActiveCount(): number;
|
|
25
|
+
}
|
|
26
|
+
export declare const processManager: ProcessManager;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process Manager
|
|
3
|
+
* Tracks and manages all child processes to ensure proper cleanup
|
|
4
|
+
*/
|
|
5
|
+
class ProcessManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
Object.defineProperty(this, "processes", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
configurable: true,
|
|
10
|
+
writable: true,
|
|
11
|
+
value: new Set()
|
|
12
|
+
});
|
|
13
|
+
Object.defineProperty(this, "isShuttingDown", {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
configurable: true,
|
|
16
|
+
writable: true,
|
|
17
|
+
value: false
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Register a child process for tracking
|
|
22
|
+
*/
|
|
23
|
+
register(process) {
|
|
24
|
+
if (this.isShuttingDown) {
|
|
25
|
+
// If we're already shutting down, kill immediately
|
|
26
|
+
this.killProcess(process);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.processes.add(process);
|
|
30
|
+
// Auto-remove when process exits
|
|
31
|
+
const cleanup = () => {
|
|
32
|
+
this.processes.delete(process);
|
|
33
|
+
};
|
|
34
|
+
process.once('exit', cleanup);
|
|
35
|
+
process.once('error', cleanup);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Kill a specific process gracefully
|
|
39
|
+
*/
|
|
40
|
+
killProcess(process) {
|
|
41
|
+
try {
|
|
42
|
+
if (process.pid && !process.killed) {
|
|
43
|
+
// Try graceful termination first
|
|
44
|
+
process.kill('SIGTERM');
|
|
45
|
+
// Force kill after 1 second if still alive
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
if (process.pid && !process.killed) {
|
|
48
|
+
process.kill('SIGKILL');
|
|
49
|
+
}
|
|
50
|
+
}, 1000);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// Process might already be dead, ignore errors
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Kill all tracked processes
|
|
59
|
+
*/
|
|
60
|
+
killAll() {
|
|
61
|
+
this.isShuttingDown = true;
|
|
62
|
+
for (const process of this.processes) {
|
|
63
|
+
this.killProcess(process);
|
|
64
|
+
}
|
|
65
|
+
this.processes.clear();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get count of active processes
|
|
69
|
+
*/
|
|
70
|
+
getActiveCount() {
|
|
71
|
+
return this.processes.size;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Export singleton instance
|
|
75
|
+
export const processManager = new ProcessManager();
|
|
@@ -63,10 +63,8 @@ export async function executeSubAgent(agentId, prompt, onMessage, abortSignal, r
|
|
|
63
63
|
// Local session-approved tools for this sub-agent execution
|
|
64
64
|
// This ensures tools approved during execution are immediately recognized
|
|
65
65
|
const sessionApprovedTools = new Set();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
while (iteration < maxIterations) {
|
|
69
|
-
iteration++;
|
|
66
|
+
// eslint-disable-next-line no-constant-condition
|
|
67
|
+
while (true) {
|
|
70
68
|
// Check abort signal
|
|
71
69
|
if (abortSignal?.aborted) {
|
|
72
70
|
return {
|
|
@@ -260,14 +258,7 @@ export async function executeSubAgent(agentId, prompt, onMessage, abortSignal, r
|
|
|
260
258
|
// Add tool results to conversation
|
|
261
259
|
messages.push(...toolResults);
|
|
262
260
|
// Continue to next iteration if there were tool calls
|
|
263
|
-
// The loop will continue until no more tool calls
|
|
264
|
-
}
|
|
265
|
-
if (iteration >= maxIterations) {
|
|
266
|
-
return {
|
|
267
|
-
success: false,
|
|
268
|
-
result: finalResponse,
|
|
269
|
-
error: 'Sub-agent exceeded maximum iterations',
|
|
270
|
-
};
|
|
261
|
+
// The loop will continue until no more tool calls
|
|
271
262
|
}
|
|
272
263
|
return {
|
|
273
264
|
success: true,
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
const IGNORE_PATTERNS = [
|
|
4
|
+
'node_modules',
|
|
5
|
+
'.git',
|
|
6
|
+
'dist',
|
|
7
|
+
'build',
|
|
8
|
+
'coverage',
|
|
9
|
+
'.next',
|
|
10
|
+
'.nuxt',
|
|
11
|
+
'.output',
|
|
12
|
+
'out',
|
|
13
|
+
'.DS_Store',
|
|
14
|
+
'*.log',
|
|
15
|
+
'*.lock',
|
|
16
|
+
'yarn.lock',
|
|
17
|
+
'package-lock.json',
|
|
18
|
+
'pnpm-lock.yaml',
|
|
19
|
+
];
|
|
20
|
+
// Common task markers - support various formats
|
|
21
|
+
// Only include markers that clearly indicate actionable tasks
|
|
22
|
+
const TODO_PATTERNS = [
|
|
23
|
+
// Single-line comments with markers (// TODO, // FIXME, etc.)
|
|
24
|
+
/\/\/\s*(?:TODO|FIXME|HACK|XXX|BUG):?\s*(.+)/i,
|
|
25
|
+
// Block comments (/* TODO */)
|
|
26
|
+
/\/\*\s*(?:TODO|FIXME|HACK|XXX|BUG):?\s*(.+?)\s*\*\//i,
|
|
27
|
+
// Hash comments (# TODO) for Python, Ruby, Shell, etc.
|
|
28
|
+
/#\s*(?:TODO|FIXME|HACK|XXX|BUG):?\s*(.+)/i,
|
|
29
|
+
// HTML/XML comments (<!-- TODO -->)
|
|
30
|
+
/<!--\s*(?:TODO|FIXME|HACK|XXX|BUG):?\s*(.+?)\s*-->/i,
|
|
31
|
+
// JSDoc/PHPDoc style (@todo)
|
|
32
|
+
/\/\*\*?\s*@(?:todo|fixme):?\s*(.+?)(?:\s*\*\/|\n)/i,
|
|
33
|
+
// TODO with brackets/parentheses (common format for task assignment)
|
|
34
|
+
/\/\/\s*TODO\s*[\(\[\{]\s*(.+?)\s*[\)\]\}]/i,
|
|
35
|
+
/#\s*TODO\s*[\(\[\{]\s*(.+?)\s*[\)\]\}]/i,
|
|
36
|
+
// Multi-line block comment TODO (catches TODO on its own line)
|
|
37
|
+
/\/\*[\s\S]*?\bTODO:?\s*(.+?)[\s\S]*?\*\//i,
|
|
38
|
+
];
|
|
39
|
+
function shouldIgnore(filePath) {
|
|
40
|
+
const relativePath = filePath;
|
|
41
|
+
return IGNORE_PATTERNS.some(pattern => {
|
|
42
|
+
if (pattern.includes('*')) {
|
|
43
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
44
|
+
return regex.test(relativePath);
|
|
45
|
+
}
|
|
46
|
+
return relativePath.includes(pattern);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function scanFileForTodos(filePath, rootDir) {
|
|
50
|
+
try {
|
|
51
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
52
|
+
const lines = content.split('\n');
|
|
53
|
+
const todos = [];
|
|
54
|
+
lines.forEach((line, index) => {
|
|
55
|
+
for (const pattern of TODO_PATTERNS) {
|
|
56
|
+
const match = line.match(pattern);
|
|
57
|
+
if (match) {
|
|
58
|
+
const todoContent = match[1]?.trim() || '';
|
|
59
|
+
const relativePath = path.relative(rootDir, filePath);
|
|
60
|
+
todos.push({
|
|
61
|
+
id: `${relativePath}:${index + 1}`,
|
|
62
|
+
file: relativePath,
|
|
63
|
+
line: index + 1,
|
|
64
|
+
content: todoContent,
|
|
65
|
+
fullLine: line.trim(),
|
|
66
|
+
});
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return todos;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
// Ignore files that can't be read
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function scanDirectory(dir, rootDir) {
|
|
79
|
+
let todos = [];
|
|
80
|
+
try {
|
|
81
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const fullPath = path.join(dir, entry.name);
|
|
84
|
+
const relativePath = path.relative(rootDir, fullPath);
|
|
85
|
+
if (shouldIgnore(relativePath)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (entry.isDirectory()) {
|
|
89
|
+
todos = todos.concat(scanDirectory(fullPath, rootDir));
|
|
90
|
+
}
|
|
91
|
+
else if (entry.isFile()) {
|
|
92
|
+
// Only scan text files
|
|
93
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
94
|
+
const textExtensions = [
|
|
95
|
+
'.ts',
|
|
96
|
+
'.tsx',
|
|
97
|
+
'.js',
|
|
98
|
+
'.jsx',
|
|
99
|
+
'.py',
|
|
100
|
+
'.go',
|
|
101
|
+
'.rs',
|
|
102
|
+
'.java',
|
|
103
|
+
'.c',
|
|
104
|
+
'.cpp',
|
|
105
|
+
'.h',
|
|
106
|
+
'.hpp',
|
|
107
|
+
'.cs',
|
|
108
|
+
'.php',
|
|
109
|
+
'.rb',
|
|
110
|
+
'.swift',
|
|
111
|
+
'.kt',
|
|
112
|
+
'.scala',
|
|
113
|
+
'.sh',
|
|
114
|
+
'.bash',
|
|
115
|
+
'.zsh',
|
|
116
|
+
'.fish',
|
|
117
|
+
'.vim',
|
|
118
|
+
'.lua',
|
|
119
|
+
'.sql',
|
|
120
|
+
'.html',
|
|
121
|
+
'.css',
|
|
122
|
+
'.scss',
|
|
123
|
+
'.sass',
|
|
124
|
+
'.less',
|
|
125
|
+
'.vue',
|
|
126
|
+
'.svelte',
|
|
127
|
+
'.md',
|
|
128
|
+
'.txt',
|
|
129
|
+
'.json',
|
|
130
|
+
'.yaml',
|
|
131
|
+
'.yml',
|
|
132
|
+
'.toml',
|
|
133
|
+
'.xml',
|
|
134
|
+
];
|
|
135
|
+
if (textExtensions.includes(ext) || ext === '') {
|
|
136
|
+
todos = todos.concat(scanFileForTodos(fullPath, rootDir));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
// Ignore directories that can't be read
|
|
143
|
+
}
|
|
144
|
+
return todos;
|
|
145
|
+
}
|
|
146
|
+
export function scanProjectTodos(projectRoot) {
|
|
147
|
+
return scanDirectory(projectRoot, projectRoot);
|
|
148
|
+
}
|