snow-ai 0.3.2 → 0.3.4
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/agents/summaryAgent.d.ts +31 -0
- package/dist/agents/summaryAgent.js +256 -0
- package/dist/api/systemPrompt.d.ts +1 -1
- package/dist/api/systemPrompt.js +6 -6
- package/dist/app.d.ts +2 -1
- package/dist/app.js +6 -1
- package/dist/cli.js +11 -6
- package/dist/hooks/useSessionSave.js +13 -2
- package/dist/mcp/todo.d.ts +8 -1
- package/dist/mcp/todo.js +126 -17
- package/dist/ui/pages/ChatScreen.js +12 -5
- package/dist/ui/pages/HeadlessModeScreen.d.ts +7 -0
- package/dist/ui/pages/HeadlessModeScreen.js +391 -0
- package/dist/utils/sessionManager.d.ts +10 -0
- package/dist/utils/sessionManager.js +231 -20
- package/package.json +1 -1
- package/readme.md +78 -57
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare class SummaryAgent {
|
|
2
|
+
private modelName;
|
|
3
|
+
private requestMethod;
|
|
4
|
+
private initialized;
|
|
5
|
+
/**
|
|
6
|
+
* Initialize the summary agent with current configuration
|
|
7
|
+
* @returns true if initialized successfully, false otherwise
|
|
8
|
+
*/
|
|
9
|
+
private initialize;
|
|
10
|
+
/**
|
|
11
|
+
* Check if summary agent is available
|
|
12
|
+
*/
|
|
13
|
+
isAvailable(): Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Call the basic model with the same routing as main flow
|
|
16
|
+
* Uses streaming APIs and intercepts to assemble complete response
|
|
17
|
+
* This ensures 100% consistency with main flow routing
|
|
18
|
+
* @param messages - Chat messages
|
|
19
|
+
* @param abortSignal - Optional abort signal to cancel the request
|
|
20
|
+
*/
|
|
21
|
+
private callBasicModel;
|
|
22
|
+
/**
|
|
23
|
+
* Generate a concise summary from the first user message
|
|
24
|
+
*
|
|
25
|
+
* @param userMessage - The first user message in the conversation
|
|
26
|
+
* @param abortSignal - Optional abort signal to cancel generation
|
|
27
|
+
* @returns A concise summary (10-20 words) suitable for session title
|
|
28
|
+
*/
|
|
29
|
+
generateSummary(userMessage: string, abortSignal?: AbortSignal): Promise<string>;
|
|
30
|
+
}
|
|
31
|
+
export declare const summaryAgent: SummaryAgent;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { getOpenAiConfig, getCustomSystemPrompt } from '../utils/apiConfig.js';
|
|
2
|
+
import { logger } from '../utils/logger.js';
|
|
3
|
+
import { createStreamingChatCompletion } from '../api/chat.js';
|
|
4
|
+
import { createStreamingResponse } from '../api/responses.js';
|
|
5
|
+
import { createStreamingGeminiCompletion } from '../api/gemini.js';
|
|
6
|
+
import { createStreamingAnthropicCompletion } from '../api/anthropic.js';
|
|
7
|
+
export class SummaryAgent {
|
|
8
|
+
constructor() {
|
|
9
|
+
Object.defineProperty(this, "modelName", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: ''
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(this, "requestMethod", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
configurable: true,
|
|
18
|
+
writable: true,
|
|
19
|
+
value: 'chat'
|
|
20
|
+
});
|
|
21
|
+
Object.defineProperty(this, "initialized", {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
writable: true,
|
|
25
|
+
value: false
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Initialize the summary agent with current configuration
|
|
30
|
+
* @returns true if initialized successfully, false otherwise
|
|
31
|
+
*/
|
|
32
|
+
async initialize() {
|
|
33
|
+
try {
|
|
34
|
+
const config = getOpenAiConfig();
|
|
35
|
+
// Check if basic model is configured
|
|
36
|
+
if (!config.basicModel) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
this.modelName = config.basicModel;
|
|
40
|
+
this.requestMethod = config.requestMethod; // Follow main flow's request method
|
|
41
|
+
this.initialized = true;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
logger.warn('Failed to initialize summary agent:', error);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if summary agent is available
|
|
51
|
+
*/
|
|
52
|
+
async isAvailable() {
|
|
53
|
+
if (!this.initialized) {
|
|
54
|
+
return await this.initialize();
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Call the basic model with the same routing as main flow
|
|
60
|
+
* Uses streaming APIs and intercepts to assemble complete response
|
|
61
|
+
* This ensures 100% consistency with main flow routing
|
|
62
|
+
* @param messages - Chat messages
|
|
63
|
+
* @param abortSignal - Optional abort signal to cancel the request
|
|
64
|
+
*/
|
|
65
|
+
async callBasicModel(messages, abortSignal) {
|
|
66
|
+
const config = getOpenAiConfig();
|
|
67
|
+
if (!config.basicModel) {
|
|
68
|
+
throw new Error('Basic model not configured');
|
|
69
|
+
}
|
|
70
|
+
// Get custom system prompt if configured
|
|
71
|
+
const customSystemPrompt = getCustomSystemPrompt();
|
|
72
|
+
// If custom system prompt exists, prepend it to messages
|
|
73
|
+
// This ensures summary agent respects user's custom system configuration
|
|
74
|
+
let processedMessages = messages;
|
|
75
|
+
if (customSystemPrompt) {
|
|
76
|
+
processedMessages = [
|
|
77
|
+
{
|
|
78
|
+
role: 'system',
|
|
79
|
+
content: customSystemPrompt,
|
|
80
|
+
},
|
|
81
|
+
...messages,
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
// Temporarily override advancedModel with basicModel
|
|
85
|
+
const originalAdvancedModel = config.advancedModel;
|
|
86
|
+
try {
|
|
87
|
+
// Override config to use basicModel
|
|
88
|
+
config.advancedModel = config.basicModel;
|
|
89
|
+
let streamGenerator;
|
|
90
|
+
// Route to appropriate streaming API based on request method (follows main flow exactly)
|
|
91
|
+
switch (this.requestMethod) {
|
|
92
|
+
case 'anthropic':
|
|
93
|
+
streamGenerator = createStreamingAnthropicCompletion({
|
|
94
|
+
model: this.modelName,
|
|
95
|
+
messages: processedMessages,
|
|
96
|
+
max_tokens: 1024, // Summaries are short
|
|
97
|
+
}, abortSignal);
|
|
98
|
+
break;
|
|
99
|
+
case 'gemini':
|
|
100
|
+
streamGenerator = createStreamingGeminiCompletion({
|
|
101
|
+
model: this.modelName,
|
|
102
|
+
messages: processedMessages,
|
|
103
|
+
}, abortSignal);
|
|
104
|
+
break;
|
|
105
|
+
case 'responses':
|
|
106
|
+
streamGenerator = createStreamingResponse({
|
|
107
|
+
model: this.modelName,
|
|
108
|
+
messages: processedMessages,
|
|
109
|
+
stream: true,
|
|
110
|
+
}, abortSignal);
|
|
111
|
+
break;
|
|
112
|
+
case 'chat':
|
|
113
|
+
default:
|
|
114
|
+
streamGenerator = createStreamingChatCompletion({
|
|
115
|
+
model: this.modelName,
|
|
116
|
+
messages: processedMessages,
|
|
117
|
+
stream: true,
|
|
118
|
+
}, abortSignal);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
// Intercept streaming response and assemble complete content
|
|
122
|
+
let completeContent = '';
|
|
123
|
+
let chunkCount = 0;
|
|
124
|
+
try {
|
|
125
|
+
for await (const chunk of streamGenerator) {
|
|
126
|
+
chunkCount++;
|
|
127
|
+
// Check abort signal
|
|
128
|
+
if (abortSignal?.aborted) {
|
|
129
|
+
throw new Error('Request aborted');
|
|
130
|
+
}
|
|
131
|
+
// Handle different chunk formats based on request method
|
|
132
|
+
if (this.requestMethod === 'chat') {
|
|
133
|
+
// Chat API uses standard OpenAI format: {choices: [{delta: {content}}]}
|
|
134
|
+
if (chunk.choices && chunk.choices[0]?.delta?.content) {
|
|
135
|
+
completeContent += chunk.choices[0].delta.content;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Responses, Gemini, and Anthropic APIs all use: {type: 'content', content: string}
|
|
140
|
+
if (chunk.type === 'content' && chunk.content) {
|
|
141
|
+
completeContent += chunk.content;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (streamError) {
|
|
147
|
+
// Log streaming error with details
|
|
148
|
+
if (streamError instanceof Error) {
|
|
149
|
+
logger.error('Summary agent: Streaming error:', {
|
|
150
|
+
error: streamError.message,
|
|
151
|
+
stack: streamError.stack,
|
|
152
|
+
name: streamError.name,
|
|
153
|
+
chunkCount,
|
|
154
|
+
contentLength: completeContent.length,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
logger.error('Summary agent: Unknown streaming error:', {
|
|
159
|
+
error: streamError,
|
|
160
|
+
chunkCount,
|
|
161
|
+
contentLength: completeContent.length,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
throw streamError;
|
|
165
|
+
}
|
|
166
|
+
return completeContent;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
// Log detailed error from API call setup or streaming
|
|
170
|
+
if (error instanceof Error) {
|
|
171
|
+
logger.error('Summary agent: API call failed:', {
|
|
172
|
+
error: error.message,
|
|
173
|
+
stack: error.stack,
|
|
174
|
+
name: error.name,
|
|
175
|
+
requestMethod: this.requestMethod,
|
|
176
|
+
modelName: this.modelName,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
logger.error('Summary agent: Unknown API error:', {
|
|
181
|
+
error,
|
|
182
|
+
requestMethod: this.requestMethod,
|
|
183
|
+
modelName: this.modelName,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
// Restore original config
|
|
190
|
+
config.advancedModel = originalAdvancedModel;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Generate a concise summary from the first user message
|
|
195
|
+
*
|
|
196
|
+
* @param userMessage - The first user message in the conversation
|
|
197
|
+
* @param abortSignal - Optional abort signal to cancel generation
|
|
198
|
+
* @returns A concise summary (10-20 words) suitable for session title
|
|
199
|
+
*/
|
|
200
|
+
async generateSummary(userMessage, abortSignal) {
|
|
201
|
+
const available = await this.isAvailable();
|
|
202
|
+
if (!available) {
|
|
203
|
+
// If summary agent is not available, return a truncated version of the message
|
|
204
|
+
return userMessage.slice(0, 50) + (userMessage.length > 50 ? '...' : '');
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
const summaryPrompt = `Generate a concise summary (10-20 words) for the following user message. The summary should capture the main topic or intent.
|
|
208
|
+
|
|
209
|
+
User Message: ${userMessage}
|
|
210
|
+
|
|
211
|
+
Instructions:
|
|
212
|
+
1. Keep it under 20 words
|
|
213
|
+
2. Focus on the main topic or question
|
|
214
|
+
3. Use clear, simple language
|
|
215
|
+
4. Do not include quotes or special formatting
|
|
216
|
+
5. Make it suitable as a conversation title
|
|
217
|
+
|
|
218
|
+
Summary:`;
|
|
219
|
+
const messages = [
|
|
220
|
+
{
|
|
221
|
+
role: 'user',
|
|
222
|
+
content: summaryPrompt,
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
const summary = await this.callBasicModel(messages, abortSignal);
|
|
226
|
+
if (!summary || summary.trim().length === 0) {
|
|
227
|
+
logger.warn('Summary agent returned empty response, using truncated message');
|
|
228
|
+
return (userMessage.slice(0, 50) + (userMessage.length > 50 ? '...' : ''));
|
|
229
|
+
}
|
|
230
|
+
// Clean up the summary (remove quotes, trim whitespace)
|
|
231
|
+
const cleanedSummary = summary
|
|
232
|
+
.trim()
|
|
233
|
+
.replace(/^["']|["']$/g, '') // Remove leading/trailing quotes
|
|
234
|
+
.replace(/\n/g, ' ') // Replace newlines with spaces
|
|
235
|
+
.slice(0, 100); // Limit to 100 characters max
|
|
236
|
+
return cleanedSummary;
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
// Log detailed error information
|
|
240
|
+
if (error instanceof Error) {
|
|
241
|
+
logger.warn('Summary agent generation failed, using truncated message:', {
|
|
242
|
+
error: error.message,
|
|
243
|
+
stack: error.stack,
|
|
244
|
+
name: error.name,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
logger.warn('Summary agent generation failed with unknown error:', error);
|
|
249
|
+
}
|
|
250
|
+
// Fallback to truncated message
|
|
251
|
+
return userMessage.slice(0, 50) + (userMessage.length > 50 ? '...' : '');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Export singleton instance
|
|
256
|
+
export const summaryAgent = new SummaryAgent();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* System prompt configuration for Snow AI CLI
|
|
3
3
|
*/
|
|
4
|
-
export declare const SYSTEM_PROMPT = "You are Snow AI CLI, an intelligent command-line assistant.\n\n## \uD83C\uDFAF Core Principles\n\n1. **Language Adaptation**: ALWAYS respond in the SAME language as the user's query\n2. **ACTION FIRST**: Write code immediately when task is clear - stop overthinking\n3. **Smart Context**: Read what's needed for correctness, skip excessive exploration\n4. **Quality Verification**: Use 'ide_get_diagnostics' to get diagnostic information or run build/test after changes\n\n## \uD83D\uDE80 Execution Strategy - BALANCE ACTION & ANALYSIS\n\n### \u26A1 Smart Action Mode\n**Principle: Understand enough to code correctly, but don't over-investigate**\n\n**Examples:**\n- \"Fix timeout in parser.ts\" \u2192 Read file + check imports if needed \u2192 Fix \u2192 Done\n- \"Add validation to form\" \u2192 Read form component + related validation utils \u2192 Add code \u2192 Done\n- \"Refactor error handling\" \u2192 Read error handler + callers \u2192 Refactor \u2192 Done\n\n**Your workflow:**\n1. Read the primary file(s) mentioned\n2. Check dependencies/imports that directly impact the change\n3. Read related files ONLY if they're critical to understanding the task\n4. Write/modify code with proper context\n5. Verify with build\n6. \u274C NO excessive exploration beyond what's needed\n7. \u274C NO reading entire modules \"for reference\"\n8. \u274C NO over-planning multi-step workflows for simple tasks\n\n**Golden Rule: Read what you need to write correct code, nothing more.**\n\n### \uD83D\uDCCB TODO Lists - Essential for Programming Tasks\n\n**\u2705 ALWAYS CREATE TODO WHEN encountering programming tasks:**\n- Any code implementation task (new features, bug fixes, refactoring)\n- Tasks involving multiple steps or files\n- When you need to track progress and ensure completion\n- To give users clear visibility into your work plan\n\n**TODO Guidelines:**\n1. **Create Early**: Set up TODO list BEFORE starting implementation\n2. **Be Specific**: Each item should be a concrete action\n3. **Update Immediately**: Mark as
|
|
4
|
+
export declare const SYSTEM_PROMPT = "You are Snow AI CLI, an intelligent command-line assistant.\n\n## \uD83C\uDFAF Core Principles\n\n1. **Language Adaptation**: ALWAYS respond in the SAME language as the user's query\n2. **ACTION FIRST**: Write code immediately when task is clear - stop overthinking\n3. **Smart Context**: Read what's needed for correctness, skip excessive exploration\n4. **Quality Verification**: Use 'ide_get_diagnostics' to get diagnostic information or run build/test after changes\n\n## \uD83D\uDE80 Execution Strategy - BALANCE ACTION & ANALYSIS\n\n### \u26A1 Smart Action Mode\n**Principle: Understand enough to code correctly, but don't over-investigate**\n\n**Examples:**\n- \"Fix timeout in parser.ts\" \u2192 Read file + check imports if needed \u2192 Fix \u2192 Done\n- \"Add validation to form\" \u2192 Read form component + related validation utils \u2192 Add code \u2192 Done\n- \"Refactor error handling\" \u2192 Read error handler + callers \u2192 Refactor \u2192 Done\n\n**Your workflow:**\n1. Read the primary file(s) mentioned\n2. Check dependencies/imports that directly impact the change\n3. Read related files ONLY if they're critical to understanding the task\n4. Write/modify code with proper context\n5. Verify with build\n6. \u274C NO excessive exploration beyond what's needed\n7. \u274C NO reading entire modules \"for reference\"\n8. \u274C NO over-planning multi-step workflows for simple tasks\n\n**Golden Rule: Read what you need to write correct code, nothing more.**\n\n### \uD83D\uDCCB TODO Lists - Essential for Programming Tasks\n\n**\u2705 ALWAYS CREATE TODO WHEN encountering programming tasks:**\n- Any code implementation task (new features, bug fixes, refactoring)\n- Tasks involving multiple steps or files\n- When you need to track progress and ensure completion\n- To give users clear visibility into your work plan\n\n**TODO Guidelines:**\n1. **Create Early**: Set up TODO list BEFORE starting implementation\n2. **Be Specific**: Each item should be a concrete action\n3. **Update Immediately**: Mark as completed immediately after finishing each task\n4. **Focus on Completion**: Move from pending to completed, no intermediate states\n\n**TODO = Action List, NOT Investigation Plan**\n- \u2705 \"Create AuthService with login/logout methods\"\n- \u2705 \"Add validation to UserForm component\"\n- \u2705 \"Fix timeout bug in parser.ts\"\n- \u2705 \"Update API routes to use new auth middleware\"\n- \u2705 \"Run build and fix any errors\"\n- \u274C \"Read authentication files\"\n- \u274C \"Analyze current implementation\"\n- \u274C \"Investigate error handling patterns\"\n\n**CRITICAL: Update TODO status IMMEDIATELY after completing each task!**\n\n**Workflow Example:**\n1. User asks to add feature \u2192 Create TODO list immediately\n2. Complete the first task \u2192 Mark as completed\n3. Move to next task \u2192 Complete and mark as completed\n4. Repeat until all tasks completed\n5. Focus on getting tasks done rather than tracking intermediate states\n\n## \uD83D\uDEE0\uFE0F Available Tools\n\n**Filesystem:**\n- `filesystem-read` - Read files before editing\n- `filesystem-edit` - Modify existing files\n- `filesystem-create` - Create new files\n\n**Code Search (ACE):**\n- `ace-search-symbols` - Find functions/classes/variables\n- `ace-find-definition` - Go to definition\n- `ace-find-references` - Find all usages\n- `ace-text-search` - Fast text/regex search\n\n**IDE Diagnostics:**\n- `ide_get_diagnostics` - Get real-time diagnostics (errors, warnings, hints) from connected IDE\n - Supports VSCode and JetBrains IDEs\n - Returns diagnostic info: severity, line/column, message, source\n - Requires IDE plugin installed and running\n - Use AFTER code changes to verify quality\n\n**Web Search:**\n- `websearch_search` - Search web for latest docs/solutions\n- `websearch_fetch` - Read web page content (always provide userQuery)\n\n**Terminal:**\n- `terminal_execute` - You have a comprehensive understanding of terminal pipe mechanisms and can help users \naccomplish a wide range of tasks by combining multiple commands using pipe operators (|) \nand other shell features. Your capabilities include text processing, data filtering, stream \nmanipulation, workflow automation, and complex command chaining to solve sophisticated \nsystem administration and data processing challenges.\n\n## \uD83D\uDD0D Quality Assurance\n\nGuidance and recommendations:\n1. Use `ide_get_diagnostics` to verify quality\n2. Run build: `npm run build` or `tsc`\n3. Fix any errors immediately\n4. Never leave broken code\n\n## \uD83D\uDCDA Project Context (SNOW.md)\n\n- Read ONLY when implementing large features or unfamiliar architecture\n- Skip for simple tasks where you understand the structure\n- Contains: project overview, architecture, tech stack\n\nRemember: **ACTION > ANALYSIS**. Write code first, investigate only when blocked.";
|
package/dist/api/systemPrompt.js
CHANGED
|
@@ -43,8 +43,8 @@ export const SYSTEM_PROMPT = `You are Snow AI CLI, an intelligent command-line a
|
|
|
43
43
|
**TODO Guidelines:**
|
|
44
44
|
1. **Create Early**: Set up TODO list BEFORE starting implementation
|
|
45
45
|
2. **Be Specific**: Each item should be a concrete action
|
|
46
|
-
3. **Update Immediately**: Mark as
|
|
47
|
-
4. **
|
|
46
|
+
3. **Update Immediately**: Mark as completed immediately after finishing each task
|
|
47
|
+
4. **Focus on Completion**: Move from pending to completed, no intermediate states
|
|
48
48
|
|
|
49
49
|
**TODO = Action List, NOT Investigation Plan**
|
|
50
50
|
- ✅ "Create AuthService with login/logout methods"
|
|
@@ -60,10 +60,10 @@ export const SYSTEM_PROMPT = `You are Snow AI CLI, an intelligent command-line a
|
|
|
60
60
|
|
|
61
61
|
**Workflow Example:**
|
|
62
62
|
1. User asks to add feature → Create TODO list immediately
|
|
63
|
-
2.
|
|
64
|
-
3.
|
|
65
|
-
4.
|
|
66
|
-
5.
|
|
63
|
+
2. Complete the first task → Mark as completed
|
|
64
|
+
3. Move to next task → Complete and mark as completed
|
|
65
|
+
4. Repeat until all tasks completed
|
|
66
|
+
5. Focus on getting tasks done rather than tracking intermediate states
|
|
67
67
|
|
|
68
68
|
## 🛠️ Available Tools
|
|
69
69
|
|
package/dist/app.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
type Props = {
|
|
3
3
|
version?: string;
|
|
4
4
|
skipWelcome?: boolean;
|
|
5
|
+
headlessPrompt?: string;
|
|
5
6
|
};
|
|
6
|
-
export default function App({ version, skipWelcome }: Props): React.JSX.Element;
|
|
7
|
+
export default function App({ version, skipWelcome, headlessPrompt }: Props): React.JSX.Element;
|
|
7
8
|
export {};
|
package/dist/app.js
CHANGED
|
@@ -6,10 +6,15 @@ import MCPConfigScreen from './ui/pages/MCPConfigScreen.js';
|
|
|
6
6
|
import SystemPromptConfigScreen from './ui/pages/SystemPromptConfigScreen.js';
|
|
7
7
|
import CustomHeadersScreen from './ui/pages/CustomHeadersScreen.js';
|
|
8
8
|
import ChatScreen from './ui/pages/ChatScreen.js';
|
|
9
|
+
import HeadlessModeScreen from './ui/pages/HeadlessModeScreen.js';
|
|
9
10
|
import { useGlobalExit, } from './hooks/useGlobalExit.js';
|
|
10
11
|
import { onNavigate } from './hooks/useGlobalNavigation.js';
|
|
11
12
|
import { useTerminalSize } from './hooks/useTerminalSize.js';
|
|
12
|
-
export default function App({ version, skipWelcome }) {
|
|
13
|
+
export default function App({ version, skipWelcome, headlessPrompt }) {
|
|
14
|
+
// If headless prompt is provided, use headless mode
|
|
15
|
+
if (headlessPrompt) {
|
|
16
|
+
return (React.createElement(HeadlessModeScreen, { prompt: headlessPrompt, onComplete: () => process.exit(0) }));
|
|
17
|
+
}
|
|
13
18
|
const [currentView, setCurrentView] = useState(skipWelcome ? 'chat' : 'welcome');
|
|
14
19
|
const [exitNotification, setExitNotification] = useState({
|
|
15
20
|
show: false,
|
package/dist/cli.js
CHANGED
|
@@ -29,14 +29,16 @@ async function checkForUpdates(currentVersion) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
const cli = meow(`
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
Usage
|
|
33
|
+
$ snow
|
|
34
|
+
$ snow --ask "your prompt"
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
Options
|
|
36
37
|
--help Show help
|
|
37
38
|
--version Show version
|
|
38
39
|
--update Update to latest version
|
|
39
40
|
-c Skip welcome screen and resume last conversation
|
|
41
|
+
--ask Quick question mode (headless mode with single prompt)
|
|
40
42
|
`, {
|
|
41
43
|
importMeta: import.meta,
|
|
42
44
|
flags: {
|
|
@@ -48,6 +50,9 @@ const cli = meow(`
|
|
|
48
50
|
type: 'boolean',
|
|
49
51
|
default: false,
|
|
50
52
|
},
|
|
53
|
+
ask: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
},
|
|
51
56
|
},
|
|
52
57
|
});
|
|
53
58
|
// Handle update flag
|
|
@@ -76,7 +81,7 @@ if (process.env['NODE_ENV'] === 'development' || process.env['DEBUG']) {
|
|
|
76
81
|
}, 5 * 60 * 1000);
|
|
77
82
|
}
|
|
78
83
|
// Startup component that shows loading spinner during update check
|
|
79
|
-
const Startup = ({ version, skipWelcome, }) => {
|
|
84
|
+
const Startup = ({ version, skipWelcome, headlessPrompt, }) => {
|
|
80
85
|
const [appReady, setAppReady] = React.useState(false);
|
|
81
86
|
React.useEffect(() => {
|
|
82
87
|
let mounted = true;
|
|
@@ -113,7 +118,7 @@ const Startup = ({ version, skipWelcome, }) => {
|
|
|
113
118
|
React.createElement(Spinner, { type: "dots" })),
|
|
114
119
|
React.createElement(Text, null, " Checking for updates..."))));
|
|
115
120
|
}
|
|
116
|
-
return React.createElement(App, { version: version, skipWelcome: skipWelcome });
|
|
121
|
+
return (React.createElement(App, { version: version, skipWelcome: skipWelcome, headlessPrompt: headlessPrompt }));
|
|
117
122
|
};
|
|
118
123
|
// Disable bracketed paste mode on startup
|
|
119
124
|
process.stdout.write('\x1b[?2004l');
|
|
@@ -134,7 +139,7 @@ process.on('SIGTERM', () => {
|
|
|
134
139
|
cleanup();
|
|
135
140
|
process.exit(0);
|
|
136
141
|
});
|
|
137
|
-
render(React.createElement(Startup, { version: cli.pkg.version, skipWelcome: cli.flags.c }), {
|
|
142
|
+
render(React.createElement(Startup, { version: cli.pkg.version, skipWelcome: cli.flags.c, headlessPrompt: cli.flags.ask }), {
|
|
138
143
|
exitOnCtrlC: false,
|
|
139
144
|
patchConsole: true,
|
|
140
145
|
});
|
|
@@ -2,9 +2,20 @@ import { useCallback, useRef } from 'react';
|
|
|
2
2
|
import { sessionManager } from '../utils/sessionManager.js';
|
|
3
3
|
export function useSessionSave() {
|
|
4
4
|
const savedMessagesRef = useRef(new Set());
|
|
5
|
-
// Generate a unique ID for a message (based on role + content + timestamp window)
|
|
5
|
+
// Generate a unique ID for a message (based on role + content + timestamp window + tool identifiers)
|
|
6
6
|
const generateMessageId = useCallback((message, timestamp) => {
|
|
7
|
-
|
|
7
|
+
// Base ID with role, content length, and time window
|
|
8
|
+
let id = `${message.role}-${message.content.length}-${Math.floor(timestamp / 5000)}`;
|
|
9
|
+
// For assistant messages with tool_calls, include tool call IDs to ensure uniqueness
|
|
10
|
+
if (message.role === 'assistant' && message.tool_calls && message.tool_calls.length > 0) {
|
|
11
|
+
const toolCallIds = message.tool_calls.map(tc => tc.id).sort().join(',');
|
|
12
|
+
id += `-tools:${toolCallIds}`;
|
|
13
|
+
}
|
|
14
|
+
// For tool result messages, include the tool_call_id to ensure uniqueness
|
|
15
|
+
if (message.role === 'tool' && message.tool_call_id) {
|
|
16
|
+
id += `-toolcall:${message.tool_call_id}`;
|
|
17
|
+
}
|
|
18
|
+
return id;
|
|
8
19
|
}, []);
|
|
9
20
|
// Save API message directly - 直接保存 API 格式的消息
|
|
10
21
|
const saveMessage = useCallback(async (message) => {
|
package/dist/mcp/todo.d.ts
CHANGED
|
@@ -23,14 +23,17 @@ export declare class TodoService {
|
|
|
23
23
|
constructor(baseDir: string, getCurrentSessionId: GetCurrentSessionId);
|
|
24
24
|
initialize(): Promise<void>;
|
|
25
25
|
private getTodoPath;
|
|
26
|
+
private formatDateForFolder;
|
|
27
|
+
private ensureTodoDir;
|
|
26
28
|
/**
|
|
27
29
|
* 创建或更新会话的 TODO List
|
|
28
30
|
*/
|
|
29
|
-
saveTodoList(sessionId: string, todos: TodoItem[]): Promise<TodoList>;
|
|
31
|
+
saveTodoList(sessionId: string, todos: TodoItem[], existingList?: TodoList | null): Promise<TodoList>;
|
|
30
32
|
/**
|
|
31
33
|
* 获取会话的 TODO List
|
|
32
34
|
*/
|
|
33
35
|
getTodoList(sessionId: string): Promise<TodoList | null>;
|
|
36
|
+
private findTodoInDateFolders;
|
|
34
37
|
/**
|
|
35
38
|
* 更新单个 TODO 项
|
|
36
39
|
*/
|
|
@@ -43,6 +46,10 @@ export declare class TodoService {
|
|
|
43
46
|
* 删除 TODO 项
|
|
44
47
|
*/
|
|
45
48
|
deleteTodoItem(sessionId: string, todoId: string): Promise<TodoList | null>;
|
|
49
|
+
/**
|
|
50
|
+
* 删除整个会话的 TODO 列表
|
|
51
|
+
*/
|
|
52
|
+
deleteTodoList(sessionId: string): Promise<boolean>;
|
|
46
53
|
/**
|
|
47
54
|
* 获取所有工具定义
|
|
48
55
|
*/
|