sapper-iq 1.0.21 → 1.0.23

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/sapper.mjs +35 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapper-iq",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "AI-powered development assistant that executes commands and builds projects",
5
5
  "main": "sapper.mjs",
6
6
  "bin": {
package/sapper.mjs CHANGED
@@ -166,8 +166,10 @@ WORKFLOW:
166
166
  }];
167
167
  }
168
168
 
169
- const ask = () => {
170
- safeQuestion(chalk.blue.bold('\nIbrahim ➔ ')).then(async (input) => {
169
+ const ask = async () => {
170
+ try {
171
+ const input = await safeQuestion(chalk.blue.bold('\nIbrahim ➔ '));
172
+
171
173
  if (input.toLowerCase() === 'exit') process.exit();
172
174
 
173
175
  // Handle reset command
@@ -180,17 +182,27 @@ WORKFLOW:
180
182
  role: 'system',
181
183
  content: messages[0].content // Keep system prompt
182
184
  }];
183
- return ask();
185
+ return await ask();
184
186
  }
185
187
 
186
188
  messages.push({ role: 'user', content: input });
187
189
 
190
+ let toolRounds = 0; // Prevent infinite loops
191
+ const MAX_TOOL_ROUNDS = 5;
192
+
188
193
  let active = true;
189
194
  while (active) {
190
195
  if (stepMode) await safeQuestion(chalk.gray('[STEP] Press Enter to let AI think...'));
191
196
 
192
197
  spinner.start('Thinking...');
193
- const response = await ollama.chat({ model: selectedModel, messages, stream: true });
198
+ let response;
199
+ try {
200
+ response = await ollama.chat({ model: selectedModel, messages, stream: true });
201
+ } catch (ollamaError) {
202
+ spinner.stop();
203
+ console.error(chalk.red('\n❌ Ollama error:'), ollamaError.message);
204
+ return await ask();
205
+ }
194
206
  spinner.stop();
195
207
 
196
208
  let msg = '';
@@ -203,10 +215,21 @@ WORKFLOW:
203
215
  messages.push({ role: 'assistant', content: msg });
204
216
 
205
217
  // Fixed regex: .+? (non-greedy) stops correctly before [/TOOL]
206
- // Old regex [^\]\n]+ was broken - it stopped at ] which is at END of [/TOOL]
207
218
  const toolMatches = [...msg.matchAll(/\[TOOL:(\w+)\](.+?)(?:\]([\s\S]*?))?\[\/TOOL\]/g)];
208
219
 
209
220
  if (toolMatches.length > 0) {
221
+ toolRounds++;
222
+
223
+ // Prevent infinite tool loops
224
+ if (toolRounds >= MAX_TOOL_ROUNDS) {
225
+ console.log(chalk.yellow(`\n⚠️ Tool limit reached (${MAX_TOOL_ROUNDS} rounds). Stopping auto-execution.`));
226
+ messages.push({
227
+ role: 'user',
228
+ content: 'STOP using tools now. You have enough information. Please provide your analysis based on what you have read.'
229
+ });
230
+ continue; // Let AI respond without tools
231
+ }
232
+
210
233
  for (const match of toolMatches) {
211
234
  const [_, type, path, content] = match;
212
235
  console.log(chalk.cyan(`\n[ACTION] ${type} -> ${path}`));
@@ -222,32 +245,31 @@ WORKFLOW:
222
245
  }
223
246
  fs.writeFileSync(CONTEXT_FILE, JSON.stringify(messages));
224
247
 
225
- // Warn if reading many files at once
226
248
  if (toolMatches.length > 30) {
227
249
  console.log(chalk.yellow('\n⚠️ Reading 30+ files! This might take time.'));
228
250
  }
229
251
  } else {
230
252
  // No tools found - check if malformed command
231
253
  if (msg.includes('[TOOL:') && msg.includes('[/]')) {
232
- console.log(chalk.red('\n❌ Malformed tool command detected! Expected format: [TOOL:TYPE]path[/TOOL]'));
254
+ console.log(chalk.red('\n❌ Malformed tool command detected!'));
233
255
  messages.push({
234
256
  role: 'user',
235
257
  content: 'ERROR: Your tool command is malformed. Use [TOOL:TYPE]path]content[/TOOL] or [TOOL:TYPE]path[/TOOL]'
236
258
  });
237
259
  } else {
238
- // Normal response without tools - save context and wait for next input
260
+ // Normal response - save and wait for next input
239
261
  fs.writeFileSync(CONTEXT_FILE, JSON.stringify(messages));
240
262
  active = false;
241
263
  }
242
264
  }
243
265
  }
244
- ask();
245
- }).catch((error) => {
266
+ } catch (error) {
246
267
  console.error(chalk.red('\n❌ Error:'), error.message);
247
- ask(); // Continue despite error
248
- });
268
+ }
269
+ // ALWAYS call ask() again with await - keep the conversation going
270
+ await ask();
249
271
  };
250
- ask();
272
+ await ask();
251
273
  }
252
274
 
253
275
  runSapper();