snow-ai 0.3.30 โ†’ 0.3.31

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.
@@ -176,6 +176,18 @@ const SYSTEM_PROMPT_TEMPLATE = `You are Snow AI CLI, an intelligent command-line
176
176
  - \`notebook-query\` - Manual search (rarely needed, auto-shown when reading files)
177
177
  - ๐Ÿ” Auto-attached: Last 10 notebooks appear when reading ANY file
178
178
  - ๐Ÿ’ก Use before: Adding features that might affect existing behavior
179
+ - \`notebook-update\` - Update existing note to fix mistakes or refine information
180
+ - โœ๏ธ Fix errors in previously recorded notes
181
+ - ๐Ÿ“ Clarify or improve wording after better understanding
182
+ - ๐Ÿ”„ Update note when code changes but constraint still applies
183
+ - \`notebook-delete\` - Remove outdated or incorrect notes
184
+ - ๐Ÿ—‘๏ธ Delete when code is refactored and note is obsolete
185
+ - โŒ Remove notes recorded by mistake
186
+ - ๐Ÿงน Clean up after workarounds are properly fixed
187
+ - \`notebook-list\` - View all notes for a specific file
188
+ - ๐Ÿ“‹ List all constraints for a file before making changes
189
+ - ๐Ÿ” Find note IDs for update/delete operations
190
+ - ๐Ÿง Review all warnings before refactoring
179
191
 
180
192
  **Web Search:**
181
193
  - \`websearch-search\` - Search web for latest docs/solutions
@@ -189,13 +201,103 @@ manipulation, workflow automation, and complex command chaining to solve sophist
189
201
  system administration and data processing challenges.
190
202
 
191
203
  **Sub-Agent:**
192
- *If you don't have a sub-agent tool, ignore this feature*
193
- - A sub-agent is a separate session isolated from the main session, and a sub-agent may have some of the tools described above to focus on solving a specific problem.
194
- If you have a sub-agent tool, then you can leave some of the work to the sub-agent to solve.
195
- For example, if you have a sub-agent of a work plan, you can hand over the work plan to the sub-agent to solve when you receive user requirements.
196
- This way, the master agent can focus on task fulfillment.
197
204
 
198
- - The user may set a sub-agent, and there will be the word \`#agent_*\` in the user's message. \`*\` Is a wildcard,is the tool name of the sub-agent, and you must use this sub-agent.
205
+ ### ๐ŸŽฏ CRITICAL: AGGRESSIVE DELEGATION TO SUB-AGENTS
206
+
207
+ **โšก Core Principle: MAXIMIZE context saving by delegating as much work as possible to sub-agents!**
208
+
209
+ **๐Ÿ”ฅ WHY DELEGATE AGGRESSIVELY:**
210
+ - ๐Ÿ’พ **Save Main Context** - Each delegated task saves thousands of tokens in the main session
211
+ - ๐Ÿš€ **Parallel Processing** - Sub-agents work independently without cluttering main context
212
+ - ๐ŸŽฏ **Focused Sessions** - Sub-agents have dedicated context for specific tasks
213
+ - ๐Ÿ”„ **Scalability** - Main agent stays lean and efficient even for complex projects
214
+
215
+ **๐Ÿ“‹ DELEGATION STRATEGY - DEFAULT TO SUB-AGENT:**
216
+
217
+ **โœ… ALWAYS DELEGATE (High Priority):**
218
+ - ๐Ÿ” **Code Analysis & Planning** - File structure analysis, architecture review, impact analysis
219
+ - ๐Ÿ“Š **Research Tasks** - Investigating patterns, finding similar code, exploring codebase
220
+ - ๐Ÿ—บ๏ธ **Work Planning** - Breaking down requirements, creating task plans, designing solutions
221
+ - ๐Ÿ“ **Documentation Review** - Reading and summarizing large files, extracting key information
222
+ - ๐Ÿ”Ž **Dependency Mapping** - Finding all imports, exports, references across files
223
+ - ๐Ÿงช **Test Planning** - Analyzing what needs testing, planning test cases
224
+ - ๐Ÿ”ง **Refactoring Analysis** - Identifying refactoring opportunities, impact assessment
225
+
226
+ **โœ… STRONGLY CONSIDER DELEGATING:**
227
+ - ๐Ÿ› **Bug Investigation** - Root cause analysis, reproduction steps, related code search
228
+ - ๐Ÿ”„ **Migration Planning** - Planning API changes, version upgrades, dependency updates
229
+ - ๐Ÿ“ **Design Reviews** - Evaluating architectural decisions, pattern consistency
230
+ - ๐Ÿ” **Code Quality Checks** - Finding code smells, inconsistencies, potential issues
231
+
232
+ **โš ๏ธ KEEP IN MAIN AGENT (Low Volume):**
233
+ - โœ๏ธ **Direct Code Edits** - Simple, well-understood modifications
234
+ - ๐Ÿ”จ **Quick Fixes** - Single-file changes with clear context
235
+ - โšก **Immediate Actions** - Terminal commands, file operations
236
+
237
+ **๐ŸŽฏ DELEGATION WORKFLOW:**
238
+
239
+ 1. **Receive User Request** โ†’ Immediately consider: "Can a sub-agent handle the analysis/planning?"
240
+ 2. **Complex Task** โ†’ Delegate research/planning to sub-agent, wait for result, then execute
241
+ 3. **Multi-Step Task** โ†’ Delegate planning to sub-agent, receive roadmap, execute in main
242
+ 4. **Unfamiliar Code** โ†’ Delegate exploration to sub-agent, get summary, then modify
243
+
244
+ **๐Ÿ’ก PRACTICAL EXAMPLES:**
245
+
246
+ โŒ **BAD - Doing everything in main agent:**
247
+ - User: "Add user authentication"
248
+ - Main: *reads 20 files, analyzes auth patterns, plans implementation, writes code*
249
+ - Result: Main context bloated with analysis that won't be reused
250
+
251
+ โœ… **GOOD - Aggressive delegation:**
252
+ - User: "Add user authentication"
253
+ - Main: Delegate to sub-agent โ†’ "Analyze current auth patterns and create implementation plan"
254
+ - Sub-agent: *analyzes, returns concise plan*
255
+ - Main: Execute plan with focused context
256
+ - Result: Main context stays lean, only contains execution context
257
+
258
+ **๐Ÿ”ง USAGE RULES:**
259
+
260
+ 1. **When tool available**: Check if you have \`subagent-agent_*\` tools in your toolkit
261
+ 2. **Explicit user request**: User message contains \`#agent_*\` โ†’ MUST use that specific sub-agent
262
+ 3. **Implicit delegation**: Even without \`#agent_*\`, proactively delegate analysis/planning tasks
263
+ 4. **Return focus**: After sub-agent responds, main agent focuses purely on execution
264
+
265
+ **๐Ÿ“Œ REMEMBER: If it's not direct code editing or immediate action, consider delegating to sub-agent first!**
266
+
267
+ **๐ŸŒฒ DECISION TREE - When to Delegate to Sub-Agent:**
268
+
269
+ \`\`\`
270
+ ๐Ÿ“ฅ User Request
271
+ โ†“
272
+ โ“ Can a sub-agent handle this task?
273
+ โ”œโ”€ โœ… YES โ†’ ๐Ÿš€ DELEGATE to sub-agent
274
+ โ”‚ โ”œโ”€ Code search/exploration
275
+ โ”‚ โ”œโ”€ Analysis & planning
276
+ โ”‚ โ”œโ”€ Research & investigation
277
+ โ”‚ โ”œโ”€ Architecture review
278
+ โ”‚ โ”œโ”€ Impact assessment
279
+ โ”‚ โ”œโ”€ Dependency mapping
280
+ โ”‚ โ”œโ”€ Documentation review
281
+ โ”‚ โ”œโ”€ Test planning
282
+ โ”‚ โ”œโ”€ Bug investigation
283
+ โ”‚ โ”œโ”€ Pattern finding
284
+ โ”‚ โ””โ”€ ANY task sub-agent can do
285
+ โ”‚
286
+ โ””โ”€ โŒ NO โ†’ Execute directly in main agent
287
+ โ”œโ”€ Direct code editing (clear target)
288
+ โ”œโ”€ File operations (create/delete)
289
+ โ”œโ”€ Simple terminal commands
290
+ โ””โ”€ Immediate actions (no research needed)
291
+ \`\`\`
292
+
293
+ **๐ŸŽฏ Golden Rule:**
294
+ **"If sub-agent CAN do it โ†’ sub-agent SHOULD do it"**
295
+
296
+ **Decision in 3 seconds:**
297
+ 1. โ“ Does this need research/exploration/planning? โ†’ **Delegate**
298
+ 2. โ“ Is this a straightforward code edit? โ†’ **Execute directly**
299
+ 3. โš ๏ธ **When in doubt** โ†’ **Delegate to sub-agent** (safer default)
300
+
199
301
 
200
302
  ## ๐Ÿ” Quality Assurance
201
303
 
@@ -692,6 +692,21 @@ export async function handleConversationWithTools(options) {
692
692
  }, requestToolConfirmation, isToolAutoApproved, yoloMode, addToAlwaysApproved);
693
693
  // Check if aborted during tool execution
694
694
  if (controller.signal.aborted) {
695
+ // Need to add tool results for all pending tool calls to complete conversation history
696
+ // This is critical for sub-agents and any tools that were being executed
697
+ if (receivedToolCalls && receivedToolCalls.length > 0) {
698
+ for (const toolCall of receivedToolCalls) {
699
+ const abortedResult = {
700
+ role: 'tool',
701
+ tool_call_id: toolCall.id,
702
+ content: 'Error: Tool execution aborted by user',
703
+ };
704
+ conversationMessages.push(abortedResult);
705
+ saveMessage(abortedResult).catch(error => {
706
+ console.error('Failed to save aborted tool result:', error);
707
+ });
708
+ }
709
+ }
695
710
  freeEncoder();
696
711
  break;
697
712
  }
@@ -1,4 +1,4 @@
1
- import { addNotebook, queryNotebook } from '../utils/notebookManager.js';
1
+ import { addNotebook, queryNotebook, updateNotebook, deleteNotebook, getNotebooksByFile, } from '../utils/notebookManager.js';
2
2
  /**
3
3
  * Notebook MCP ๅทฅๅ…ทๅฎšไน‰
4
4
  * ็”จไบŽไปฃ็ ๅค‡ๅฟ˜ๅฝ•็ฎก็†๏ผŒๅธฎๅŠฉAI่ฎฐๅฝ•้‡่ฆ็š„ไปฃ็ ๆณจๆ„ไบ‹้กน
@@ -60,6 +60,91 @@ export const mcpTools = [
60
60
  },
61
61
  },
62
62
  },
63
+ {
64
+ name: 'notebook-update',
65
+ description: `โœ๏ธ Update an existing notebook entry to fix mistakes or refine notes.
66
+
67
+ **Core Purpose:** Correct errors in previously recorded notes or update outdated information.
68
+
69
+ **When to use:**
70
+ - Found a mistake in a previously recorded note
71
+ - Need to clarify or improve the wording
72
+ - Update note after code changes
73
+ - Refine warning messages for better clarity
74
+
75
+ **Usage:**
76
+ 1. Use notebook-query or notebook-list to find the entry ID
77
+ 2. Call notebook-update with the ID and new note content
78
+
79
+ **Example:**
80
+ - Old: "โš ๏ธ Don't change this"
81
+ - New: "โš ๏ธ validateInput() MUST be called first - parser depends on sanitized input"`,
82
+ inputSchema: {
83
+ type: 'object',
84
+ properties: {
85
+ notebookId: {
86
+ type: 'string',
87
+ description: 'Notebook entry ID to update (get from notebook-query or notebook-list)',
88
+ },
89
+ note: {
90
+ type: 'string',
91
+ description: 'New note content to replace the existing one',
92
+ },
93
+ },
94
+ required: ['notebookId', 'note'],
95
+ },
96
+ },
97
+ {
98
+ name: 'notebook-delete',
99
+ description: `๐Ÿ—‘๏ธ Delete an outdated or incorrect notebook entry.
100
+
101
+ **Core Purpose:** Remove notes that are no longer relevant or were recorded by mistake.
102
+
103
+ **When to use:**
104
+ - Code has been refactored and note is obsolete
105
+ - Note was recorded by mistake
106
+ - Workaround has been properly fixed
107
+ - Entry is duplicate or redundant
108
+
109
+ **Usage:**
110
+ 1. Use notebook-query or notebook-list to find the entry ID
111
+ 2. Call notebook-delete with the ID to remove it
112
+
113
+ **โš ๏ธ Warning:** Deletion is permanent. Make sure the note is truly obsolete.`,
114
+ inputSchema: {
115
+ type: 'object',
116
+ properties: {
117
+ notebookId: {
118
+ type: 'string',
119
+ description: 'Notebook entry ID to delete (get from notebook-query)',
120
+ },
121
+ },
122
+ required: ['notebookId'],
123
+ },
124
+ },
125
+ {
126
+ name: 'notebook-list',
127
+ description: `๐Ÿ“‹ List all notebook entries for a specific file.
128
+
129
+ **Core Purpose:** View all notes associated with a particular file for management.
130
+
131
+ **When to use:**
132
+ - Need to see all notes for a file before editing
133
+ - Want to clean up old notes for a specific file
134
+ - Review constraints before making changes to a file
135
+
136
+ **Returns:** All notebook entries for the specified file, ordered by creation time.`,
137
+ inputSchema: {
138
+ type: 'object',
139
+ properties: {
140
+ filePath: {
141
+ type: 'string',
142
+ description: 'File path (relative or absolute) to list notebooks for',
143
+ },
144
+ },
145
+ required: ['filePath'],
146
+ },
147
+ },
63
148
  ];
64
149
  /**
65
150
  * ๆ‰ง่กŒ Notebook ๅทฅๅ…ท
@@ -135,6 +220,127 @@ export async function executeNotebookTool(toolName, args) {
135
220
  ],
136
221
  };
137
222
  }
223
+ case 'notebook-update': {
224
+ const { notebookId, note } = args;
225
+ if (!notebookId || !note) {
226
+ return {
227
+ content: [
228
+ {
229
+ type: 'text',
230
+ text: 'Error: Both notebookId and note are required',
231
+ },
232
+ ],
233
+ isError: true,
234
+ };
235
+ }
236
+ const updatedEntry = updateNotebook(notebookId, note);
237
+ if (!updatedEntry) {
238
+ return {
239
+ content: [
240
+ {
241
+ type: 'text',
242
+ text: JSON.stringify({
243
+ success: false,
244
+ message: `Notebook entry not found: ${notebookId}`,
245
+ }, null, 2),
246
+ },
247
+ ],
248
+ isError: true,
249
+ };
250
+ }
251
+ return {
252
+ content: [
253
+ {
254
+ type: 'text',
255
+ text: JSON.stringify({
256
+ success: true,
257
+ message: `Notebook entry updated: ${notebookId}`,
258
+ entry: {
259
+ id: updatedEntry.id,
260
+ filePath: updatedEntry.filePath,
261
+ note: updatedEntry.note,
262
+ updatedAt: updatedEntry.updatedAt,
263
+ },
264
+ }, null, 2),
265
+ },
266
+ ],
267
+ };
268
+ }
269
+ case 'notebook-delete': {
270
+ const { notebookId } = args;
271
+ if (!notebookId) {
272
+ return {
273
+ content: [
274
+ {
275
+ type: 'text',
276
+ text: 'Error: notebookId is required',
277
+ },
278
+ ],
279
+ isError: true,
280
+ };
281
+ }
282
+ const deleted = deleteNotebook(notebookId);
283
+ if (!deleted) {
284
+ return {
285
+ content: [
286
+ {
287
+ type: 'text',
288
+ text: JSON.stringify({
289
+ success: false,
290
+ message: `Notebook entry not found: ${notebookId}`,
291
+ }, null, 2),
292
+ },
293
+ ],
294
+ isError: true,
295
+ };
296
+ }
297
+ return {
298
+ content: [
299
+ {
300
+ type: 'text',
301
+ text: JSON.stringify({
302
+ success: true,
303
+ message: `Notebook entry deleted: ${notebookId}`,
304
+ }, null, 2),
305
+ },
306
+ ],
307
+ };
308
+ }
309
+ case 'notebook-list': {
310
+ const { filePath } = args;
311
+ if (!filePath) {
312
+ return {
313
+ content: [
314
+ {
315
+ type: 'text',
316
+ text: 'Error: filePath is required',
317
+ },
318
+ ],
319
+ isError: true,
320
+ };
321
+ }
322
+ const entries = getNotebooksByFile(filePath);
323
+ return {
324
+ content: [
325
+ {
326
+ type: 'text',
327
+ text: JSON.stringify({
328
+ message: entries.length > 0
329
+ ? `Found ${entries.length} notebook entries for: ${filePath}`
330
+ : `No notebook entries found for: ${filePath}`,
331
+ filePath,
332
+ totalEntries: entries.length,
333
+ entries: entries.map(entry => ({
334
+ id: entry.id,
335
+ note: entry.note,
336
+ createdAt: entry.createdAt,
337
+ updatedAt: entry.updatedAt,
338
+ })),
339
+ }, null, 2),
340
+ },
341
+ ],
342
+ };
343
+ }
138
344
  default:
139
345
  return {
140
346
  content: [
@@ -28,6 +28,13 @@ export declare function queryNotebook(filePathPattern?: string, topN?: number):
28
28
  * @returns ่ฏฅๆ–‡ไปถ็š„ๆ‰€ๆœ‰ๅค‡ๅฟ˜ๅฝ•
29
29
  */
30
30
  export declare function getNotebooksByFile(filePath: string): NotebookEntry[];
31
+ /**
32
+ * ๆ›ดๆ–ฐๅค‡ๅฟ˜ๅฝ•ๅ†…ๅฎน
33
+ * @param notebookId ๅค‡ๅฟ˜ๅฝ•ID
34
+ * @param newNote ๆ–ฐ็š„ๅค‡ๅฟ˜่ฏดๆ˜Ž
35
+ * @returns ๆ›ดๆ–ฐๅŽ็š„ๅค‡ๅฟ˜ๅฝ•ๆก็›ฎ๏ผŒๅฆ‚ๆžœๆœชๆ‰พๅˆฐๅˆ™่ฟ”ๅ›žnull
36
+ */
37
+ export declare function updateNotebook(notebookId: string, newNote: string): NotebookEntry | null;
31
38
  /**
32
39
  * ๅˆ ้™คๅค‡ๅฟ˜ๅฝ•
33
40
  * @param notebookId ๅค‡ๅฟ˜ๅฝ•ID
@@ -130,6 +130,31 @@ export function getNotebooksByFile(filePath) {
130
130
  const data = readNotebookData();
131
131
  return data[normalizedPath] || [];
132
132
  }
133
+ /**
134
+ * ๆ›ดๆ–ฐๅค‡ๅฟ˜ๅฝ•ๅ†…ๅฎน
135
+ * @param notebookId ๅค‡ๅฟ˜ๅฝ•ID
136
+ * @param newNote ๆ–ฐ็š„ๅค‡ๅฟ˜่ฏดๆ˜Ž
137
+ * @returns ๆ›ดๆ–ฐๅŽ็š„ๅค‡ๅฟ˜ๅฝ•ๆก็›ฎ๏ผŒๅฆ‚ๆžœๆœชๆ‰พๅˆฐๅˆ™่ฟ”ๅ›žnull
138
+ */
139
+ export function updateNotebook(notebookId, newNote) {
140
+ const data = readNotebookData();
141
+ let updatedEntry = null;
142
+ for (const [, entries] of Object.entries(data)) {
143
+ const entry = entries.find(e => e.id === notebookId);
144
+ if (entry) {
145
+ // ๆ›ดๆ–ฐ็ฌ”่ฎฐๅ†…ๅฎนๅ’Œๆ›ดๆ–ฐๆ—ถ้—ด
146
+ entry.note = newNote;
147
+ const now = new Date();
148
+ entry.updatedAt = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}T${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}.${String(now.getMilliseconds()).padStart(3, '0')}`;
149
+ updatedEntry = entry;
150
+ break;
151
+ }
152
+ }
153
+ if (updatedEntry) {
154
+ saveNotebookData(data);
155
+ }
156
+ return updatedEntry;
157
+ }
133
158
  /**
134
159
  * ๅˆ ้™คๅค‡ๅฟ˜ๅฝ•
135
160
  * @param notebookId ๅค‡ๅฟ˜ๅฝ•ID
@@ -70,8 +70,19 @@ export async function executeSubAgent(agentId, prompt, onMessage, abortSignal, r
70
70
  const sessionApprovedTools = new Set();
71
71
  // eslint-disable-next-line no-constant-condition
72
72
  while (true) {
73
- // Check abort signal
73
+ // Check abort signal before streaming
74
74
  if (abortSignal?.aborted) {
75
+ // Send done message to mark completion (like normal tool abort)
76
+ if (onMessage) {
77
+ onMessage({
78
+ type: 'sub_agent_message',
79
+ agentId: agent.id,
80
+ agentName: agent.name,
81
+ message: {
82
+ type: 'done',
83
+ },
84
+ });
85
+ }
75
86
  return {
76
87
  success: false,
77
88
  result: finalResponse,
@@ -200,6 +211,17 @@ export async function executeSubAgent(agentId, prompt, onMessage, abortSignal, r
200
211
  }
201
212
  // Handle rejected tools
202
213
  if (rejectedToolCalls.length > 0) {
214
+ // Send done message to mark completion when tools are rejected
215
+ if (onMessage) {
216
+ onMessage({
217
+ type: 'sub_agent_message',
218
+ agentId: agent.id,
219
+ agentName: agent.name,
220
+ message: {
221
+ type: 'done',
222
+ },
223
+ });
224
+ }
203
225
  return {
204
226
  success: false,
205
227
  result: finalResponse,
@@ -211,6 +233,25 @@ export async function executeSubAgent(agentId, prompt, onMessage, abortSignal, r
211
233
  // Execute approved tool calls
212
234
  const toolResults = [];
213
235
  for (const toolCall of approvedToolCalls) {
236
+ // Check abort signal before executing each tool
237
+ if (abortSignal?.aborted) {
238
+ // Send done message to mark completion
239
+ if (onMessage) {
240
+ onMessage({
241
+ type: 'sub_agent_message',
242
+ agentId: agent.id,
243
+ agentName: agent.name,
244
+ message: {
245
+ type: 'done',
246
+ },
247
+ });
248
+ }
249
+ return {
250
+ success: false,
251
+ result: finalResponse,
252
+ error: 'Sub-agent execution aborted during tool execution',
253
+ };
254
+ }
214
255
  try {
215
256
  const args = JSON.parse(toolCall.function.arguments);
216
257
  const result = await executeMCPTool(toolCall.function.name, args, abortSignal);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-ai",
3
- "version": "0.3.30",
3
+ "version": "0.3.31",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {