neuro-cli 4.1.0
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/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- package/package.json +83 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Advisor Model
|
|
3
|
+
// Second model consultation during tasks (like Claude Code's Advisor)
|
|
4
|
+
// ============================================================
|
|
5
|
+
export class AdvisorSystem {
|
|
6
|
+
client;
|
|
7
|
+
config;
|
|
8
|
+
consultationCount = 0;
|
|
9
|
+
recentErrors = [];
|
|
10
|
+
constructor(client, config) {
|
|
11
|
+
this.client = client;
|
|
12
|
+
this.config = {
|
|
13
|
+
enabled: config?.enabled ?? true,
|
|
14
|
+
model: config?.model ?? 'nvidia/nemotron-3-super-120b-a12b:free',
|
|
15
|
+
consultOn: config?.consultOn ?? ['before_complete', 'recurring_error', 'security_sensitive'],
|
|
16
|
+
maxConsultations: config?.maxConsultations ?? 5,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Check if advisor should be consulted for a given trigger
|
|
21
|
+
*/
|
|
22
|
+
shouldConsult(trigger) {
|
|
23
|
+
if (!this.config.enabled)
|
|
24
|
+
return false;
|
|
25
|
+
if (this.consultationCount >= this.config.maxConsultations)
|
|
26
|
+
return false;
|
|
27
|
+
return this.config.consultOn.includes(trigger);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Consult the advisor model
|
|
31
|
+
*/
|
|
32
|
+
async consult(conversation, trigger, currentAction, context) {
|
|
33
|
+
this.consultationCount++;
|
|
34
|
+
const triggerDescriptions = {
|
|
35
|
+
'before_approach': 'The agent is about to commit to a specific approach. Review the plan.',
|
|
36
|
+
'recurring_error': 'The agent has encountered a recurring error. Suggest alternative approaches.',
|
|
37
|
+
'before_complete': 'The agent is about to declare the task complete. Verify quality.',
|
|
38
|
+
'complex_decision': 'The agent faces a complex architectural decision. Provide guidance.',
|
|
39
|
+
'security_sensitive': 'The agent is modifying security-sensitive code. Review for vulnerabilities.',
|
|
40
|
+
'manual': 'The user requested an advisor consultation.',
|
|
41
|
+
};
|
|
42
|
+
// Build advisor prompt
|
|
43
|
+
const advisorMessages = [
|
|
44
|
+
{
|
|
45
|
+
role: 'system',
|
|
46
|
+
content: `You are an expert advisor AI. Your role is to review the agent's work and provide constructive guidance.
|
|
47
|
+
|
|
48
|
+
You have access to the full conversation including every tool call and result.
|
|
49
|
+
|
|
50
|
+
When advising:
|
|
51
|
+
1. Identify potential issues or risks
|
|
52
|
+
2. Suggest better approaches if applicable
|
|
53
|
+
3. Point out anything the agent might have missed
|
|
54
|
+
4. Assess code quality and correctness
|
|
55
|
+
5. Consider security implications
|
|
56
|
+
|
|
57
|
+
Be concise but thorough. End with a clear recommendation: PROCEED or REVISE.`,
|
|
58
|
+
timestamp: Date.now(),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
role: 'user',
|
|
62
|
+
content: `## Advisor Consultation: ${trigger}
|
|
63
|
+
|
|
64
|
+
${triggerDescriptions[trigger]}
|
|
65
|
+
|
|
66
|
+
## Current Action
|
|
67
|
+
${currentAction}
|
|
68
|
+
|
|
69
|
+
${context ? `## Additional Context\n${context}\n` : ''}
|
|
70
|
+
|
|
71
|
+
## Conversation So Far
|
|
72
|
+
${this.summarizeConversation(conversation)}
|
|
73
|
+
|
|
74
|
+
Please provide your assessment and recommendation.`,
|
|
75
|
+
timestamp: Date.now(),
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
try {
|
|
79
|
+
const response = await this.client.quickChat(this.config.model, advisorMessages);
|
|
80
|
+
const advice = response.content;
|
|
81
|
+
const shouldProceed = advice.toUpperCase().includes('PROCEED') &&
|
|
82
|
+
!advice.toUpperCase().includes('REVISE');
|
|
83
|
+
return {
|
|
84
|
+
advice,
|
|
85
|
+
shouldProceed,
|
|
86
|
+
suggestedChanges: shouldProceed ? undefined : this.extractSuggestions(advice),
|
|
87
|
+
usage: response.usage,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return {
|
|
92
|
+
advice: 'Advisor consultation failed. Proceed with caution.',
|
|
93
|
+
shouldProceed: true,
|
|
94
|
+
usage: { inputTokens: 0, outputTokens: 0, cost: 0 },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Track errors for recurring error detection
|
|
100
|
+
*/
|
|
101
|
+
trackError(error) {
|
|
102
|
+
this.recentErrors.push(error);
|
|
103
|
+
// Keep last 5 errors
|
|
104
|
+
if (this.recentErrors.length > 5) {
|
|
105
|
+
this.recentErrors.shift();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if an error is recurring
|
|
110
|
+
*/
|
|
111
|
+
isRecurringError(error) {
|
|
112
|
+
const similar = this.recentErrors.filter(e => this.computeSimilarity(e, error) > 0.5);
|
|
113
|
+
return similar.length >= 2;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get consultation count
|
|
117
|
+
*/
|
|
118
|
+
getConsultationCount() {
|
|
119
|
+
return this.consultationCount;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Reset for new task
|
|
123
|
+
*/
|
|
124
|
+
reset() {
|
|
125
|
+
this.consultationCount = 0;
|
|
126
|
+
this.recentErrors = [];
|
|
127
|
+
}
|
|
128
|
+
// ---- Private ----
|
|
129
|
+
summarizeConversation(messages) {
|
|
130
|
+
const parts = [];
|
|
131
|
+
const maxMessages = 30;
|
|
132
|
+
const recent = messages.slice(-maxMessages);
|
|
133
|
+
for (const msg of recent) {
|
|
134
|
+
switch (msg.role) {
|
|
135
|
+
case 'user':
|
|
136
|
+
parts.push(`[User]: ${msg.content.slice(0, 200)}`);
|
|
137
|
+
break;
|
|
138
|
+
case 'assistant':
|
|
139
|
+
if (msg.toolCalls?.length) {
|
|
140
|
+
parts.push(`[Assistant called: ${msg.toolCalls.map(tc => tc.function.name).join(', ')}]`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
parts.push(`[Assistant]: ${msg.content.slice(0, 300)}`);
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
case 'tool':
|
|
147
|
+
parts.push(`[Tool result]: ${msg.content.slice(0, 200)}`);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return parts.join('\n');
|
|
152
|
+
}
|
|
153
|
+
extractSuggestions(advice) {
|
|
154
|
+
// Extract suggested changes from advisor response
|
|
155
|
+
const lines = advice.split('\n');
|
|
156
|
+
const suggestions = [];
|
|
157
|
+
let inSuggestion = false;
|
|
158
|
+
for (const line of lines) {
|
|
159
|
+
if (line.match(/^#{1,3}\s+(suggestion|recommendation|change|fix)/i)) {
|
|
160
|
+
inSuggestion = true;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (inSuggestion && line.trim()) {
|
|
164
|
+
suggestions.push(line.trim());
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return suggestions.join('\n') || advice.slice(0, 500);
|
|
168
|
+
}
|
|
169
|
+
computeSimilarity(a, b) {
|
|
170
|
+
// Simple similarity: word overlap
|
|
171
|
+
const wordsA = new Set(a.toLowerCase().split(/\W+/));
|
|
172
|
+
const wordsB = new Set(b.toLowerCase().split(/\W+/));
|
|
173
|
+
const intersection = new Set([...wordsA].filter(w => wordsB.has(w)));
|
|
174
|
+
const union = new Set([...wordsA, ...wordsB]);
|
|
175
|
+
return union.size === 0 ? 0 : intersection.size / union.size;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=advisor.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Message, ToolCall, AgentConfig, AgentExecution } from '../core/types.js';
|
|
2
|
+
import { OpenRouterClient, TokenUsage } from '../api/openrouter.js';
|
|
3
|
+
import { ToolRegistry } from '../tools/registry.js';
|
|
4
|
+
export interface AgentRunResult {
|
|
5
|
+
content: string;
|
|
6
|
+
toolCallsMade: number;
|
|
7
|
+
iterations: number;
|
|
8
|
+
usage: TokenUsage;
|
|
9
|
+
execution: AgentExecution;
|
|
10
|
+
}
|
|
11
|
+
export interface AgentCallbacks {
|
|
12
|
+
onThinking?: (thinking: string) => void;
|
|
13
|
+
onToken?: (token: string) => void;
|
|
14
|
+
onToolCall?: (toolName: string, args: Record<string, unknown>) => void;
|
|
15
|
+
onToolResult?: (toolName: string, result: string, isError: boolean) => void;
|
|
16
|
+
onApprovalNeeded?: (toolName: string, args: Record<string, unknown>, risk: string) => Promise<boolean>;
|
|
17
|
+
onIteration?: (iteration: number, maxIterations: number) => void;
|
|
18
|
+
onComplete?: (result: AgentRunResult) => void;
|
|
19
|
+
}
|
|
20
|
+
export declare class BaseAgent {
|
|
21
|
+
protected config: AgentConfig;
|
|
22
|
+
protected client: OpenRouterClient;
|
|
23
|
+
protected registry: ToolRegistry;
|
|
24
|
+
protected workingDirectory: string;
|
|
25
|
+
protected sessionId: string;
|
|
26
|
+
protected messages: Message[];
|
|
27
|
+
constructor(config: AgentConfig, client: OpenRouterClient, registry: ToolRegistry, workingDirectory: string, sessionId: string);
|
|
28
|
+
get name(): string;
|
|
29
|
+
get description(): string;
|
|
30
|
+
/** Access to agent config for model overrides etc. */
|
|
31
|
+
get configModel(): string | undefined;
|
|
32
|
+
set configModel(model: string | undefined);
|
|
33
|
+
/**
|
|
34
|
+
* Initialize agent with system prompt and context
|
|
35
|
+
*/
|
|
36
|
+
protected initializeMessages(taskContext?: string): Message[];
|
|
37
|
+
/**
|
|
38
|
+
* Build the system prompt with context
|
|
39
|
+
*/
|
|
40
|
+
protected buildSystemPrompt(taskContext?: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Add a user message to the conversation
|
|
43
|
+
*/
|
|
44
|
+
addUserMessage(content: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Add an assistant message
|
|
47
|
+
*/
|
|
48
|
+
protected addAssistantMessage(content: string, toolCalls?: ToolCall[]): void;
|
|
49
|
+
/**
|
|
50
|
+
* Add a tool result message
|
|
51
|
+
*/
|
|
52
|
+
protected addToolResult(toolCallId: string, content: string, name: string, isError?: boolean): void;
|
|
53
|
+
/**
|
|
54
|
+
* Run the agent loop
|
|
55
|
+
*/
|
|
56
|
+
run(task: string, callbacks?: AgentCallbacks, maxIterations?: number): Promise<AgentRunResult>;
|
|
57
|
+
/**
|
|
58
|
+
* Quick single-turn query (no tool loop)
|
|
59
|
+
*/
|
|
60
|
+
query(prompt: string): Promise<string>;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Base Agent
|
|
3
|
+
// Foundation class for all agents
|
|
4
|
+
// ============================================================
|
|
5
|
+
import { MODELS } from '../api/models.js';
|
|
6
|
+
export class BaseAgent {
|
|
7
|
+
config;
|
|
8
|
+
client;
|
|
9
|
+
registry;
|
|
10
|
+
workingDirectory;
|
|
11
|
+
sessionId;
|
|
12
|
+
messages = [];
|
|
13
|
+
constructor(config, client, registry, workingDirectory, sessionId) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.client = client;
|
|
16
|
+
this.registry = registry;
|
|
17
|
+
this.workingDirectory = workingDirectory;
|
|
18
|
+
this.sessionId = sessionId;
|
|
19
|
+
}
|
|
20
|
+
get name() {
|
|
21
|
+
return this.config.name;
|
|
22
|
+
}
|
|
23
|
+
get description() {
|
|
24
|
+
return this.config.description;
|
|
25
|
+
}
|
|
26
|
+
/** Access to agent config for model overrides etc. */
|
|
27
|
+
get configModel() {
|
|
28
|
+
return this.config.model;
|
|
29
|
+
}
|
|
30
|
+
set configModel(model) {
|
|
31
|
+
this.config.model = model;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Initialize agent with system prompt and context
|
|
35
|
+
*/
|
|
36
|
+
initializeMessages(taskContext) {
|
|
37
|
+
const systemContent = this.buildSystemPrompt(taskContext);
|
|
38
|
+
this.messages = [
|
|
39
|
+
{ role: 'system', content: systemContent, timestamp: Date.now() },
|
|
40
|
+
];
|
|
41
|
+
return this.messages;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build the system prompt with context
|
|
45
|
+
*/
|
|
46
|
+
buildSystemPrompt(taskContext) {
|
|
47
|
+
const model = MODELS[this.config.model || ''];
|
|
48
|
+
const cwd = this.workingDirectory;
|
|
49
|
+
let prompt = this.config.systemPrompt;
|
|
50
|
+
prompt += `\n\n## Environment`;
|
|
51
|
+
prompt += `\n- Working Directory: ${cwd}`;
|
|
52
|
+
prompt += `\n- Current Model: ${model?.name || this.config.model}`;
|
|
53
|
+
prompt += `\n- Agent: ${this.config.name} (${this.config.description})`;
|
|
54
|
+
prompt += `\n- Available Tools: ${(this.config.tools || []).join(', ')}`;
|
|
55
|
+
if (taskContext) {
|
|
56
|
+
prompt += `\n\n## Task Context\n${taskContext}`;
|
|
57
|
+
}
|
|
58
|
+
prompt += `\n\n## Guidelines
|
|
59
|
+
- Be precise and thorough in your responses
|
|
60
|
+
- Use tools to verify your assumptions before making changes
|
|
61
|
+
- When modifying files, make minimal, targeted changes
|
|
62
|
+
- Always read a file before editing it
|
|
63
|
+
- Report what you've done and what still needs to be done
|
|
64
|
+
- If you encounter errors, analyze them before retrying
|
|
65
|
+
- Do NOT repeat the same action if it failed - try a different approach`;
|
|
66
|
+
return prompt;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Add a user message to the conversation
|
|
70
|
+
*/
|
|
71
|
+
addUserMessage(content) {
|
|
72
|
+
this.messages.push({ role: 'user', content, timestamp: Date.now() });
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Add an assistant message
|
|
76
|
+
*/
|
|
77
|
+
addAssistantMessage(content, toolCalls) {
|
|
78
|
+
this.messages.push({ role: 'assistant', content, toolCalls, timestamp: Date.now() });
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Add a tool result message
|
|
82
|
+
*/
|
|
83
|
+
addToolResult(toolCallId, content, name, isError = false) {
|
|
84
|
+
this.messages.push({
|
|
85
|
+
role: 'tool',
|
|
86
|
+
content: isError ? `ERROR: ${content}` : content,
|
|
87
|
+
toolCallId,
|
|
88
|
+
name,
|
|
89
|
+
timestamp: Date.now(),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Run the agent loop
|
|
94
|
+
*/
|
|
95
|
+
async run(task, callbacks, maxIterations) {
|
|
96
|
+
const maxIter = maxIterations || this.config.maxIterations || 10;
|
|
97
|
+
const startTime = Date.now();
|
|
98
|
+
let totalUsage = { inputTokens: 0, outputTokens: 0, cost: 0 };
|
|
99
|
+
let toolCallsMade = 0;
|
|
100
|
+
let iteration = 0;
|
|
101
|
+
const execution = {
|
|
102
|
+
agentName: this.config.name,
|
|
103
|
+
task,
|
|
104
|
+
startTime,
|
|
105
|
+
iterations: 0,
|
|
106
|
+
tokensUsed: 0,
|
|
107
|
+
status: 'running',
|
|
108
|
+
};
|
|
109
|
+
// Initialize messages
|
|
110
|
+
this.initializeMessages();
|
|
111
|
+
this.addUserMessage(task);
|
|
112
|
+
for (iteration = 1; iteration <= maxIter; iteration++) {
|
|
113
|
+
callbacks?.onIteration?.(iteration, maxIter);
|
|
114
|
+
// Get tool definitions for this agent
|
|
115
|
+
const toolDefs = this.registry.getDefinitions(this.config.tools);
|
|
116
|
+
// Prepare stream callbacks
|
|
117
|
+
const streamCallbacks = {
|
|
118
|
+
onToken: (token) => callbacks?.onToken?.(token),
|
|
119
|
+
onThinking: (thinking) => callbacks?.onThinking?.(thinking),
|
|
120
|
+
onToolCall: (tc) => callbacks?.onToolCall?.(tc.function.name, JSON.parse(tc.function.arguments)),
|
|
121
|
+
};
|
|
122
|
+
// Call LLM
|
|
123
|
+
try {
|
|
124
|
+
const response = await this.client.chat({
|
|
125
|
+
model: this.config.model || 'qwen/qwen3-coder:free',
|
|
126
|
+
messages: this.messages,
|
|
127
|
+
tools: toolDefs.length > 0 ? toolDefs : undefined,
|
|
128
|
+
temperature: this.config.temperature,
|
|
129
|
+
maxTokens: this.config.maxTokens,
|
|
130
|
+
stream: false, // Disable streaming for better compatibility with free models
|
|
131
|
+
}, streamCallbacks);
|
|
132
|
+
totalUsage.inputTokens += response.usage.inputTokens;
|
|
133
|
+
totalUsage.outputTokens += response.usage.outputTokens;
|
|
134
|
+
totalUsage.cost += response.usage.cost;
|
|
135
|
+
// If no tool calls, we're done
|
|
136
|
+
if (response.toolCalls.length === 0) {
|
|
137
|
+
this.addAssistantMessage(response.content);
|
|
138
|
+
execution.status = 'completed';
|
|
139
|
+
execution.result = response.content;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
// Process tool calls
|
|
143
|
+
this.addAssistantMessage(response.content, response.toolCalls);
|
|
144
|
+
for (const toolCall of response.toolCalls) {
|
|
145
|
+
let args;
|
|
146
|
+
try {
|
|
147
|
+
args = JSON.parse(toolCall.function.arguments);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
args = {};
|
|
151
|
+
}
|
|
152
|
+
const toolName = toolCall.function.name;
|
|
153
|
+
toolCallsMade++;
|
|
154
|
+
callbacks?.onToolCall?.(toolName, args);
|
|
155
|
+
// Check if approval is needed
|
|
156
|
+
const needsApproval = this.registry.getApprovalRequest(toolName, args);
|
|
157
|
+
if (needsApproval && this.config.autoApprove !== true) {
|
|
158
|
+
const approved = await callbacks?.onApprovalNeeded?.(toolName, args, needsApproval.risk) ?? true; // Default to approve if no callback
|
|
159
|
+
if (!approved) {
|
|
160
|
+
this.addToolResult(toolCall.id, 'User denied this tool call.', toolName);
|
|
161
|
+
callbacks?.onToolResult?.(toolName, 'Denied by user', false);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Execute tool
|
|
166
|
+
const toolContext = {
|
|
167
|
+
workingDirectory: this.workingDirectory,
|
|
168
|
+
sessionId: this.sessionId,
|
|
169
|
+
agentName: this.config.name,
|
|
170
|
+
onProgress: (msg) => callbacks?.onThinking?.(msg),
|
|
171
|
+
};
|
|
172
|
+
const result = await this.registry.execute(toolName, args, toolContext);
|
|
173
|
+
// Use the original tool_call_id from the assistant's tool call, not the registry's generated one
|
|
174
|
+
this.addToolResult(toolCall.id, result.content, toolName, result.isError);
|
|
175
|
+
callbacks?.onToolResult?.(toolName, result.content, result.isError || false);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
180
|
+
this.addAssistantMessage(`Error encountered: ${errMsg}`);
|
|
181
|
+
execution.status = 'failed';
|
|
182
|
+
execution.result = errMsg;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (iteration > maxIter) {
|
|
187
|
+
execution.status = 'completed';
|
|
188
|
+
execution.result = 'Max iterations reached';
|
|
189
|
+
}
|
|
190
|
+
execution.endTime = Date.now();
|
|
191
|
+
execution.iterations = iteration;
|
|
192
|
+
execution.tokensUsed = totalUsage.inputTokens + totalUsage.outputTokens;
|
|
193
|
+
const result = {
|
|
194
|
+
content: this.messages.filter(m => m.role === 'assistant' && !m.toolCalls?.length).pop()?.content || '',
|
|
195
|
+
toolCallsMade,
|
|
196
|
+
iterations: iteration,
|
|
197
|
+
usage: totalUsage,
|
|
198
|
+
execution,
|
|
199
|
+
};
|
|
200
|
+
callbacks?.onComplete?.(result);
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Quick single-turn query (no tool loop)
|
|
205
|
+
*/
|
|
206
|
+
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;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { BaseAgent, AgentRunResult, AgentCallbacks } from './base.js';
|
|
2
|
+
import { OpenRouterClient, TokenUsage } from '../api/openrouter.js';
|
|
3
|
+
import { ToolRegistry } from '../tools/registry.js';
|
|
4
|
+
import { AgentConfig, AgentExecution } from '../core/types.js';
|
|
5
|
+
export interface SubAgentTask {
|
|
6
|
+
agent: string;
|
|
7
|
+
task: string;
|
|
8
|
+
context?: string;
|
|
9
|
+
dependsOn?: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface OrchestratedPlan {
|
|
12
|
+
tasks: SubAgentTask[];
|
|
13
|
+
reasoning: string;
|
|
14
|
+
}
|
|
15
|
+
export interface OrchestratorResult {
|
|
16
|
+
content: string;
|
|
17
|
+
plan: OrchestratedPlan | null;
|
|
18
|
+
agentResults: Map<string, AgentRunResult>;
|
|
19
|
+
totalUsage: TokenUsage;
|
|
20
|
+
execution: AgentExecution;
|
|
21
|
+
}
|
|
22
|
+
export declare class Orchestrator extends BaseAgent {
|
|
23
|
+
private agents;
|
|
24
|
+
constructor(config: AgentConfig, client: OpenRouterClient, registry: ToolRegistry, workingDirectory: string, sessionId: string);
|
|
25
|
+
/**
|
|
26
|
+
* Register a sub-agent
|
|
27
|
+
*/
|
|
28
|
+
registerAgent(agent: BaseAgent): void;
|
|
29
|
+
/**
|
|
30
|
+
* Get all registered agents
|
|
31
|
+
*/
|
|
32
|
+
getAgents(): BaseAgent[];
|
|
33
|
+
/**
|
|
34
|
+
* Plan the task decomposition using the orchestrator model
|
|
35
|
+
*/
|
|
36
|
+
plan(task: string, callbacks?: AgentCallbacks): Promise<OrchestratedPlan>;
|
|
37
|
+
/**
|
|
38
|
+
* Execute a planned task with sub-agents
|
|
39
|
+
*/
|
|
40
|
+
orchestrate(task: string, callbacks?: AgentCallbacks, autoPlan?: boolean): Promise<OrchestratorResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Direct run - delegate to the most appropriate agent
|
|
43
|
+
*/
|
|
44
|
+
runDirect(task: string, agentName: string, callbacks?: AgentCallbacks): Promise<AgentRunResult>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=orchestrator.d.ts.map
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Orchestrator Agent
|
|
3
|
+
// Central coordinator that manages sub-agents
|
|
4
|
+
// ============================================================
|
|
5
|
+
import { BaseAgent } from './base.js';
|
|
6
|
+
export class Orchestrator extends BaseAgent {
|
|
7
|
+
agents = new Map();
|
|
8
|
+
constructor(config, client, registry, workingDirectory, sessionId) {
|
|
9
|
+
super(config, client, registry, workingDirectory, sessionId);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Register a sub-agent
|
|
13
|
+
*/
|
|
14
|
+
registerAgent(agent) {
|
|
15
|
+
this.agents.set(agent.name, agent);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get all registered agents
|
|
19
|
+
*/
|
|
20
|
+
getAgents() {
|
|
21
|
+
return Array.from(this.agents.values());
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Plan the task decomposition using the orchestrator model
|
|
25
|
+
*/
|
|
26
|
+
async plan(task, callbacks) {
|
|
27
|
+
const agentDescriptions = Array.from(this.agents.entries())
|
|
28
|
+
.map(([name, agent]) => `- ${name}: ${agent.description}`)
|
|
29
|
+
.join('\n');
|
|
30
|
+
const planPrompt = `You are the Orchestrator. Analyze the following task and create an execution plan using the available agents.
|
|
31
|
+
|
|
32
|
+
## Available Agents
|
|
33
|
+
${agentDescriptions}
|
|
34
|
+
|
|
35
|
+
## Task
|
|
36
|
+
${task}
|
|
37
|
+
|
|
38
|
+
## Instructions
|
|
39
|
+
1. Break down the task into specific sub-tasks
|
|
40
|
+
2. Assign each sub-task to the most appropriate agent
|
|
41
|
+
3. Specify dependencies between tasks (which tasks must complete before others can start)
|
|
42
|
+
4. Provide any context each agent needs
|
|
43
|
+
|
|
44
|
+
Respond with a JSON plan in this exact format:
|
|
45
|
+
\`\`\`json
|
|
46
|
+
{
|
|
47
|
+
"reasoning": "Your analysis of the task and why you chose this approach",
|
|
48
|
+
"tasks": [
|
|
49
|
+
{
|
|
50
|
+
"agent": "agent_name",
|
|
51
|
+
"task": "specific task description",
|
|
52
|
+
"context": "any additional context",
|
|
53
|
+
"dependsOn": []
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
\`\`\``;
|
|
58
|
+
const messages = [
|
|
59
|
+
{ role: 'system', content: 'You are an expert task orchestrator. Always respond with valid JSON.', timestamp: Date.now() },
|
|
60
|
+
{ role: 'user', content: planPrompt, timestamp: Date.now() },
|
|
61
|
+
];
|
|
62
|
+
const response = await this.client.quickChat(this.config.model || 'anthropic/claude-sonnet-4', messages);
|
|
63
|
+
try {
|
|
64
|
+
// Extract JSON from response
|
|
65
|
+
const jsonMatch = response.content.match(/```json\n([\s\S]*?)\n```/) ||
|
|
66
|
+
response.content.match(/\{[\s\S]*\}/);
|
|
67
|
+
const jsonStr = jsonMatch ? (jsonMatch[1] || jsonMatch[0]) : response.content;
|
|
68
|
+
return JSON.parse(jsonStr);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Fallback: use coder agent directly
|
|
72
|
+
return {
|
|
73
|
+
reasoning: 'Could not parse plan, defaulting to direct coding approach',
|
|
74
|
+
tasks: [{ agent: 'Coder', task, dependsOn: [] }],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Execute a planned task with sub-agents
|
|
80
|
+
*/
|
|
81
|
+
async orchestrate(task, callbacks, autoPlan = true) {
|
|
82
|
+
const startTime = Date.now();
|
|
83
|
+
const totalUsage = { inputTokens: 0, outputTokens: 0, cost: 0 };
|
|
84
|
+
const agentResults = new Map();
|
|
85
|
+
const execution = {
|
|
86
|
+
agentName: 'Orchestrator',
|
|
87
|
+
task,
|
|
88
|
+
startTime,
|
|
89
|
+
iterations: 0,
|
|
90
|
+
tokensUsed: 0,
|
|
91
|
+
status: 'running',
|
|
92
|
+
};
|
|
93
|
+
// Step 1: Create execution plan
|
|
94
|
+
let plan;
|
|
95
|
+
if (autoPlan) {
|
|
96
|
+
callbacks?.onThinking?.('š§ Planning task decomposition...');
|
|
97
|
+
plan = await this.plan(task, callbacks);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
plan = {
|
|
101
|
+
reasoning: 'Direct execution without planning',
|
|
102
|
+
tasks: [{ agent: 'Coder', task }],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
callbacks?.onThinking?.(`š Plan: ${plan.tasks.length} sub-tasks`);
|
|
106
|
+
callbacks?.onThinking?.(`š ${plan.reasoning}`);
|
|
107
|
+
// Step 2: Execute tasks respecting dependencies
|
|
108
|
+
const completedTasks = new Set();
|
|
109
|
+
let taskIndex = 0;
|
|
110
|
+
while (completedTasks.size < plan.tasks.length) {
|
|
111
|
+
// Find tasks whose dependencies are all completed
|
|
112
|
+
const readyTasks = plan.tasks.filter(t => {
|
|
113
|
+
if (completedTasks.has(t.agent + ':' + t.task))
|
|
114
|
+
return false;
|
|
115
|
+
if (!t.dependsOn || t.dependsOn.length === 0)
|
|
116
|
+
return true;
|
|
117
|
+
return t.dependsOn.every(dep => completedTasks.has(dep));
|
|
118
|
+
});
|
|
119
|
+
if (readyTasks.length === 0) {
|
|
120
|
+
// Deadlock or all tasks completed
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
// Execute ready tasks (could be parallelized in future)
|
|
124
|
+
for (const subTask of readyTasks) {
|
|
125
|
+
const agent = this.agents.get(subTask.agent);
|
|
126
|
+
if (!agent) {
|
|
127
|
+
callbacks?.onThinking?.(`ā ļø Agent not found: ${subTask.agent}`);
|
|
128
|
+
completedTasks.add(subTask.agent + ':' + subTask.task);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
taskIndex++;
|
|
132
|
+
callbacks?.onThinking?.(`\nš¤ [${taskIndex}/${plan.tasks.length}] Running ${subTask.agent}: ${subTask.task.slice(0, 80)}...`);
|
|
133
|
+
// Build context from previous agent results
|
|
134
|
+
let fullTask = subTask.task;
|
|
135
|
+
if (subTask.context) {
|
|
136
|
+
fullTask = `${subTask.context}\n\nTask: ${subTask.task}`;
|
|
137
|
+
}
|
|
138
|
+
// Add results from dependent tasks as context
|
|
139
|
+
if (subTask.dependsOn && subTask.dependsOn.length > 0) {
|
|
140
|
+
const depResults = subTask.dependsOn
|
|
141
|
+
.map(dep => agentResults.get(dep))
|
|
142
|
+
.filter(Boolean)
|
|
143
|
+
.map(r => `Previous agent result:\n${r.content}`)
|
|
144
|
+
.join('\n\n');
|
|
145
|
+
if (depResults) {
|
|
146
|
+
fullTask = `${fullTask}\n\n## Context from previous agents:\n${depResults}`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
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
|
+
});
|
|
158
|
+
agentResults.set(subTask.agent + ':' + subTask.task, result);
|
|
159
|
+
totalUsage.inputTokens += result.usage.inputTokens;
|
|
160
|
+
totalUsage.outputTokens += result.usage.outputTokens;
|
|
161
|
+
totalUsage.cost += result.usage.cost;
|
|
162
|
+
completedTasks.add(subTask.agent + ':' + subTask.task);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
execution.endTime = Date.now();
|
|
166
|
+
execution.iterations = taskIndex;
|
|
167
|
+
execution.tokensUsed = totalUsage.inputTokens + totalUsage.outputTokens;
|
|
168
|
+
execution.status = 'completed';
|
|
169
|
+
// Step 3: Synthesize final result
|
|
170
|
+
const resultSummary = Array.from(agentResults.entries())
|
|
171
|
+
.map(([key, result]) => `### ${key}\n${result.content}`)
|
|
172
|
+
.join('\n\n');
|
|
173
|
+
return {
|
|
174
|
+
content: resultSummary,
|
|
175
|
+
plan,
|
|
176
|
+
agentResults,
|
|
177
|
+
totalUsage,
|
|
178
|
+
execution,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Direct run - delegate to the most appropriate agent
|
|
183
|
+
*/
|
|
184
|
+
async runDirect(task, agentName, callbacks) {
|
|
185
|
+
const agent = this.agents.get(agentName);
|
|
186
|
+
if (!agent) {
|
|
187
|
+
throw new Error(`Agent not found: ${agentName}. Available: ${Array.from(this.agents.keys()).join(', ')}`);
|
|
188
|
+
}
|
|
189
|
+
return agent.run(task, callbacks);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=orchestrator.js.map
|