@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.
Files changed (176) hide show
  1. package/.eslintrc.json +36 -0
  2. package/.prettierrc +8 -0
  3. package/README.md +288 -0
  4. package/dist/cli/app.d.ts +12 -0
  5. package/dist/cli/app.d.ts.map +1 -0
  6. package/dist/cli/app.js +201 -0
  7. package/dist/cli/app.js.map +1 -0
  8. package/dist/cli/commands/index.d.ts +56 -0
  9. package/dist/cli/commands/index.d.ts.map +1 -0
  10. package/dist/cli/commands/index.js +201 -0
  11. package/dist/cli/commands/index.js.map +1 -0
  12. package/dist/config/constants.d.ts +32 -0
  13. package/dist/config/constants.d.ts.map +1 -0
  14. package/dist/config/constants.js +39 -0
  15. package/dist/config/constants.js.map +1 -0
  16. package/dist/config/index.d.ts +4 -0
  17. package/dist/config/index.d.ts.map +1 -0
  18. package/dist/config/index.js +4 -0
  19. package/dist/config/index.js.map +1 -0
  20. package/dist/config/schema.d.ts +56 -0
  21. package/dist/config/schema.d.ts.map +1 -0
  22. package/dist/config/schema.js +28 -0
  23. package/dist/config/schema.js.map +1 -0
  24. package/dist/config/settings.d.ts +20 -0
  25. package/dist/config/settings.d.ts.map +1 -0
  26. package/dist/config/settings.js +102 -0
  27. package/dist/config/settings.js.map +1 -0
  28. package/dist/core/agents/agent.d.ts +33 -0
  29. package/dist/core/agents/agent.d.ts.map +1 -0
  30. package/dist/core/agents/agent.js +156 -0
  31. package/dist/core/agents/agent.js.map +1 -0
  32. package/dist/core/agents/index.d.ts +3 -0
  33. package/dist/core/agents/index.d.ts.map +1 -0
  34. package/dist/core/agents/index.js +3 -0
  35. package/dist/core/agents/index.js.map +1 -0
  36. package/dist/core/agents/orchestrator.d.ts +56 -0
  37. package/dist/core/agents/orchestrator.d.ts.map +1 -0
  38. package/dist/core/agents/orchestrator.js +151 -0
  39. package/dist/core/agents/orchestrator.js.map +1 -0
  40. package/dist/core/api/client.d.ts +46 -0
  41. package/dist/core/api/client.d.ts.map +1 -0
  42. package/dist/core/api/client.js +223 -0
  43. package/dist/core/api/client.js.map +1 -0
  44. package/dist/core/api/index.d.ts +3 -0
  45. package/dist/core/api/index.d.ts.map +1 -0
  46. package/dist/core/api/index.js +3 -0
  47. package/dist/core/api/index.js.map +1 -0
  48. package/dist/core/api/types.d.ts +126 -0
  49. package/dist/core/api/types.d.ts.map +1 -0
  50. package/dist/core/api/types.js +16 -0
  51. package/dist/core/api/types.js.map +1 -0
  52. package/dist/core/hooks/index.d.ts +3 -0
  53. package/dist/core/hooks/index.d.ts.map +1 -0
  54. package/dist/core/hooks/index.js +3 -0
  55. package/dist/core/hooks/index.js.map +1 -0
  56. package/dist/core/hooks/manager.d.ts +43 -0
  57. package/dist/core/hooks/manager.d.ts.map +1 -0
  58. package/dist/core/hooks/manager.js +178 -0
  59. package/dist/core/hooks/manager.js.map +1 -0
  60. package/dist/core/hooks/types.d.ts +68 -0
  61. package/dist/core/hooks/types.d.ts.map +1 -0
  62. package/dist/core/hooks/types.js +6 -0
  63. package/dist/core/hooks/types.js.map +1 -0
  64. package/dist/core/mcp/client.d.ts +48 -0
  65. package/dist/core/mcp/client.d.ts.map +1 -0
  66. package/dist/core/mcp/client.js +139 -0
  67. package/dist/core/mcp/client.js.map +1 -0
  68. package/dist/core/mcp/index.d.ts +3 -0
  69. package/dist/core/mcp/index.d.ts.map +1 -0
  70. package/dist/core/mcp/index.js +3 -0
  71. package/dist/core/mcp/index.js.map +1 -0
  72. package/dist/core/mcp/manager.d.ts +46 -0
  73. package/dist/core/mcp/manager.d.ts.map +1 -0
  74. package/dist/core/mcp/manager.js +133 -0
  75. package/dist/core/mcp/manager.js.map +1 -0
  76. package/dist/core/plugins/index.d.ts +3 -0
  77. package/dist/core/plugins/index.d.ts.map +1 -0
  78. package/dist/core/plugins/index.js +3 -0
  79. package/dist/core/plugins/index.js.map +1 -0
  80. package/dist/core/plugins/loader.d.ts +63 -0
  81. package/dist/core/plugins/loader.d.ts.map +1 -0
  82. package/dist/core/plugins/loader.js +258 -0
  83. package/dist/core/plugins/loader.js.map +1 -0
  84. package/dist/core/plugins/types.d.ts +95 -0
  85. package/dist/core/plugins/types.d.ts.map +1 -0
  86. package/dist/core/plugins/types.js +6 -0
  87. package/dist/core/plugins/types.js.map +1 -0
  88. package/dist/core/session/index.d.ts +3 -0
  89. package/dist/core/session/index.d.ts.map +1 -0
  90. package/dist/core/session/index.js +3 -0
  91. package/dist/core/session/index.js.map +1 -0
  92. package/dist/core/session/manager.d.ts +57 -0
  93. package/dist/core/session/manager.d.ts.map +1 -0
  94. package/dist/core/session/manager.js +182 -0
  95. package/dist/core/session/manager.js.map +1 -0
  96. package/dist/core/session/storage.d.ts +42 -0
  97. package/dist/core/session/storage.d.ts.map +1 -0
  98. package/dist/core/session/storage.js +138 -0
  99. package/dist/core/session/storage.js.map +1 -0
  100. package/dist/core/tools/definitions/file.d.ts +6 -0
  101. package/dist/core/tools/definitions/file.d.ts.map +1 -0
  102. package/dist/core/tools/definitions/file.js +276 -0
  103. package/dist/core/tools/definitions/file.js.map +1 -0
  104. package/dist/core/tools/definitions/git.d.ts +6 -0
  105. package/dist/core/tools/definitions/git.d.ts.map +1 -0
  106. package/dist/core/tools/definitions/git.js +294 -0
  107. package/dist/core/tools/definitions/git.js.map +1 -0
  108. package/dist/core/tools/definitions/index.d.ts +11 -0
  109. package/dist/core/tools/definitions/index.d.ts.map +1 -0
  110. package/dist/core/tools/definitions/index.js +22 -0
  111. package/dist/core/tools/definitions/index.js.map +1 -0
  112. package/dist/core/tools/definitions/search.d.ts +6 -0
  113. package/dist/core/tools/definitions/search.d.ts.map +1 -0
  114. package/dist/core/tools/definitions/search.js +223 -0
  115. package/dist/core/tools/definitions/search.js.map +1 -0
  116. package/dist/core/tools/definitions/shell.d.ts +6 -0
  117. package/dist/core/tools/definitions/shell.d.ts.map +1 -0
  118. package/dist/core/tools/definitions/shell.js +190 -0
  119. package/dist/core/tools/definitions/shell.js.map +1 -0
  120. package/dist/core/tools/definitions/web.d.ts +6 -0
  121. package/dist/core/tools/definitions/web.d.ts.map +1 -0
  122. package/dist/core/tools/definitions/web.js +104 -0
  123. package/dist/core/tools/definitions/web.js.map +1 -0
  124. package/dist/core/tools/executor.d.ts +24 -0
  125. package/dist/core/tools/executor.d.ts.map +1 -0
  126. package/dist/core/tools/executor.js +111 -0
  127. package/dist/core/tools/executor.js.map +1 -0
  128. package/dist/core/tools/index.d.ts +4 -0
  129. package/dist/core/tools/index.d.ts.map +1 -0
  130. package/dist/core/tools/index.js +4 -0
  131. package/dist/core/tools/index.js.map +1 -0
  132. package/dist/core/tools/registry.d.ts +23 -0
  133. package/dist/core/tools/registry.d.ts.map +1 -0
  134. package/dist/core/tools/registry.js +28 -0
  135. package/dist/core/tools/registry.js.map +1 -0
  136. package/dist/index.d.ts +7 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +352 -0
  139. package/dist/index.js.map +1 -0
  140. package/package.json +62 -0
  141. package/src/cli/app.tsx +319 -0
  142. package/src/cli/commands/index.ts +261 -0
  143. package/src/config/constants.ts +45 -0
  144. package/src/config/index.ts +3 -0
  145. package/src/config/schema.ts +36 -0
  146. package/src/config/settings.ts +121 -0
  147. package/src/core/agents/agent.ts +205 -0
  148. package/src/core/agents/index.ts +2 -0
  149. package/src/core/agents/orchestrator.ts +223 -0
  150. package/src/core/api/client.ts +300 -0
  151. package/src/core/api/index.ts +2 -0
  152. package/src/core/api/types.ts +149 -0
  153. package/src/core/hooks/index.ts +2 -0
  154. package/src/core/hooks/manager.ts +212 -0
  155. package/src/core/hooks/types.ts +116 -0
  156. package/src/core/mcp/client.ts +198 -0
  157. package/src/core/mcp/index.ts +2 -0
  158. package/src/core/mcp/manager.ts +153 -0
  159. package/src/core/plugins/index.ts +2 -0
  160. package/src/core/plugins/loader.ts +312 -0
  161. package/src/core/plugins/types.ts +111 -0
  162. package/src/core/session/index.ts +2 -0
  163. package/src/core/session/manager.ts +246 -0
  164. package/src/core/session/storage.ts +184 -0
  165. package/src/core/tools/definitions/file.ts +312 -0
  166. package/src/core/tools/definitions/git.ts +326 -0
  167. package/src/core/tools/definitions/index.ts +24 -0
  168. package/src/core/tools/definitions/search.ts +266 -0
  169. package/src/core/tools/definitions/shell.ts +228 -0
  170. package/src/core/tools/definitions/web.ts +113 -0
  171. package/src/core/tools/executor.ts +145 -0
  172. package/src/core/tools/index.ts +3 -0
  173. package/src/core/tools/registry.ts +44 -0
  174. package/src/index.ts +407 -0
  175. package/tsconfig.json +40 -0
  176. 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,2 @@
1
+ export * from './agent.js';
2
+ export * from './orchestrator.js';
@@ -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
+ }