closed-loop-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.
Potentially problematic release.
This version of closed-loop-cli might be problematic. Click here for more details.
- package/dist/dashboard/server.js +237 -0
- package/dist/index.js +272 -0
- package/dist/orchestrator/agent-prompts.js +42 -0
- package/dist/orchestrator/autogenesis.js +973 -0
- package/dist/orchestrator/dgm-archive.js +223 -0
- package/dist/orchestrator/event-stream.js +103 -0
- package/dist/orchestrator/fitness-evaluator.js +99 -0
- package/dist/orchestrator/meta-agent.js +421 -0
- package/dist/orchestrator/microagent-registry.js +134 -0
- package/dist/orchestrator/mutation-strategies.js +174 -0
- package/dist/orchestrator/prompt-benchmark.js +102 -0
- package/dist/orchestrator/prompt-optimizer.js +169 -0
- package/dist/orchestrator/refactor-scanner.js +222 -0
- package/dist/orchestrator/research-manager.js +104 -0
- package/dist/orchestrator/rulez.js +135 -0
- package/dist/orchestrator/sahoo-gateway.js +261 -0
- package/dist/orchestrator/state-manager.js +121 -0
- package/dist/orchestrator/task-agent.js +444 -0
- package/dist/orchestrator/telegram-bot.js +374 -0
- package/dist/orchestrator/types.js +2 -0
- package/dist/tests/dynamic/dependencies.test.js +37 -0
- package/dist/tests/dynamic/dummy.test.js +7 -0
- package/dist/tests/dynamic/fuzzy-patch.test.js +68 -0
- package/dist/tests/dynamic/indexer.test.js +60 -0
- package/dist/tests/dynamic/openhands.test.js +83 -0
- package/dist/tests/dynamic/skills.test.js +88 -0
- package/dist/tests/run-tests.js +294 -0
- package/dist/tools/diff-tools.js +24 -0
- package/dist/tools/file-tools.js +191 -0
- package/dist/tools/indexer.js +301 -0
- package/dist/tools/math-helper.js +6 -0
- package/dist/tools/repo-map.js +122 -0
- package/dist/tools/search-tools.js +271 -0
- package/dist/tools/shell-tools.js +75 -0
- package/dist/tools/skills.js +122 -0
- package/dist/tools/tui-tools.js +82 -0
- package/docs/AI_Arch_Opt_Anti_Gaming.md +227 -0
- package/docs/AI_Self_Improvement_Safety.md +457 -0
- package/docs/Anthropic AI Agents_ Capabilities and Concerns.md +134 -0
- package/docs/Auto_ClosedLoop_AI_Agent.md +415 -0
- package/docs/Autonomous AI Agents_ Closing the Loop.docx +0 -0
- package/docs/Secure_AI_Sandbox_Framework.md +358 -0
- package/docs/skills/add-file-existence-check-utility.json +9 -0
- package/docs/skills/add-utility-function-for-file-existence-check.json +9 -0
- package/docs/skills/add-utility-function-to-module.json +9 -0
- package/docs/skills/extract-command-runner-utility.json +9 -0
- package/docs/skills/file-existence-check-utility.json +9 -0
- package/package.json +36 -0
- package/src/dashboard/public/index.css +1334 -0
- package/src/dashboard/public/index.html +385 -0
- package/src/dashboard/public/index.js +1059 -0
- package/src/dashboard/server.ts +209 -0
- package/src/index.ts +256 -0
- package/src/orchestrator/agent-prompts.ts +43 -0
- package/src/orchestrator/autogenesis.ts +1078 -0
- package/src/orchestrator/dgm-archive.ts +257 -0
- package/src/orchestrator/event-stream.ts +90 -0
- package/src/orchestrator/fitness-evaluator.ts +154 -0
- package/src/orchestrator/meta-agent.ts +434 -0
- package/src/orchestrator/microagent-registry.ts +115 -0
- package/src/orchestrator/microagents/git-helper.md +11 -0
- package/src/orchestrator/microagents/test-fixer.md +10 -0
- package/src/orchestrator/microagents/typescript-expert.md +11 -0
- package/src/orchestrator/mutation-strategies.ts +214 -0
- package/src/orchestrator/research-manager.ts +88 -0
- package/src/orchestrator/rulez.ts +118 -0
- package/src/orchestrator/sahoo-gateway.ts +300 -0
- package/src/orchestrator/state-manager.ts +161 -0
- package/src/orchestrator/system-prompt.txt +1 -0
- package/src/orchestrator/task-agent.ts +461 -0
- package/src/orchestrator/telegram-bot.ts +358 -0
- package/src/tests/dynamic/dependencies.test.ts +48 -0
- package/src/tests/dynamic/dummy.test.ts +4 -0
- package/src/tests/dynamic/fuzzy-patch.test.ts +42 -0
- package/src/tests/dynamic/indexer.test.ts +31 -0
- package/src/tests/dynamic/openhands.test.ts +59 -0
- package/src/tests/dynamic/skills.test.ts +63 -0
- package/src/tests/run-tests.ts +296 -0
- package/src/tools/diff-tools.ts +27 -0
- package/src/tools/file-tools.ts +187 -0
- package/src/tools/indexer.ts +325 -0
- package/src/tools/repo-map.ts +96 -0
- package/src/tools/search-tools.ts +258 -0
- package/src/tools/shell-tools.ts +90 -0
- package/src/tools/skills.ts +101 -0
- package/src/tools/tui-tools.ts +87 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
import * as fileTools from '../tools/file-tools';
|
|
4
|
+
import * as shellTools from '../tools/shell-tools';
|
|
5
|
+
import * as rulez from './rulez';
|
|
6
|
+
import * as searchTools from '../tools/search-tools';
|
|
7
|
+
import { Spinner } from '../tools/tui-tools';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { updateEvolutionState } from './state-manager';
|
|
11
|
+
import { EventStream } from './event-stream';
|
|
12
|
+
|
|
13
|
+
// Load environment variables
|
|
14
|
+
dotenv.config();
|
|
15
|
+
|
|
16
|
+
// Initialize Anthropic client
|
|
17
|
+
const apiKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN || '';
|
|
18
|
+
const baseURL = process.env.ANTHROPIC_BASE_URL || undefined;
|
|
19
|
+
|
|
20
|
+
if (!apiKey) {
|
|
21
|
+
console.warn('\x1b[33m%s\x1b[0m', 'Warning: ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN is not set in environment.');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const anthropic = new Anthropic({
|
|
25
|
+
apiKey: apiKey,
|
|
26
|
+
baseURL: baseURL,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Select main model
|
|
30
|
+
const defaultModel = process.env.ANTHROPIC_MODEL || 'mimo-v2.5-pro[1m]';
|
|
31
|
+
|
|
32
|
+
// Define tools schemas for Anthropic API
|
|
33
|
+
const toolsList: Anthropic.Tool[] = [
|
|
34
|
+
{
|
|
35
|
+
name: 'readFile',
|
|
36
|
+
description: 'Reads the full contents of a file inside the workspace.',
|
|
37
|
+
input_schema: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
path: { type: 'string', description: 'The relative or absolute path of the file to read.' }
|
|
41
|
+
},
|
|
42
|
+
required: ['path']
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'writeFile',
|
|
47
|
+
description: 'Creates a new file or overwrites an existing file with the specified content.',
|
|
48
|
+
input_schema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
path: { type: 'string', description: 'The relative path of the file to create or overwrite.' },
|
|
52
|
+
content: { type: 'string', description: 'The text content to write to the file.' }
|
|
53
|
+
},
|
|
54
|
+
required: ['path', 'content']
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'editFile',
|
|
59
|
+
description: 'Edits an existing file by replacing a unique, exact block of text with a new block of text.',
|
|
60
|
+
input_schema: {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
path: { type: 'string', description: 'The relative path of the file to edit.' },
|
|
64
|
+
targetContent: { type: 'string', description: 'The exact lines of code/text to be replaced. Must be unique within the file.' },
|
|
65
|
+
replacementContent: { type: 'string', description: 'The new lines of code/text to replace the targetContent.' }
|
|
66
|
+
},
|
|
67
|
+
required: ['path', 'targetContent', 'replacementContent']
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'listDir',
|
|
72
|
+
description: 'Lists files and folders in a workspace directory.',
|
|
73
|
+
input_schema: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {
|
|
76
|
+
path: { type: 'string', description: 'The directory path to list. Defaults to \'.\' (workspace root).' }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'grepSearch',
|
|
82
|
+
description: 'Performs a case-insensitive search for a text pattern/query in the workspace code files.',
|
|
83
|
+
input_schema: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
pattern: { type: 'string', description: 'The text string or regex pattern to search for.' },
|
|
87
|
+
path: { type: 'string', description: 'The directory path to search within. Defaults to \'.\'.' }
|
|
88
|
+
},
|
|
89
|
+
required: ['pattern']
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'runCommand',
|
|
94
|
+
description: 'Runs a terminal command in the workspace. Use this to compile code, run tests, or lint files.',
|
|
95
|
+
input_schema: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
command: { type: 'string', description: 'The exact command string to execute in the terminal.' }
|
|
99
|
+
},
|
|
100
|
+
required: ['command']
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'searchWeb',
|
|
105
|
+
description: 'Searches the web using DuckDuckGo to find answers or information on APIs, libraries, and debugging solutions.',
|
|
106
|
+
input_schema: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {
|
|
109
|
+
query: { type: 'string', description: 'The search query string.' }
|
|
110
|
+
},
|
|
111
|
+
required: ['query']
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'searchCodeIndex',
|
|
116
|
+
description: 'Searches the compiled local AST code index (code-index.json) for matching class, function, interface, or method names.',
|
|
117
|
+
input_schema: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
query: { type: 'string', description: 'The keyword or name pattern to search for in the codebase index.' }
|
|
121
|
+
},
|
|
122
|
+
required: ['query']
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'askUser',
|
|
127
|
+
description: 'Pauses the execution loop and asks the user for clarification, feedback, credentials, or review. Use when you need input to make a decision.',
|
|
128
|
+
input_schema: {
|
|
129
|
+
type: 'object',
|
|
130
|
+
properties: {
|
|
131
|
+
question: { type: 'string', description: 'The query, question, or review message to print for the user.' }
|
|
132
|
+
},
|
|
133
|
+
required: ['question']
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
export interface TaskAgentResult {
|
|
139
|
+
result: string;
|
|
140
|
+
inputTokens: number;
|
|
141
|
+
outputTokens: number;
|
|
142
|
+
timeSeconds: number;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface AgentOptions {
|
|
146
|
+
model?: string;
|
|
147
|
+
systemPrompt?: string;
|
|
148
|
+
maxSteps?: number;
|
|
149
|
+
role?: 'architect' | 'coder' | 'debugger' | 'gatekeeper';
|
|
150
|
+
effort?: 'standard' | 'ultracode';
|
|
151
|
+
history?: Anthropic.MessageParam[];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function runTaskAgent(task: string, options: AgentOptions = {}): Promise<TaskAgentResult> {
|
|
155
|
+
const modelName = options.model || defaultModel;
|
|
156
|
+
const effort = options.effort || 'standard';
|
|
157
|
+
const maxSteps = options.maxSteps || (effort === 'ultracode' ? 45 : 25);
|
|
158
|
+
|
|
159
|
+
let filteredTools = toolsList;
|
|
160
|
+
if (options.role) {
|
|
161
|
+
switch (options.role) {
|
|
162
|
+
case 'architect':
|
|
163
|
+
filteredTools = toolsList.filter(t => ['readFile', 'listDir', 'grepSearch', 'searchWeb', 'searchCodeIndex', 'askUser'].includes(t.name));
|
|
164
|
+
break;
|
|
165
|
+
case 'coder':
|
|
166
|
+
filteredTools = toolsList.filter(t => ['readFile', 'writeFile', 'editFile', 'listDir', 'searchCodeIndex', 'askUser'].includes(t.name));
|
|
167
|
+
break;
|
|
168
|
+
case 'coder_codeact' as any:
|
|
169
|
+
filteredTools = toolsList.filter(t => ['readFile', 'writeFile', 'editFile', 'listDir', 'runCommand', 'searchCodeIndex', 'askUser'].includes(t.name));
|
|
170
|
+
break;
|
|
171
|
+
case 'debugger':
|
|
172
|
+
case 'gatekeeper':
|
|
173
|
+
filteredTools = toolsList.filter(t => ['readFile', 'listDir', 'runCommand', 'searchCodeIndex', 'askUser'].includes(t.name));
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Load system prompt from external text file if not overridden by options
|
|
179
|
+
let systemPrompt = options.systemPrompt || '';
|
|
180
|
+
if (!systemPrompt) {
|
|
181
|
+
let promptPath = path.join(__dirname, 'system-prompt.txt');
|
|
182
|
+
if (!fs.existsSync(promptPath)) {
|
|
183
|
+
promptPath = path.join(process.cwd(), 'src/orchestrator/system-prompt.txt');
|
|
184
|
+
}
|
|
185
|
+
if (fs.existsSync(promptPath)) {
|
|
186
|
+
systemPrompt = fs.readFileSync(promptPath, 'utf-8');
|
|
187
|
+
} else {
|
|
188
|
+
// Fallback
|
|
189
|
+
systemPrompt = `You are an elite coding agent designed to operate inside a codebase workspace.
|
|
190
|
+
Your primary objective is to fulfill the user's coding tasks accurately and cleanly.
|
|
191
|
+
|
|
192
|
+
You have access to a set of local tools to read, write, edit files and execute terminal commands.
|
|
193
|
+
Always prefer editing precise parts of files using editFile instead of overwriting the whole file with writeFile unless it is a new file.
|
|
194
|
+
When running commands, verify compilation and test outcomes. If a test fails, you must attempt to fix the issues (Self-Healing).
|
|
195
|
+
|
|
196
|
+
Be concise and professional. Formulate plans before making changes.`;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (effort === 'ultracode') {
|
|
201
|
+
const ultracodePrompt = `\n\n---
|
|
202
|
+
ULTRACODE MAXIMUM EFFORT PROTOCOL ENFORCED:
|
|
203
|
+
1. Spend extra reasoning tokens/steps to thoroughly evaluate alternative systems architectures and anticipate regression errors across the codebase.
|
|
204
|
+
2. In any task involving scripting, tools, or creating new files, you MUST determine and specify a precise, workspace-relative or absolute destination filepath (e.g. 'src/tools/math-helper.ts') and never leave it vague or ambiguous.
|
|
205
|
+
3. Validate that your proposed code modifications do not introduce unexpected side-effects or compromise codebase safety boundaries.
|
|
206
|
+
---`;
|
|
207
|
+
systemPrompt += ultracodePrompt;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Inject OS environment awareness to guide command execution safely
|
|
211
|
+
const osType = process.platform === 'win32' ? 'Windows (win32)' : process.platform;
|
|
212
|
+
const shellType = process.platform === 'win32' ? 'PowerShell or Command Prompt (cmd.exe)' : 'Bash or Sh';
|
|
213
|
+
const osContext = `\n\n---
|
|
214
|
+
CURRENT ENVIRONMENT & OS SAFETY GUIDELINES:
|
|
215
|
+
- Operating System: ${osType}
|
|
216
|
+
- Active Terminal Shell: ${shellType}
|
|
217
|
+
${process.platform === 'win32' ? `
|
|
218
|
+
IMPORTANT: You are running on Windows. Unix terminal commands such as 'cat', 'ls', 'grep', 'rm -rf', and pipes like '| head' or '| grep' are NOT supported natively.
|
|
219
|
+
- Use Windows/PowerShell equivalent commands (e.g., 'Get-Content' instead of 'cat', 'dir' or 'Get-ChildItem' instead of 'ls', and Node scripts or PowerShell commands for string filtering).
|
|
220
|
+
- Keep commands compatible with Windows environment syntax.` : `
|
|
221
|
+
- Use Unix terminal commands natively compatible with the active shell.`}
|
|
222
|
+
---`;
|
|
223
|
+
|
|
224
|
+
systemPrompt = systemPrompt + osContext;
|
|
225
|
+
|
|
226
|
+
console.log(`\x1b[36m[TaskAgent]\x1b[0m Starting task with model: \x1b[32m${modelName}\x1b[0m`);
|
|
227
|
+
console.log(`\x1b[36m[TaskAgent]\x1b[0m Task: "${task}"\n`);
|
|
228
|
+
|
|
229
|
+
let inputTokens = 0;
|
|
230
|
+
let outputTokens = 0;
|
|
231
|
+
const startTime = Date.now();
|
|
232
|
+
|
|
233
|
+
// Start message history
|
|
234
|
+
const messages: Anthropic.MessageParam[] = options.history || [];
|
|
235
|
+
if (task) {
|
|
236
|
+
messages.push({ role: 'user', content: task });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let step = 0;
|
|
240
|
+
while (step < maxSteps) {
|
|
241
|
+
step++;
|
|
242
|
+
const spinner = new Spinner(`TaskAgent - Step ${step}/${maxSteps}: Thinking...`);
|
|
243
|
+
spinner.start();
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
const response = await anthropic.messages.create({
|
|
247
|
+
model: modelName,
|
|
248
|
+
max_tokens: 4000,
|
|
249
|
+
system: systemPrompt,
|
|
250
|
+
messages: messages,
|
|
251
|
+
tools: filteredTools,
|
|
252
|
+
});
|
|
253
|
+
if (response.usage) {
|
|
254
|
+
inputTokens += response.usage.input_tokens || 0;
|
|
255
|
+
outputTokens += response.usage.output_tokens || 0;
|
|
256
|
+
}
|
|
257
|
+
spinner.stop(true, `TaskAgent - Step ${step}/${maxSteps}: Thought completed.`);
|
|
258
|
+
|
|
259
|
+
// Find text content if any
|
|
260
|
+
const textBlock = response.content.find(block => block.type === 'text') as Anthropic.TextBlock | undefined;
|
|
261
|
+
if (textBlock && textBlock.text) {
|
|
262
|
+
console.log('\x1b[34m┌── Agent Thought ───────────────────────────────────────────────────────────────┐\x1b[0m');
|
|
263
|
+
textBlock.text.split('\n').forEach(line => {
|
|
264
|
+
console.log(`\x1b[34m│\x1b[0m ${line}`);
|
|
265
|
+
});
|
|
266
|
+
console.log('\x1b[34m└────────────────────────────────────────────────────────────────────────────────┘\x1b[0m\n');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Add response to message history
|
|
270
|
+
messages.push({
|
|
271
|
+
role: 'assistant',
|
|
272
|
+
content: response.content
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Find tool calls
|
|
276
|
+
const toolCalls = response.content.filter(block => block.type === 'tool_use') as Anthropic.ToolUseBlock[];
|
|
277
|
+
|
|
278
|
+
if (toolCalls.length === 0) {
|
|
279
|
+
// No tools used, agent finished
|
|
280
|
+
console.log(`\x1b[32m[TaskAgent] Completed. No further tool calls needed.\x1b[0m`);
|
|
281
|
+
const timeSeconds = (Date.now() - startTime) / 1000;
|
|
282
|
+
recordTokens(inputTokens, outputTokens);
|
|
283
|
+
return {
|
|
284
|
+
result: textBlock ? textBlock.text : 'Task completed successfully.',
|
|
285
|
+
inputTokens,
|
|
286
|
+
outputTokens,
|
|
287
|
+
timeSeconds
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const toolResults: Anthropic.ToolResultBlockParam[] = [];
|
|
292
|
+
|
|
293
|
+
for (const call of toolCalls) {
|
|
294
|
+
const { name, id, input } = call;
|
|
295
|
+
console.log('\x1b[33m┌── Tool Call Interception ──────────────────────────────────────────────────────┐\x1b[0m');
|
|
296
|
+
console.log(`\x1b[33m│\x1b[0m Tool: \x1b[1m${name}\x1b[0m`);
|
|
297
|
+
console.log(`\x1b[33m│\x1b[0m Input: ${JSON.stringify(input)}`);
|
|
298
|
+
|
|
299
|
+
EventStream.getInstance().publish(
|
|
300
|
+
'agent',
|
|
301
|
+
'action',
|
|
302
|
+
name,
|
|
303
|
+
JSON.stringify(input)
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
let outputContent = '';
|
|
307
|
+
let isError = false;
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
const args = input as any;
|
|
311
|
+
switch (name) {
|
|
312
|
+
case 'readFile':
|
|
313
|
+
outputContent = fileTools.readFile(args.path);
|
|
314
|
+
break;
|
|
315
|
+
case 'writeFile': {
|
|
316
|
+
const policy = rulez.checkFileEdit(args.path, args.content);
|
|
317
|
+
if (!policy.allowed) {
|
|
318
|
+
outputContent = policy.message;
|
|
319
|
+
isError = true;
|
|
320
|
+
} else {
|
|
321
|
+
fileTools.writeFile(args.path, args.content);
|
|
322
|
+
outputContent = `Successfully wrote to file ${args.path}`;
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
case 'editFile': {
|
|
327
|
+
const policy = rulez.checkFileEdit(args.path, args.replacementContent);
|
|
328
|
+
if (!policy.allowed) {
|
|
329
|
+
outputContent = policy.message;
|
|
330
|
+
isError = true;
|
|
331
|
+
} else {
|
|
332
|
+
fileTools.editFile(args.path, args.targetContent, args.replacementContent);
|
|
333
|
+
outputContent = `Successfully edited file ${args.path}`;
|
|
334
|
+
}
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
case 'listDir':
|
|
338
|
+
const files = fileTools.listDir(args.path || '.');
|
|
339
|
+
outputContent = JSON.stringify(files, null, 2);
|
|
340
|
+
break;
|
|
341
|
+
case 'grepSearch':
|
|
342
|
+
const matches = fileTools.grepSearch(args.pattern, args.path || '.');
|
|
343
|
+
outputContent = JSON.stringify(matches, null, 2);
|
|
344
|
+
break;
|
|
345
|
+
case 'runCommand': {
|
|
346
|
+
console.log(`\x1b[33m│\x1b[0m \x1b[31m[Security Warning]\x1b[0m Agent wants to run: "\x1b[31m${args.command}\x1b[0m"`);
|
|
347
|
+
const policy = rulez.checkCommand(args.command);
|
|
348
|
+
if (!policy.allowed) {
|
|
349
|
+
console.log(`\x1b[33m│\x1b[0m \x1b[31m[RuleZ Blocked]\x1b[0m ${policy.message}`);
|
|
350
|
+
outputContent = `Exit Code: ${policy.exitCode}\nError: ${policy.message}`;
|
|
351
|
+
isError = true;
|
|
352
|
+
} else {
|
|
353
|
+
// Run the command
|
|
354
|
+
const cmdRes = await shellTools.runCommand(args.command);
|
|
355
|
+
outputContent = `Exit Code: ${cmdRes.exitCode}\nStdout:\n${cmdRes.stdout}\nStderr:\n${cmdRes.stderr}`;
|
|
356
|
+
if (cmdRes.error) {
|
|
357
|
+
outputContent += `\nExecution Error: ${cmdRes.error}`;
|
|
358
|
+
}
|
|
359
|
+
if (cmdRes.exitCode !== 0) {
|
|
360
|
+
isError = true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
case 'searchWeb':
|
|
366
|
+
outputContent = await searchTools.searchWeb(args.query);
|
|
367
|
+
break;
|
|
368
|
+
case 'searchCodeIndex': {
|
|
369
|
+
const { queryCodeIndex } = require('../tools/indexer');
|
|
370
|
+
outputContent = queryCodeIndex(args.query);
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
case 'askUser': {
|
|
374
|
+
const rl = require('readline').createInterface({
|
|
375
|
+
input: process.stdin,
|
|
376
|
+
output: process.stdout
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
console.log(`\n\x1b[33;1m🤖 Agent Question: ${args.question}\x1b[0m`);
|
|
380
|
+
|
|
381
|
+
const answer: string = await new Promise((resolvePrompt) => {
|
|
382
|
+
rl.question('\x1b[32;1mUser Response > \x1b[0m', (inputStr: string) => {
|
|
383
|
+
rl.close();
|
|
384
|
+
resolvePrompt(inputStr);
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
outputContent = answer;
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
default:
|
|
392
|
+
outputContent = `Error: Tool '${name}' is not recognized.`;
|
|
393
|
+
isError = true;
|
|
394
|
+
}
|
|
395
|
+
} catch (err: any) {
|
|
396
|
+
outputContent = `Error executing tool '${name}': ${err.message}`;
|
|
397
|
+
isError = true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
EventStream.getInstance().publish(
|
|
401
|
+
'environment',
|
|
402
|
+
'observation',
|
|
403
|
+
name + 'Observation',
|
|
404
|
+
outputContent,
|
|
405
|
+
{ isError }
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
console.log(`\x1b[33m│\x1b[0m Status: ${isError ? '\x1b[31mFailed ❌' : '\x1b[32mSucceeded ✅'}\x1b[0m (Output length: ${outputContent.length} chars)`);
|
|
409
|
+
console.log('\x1b[33m└────────────────────────────────────────────────────────────────────────────────┘\x1b[0m\n');
|
|
410
|
+
|
|
411
|
+
toolResults.push({
|
|
412
|
+
type: 'tool_result',
|
|
413
|
+
tool_use_id: id,
|
|
414
|
+
content: [{ type: 'text', text: outputContent }],
|
|
415
|
+
is_error: isError
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Add tool results to message history
|
|
420
|
+
messages.push({
|
|
421
|
+
role: 'user',
|
|
422
|
+
content: toolResults
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
} catch (apiError: any) {
|
|
426
|
+
spinner.stop(false, `TaskAgent - Step ${step}/${maxSteps}: API call failed.`);
|
|
427
|
+
console.error(`\x1b[31m[API Error]\x1b[0m Error communicating with Anthropic API: ${apiError.message}`);
|
|
428
|
+
if (apiError.stack) {
|
|
429
|
+
console.error(apiError.stack);
|
|
430
|
+
}
|
|
431
|
+
throw apiError;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const timeSeconds = (Date.now() - startTime) / 1000;
|
|
436
|
+
recordTokens(inputTokens, outputTokens);
|
|
437
|
+
throw new Error(`Agent reached maximum steps (${maxSteps}) without finishing. Used tokens: Input=${inputTokens}, Output=${outputTokens}, Time=${timeSeconds}s`);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function recordTokens(input: number, output: number) {
|
|
441
|
+
try {
|
|
442
|
+
const tokenPath = path.join(process.cwd(), 'session-tokens.json');
|
|
443
|
+
let data = { inputTokens: 0, outputTokens: 0 };
|
|
444
|
+
if (fs.existsSync(tokenPath)) {
|
|
445
|
+
try {
|
|
446
|
+
data = JSON.parse(fs.readFileSync(tokenPath, 'utf-8'));
|
|
447
|
+
} catch (e) {}
|
|
448
|
+
}
|
|
449
|
+
data.inputTokens = (data.inputTokens || 0) + input;
|
|
450
|
+
data.outputTokens = (data.outputTokens || 0) + output;
|
|
451
|
+
fs.writeFileSync(tokenPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
452
|
+
|
|
453
|
+
updateEvolutionState({
|
|
454
|
+
tokensUsed: {
|
|
455
|
+
input: data.inputTokens,
|
|
456
|
+
output: data.outputTokens,
|
|
457
|
+
total: data.inputTokens + data.outputTokens
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
} catch (err) {}
|
|
461
|
+
}
|