neuro-cli 4.1.2 → 4.1.3

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.
@@ -117,7 +117,14 @@ export class BaseAgent {
117
117
  const streamCallbacks = {
118
118
  onToken: (token) => callbacks?.onToken?.(token),
119
119
  onThinking: (thinking) => callbacks?.onThinking?.(thinking),
120
- onToolCall: (tc) => callbacks?.onToolCall?.(tc.function.name, JSON.parse(tc.function.arguments)),
120
+ onToolCall: (tc) => {
121
+ let args = {};
122
+ try {
123
+ args = JSON.parse(tc.function.arguments);
124
+ }
125
+ catch { /* malformed LLM args */ }
126
+ callbacks?.onToolCall?.(tc.function.name, args);
127
+ },
121
128
  };
122
129
  // Call LLM
123
130
  try {
@@ -183,7 +190,7 @@ export class BaseAgent {
183
190
  break;
184
191
  }
185
192
  }
186
- if (iteration > maxIter) {
193
+ if (iteration > maxIter && execution.status === 'running') {
187
194
  execution.status = 'completed';
188
195
  execution.result = 'Max iterations reached';
189
196
  }
@@ -204,12 +211,17 @@ export class BaseAgent {
204
211
  * Quick single-turn query (no tool loop)
205
212
  */
206
213
  async query(prompt) {
207
- const messages = [
208
- { role: 'system', content: this.config.systemPrompt, timestamp: Date.now() },
209
- { role: 'user', content: prompt, timestamp: Date.now() },
210
- ];
211
- const response = await this.client.quickChat(this.config.model || 'anthropic/claude-sonnet-4', messages);
212
- return response.content;
214
+ try {
215
+ const messages = [
216
+ { role: 'system', content: this.config.systemPrompt, timestamp: Date.now() },
217
+ { role: 'user', content: prompt, timestamp: Date.now() },
218
+ ];
219
+ const response = await this.client.quickChat(this.config.model || 'anthropic/claude-sonnet-4', messages);
220
+ return response.content;
221
+ }
222
+ catch (error) {
223
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
224
+ }
213
225
  }
214
226
  }
215
227
  //# sourceMappingURL=base.js.map
@@ -59,7 +59,17 @@ Respond with a JSON plan in this exact format:
59
59
  { role: 'system', content: 'You are an expert task orchestrator. Always respond with valid JSON.', timestamp: Date.now() },
60
60
  { role: 'user', content: planPrompt, timestamp: Date.now() },
61
61
  ];
62
- const response = await this.client.quickChat(this.config.model || 'anthropic/claude-sonnet-4', messages);
62
+ let response;
63
+ try {
64
+ response = await this.client.quickChat(this.config.model || 'anthropic/claude-sonnet-4', messages);
65
+ }
66
+ catch {
67
+ // API error during planning, fallback to direct approach
68
+ return {
69
+ reasoning: 'API error during planning, falling back to direct approach',
70
+ tasks: [{ agent: 'Coder', task, dependsOn: [] }],
71
+ };
72
+ }
63
73
  try {
64
74
  // Extract JSON from response
65
75
  const jsonMatch = response.content.match(/```json\n([\s\S]*?)\n```/) ||
@@ -117,7 +127,11 @@ Respond with a JSON plan in this exact format:
117
127
  return t.dependsOn.every(dep => completedTasks.has(dep));
118
128
  });
119
129
  if (readyTasks.length === 0) {
120
- // Deadlock or all tasks completed
130
+ // Deadlock detection - log unresolved tasks
131
+ const unresolved = plan.tasks.filter(t => !completedTasks.has(t.agent + ':' + t.task));
132
+ if (unresolved.length > 0) {
133
+ callbacks?.onThinking?.(`⚠️ Deadlock detected. Unresolved tasks: ${unresolved.map(t => t.agent + ':' + t.task).join(', ')}`);
134
+ }
121
135
  break;
122
136
  }
123
137
  // Execute ready tasks (could be parallelized in future)
@@ -146,15 +160,27 @@ Respond with a JSON plan in this exact format:
146
160
  fullTask = `${fullTask}\n\n## Context from previous agents:\n${depResults}`;
147
161
  }
148
162
  }
149
- const result = await agent.run(fullTask, {
150
- onToken: (token) => callbacks?.onToken?.(token),
151
- onToolCall: (name, args) => callbacks?.onToolCall?.(name, args),
152
- onToolResult: (name, result, isError) => callbacks?.onToolResult?.(name, result, isError),
153
- onApprovalNeeded: async (name, args, risk) => {
154
- return callbacks?.onApprovalNeeded?.(name, args, risk) ?? true;
155
- },
156
- onThinking: (thinking) => callbacks?.onThinking?.(thinking),
157
- });
163
+ let result;
164
+ try {
165
+ result = await agent.run(fullTask, {
166
+ onToken: (token) => callbacks?.onToken?.(token),
167
+ onToolCall: (name, args) => callbacks?.onToolCall?.(name, args),
168
+ onToolResult: (name, result, isError) => callbacks?.onToolResult?.(name, result, isError),
169
+ onApprovalNeeded: async (name, args, risk) => {
170
+ return callbacks?.onApprovalNeeded?.(name, args, risk) ?? true;
171
+ },
172
+ onThinking: (thinking) => callbacks?.onThinking?.(thinking),
173
+ });
174
+ }
175
+ catch (error) {
176
+ const errMsg = error instanceof Error ? error.message : String(error);
177
+ result = {
178
+ content: `Agent ${subTask.agent} failed: ${errMsg}`,
179
+ toolCallsMade: 0, iterations: 0,
180
+ usage: { inputTokens: 0, outputTokens: 0, cost: 0 },
181
+ execution: { agentName: subTask.agent, task: subTask.task, startTime: Date.now(), iterations: 0, tokensUsed: 0, status: 'failed' },
182
+ };
183
+ }
158
184
  agentResults.set(subTask.agent + ':' + subTask.task, result);
159
185
  totalUsage.inputTokens += result.usage.inputTokens;
160
186
  totalUsage.outputTokens += result.usage.outputTokens;
@@ -173,13 +173,26 @@ export class OpenRouterClient {
173
173
  const { done, value } = await reader.read();
174
174
  if (done)
175
175
  break;
176
- const chunk = decoder.decode(value, { stream: true });
177
- parser.feed(chunk);
176
+ try {
177
+ const chunk = decoder.decode(value, { stream: true });
178
+ parser.feed(chunk);
179
+ }
180
+ catch {
181
+ // Skip malformed SSE chunks
182
+ }
178
183
  }
179
184
  }
180
185
  catch (error) {
181
186
  callbacks.onError?.(error);
182
- throw error;
187
+ // Return partial result instead of crashing the agent
188
+ const content = fullContent.join('');
189
+ const toolCallList = Array.from(toolCalls.values());
190
+ const cost = calculateCost(modelId, inputTokens, outputTokens);
191
+ const usage = { inputTokens, outputTokens, cost };
192
+ this.totalUsage.inputTokens += inputTokens;
193
+ this.totalUsage.outputTokens += outputTokens;
194
+ this.totalUsage.cost += cost;
195
+ return { content, toolCalls: toolCallList, usage };
183
196
  }
184
197
  const content = fullContent.join('');
185
198
  const toolCallList = Array.from(toolCalls.values());
@@ -195,7 +208,13 @@ export class OpenRouterClient {
195
208
  * Handle non-streaming response
196
209
  */
197
210
  async handleNonStreamingResponse(response, modelId) {
198
- const data = await response.json();
211
+ let data;
212
+ try {
213
+ data = await response.json();
214
+ }
215
+ catch {
216
+ throw new Error('Invalid JSON response from OpenRouter API');
217
+ }
199
218
  const content = data.choices?.[0]?.message?.content || '';
200
219
  const toolCalls = data.choices?.[0]?.message?.tool_calls || [];
201
220
  const inputTokens = data.usage?.prompt_tokens || 0;
@@ -1,5 +1,5 @@
1
1
  // ============================================================
2
- // NeuroCLI - NeuroEngine v4.1.2
2
+ // NeuroCLI - NeuroEngine v4.1.3
3
3
  // The main engine that ties everything together
4
4
  // Now with: Sandbox, Plugin SDK, Enhanced MCP, Enhanced Approval,
5
5
  // Model Router, Prompt Cache, Undo/Redo, Output Styles,
@@ -483,7 +483,7 @@ export class NeuroEngine {
483
483
  this.gitWorktree = new GitWorktreeManager(process.cwd());
484
484
  // Auto-Updater
485
485
  this.updater = new AutoUpdater({
486
- currentVersion: '4.1.2',
486
+ currentVersion: '4.1.3',
487
487
  autoCheck: true,
488
488
  autoUpdate: false,
489
489
  });
@@ -729,8 +729,12 @@ Always consider the strengths of each agent when delegating:
729
729
  agent.configModel = this.config.defaultModel;
730
730
  }
731
731
  this.ui.startStreaming();
732
- result = await agent.run(message, callbacks);
733
- this.ui.endStreaming();
732
+ try {
733
+ result = await agent.run(message, callbacks);
734
+ }
735
+ finally {
736
+ this.ui.endStreaming();
737
+ }
734
738
  // Restore original model
735
739
  agent.configModel = originalModel;
736
740
  }
@@ -750,11 +754,24 @@ Always consider the strengths of each agent when delegating:
750
754
  const agent = this.agents.get('Coder');
751
755
  if (agent) {
752
756
  this.ui.startStreaming();
753
- result = await agent.run(message, callbacks);
754
- this.ui.endStreaming();
757
+ try {
758
+ result = await agent.run(message, callbacks);
759
+ }
760
+ finally {
761
+ this.ui.endStreaming();
762
+ }
755
763
  }
756
764
  else {
757
- throw new Error('Coder agent not initialized');
765
+ // Fallback to orchestration instead of crashing
766
+ this.ui.warning('Coder agent not initialized, using orchestration mode');
767
+ const orchestrateResult = await this.orchestrator.orchestrate(message, callbacks);
768
+ result = {
769
+ content: orchestrateResult.content,
770
+ toolCallsMade: 0,
771
+ iterations: orchestrateResult.execution.iterations,
772
+ usage: orchestrateResult.totalUsage,
773
+ execution: orchestrateResult.execution,
774
+ };
758
775
  }
759
776
  }
760
777
  else {