@bluehawks/cli 1.0.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/.eslintrc.json +36 -0
- package/.prettierrc +8 -0
- package/README.md +288 -0
- package/dist/cli/app.d.ts +12 -0
- package/dist/cli/app.d.ts.map +1 -0
- package/dist/cli/app.js +201 -0
- package/dist/cli/app.js.map +1 -0
- package/dist/cli/commands/index.d.ts +56 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +201 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/config/constants.d.ts +32 -0
- package/dist/config/constants.d.ts.map +1 -0
- package/dist/config/constants.js +39 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +4 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +56 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +28 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/settings.d.ts +20 -0
- package/dist/config/settings.d.ts.map +1 -0
- package/dist/config/settings.js +102 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/core/agents/agent.d.ts +33 -0
- package/dist/core/agents/agent.d.ts.map +1 -0
- package/dist/core/agents/agent.js +156 -0
- package/dist/core/agents/agent.js.map +1 -0
- package/dist/core/agents/index.d.ts +3 -0
- package/dist/core/agents/index.d.ts.map +1 -0
- package/dist/core/agents/index.js +3 -0
- package/dist/core/agents/index.js.map +1 -0
- package/dist/core/agents/orchestrator.d.ts +56 -0
- package/dist/core/agents/orchestrator.d.ts.map +1 -0
- package/dist/core/agents/orchestrator.js +151 -0
- package/dist/core/agents/orchestrator.js.map +1 -0
- package/dist/core/api/client.d.ts +46 -0
- package/dist/core/api/client.d.ts.map +1 -0
- package/dist/core/api/client.js +223 -0
- package/dist/core/api/client.js.map +1 -0
- package/dist/core/api/index.d.ts +3 -0
- package/dist/core/api/index.d.ts.map +1 -0
- package/dist/core/api/index.js +3 -0
- package/dist/core/api/index.js.map +1 -0
- package/dist/core/api/types.d.ts +126 -0
- package/dist/core/api/types.d.ts.map +1 -0
- package/dist/core/api/types.js +16 -0
- package/dist/core/api/types.js.map +1 -0
- package/dist/core/hooks/index.d.ts +3 -0
- package/dist/core/hooks/index.d.ts.map +1 -0
- package/dist/core/hooks/index.js +3 -0
- package/dist/core/hooks/index.js.map +1 -0
- package/dist/core/hooks/manager.d.ts +43 -0
- package/dist/core/hooks/manager.d.ts.map +1 -0
- package/dist/core/hooks/manager.js +178 -0
- package/dist/core/hooks/manager.js.map +1 -0
- package/dist/core/hooks/types.d.ts +68 -0
- package/dist/core/hooks/types.d.ts.map +1 -0
- package/dist/core/hooks/types.js +6 -0
- package/dist/core/hooks/types.js.map +1 -0
- package/dist/core/mcp/client.d.ts +48 -0
- package/dist/core/mcp/client.d.ts.map +1 -0
- package/dist/core/mcp/client.js +139 -0
- package/dist/core/mcp/client.js.map +1 -0
- package/dist/core/mcp/index.d.ts +3 -0
- package/dist/core/mcp/index.d.ts.map +1 -0
- package/dist/core/mcp/index.js +3 -0
- package/dist/core/mcp/index.js.map +1 -0
- package/dist/core/mcp/manager.d.ts +46 -0
- package/dist/core/mcp/manager.d.ts.map +1 -0
- package/dist/core/mcp/manager.js +133 -0
- package/dist/core/mcp/manager.js.map +1 -0
- package/dist/core/plugins/index.d.ts +3 -0
- package/dist/core/plugins/index.d.ts.map +1 -0
- package/dist/core/plugins/index.js +3 -0
- package/dist/core/plugins/index.js.map +1 -0
- package/dist/core/plugins/loader.d.ts +63 -0
- package/dist/core/plugins/loader.d.ts.map +1 -0
- package/dist/core/plugins/loader.js +258 -0
- package/dist/core/plugins/loader.js.map +1 -0
- package/dist/core/plugins/types.d.ts +95 -0
- package/dist/core/plugins/types.d.ts.map +1 -0
- package/dist/core/plugins/types.js +6 -0
- package/dist/core/plugins/types.js.map +1 -0
- package/dist/core/session/index.d.ts +3 -0
- package/dist/core/session/index.d.ts.map +1 -0
- package/dist/core/session/index.js +3 -0
- package/dist/core/session/index.js.map +1 -0
- package/dist/core/session/manager.d.ts +57 -0
- package/dist/core/session/manager.d.ts.map +1 -0
- package/dist/core/session/manager.js +182 -0
- package/dist/core/session/manager.js.map +1 -0
- package/dist/core/session/storage.d.ts +42 -0
- package/dist/core/session/storage.d.ts.map +1 -0
- package/dist/core/session/storage.js +138 -0
- package/dist/core/session/storage.js.map +1 -0
- package/dist/core/tools/definitions/file.d.ts +6 -0
- package/dist/core/tools/definitions/file.d.ts.map +1 -0
- package/dist/core/tools/definitions/file.js +276 -0
- package/dist/core/tools/definitions/file.js.map +1 -0
- package/dist/core/tools/definitions/git.d.ts +6 -0
- package/dist/core/tools/definitions/git.d.ts.map +1 -0
- package/dist/core/tools/definitions/git.js +294 -0
- package/dist/core/tools/definitions/git.js.map +1 -0
- package/dist/core/tools/definitions/index.d.ts +11 -0
- package/dist/core/tools/definitions/index.d.ts.map +1 -0
- package/dist/core/tools/definitions/index.js +22 -0
- package/dist/core/tools/definitions/index.js.map +1 -0
- package/dist/core/tools/definitions/search.d.ts +6 -0
- package/dist/core/tools/definitions/search.d.ts.map +1 -0
- package/dist/core/tools/definitions/search.js +223 -0
- package/dist/core/tools/definitions/search.js.map +1 -0
- package/dist/core/tools/definitions/shell.d.ts +6 -0
- package/dist/core/tools/definitions/shell.d.ts.map +1 -0
- package/dist/core/tools/definitions/shell.js +190 -0
- package/dist/core/tools/definitions/shell.js.map +1 -0
- package/dist/core/tools/definitions/web.d.ts +6 -0
- package/dist/core/tools/definitions/web.d.ts.map +1 -0
- package/dist/core/tools/definitions/web.js +104 -0
- package/dist/core/tools/definitions/web.js.map +1 -0
- package/dist/core/tools/executor.d.ts +24 -0
- package/dist/core/tools/executor.d.ts.map +1 -0
- package/dist/core/tools/executor.js +111 -0
- package/dist/core/tools/executor.js.map +1 -0
- package/dist/core/tools/index.d.ts +4 -0
- package/dist/core/tools/index.d.ts.map +1 -0
- package/dist/core/tools/index.js +4 -0
- package/dist/core/tools/index.js.map +1 -0
- package/dist/core/tools/registry.d.ts +23 -0
- package/dist/core/tools/registry.d.ts.map +1 -0
- package/dist/core/tools/registry.js +28 -0
- package/dist/core/tools/registry.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +352 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
- package/src/cli/app.tsx +319 -0
- package/src/cli/commands/index.ts +261 -0
- package/src/config/constants.ts +45 -0
- package/src/config/index.ts +3 -0
- package/src/config/schema.ts +36 -0
- package/src/config/settings.ts +121 -0
- package/src/core/agents/agent.ts +205 -0
- package/src/core/agents/index.ts +2 -0
- package/src/core/agents/orchestrator.ts +223 -0
- package/src/core/api/client.ts +300 -0
- package/src/core/api/index.ts +2 -0
- package/src/core/api/types.ts +149 -0
- package/src/core/hooks/index.ts +2 -0
- package/src/core/hooks/manager.ts +212 -0
- package/src/core/hooks/types.ts +116 -0
- package/src/core/mcp/client.ts +198 -0
- package/src/core/mcp/index.ts +2 -0
- package/src/core/mcp/manager.ts +153 -0
- package/src/core/plugins/index.ts +2 -0
- package/src/core/plugins/loader.ts +312 -0
- package/src/core/plugins/types.ts +111 -0
- package/src/core/session/index.ts +2 -0
- package/src/core/session/manager.ts +246 -0
- package/src/core/session/storage.ts +184 -0
- package/src/core/tools/definitions/file.ts +312 -0
- package/src/core/tools/definitions/git.ts +326 -0
- package/src/core/tools/definitions/index.ts +24 -0
- package/src/core/tools/definitions/search.ts +266 -0
- package/src/core/tools/definitions/shell.ts +228 -0
- package/src/core/tools/definitions/web.ts +113 -0
- package/src/core/tools/executor.ts +145 -0
- package/src/core/tools/index.ts +3 -0
- package/src/core/tools/registry.ts +44 -0
- package/src/index.ts +407 -0
- package/tsconfig.json +40 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bluehawks CLI - Settings Manager
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'node:fs/promises';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import * as os from 'node:os';
|
|
8
|
+
import { settingsSchema, defaultSettings, type Settings } from './schema.js';
|
|
9
|
+
import { CONFIG_DIR_NAME, SETTINGS_FILE, ENV_FILE } from './constants.js';
|
|
10
|
+
|
|
11
|
+
export class SettingsManager {
|
|
12
|
+
private globalConfigPath: string;
|
|
13
|
+
private projectConfigPath: string;
|
|
14
|
+
private settings: Settings;
|
|
15
|
+
|
|
16
|
+
constructor(projectPath: string = process.cwd()) {
|
|
17
|
+
this.globalConfigPath = path.join(os.homedir(), CONFIG_DIR_NAME);
|
|
18
|
+
this.projectConfigPath = path.join(projectPath, CONFIG_DIR_NAME);
|
|
19
|
+
this.settings = { ...defaultSettings };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async load(): Promise<Settings> {
|
|
23
|
+
// Load in order of increasing precedence
|
|
24
|
+
const globalSettings = await this.loadFromFile(
|
|
25
|
+
path.join(this.globalConfigPath, SETTINGS_FILE)
|
|
26
|
+
);
|
|
27
|
+
const projectSettings = await this.loadFromFile(
|
|
28
|
+
path.join(this.projectConfigPath, SETTINGS_FILE)
|
|
29
|
+
);
|
|
30
|
+
const envSettings = await this.loadFromEnv();
|
|
31
|
+
|
|
32
|
+
// Merge settings
|
|
33
|
+
this.settings = settingsSchema.parse({
|
|
34
|
+
...defaultSettings,
|
|
35
|
+
...globalSettings,
|
|
36
|
+
...projectSettings,
|
|
37
|
+
...envSettings,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return this.settings;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async loadFromFile(filePath: string): Promise<Partial<Settings>> {
|
|
44
|
+
try {
|
|
45
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
46
|
+
return JSON.parse(content);
|
|
47
|
+
} catch {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private async loadFromEnv(): Promise<Partial<Settings>> {
|
|
53
|
+
// Load from .env file if exists
|
|
54
|
+
const envPath = path.join(this.projectConfigPath, ENV_FILE);
|
|
55
|
+
try {
|
|
56
|
+
const content = await fs.readFile(envPath, 'utf-8');
|
|
57
|
+
const lines = content.split('\n');
|
|
58
|
+
for (const line of lines) {
|
|
59
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
60
|
+
if (match) {
|
|
61
|
+
const [, key, value] = match;
|
|
62
|
+
if (!process.env[key.trim()]) {
|
|
63
|
+
process.env[key.trim()] = value.trim().replace(/^['"]|['"]$/g, '');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
// No .env file
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Map environment variables to settings
|
|
72
|
+
const settings: Partial<Settings> = {};
|
|
73
|
+
|
|
74
|
+
if (process.env.BLUEHAWKS_API_URL) {
|
|
75
|
+
settings.apiUrl = process.env.BLUEHAWKS_API_URL;
|
|
76
|
+
}
|
|
77
|
+
if (process.env.BLUEHAWKS_API_KEY) {
|
|
78
|
+
settings.apiKey = process.env.BLUEHAWKS_API_KEY;
|
|
79
|
+
}
|
|
80
|
+
if (process.env.BLUEHAWKS_MODEL) {
|
|
81
|
+
settings.model = process.env.BLUEHAWKS_MODEL;
|
|
82
|
+
}
|
|
83
|
+
if (process.env.BLUEHAWKS_MAX_TOKENS) {
|
|
84
|
+
settings.maxTokens = parseInt(process.env.BLUEHAWKS_MAX_TOKENS, 10);
|
|
85
|
+
}
|
|
86
|
+
if (process.env.BLUEHAWKS_TEMPERATURE) {
|
|
87
|
+
settings.temperature = parseFloat(process.env.BLUEHAWKS_TEMPERATURE);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return settings;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async save(scope: 'global' | 'project' = 'project'): Promise<void> {
|
|
94
|
+
const configPath = scope === 'global' ? this.globalConfigPath : this.projectConfigPath;
|
|
95
|
+
const filePath = path.join(configPath, SETTINGS_FILE);
|
|
96
|
+
|
|
97
|
+
await fs.mkdir(configPath, { recursive: true });
|
|
98
|
+
await fs.writeFile(filePath, JSON.stringify(this.settings, null, 2), 'utf-8');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get<K extends keyof Settings>(key: K): Settings[K] {
|
|
102
|
+
return this.settings[key];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
set<K extends keyof Settings>(key: K, value: Settings[K]): void {
|
|
106
|
+
this.settings[key] = value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getAll(): Settings {
|
|
110
|
+
return { ...this.settings };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
update(partial: Partial<Settings>): void {
|
|
114
|
+
this.settings = settingsSchema.parse({
|
|
115
|
+
...this.settings,
|
|
116
|
+
...partial,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const settingsManager = new SettingsManager();
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bluehawks CLI - Base Agent
|
|
3
|
+
* Base class for all agents
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { APIClient } from '../api/client.js';
|
|
7
|
+
import { toolRegistry } from '../tools/registry.js';
|
|
8
|
+
import { ToolExecutor } from '../tools/executor.js';
|
|
9
|
+
import type { Message, ToolResult, ToolDefinition } from '../api/types.js';
|
|
10
|
+
import { hooksManager } from '../hooks/index.js';
|
|
11
|
+
import type { PreToolUseInput, PostToolUseInput, PostToolUseFailureInput } from '../hooks/types.js';
|
|
12
|
+
|
|
13
|
+
export interface AgentOptions {
|
|
14
|
+
name: string;
|
|
15
|
+
systemPrompt: string;
|
|
16
|
+
tools?: string[];
|
|
17
|
+
maxIterations?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AgentResponse {
|
|
21
|
+
content: string;
|
|
22
|
+
toolsUsed: string[];
|
|
23
|
+
iterations: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class Agent {
|
|
27
|
+
protected name: string;
|
|
28
|
+
protected systemPrompt: string;
|
|
29
|
+
protected tools: ToolDefinition[];
|
|
30
|
+
protected maxIterations: number;
|
|
31
|
+
protected apiClient: APIClient;
|
|
32
|
+
protected toolExecutor: ToolExecutor;
|
|
33
|
+
protected messages: Message[];
|
|
34
|
+
protected sessionId: string;
|
|
35
|
+
|
|
36
|
+
constructor(options: AgentOptions, apiClient: APIClient, toolExecutor: ToolExecutor) {
|
|
37
|
+
this.name = options.name;
|
|
38
|
+
this.systemPrompt = options.systemPrompt;
|
|
39
|
+
this.maxIterations = options.maxIterations || 10;
|
|
40
|
+
this.apiClient = apiClient;
|
|
41
|
+
this.toolExecutor = toolExecutor;
|
|
42
|
+
this.messages = [];
|
|
43
|
+
this.sessionId = `session_${Date.now()}`;
|
|
44
|
+
|
|
45
|
+
// Get tool definitions
|
|
46
|
+
if (options.tools && options.tools.length > 0) {
|
|
47
|
+
this.tools = options.tools
|
|
48
|
+
.map((name) => toolRegistry.get(name)?.definition)
|
|
49
|
+
.filter((def): def is ToolDefinition => def !== undefined);
|
|
50
|
+
} else {
|
|
51
|
+
this.tools = toolRegistry.getDefinitions();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async run(
|
|
56
|
+
userMessage: string,
|
|
57
|
+
onChunk?: (content: string) => void,
|
|
58
|
+
onToolStart?: (name: string) => void,
|
|
59
|
+
onToolEnd?: (name: string, result: string) => void
|
|
60
|
+
): Promise<AgentResponse> {
|
|
61
|
+
// Initialize with system message
|
|
62
|
+
this.messages = [
|
|
63
|
+
{ role: 'system', content: this.systemPrompt },
|
|
64
|
+
{ role: 'user', content: userMessage },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const toolsUsed: string[] = [];
|
|
68
|
+
let iterations = 0;
|
|
69
|
+
let finalContent = '';
|
|
70
|
+
|
|
71
|
+
while (iterations < this.maxIterations) {
|
|
72
|
+
iterations++;
|
|
73
|
+
|
|
74
|
+
// Use non-streaming with tools for agent loop (vLLM doesn't support streaming + tool_choice)
|
|
75
|
+
const response = await this.apiClient.createChatCompletion(
|
|
76
|
+
this.messages,
|
|
77
|
+
this.tools,
|
|
78
|
+
'auto'
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const choice = response.choices[0];
|
|
82
|
+
const message = choice.message;
|
|
83
|
+
|
|
84
|
+
// Add assistant message
|
|
85
|
+
const assistantMessage: Message = {
|
|
86
|
+
role: 'assistant',
|
|
87
|
+
content: message.content || '',
|
|
88
|
+
};
|
|
89
|
+
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
90
|
+
assistantMessage.tool_calls = message.tool_calls;
|
|
91
|
+
}
|
|
92
|
+
this.messages.push(assistantMessage);
|
|
93
|
+
|
|
94
|
+
// If no tool calls, we're done - output the response
|
|
95
|
+
if (!message.tool_calls || message.tool_calls.length === 0) {
|
|
96
|
+
const content = typeof message.content === 'string' ? message.content : '';
|
|
97
|
+
finalContent = content;
|
|
98
|
+
// Simulate streaming by outputting content progressively
|
|
99
|
+
if (onChunk && finalContent) {
|
|
100
|
+
// Output in small chunks for streaming effect
|
|
101
|
+
const words = finalContent.split(' ');
|
|
102
|
+
for (const word of words) {
|
|
103
|
+
onChunk(word + ' ');
|
|
104
|
+
await new Promise(r => setTimeout(r, 20)); // Small delay for streaming effect
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Execute tool calls
|
|
111
|
+
const toolResults: ToolResult[] = [];
|
|
112
|
+
for (const toolCall of message.tool_calls) {
|
|
113
|
+
const toolName = toolCall.function.name;
|
|
114
|
+
const toolInput = JSON.parse(toolCall.function.arguments || '{}');
|
|
115
|
+
toolsUsed.push(toolName);
|
|
116
|
+
|
|
117
|
+
// Execute PreToolUse hooks
|
|
118
|
+
const hookContext = {
|
|
119
|
+
sessionId: this.sessionId,
|
|
120
|
+
projectPath: process.cwd(),
|
|
121
|
+
model: this.apiClient.currentModel,
|
|
122
|
+
timestamp: new Date().toISOString(),
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const preHookInput: PreToolUseInput = {
|
|
126
|
+
...hookContext,
|
|
127
|
+
toolName,
|
|
128
|
+
toolInput,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const preResults = await hooksManager.execute('PreToolUse', preHookInput);
|
|
132
|
+
|
|
133
|
+
// Check if any hook blocked the tool
|
|
134
|
+
const blocked = preResults.find(r => r.block);
|
|
135
|
+
if (blocked) {
|
|
136
|
+
toolResults.push({
|
|
137
|
+
tool_call_id: toolCall.id,
|
|
138
|
+
content: `Tool blocked by hook: ${blocked.blockReason || 'No reason provided'}`,
|
|
139
|
+
});
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
onToolStart?.(toolName);
|
|
144
|
+
const startTime = Date.now();
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const result = await this.toolExecutor.executeToolCall(toolCall);
|
|
148
|
+
const duration = Date.now() - startTime;
|
|
149
|
+
toolResults.push(result);
|
|
150
|
+
|
|
151
|
+
// Execute PostToolUse hooks
|
|
152
|
+
const postHookInput: PostToolUseInput = {
|
|
153
|
+
...hookContext,
|
|
154
|
+
toolName,
|
|
155
|
+
toolInput,
|
|
156
|
+
toolOutput: result.content,
|
|
157
|
+
duration,
|
|
158
|
+
};
|
|
159
|
+
await hooksManager.execute('PostToolUse', postHookInput);
|
|
160
|
+
|
|
161
|
+
onToolEnd?.(toolName, result.content);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
// Execute PostToolUseFailure hooks
|
|
164
|
+
const failureHookInput: PostToolUseFailureInput = {
|
|
165
|
+
...hookContext,
|
|
166
|
+
toolName,
|
|
167
|
+
toolInput,
|
|
168
|
+
error: error instanceof Error ? error.message : String(error),
|
|
169
|
+
};
|
|
170
|
+
await hooksManager.execute('PostToolUseFailure', failureHookInput);
|
|
171
|
+
|
|
172
|
+
toolResults.push({
|
|
173
|
+
tool_call_id: toolCall.id,
|
|
174
|
+
content: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
175
|
+
});
|
|
176
|
+
onToolEnd?.(toolName, 'Error');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
// Add tool results to messages
|
|
182
|
+
for (const result of toolResults) {
|
|
183
|
+
this.messages.push({
|
|
184
|
+
role: 'tool',
|
|
185
|
+
tool_call_id: result.tool_call_id,
|
|
186
|
+
content: result.content,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
content: finalContent,
|
|
193
|
+
toolsUsed,
|
|
194
|
+
iterations,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
getMessages(): Message[] {
|
|
199
|
+
return [...this.messages];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getName(): string {
|
|
203
|
+
return this.name;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bluehawks CLI - Agent Orchestrator
|
|
3
|
+
* Coordinates multiple agents for complex tasks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { APIClient } from '../api/client.js';
|
|
7
|
+
import { ToolExecutor } from '../tools/executor.js';
|
|
8
|
+
import { Agent, type AgentResponse } from './agent.js';
|
|
9
|
+
import { CONTEXT_FILE } from '../../config/constants.js';
|
|
10
|
+
import * as fs from 'node:fs/promises';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
|
|
13
|
+
export interface SubAgentConfig {
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
systemPrompt: string;
|
|
17
|
+
tools?: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface OrchestratorOptions {
|
|
21
|
+
projectPath: string;
|
|
22
|
+
apiClient: APIClient;
|
|
23
|
+
toolExecutor: ToolExecutor;
|
|
24
|
+
planMode?: boolean;
|
|
25
|
+
maxTurns?: number;
|
|
26
|
+
systemPrompt?: string;
|
|
27
|
+
appendSystemPrompt?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DEFAULT_SYSTEM_PROMPT = `You are Bluehawks, a powerful AI coding assistant that helps developers understand and modify their codebase.
|
|
31
|
+
|
|
32
|
+
You have access to various tools for:
|
|
33
|
+
- Reading and writing files
|
|
34
|
+
- Executing shell commands
|
|
35
|
+
- Searching code with grep and finding files
|
|
36
|
+
- Git operations (status, diff, commit, etc.)
|
|
37
|
+
- Fetching web content
|
|
38
|
+
|
|
39
|
+
Guidelines:
|
|
40
|
+
1. Always read relevant files before making changes
|
|
41
|
+
2. Make targeted, minimal changes
|
|
42
|
+
3. Test your changes when possible
|
|
43
|
+
4. Be careful with destructive operations
|
|
44
|
+
5. Explain what you're doing and why
|
|
45
|
+
|
|
46
|
+
When the user asks a question:
|
|
47
|
+
1. First, understand what they're asking
|
|
48
|
+
2. Use tools to gather necessary information
|
|
49
|
+
3. Provide a clear, helpful response
|
|
50
|
+
4. If making changes, explain what you changed and why`;
|
|
51
|
+
|
|
52
|
+
export class Orchestrator {
|
|
53
|
+
private apiClient: APIClient;
|
|
54
|
+
private toolExecutor: ToolExecutor;
|
|
55
|
+
private projectPath: string;
|
|
56
|
+
private planMode: boolean;
|
|
57
|
+
private maxTurns: number;
|
|
58
|
+
private customSystemPrompt?: string;
|
|
59
|
+
private appendSystemPrompt?: string;
|
|
60
|
+
private contextContent: string = '';
|
|
61
|
+
private subAgents: Map<string, SubAgentConfig> = new Map();
|
|
62
|
+
|
|
63
|
+
constructor(options: OrchestratorOptions) {
|
|
64
|
+
this.apiClient = options.apiClient;
|
|
65
|
+
this.toolExecutor = options.toolExecutor;
|
|
66
|
+
this.projectPath = options.projectPath;
|
|
67
|
+
this.planMode = options.planMode || false;
|
|
68
|
+
this.maxTurns = options.maxTurns || 15;
|
|
69
|
+
this.customSystemPrompt = options.systemPrompt;
|
|
70
|
+
this.appendSystemPrompt = options.appendSystemPrompt;
|
|
71
|
+
|
|
72
|
+
// Register default sub-agents
|
|
73
|
+
this.registerDefaultSubAgents();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
private registerDefaultSubAgents(): void {
|
|
78
|
+
this.subAgents.set('coder', {
|
|
79
|
+
name: 'coder',
|
|
80
|
+
description: 'Specialized in writing and modifying code',
|
|
81
|
+
systemPrompt: `You are a code-focused agent. Your job is to write clean, efficient code and make targeted modifications to existing code. Focus on:
|
|
82
|
+
- Writing idiomatic code for the project's language
|
|
83
|
+
- Following existing code conventions
|
|
84
|
+
- Making minimal, focused changes
|
|
85
|
+
- Adding appropriate comments when needed`,
|
|
86
|
+
tools: ['read_file', 'write_file', 'edit_file', 'grep_search', 'find_files'],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
this.subAgents.set('researcher', {
|
|
90
|
+
name: 'researcher',
|
|
91
|
+
description: 'Specialized in gathering information and research',
|
|
92
|
+
systemPrompt: `You are a research agent. Your job is to gather information about the codebase and external resources. Focus on:
|
|
93
|
+
- Reading and understanding code structure
|
|
94
|
+
- Finding relevant files and functions
|
|
95
|
+
- Fetching documentation when needed
|
|
96
|
+
- Summarizing findings clearly`,
|
|
97
|
+
tools: ['read_file', 'list_directory', 'grep_search', 'find_files', 'fetch_url'],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
this.subAgents.set('shell', {
|
|
101
|
+
name: 'shell',
|
|
102
|
+
description: 'Specialized in running commands and automation',
|
|
103
|
+
systemPrompt: `You are a shell execution agent. Your job is to run commands safely and interpret their output. Focus on:
|
|
104
|
+
- Running build, test, and utility commands
|
|
105
|
+
- Interpreting command output
|
|
106
|
+
- Suggesting fixes for errors
|
|
107
|
+
- Managing git operations`,
|
|
108
|
+
tools: ['run_command', 'git_status', 'git_diff', 'git_add', 'git_commit'],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async initialize(): Promise<void> {
|
|
113
|
+
// Load context file if it exists
|
|
114
|
+
await this.loadContextFile();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private async loadContextFile(): Promise<void> {
|
|
118
|
+
const contextPath = path.join(this.projectPath, CONTEXT_FILE);
|
|
119
|
+
try {
|
|
120
|
+
this.contextContent = await fs.readFile(contextPath, 'utf-8');
|
|
121
|
+
} catch {
|
|
122
|
+
this.contextContent = '';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private buildSystemPrompt(): string {
|
|
127
|
+
// Use custom system prompt if provided, otherwise default
|
|
128
|
+
let prompt = this.customSystemPrompt || DEFAULT_SYSTEM_PROMPT;
|
|
129
|
+
|
|
130
|
+
// Append additional prompt content if provided
|
|
131
|
+
if (this.appendSystemPrompt) {
|
|
132
|
+
prompt += `\n\n${this.appendSystemPrompt}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (this.contextContent) {
|
|
136
|
+
prompt += `\n\n## Project Context (from ${CONTEXT_FILE})\n\n${this.contextContent}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (this.planMode) {
|
|
140
|
+
prompt += `\n\n## Plan Mode Active
|
|
141
|
+
Before making any changes, first:
|
|
142
|
+
1. Analyze the request
|
|
143
|
+
2. Create a step-by-step plan
|
|
144
|
+
3. Present the plan to the user
|
|
145
|
+
4. Wait for approval before proceeding
|
|
146
|
+
5. Execute each step, reporting progress`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return prompt;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async chat(
|
|
153
|
+
userMessage: string,
|
|
154
|
+
_history: Array<{ role: string; content: string }> = [],
|
|
155
|
+
callbacks?: {
|
|
156
|
+
onChunk?: (content: string) => void;
|
|
157
|
+
onToolStart?: (name: string) => void;
|
|
158
|
+
onToolEnd?: (name: string, result: string) => void;
|
|
159
|
+
}
|
|
160
|
+
): Promise<AgentResponse> {
|
|
161
|
+
const mainAgent = new Agent(
|
|
162
|
+
{
|
|
163
|
+
name: 'main',
|
|
164
|
+
systemPrompt: this.buildSystemPrompt(),
|
|
165
|
+
maxIterations: this.maxTurns,
|
|
166
|
+
},
|
|
167
|
+
this.apiClient,
|
|
168
|
+
this.toolExecutor
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
return mainAgent.run(
|
|
173
|
+
userMessage,
|
|
174
|
+
callbacks?.onChunk,
|
|
175
|
+
callbacks?.onToolStart,
|
|
176
|
+
callbacks?.onToolEnd
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async runSubAgent(
|
|
181
|
+
agentName: string,
|
|
182
|
+
task: string,
|
|
183
|
+
callbacks?: {
|
|
184
|
+
onChunk?: (content: string) => void;
|
|
185
|
+
onToolStart?: (name: string) => void;
|
|
186
|
+
onToolEnd?: (name: string, result: string) => void;
|
|
187
|
+
}
|
|
188
|
+
): Promise<AgentResponse> {
|
|
189
|
+
const config = this.subAgents.get(agentName);
|
|
190
|
+
if (!config) {
|
|
191
|
+
throw new Error(`Unknown sub-agent: ${agentName}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const agent = new Agent(
|
|
195
|
+
{
|
|
196
|
+
name: config.name,
|
|
197
|
+
systemPrompt: config.systemPrompt,
|
|
198
|
+
tools: config.tools,
|
|
199
|
+
maxIterations: 10,
|
|
200
|
+
},
|
|
201
|
+
this.apiClient,
|
|
202
|
+
this.toolExecutor
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
return agent.run(task, callbacks?.onChunk, callbacks?.onToolStart, callbacks?.onToolEnd);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
setPlanMode(enabled: boolean): void {
|
|
209
|
+
this.planMode = enabled;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
isPlanMode(): boolean {
|
|
213
|
+
return this.planMode;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
getSubAgents(): SubAgentConfig[] {
|
|
217
|
+
return Array.from(this.subAgents.values());
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
registerSubAgent(config: SubAgentConfig): void {
|
|
221
|
+
this.subAgents.set(config.name, config);
|
|
222
|
+
}
|
|
223
|
+
}
|