neuro-cli 4.1.1 → 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.
- package/dist/agents/base.js +20 -8
- package/dist/agents/orchestrator.js +37 -11
- package/dist/api/openrouter.js +23 -4
- package/dist/core/approval.d.ts +7 -0
- package/dist/core/approval.js +32 -3
- package/dist/core/diff-preview.js +2 -1
- package/dist/core/engine.js +24 -7
- package/dist/index.js +688 -639
- package/package.json +1 -1
package/dist/agents/base.js
CHANGED
|
@@ -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) =>
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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;
|
package/dist/api/openrouter.js
CHANGED
|
@@ -173,13 +173,26 @@ export class OpenRouterClient {
|
|
|
173
173
|
const { done, value } = await reader.read();
|
|
174
174
|
if (done)
|
|
175
175
|
break;
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/core/approval.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Interface as ReadLineInterface } from 'readline';
|
|
1
2
|
export type PermissionMode = 'manual' | 'auto' | 'plan' | 'yolo';
|
|
2
3
|
export interface ApprovalResult {
|
|
3
4
|
approved: boolean;
|
|
@@ -39,6 +40,7 @@ export declare class ApprovalSystem {
|
|
|
39
40
|
private persistDecisions;
|
|
40
41
|
private consecutiveAutoApproves;
|
|
41
42
|
private rl;
|
|
43
|
+
private mainRl;
|
|
42
44
|
private stats;
|
|
43
45
|
private pendingBatch;
|
|
44
46
|
private batchTimer;
|
|
@@ -65,6 +67,11 @@ export declare class ApprovalSystem {
|
|
|
65
67
|
private isWhitelisted;
|
|
66
68
|
private getPattern;
|
|
67
69
|
private getAlwaysKey;
|
|
70
|
+
/**
|
|
71
|
+
* Set the main readline interface from index.ts
|
|
72
|
+
* This prevents creating a second readline on the same stdin
|
|
73
|
+
*/
|
|
74
|
+
setMainReadline(rl: ReadLineInterface): void;
|
|
68
75
|
private readline;
|
|
69
76
|
close(): void;
|
|
70
77
|
addAutoApprove(toolName: string): void;
|
package/dist/core/approval.js
CHANGED
|
@@ -23,6 +23,7 @@ export class ApprovalSystem {
|
|
|
23
23
|
persistDecisions = true;
|
|
24
24
|
consecutiveAutoApproves = 0;
|
|
25
25
|
rl = null;
|
|
26
|
+
mainRl = null;
|
|
26
27
|
stats = new Map();
|
|
27
28
|
pendingBatch = [];
|
|
28
29
|
batchTimer = null;
|
|
@@ -333,10 +334,38 @@ export class ApprovalSystem {
|
|
|
333
334
|
getAlwaysKey(toolName, args) {
|
|
334
335
|
return `${toolName}:${this.getPattern(args)}`;
|
|
335
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Set the main readline interface from index.ts
|
|
339
|
+
* This prevents creating a second readline on the same stdin
|
|
340
|
+
*/
|
|
341
|
+
setMainReadline(rl) {
|
|
342
|
+
this.mainRl = rl;
|
|
343
|
+
}
|
|
336
344
|
readline(prompt) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
345
|
+
// Pause the main readline to prevent it from also reading stdin input
|
|
346
|
+
// This is the root cause of double character input (YY instead of Y)
|
|
347
|
+
const mainInputStream = this.mainRl?.input;
|
|
348
|
+
const wasMainPaused = mainInputStream?.isPaused?.();
|
|
349
|
+
if (this.mainRl && mainInputStream && mainInputStream.isPaused()) {
|
|
350
|
+
// Already paused, skip
|
|
351
|
+
}
|
|
352
|
+
else if (this.mainRl) {
|
|
353
|
+
this.mainRl.pause();
|
|
354
|
+
}
|
|
355
|
+
return new Promise((resolve) => {
|
|
356
|
+
// Reuse the main readline if available, otherwise create a temporary one
|
|
357
|
+
const rl = this.mainRl || this.rl || createInterface({ input: process.stdin, output: process.stdout });
|
|
358
|
+
if (!this.rl && !this.mainRl)
|
|
359
|
+
this.rl = rl;
|
|
360
|
+
rl.question(prompt, (answer) => {
|
|
361
|
+
// Resume the main readline after getting the answer
|
|
362
|
+
if (this.mainRl) {
|
|
363
|
+
this.mainRl.resume();
|
|
364
|
+
this.mainRl.prompt();
|
|
365
|
+
}
|
|
366
|
+
resolve(answer);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
340
369
|
}
|
|
341
370
|
close() {
|
|
342
371
|
if (this.rl) {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Shows file changes before applying them
|
|
4
4
|
// ============================================================
|
|
5
5
|
import { readFileSync, existsSync } from 'fs';
|
|
6
|
+
import { createInterface } from 'readline';
|
|
6
7
|
import chalk from 'chalk';
|
|
7
8
|
export class DiffPreview {
|
|
8
9
|
/**
|
|
@@ -128,7 +129,7 @@ export class DiffPreview {
|
|
|
128
129
|
*/
|
|
129
130
|
static async confirmDiff(diff) {
|
|
130
131
|
DiffPreview.renderDiff(diff);
|
|
131
|
-
const rl =
|
|
132
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
132
133
|
return new Promise((resolve) => {
|
|
133
134
|
rl.question(chalk.cyan(' Apply these changes? [y/n]: '), (answer) => {
|
|
134
135
|
rl.close();
|
package/dist/core/engine.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ============================================================
|
|
2
|
-
// NeuroCLI - NeuroEngine v4.1.
|
|
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.
|
|
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
|
-
|
|
733
|
-
|
|
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
|
-
|
|
754
|
-
|
|
757
|
+
try {
|
|
758
|
+
result = await agent.run(message, callbacks);
|
|
759
|
+
}
|
|
760
|
+
finally {
|
|
761
|
+
this.ui.endStreaming();
|
|
762
|
+
}
|
|
755
763
|
}
|
|
756
764
|
else {
|
|
757
|
-
|
|
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 {
|