protoagent 0.0.2 ā 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agentic-loop.js +262 -29
- package/dist/config/client.js +157 -18
- package/dist/config/commands.js +29 -28
- package/dist/config/setup.js +20 -19
- package/dist/config/system-prompt.js +62 -0
- package/dist/index.js +84 -17
- package/dist/tools/index.js +72 -16
- package/dist/tools/run-shell-command.js +15 -14
- package/dist/utils/conversation-compactor.js +7 -6
- package/dist/utils/cost-tracker.js +5 -4
- package/dist/utils/file-operations-approval.js +33 -45
- package/dist/utils/logger.js +176 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,7 +18,9 @@ program
|
|
|
18
18
|
.description('Interactive AI coding agent CLI with file system capabilities')
|
|
19
19
|
.version('1.0.0')
|
|
20
20
|
.option('--dangerously-accept-all', 'Auto-approve all shell commands and file operations without confirmation (DANGEROUS)')
|
|
21
|
-
.option('--log-level <level>', 'Set logging level (ERROR, WARN, INFO, DEBUG, TRACE)', 'INFO')
|
|
21
|
+
.option('--log-level <level>', 'Set logging level (ERROR, WARN, INFO, DEBUG, TRACE)', 'INFO')
|
|
22
|
+
.option('--log-to-file', 'Enable file logging to ~/.protoagent/logs/')
|
|
23
|
+
.option('--log-dir <path>', 'Custom directory for log files (requires --log-to-file)');
|
|
22
24
|
// Configuration management command
|
|
23
25
|
program
|
|
24
26
|
.command('config')
|
|
@@ -41,73 +43,99 @@ program
|
|
|
41
43
|
await resetConfiguration();
|
|
42
44
|
}
|
|
43
45
|
else {
|
|
44
|
-
|
|
46
|
+
logger.consoleLog('Use --help to see available configuration options');
|
|
45
47
|
}
|
|
46
48
|
});
|
|
47
49
|
async function respondToUser(userInput) {
|
|
50
|
+
logger.debug('š¤ respondToUser called', { component: 'Main', inputLength: userInput.length });
|
|
48
51
|
if (agenticLoop) {
|
|
52
|
+
logger.debug('ā
Agentic loop available - processing input');
|
|
49
53
|
await agenticLoop.processUserInput(userInput);
|
|
54
|
+
logger.debug('ā
Agentic loop processing complete');
|
|
50
55
|
}
|
|
51
56
|
else {
|
|
57
|
+
logger.error('ā Agentic loop not initialized', { component: 'Main' });
|
|
52
58
|
console.error('ā Agentic loop not initialized');
|
|
53
59
|
}
|
|
54
60
|
}
|
|
55
61
|
async function initializeConfig() {
|
|
62
|
+
logger.debug('š§ Starting configuration initialization', { component: 'Config' });
|
|
56
63
|
// Check if configuration exists
|
|
57
64
|
if (await hasConfig()) {
|
|
65
|
+
logger.debug('š Configuration file exists - loading', { component: 'Config' });
|
|
58
66
|
try {
|
|
59
67
|
config = await loadConfig();
|
|
68
|
+
logger.debug('ā
Configuration loaded successfully', { component: 'Config', provider: config.provider, model: config.model });
|
|
60
69
|
if (!validateConfig(config)) {
|
|
61
|
-
|
|
70
|
+
logger.warn('ā ļø Configuration validation failed', { component: 'Config' });
|
|
71
|
+
logger.consoleLog('ā ļø Configuration is invalid or corrupted.');
|
|
72
|
+
logger.info('ā ļø Invalid config message displayed to user');
|
|
62
73
|
if (await promptReconfigure()) {
|
|
74
|
+
logger.debug('š User chose to reconfigure', { component: 'Config' });
|
|
63
75
|
config = await setupConfig();
|
|
64
76
|
}
|
|
65
77
|
else {
|
|
66
|
-
|
|
78
|
+
logger.error('ā User declined reconfiguration', { component: 'Config' });
|
|
79
|
+
logger.consoleLog('Cannot proceed without valid configuration.');
|
|
80
|
+
logger.error('ā Cannot proceed message displayed');
|
|
67
81
|
process.exit(1);
|
|
68
82
|
}
|
|
69
83
|
}
|
|
70
84
|
}
|
|
71
85
|
catch (error) {
|
|
72
|
-
|
|
86
|
+
logger.error('ā Error loading configuration', { component: 'Config', error: error.message });
|
|
87
|
+
logger.consoleLog(`ā Error loading configuration: ${error.message}`);
|
|
88
|
+
logger.info('ā Config error message displayed to user');
|
|
73
89
|
if (await promptReconfigure()) {
|
|
90
|
+
logger.debug('š User chose to reconfigure after error', { component: 'Config' });
|
|
74
91
|
config = await setupConfig();
|
|
75
92
|
}
|
|
76
93
|
else {
|
|
77
|
-
|
|
94
|
+
logger.error('ā User declined reconfiguration after error', { component: 'Config' });
|
|
95
|
+
logger.consoleLog('Cannot proceed without valid configuration.');
|
|
96
|
+
logger.error('ā Cannot proceed after error message displayed');
|
|
78
97
|
process.exit(1);
|
|
79
98
|
}
|
|
80
99
|
}
|
|
81
100
|
}
|
|
82
101
|
else {
|
|
102
|
+
logger.debug('š No configuration exists - running setup', { component: 'Config' });
|
|
83
103
|
// No configuration exists, run setup
|
|
84
104
|
config = await setupConfig();
|
|
85
105
|
}
|
|
106
|
+
logger.debug('š§ Setting tools configuration', { component: 'Config' });
|
|
86
107
|
// Set config for tools
|
|
87
108
|
setToolsConfig(config);
|
|
109
|
+
logger.debug('š¤ Initializing OpenAI client', { component: 'Config', provider: config.provider });
|
|
88
110
|
// Initialize OpenAI client with the configuration
|
|
89
111
|
try {
|
|
90
112
|
openaiClient = createOpenAIClient(config);
|
|
113
|
+
if (!openaiClient) {
|
|
114
|
+
throw new Error('Failed to create OpenAI client');
|
|
115
|
+
}
|
|
116
|
+
logger.debug('ā
OpenAI client created successfully', { component: 'Config' });
|
|
117
|
+
logger.debug('š Creating agentic loop', { component: 'Config', maxIterations: 100, streamOutput: true });
|
|
91
118
|
// Initialize the agentic loop
|
|
92
119
|
agenticLoop = await createAgenticLoop(openaiClient, config, {
|
|
93
120
|
maxIterations: 100,
|
|
94
121
|
streamOutput: true
|
|
95
122
|
});
|
|
96
|
-
logger.debug(`ā
Successfully initialized ProtoAgent
|
|
123
|
+
logger.debug(`ā
Successfully initialized ProtoAgent`, { component: 'Config' });
|
|
97
124
|
}
|
|
98
125
|
catch (error) {
|
|
99
|
-
logger.error(`ā Failed to initialize OpenAI client: ${error.message}
|
|
126
|
+
logger.error(`ā Failed to initialize OpenAI client: ${error.message}`, { component: 'Config', error: error.message });
|
|
100
127
|
process.exit(1);
|
|
101
128
|
}
|
|
102
129
|
}
|
|
103
130
|
async function startInteractiveMode() {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
console.log('š§ Ask me to create files, analyze code, run npm commands, git operations, or execute any shell command.');
|
|
131
|
+
logger.debug('š Starting interactive mode initialization');
|
|
132
|
+
logger.debug(' Parsing command line options');
|
|
107
133
|
// Check for options
|
|
108
134
|
const options = program.opts();
|
|
135
|
+
logger.debug('š Command line options parsed', { component: 'Main', options });
|
|
109
136
|
// Set log level
|
|
110
137
|
if (options.logLevel) {
|
|
138
|
+
logger.debug('š§ Setting log level', { component: 'Main', requestedLevel: options.logLevel });
|
|
111
139
|
const logLevel = LogLevel[options.logLevel.toUpperCase()];
|
|
112
140
|
if (logLevel !== undefined) {
|
|
113
141
|
logger.setLevel(logLevel);
|
|
@@ -117,19 +145,50 @@ async function startInteractiveMode() {
|
|
|
117
145
|
logger.warn(`ā ļø Invalid log level: ${options.logLevel}. Using INFO.`);
|
|
118
146
|
}
|
|
119
147
|
}
|
|
148
|
+
// Enable file logging if requested (BEFORE welcome messages)
|
|
149
|
+
if (options.logToFile) {
|
|
150
|
+
logger.debug('š Enabling file logging', { component: 'Main', logDir: options.logDir || 'default' });
|
|
151
|
+
logger.enableFileLogging(options.logDir);
|
|
152
|
+
logger.info('š File logging enabled');
|
|
153
|
+
}
|
|
120
154
|
// Check for dangerous flag
|
|
121
155
|
if (options.dangerouslyAcceptAll) {
|
|
156
|
+
logger.debug('ā ļø Enabling dangerous mode', { component: 'Main' });
|
|
122
157
|
logger.warn('ā ļø DANGER MODE: All shell commands and file operations will be auto-approved without confirmation!');
|
|
123
158
|
setToolsDangerouslyAcceptAll(true);
|
|
124
159
|
}
|
|
125
|
-
|
|
160
|
+
// NOW display welcome messages (after file logging is enabled)
|
|
161
|
+
logger.consoleLog('š¤ Welcome to ProtoAgent - Your AI Coding Assistant with File System & Shell Powers!');
|
|
162
|
+
logger.debug('š¤ Welcome message displayed');
|
|
163
|
+
logger.consoleLog('š” I can read, write, edit, search files and run shell commands in your project.');
|
|
164
|
+
logger.consoleLog('š§ Ask me to create files, analyze code, run npm commands, git operations, or execute any shell command.');
|
|
165
|
+
logger.debug('š” Capability messages displayed');
|
|
166
|
+
logger.consoleLog('šŖ Type "exit" to quit.');
|
|
167
|
+
logger.debug('šŖ Exit instruction displayed');
|
|
168
|
+
logger.debug('š§ Initializing configuration');
|
|
126
169
|
// Initialize configuration
|
|
127
170
|
await initializeConfig();
|
|
128
|
-
|
|
171
|
+
// Set up cleanup handlers for file logging
|
|
172
|
+
logger.debug('š”ļø Setting up cleanup handlers');
|
|
173
|
+
process.on('SIGINT', () => {
|
|
174
|
+
logger.debug('š SIGINT received - cleaning up');
|
|
175
|
+
logger.consoleLog('\nš Goodbye! Happy coding!');
|
|
176
|
+
logger.info('š Goodbye message displayed (SIGINT)');
|
|
177
|
+
logger.disableFileLogging();
|
|
178
|
+
process.exit(0);
|
|
179
|
+
});
|
|
180
|
+
process.on('SIGTERM', () => {
|
|
181
|
+
logger.debug('š SIGTERM received - cleaning up');
|
|
182
|
+
logger.disableFileLogging();
|
|
183
|
+
process.exit(0);
|
|
184
|
+
});
|
|
185
|
+
logger.consoleLog(`š§ Using ${config.provider} with model: ${config.model}`);
|
|
129
186
|
// Show current working directory
|
|
130
|
-
logger.
|
|
131
|
-
|
|
187
|
+
logger.consoleLog(`š Working directory: ${process.cwd()}`);
|
|
188
|
+
logger.debug('š Working directory displayed to user');
|
|
189
|
+
logger.debug('š Starting main interaction loop');
|
|
132
190
|
while (true) {
|
|
191
|
+
logger.trace('š„ Waiting for user input');
|
|
133
192
|
const { input } = await inquirer.prompt([
|
|
134
193
|
{
|
|
135
194
|
type: 'input',
|
|
@@ -137,15 +196,23 @@ async function startInteractiveMode() {
|
|
|
137
196
|
message: 'protoagent>',
|
|
138
197
|
}
|
|
139
198
|
]);
|
|
199
|
+
logger.debug('š User input received', { component: 'Main', inputLength: input.length, isEmpty: input.trim() === '' });
|
|
140
200
|
if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
|
|
141
|
-
|
|
201
|
+
logger.debug('šŖ Exit command received');
|
|
202
|
+
logger.consoleLog('š Goodbye! Happy coding!');
|
|
203
|
+
logger.info('š Goodbye message displayed (normal exit)');
|
|
204
|
+
// Clean up file logging
|
|
205
|
+
logger.debug('š§¹ Cleaning up file logging');
|
|
206
|
+
logger.disableFileLogging();
|
|
142
207
|
break;
|
|
143
208
|
}
|
|
144
209
|
if (input.trim() === '') {
|
|
210
|
+
logger.trace('āļø Empty input - continuing');
|
|
145
211
|
continue;
|
|
146
212
|
}
|
|
213
|
+
logger.debug('š¤ Forwarding input to agent', { component: 'Main', input: input.slice(0, 100) + (input.length > 100 ? '...' : '') });
|
|
147
214
|
await respondToUser(input);
|
|
148
|
-
|
|
215
|
+
logger.trace('ā
User input processing complete');
|
|
149
216
|
}
|
|
150
217
|
}
|
|
151
218
|
program
|
package/dist/tools/index.js
CHANGED
|
@@ -29,6 +29,7 @@ import { viewDirectoryTree } from './view-directory-tree.js';
|
|
|
29
29
|
import { searchFiles } from './search-files.js';
|
|
30
30
|
import { runShellCommand } from './run-shell-command.js';
|
|
31
31
|
import { handleTodoTool } from './todo.js';
|
|
32
|
+
import { logger } from '../utils/logger.js';
|
|
32
33
|
// Consolidated tool definitions
|
|
33
34
|
export const tools = [
|
|
34
35
|
readFileTool,
|
|
@@ -44,41 +45,96 @@ export const tools = [
|
|
|
44
45
|
];
|
|
45
46
|
// Tool handler function
|
|
46
47
|
export async function handleToolCall(toolName, args) {
|
|
48
|
+
logger.debug('š ļø handleToolCall invoked', {
|
|
49
|
+
component: 'ToolHandler',
|
|
50
|
+
toolName,
|
|
51
|
+
argsKeys: Object.keys(args || {})
|
|
52
|
+
});
|
|
47
53
|
try {
|
|
54
|
+
const startTime = Date.now();
|
|
55
|
+
let result;
|
|
48
56
|
switch (toolName) {
|
|
49
57
|
case 'read_file':
|
|
58
|
+
logger.debug('š Executing read_file', { component: 'ToolHandler', filePath: args.file_path });
|
|
50
59
|
const content = await readFile(args.file_path, args.offset, args.limit);
|
|
51
|
-
|
|
60
|
+
result = `File content of ${args.file_path}:\n\n${content}`;
|
|
61
|
+
break;
|
|
52
62
|
case 'write_file':
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
logger.debug('āļø Executing write_file', { component: 'ToolHandler', filePath: args.file_path, contentLength: args.content?.length });
|
|
64
|
+
result = await writeFile(args.file_path, args.content);
|
|
65
|
+
break;
|
|
55
66
|
case 'edit_file':
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
logger.debug('āļø Executing edit_file', {
|
|
68
|
+
component: 'ToolHandler',
|
|
69
|
+
filePath: args.file_path,
|
|
70
|
+
oldStringLength: args.old_string?.length,
|
|
71
|
+
newStringLength: args.new_string?.length,
|
|
72
|
+
expectedReplacements: args.expected_replacements
|
|
73
|
+
});
|
|
74
|
+
result = await editFile(args.file_path, args.old_string, args.new_string, args.expected_replacements);
|
|
75
|
+
break;
|
|
58
76
|
case 'list_directory':
|
|
77
|
+
logger.debug('š Executing list_directory', { component: 'ToolHandler', directoryPath: args.directory_path });
|
|
59
78
|
const listResult = await listDirectory(args.directory_path);
|
|
60
|
-
|
|
79
|
+
result = `Contents of ${args.directory_path}:\n\n${listResult}`;
|
|
80
|
+
break;
|
|
61
81
|
case 'create_directory':
|
|
62
|
-
|
|
63
|
-
|
|
82
|
+
logger.debug('š Executing create_directory', { component: 'ToolHandler', directoryPath: args.directory_path });
|
|
83
|
+
result = await createDirectory(args.directory_path);
|
|
84
|
+
break;
|
|
64
85
|
case 'view_directory_tree':
|
|
65
|
-
|
|
66
|
-
|
|
86
|
+
logger.debug('š³ Executing view_directory_tree', {
|
|
87
|
+
component: 'ToolHandler',
|
|
88
|
+
directoryPath: args.directory_path,
|
|
89
|
+
maxDepth: args.max_depth
|
|
90
|
+
});
|
|
91
|
+
result = await viewDirectoryTree(args.directory_path, args.max_depth);
|
|
92
|
+
break;
|
|
67
93
|
case 'search_files':
|
|
68
|
-
|
|
69
|
-
|
|
94
|
+
logger.debug('š Executing search_files', {
|
|
95
|
+
component: 'ToolHandler',
|
|
96
|
+
searchTerm: args.search_term,
|
|
97
|
+
directoryPath: args.directory_path,
|
|
98
|
+
caseSensitive: args.case_sensitive,
|
|
99
|
+
fileExtensions: args.file_extensions
|
|
100
|
+
});
|
|
101
|
+
result = await searchFiles(args.search_term, args.directory_path, args.case_sensitive, args.file_extensions);
|
|
102
|
+
break;
|
|
70
103
|
case 'run_shell_command':
|
|
71
|
-
|
|
72
|
-
|
|
104
|
+
logger.debug('š Executing run_shell_command', {
|
|
105
|
+
component: 'ToolHandler',
|
|
106
|
+
command: args.command,
|
|
107
|
+
argsCount: args.args?.length || 0,
|
|
108
|
+
timeout: args.timeout_ms || 30000
|
|
109
|
+
});
|
|
110
|
+
result = await runShellCommand(args.command, args.args || [], args.timeout_ms || 30000);
|
|
111
|
+
break;
|
|
73
112
|
case 'todo_read':
|
|
74
113
|
case 'todo_write':
|
|
75
|
-
|
|
114
|
+
logger.debug('š Executing todo tool', { component: 'ToolHandler', toolName });
|
|
115
|
+
result = await handleTodoTool(toolName, args);
|
|
116
|
+
break;
|
|
76
117
|
default:
|
|
77
|
-
|
|
118
|
+
logger.error('ā Unknown tool requested', { component: 'ToolHandler', toolName });
|
|
119
|
+
result = `Error: Unknown tool ${toolName}`;
|
|
78
120
|
}
|
|
121
|
+
const executionTime = Date.now() - startTime;
|
|
122
|
+
logger.debug('ā
Tool execution completed', {
|
|
123
|
+
component: 'ToolHandler',
|
|
124
|
+
toolName,
|
|
125
|
+
executionTime,
|
|
126
|
+
resultLength: result.length,
|
|
127
|
+
success: !result.startsWith('Error:')
|
|
128
|
+
});
|
|
129
|
+
return result;
|
|
79
130
|
}
|
|
80
131
|
catch (error) {
|
|
81
132
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
133
|
+
logger.error('ā Tool execution failed with exception', {
|
|
134
|
+
component: 'ToolHandler',
|
|
135
|
+
toolName,
|
|
136
|
+
error: errorMessage
|
|
137
|
+
});
|
|
82
138
|
return `Error executing ${toolName}: ${errorMessage}`;
|
|
83
139
|
}
|
|
84
140
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
3
4
|
// Current working directory for file operations
|
|
4
5
|
const workingDirectory = process.cwd();
|
|
5
6
|
// Global flags and session state
|
|
@@ -39,10 +40,10 @@ async function runShellCommandWithRetry(command, args = [], timeoutMs = 30000, r
|
|
|
39
40
|
// Check if we should auto-approve (only show approval prompt on first attempt)
|
|
40
41
|
if (retryCount === 0) {
|
|
41
42
|
if (dangerouslyAcceptAll) {
|
|
42
|
-
|
|
43
|
+
logger.consoleLog(`š Auto-executing (--dangerously-accept-all): ${commandString}`);
|
|
43
44
|
}
|
|
44
45
|
else if (approvedCommandsForSession.has(baseCommand)) {
|
|
45
|
-
|
|
46
|
+
logger.consoleLog(`š Auto-executing (${baseCommand} approved for session): ${commandString}`);
|
|
46
47
|
}
|
|
47
48
|
else {
|
|
48
49
|
// Check if it's a safe command
|
|
@@ -54,12 +55,12 @@ async function runShellCommandWithRetry(command, args = [], timeoutMs = 30000, r
|
|
|
54
55
|
(!normalizedSafe.includes(' ') && normalizedFull.split(' ')[0] === normalizedSafe);
|
|
55
56
|
});
|
|
56
57
|
if (isSafeCommand) {
|
|
57
|
-
|
|
58
|
+
logger.consoleLog(`š¢ Executing safe command: ${commandString}`);
|
|
58
59
|
}
|
|
59
60
|
else {
|
|
60
61
|
// Require user confirmation with enhanced options
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
logger.consoleLog(`\nš Shell Command Requested: ${commandString}`);
|
|
63
|
+
logger.consoleLog(`š Working Directory: ${workingDirectory}`);
|
|
63
64
|
const { choice } = await inquirer.prompt([
|
|
64
65
|
{
|
|
65
66
|
type: 'list',
|
|
@@ -75,12 +76,12 @@ async function runShellCommandWithRetry(command, args = [], timeoutMs = 30000, r
|
|
|
75
76
|
]);
|
|
76
77
|
switch (choice) {
|
|
77
78
|
case 'execute':
|
|
78
|
-
|
|
79
|
+
logger.consoleLog(`š Executing: ${commandString}`);
|
|
79
80
|
break;
|
|
80
81
|
case 'approve_session':
|
|
81
82
|
approvedCommandsForSession.add(baseCommand);
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
logger.consoleLog(`š "${baseCommand}" approved for session - all future ${baseCommand} commands will auto-execute`);
|
|
84
|
+
logger.consoleLog(`š Executing: ${commandString}`);
|
|
84
85
|
break;
|
|
85
86
|
case 'cancel':
|
|
86
87
|
return getSuggestion(commandString);
|
|
@@ -91,7 +92,7 @@ async function runShellCommandWithRetry(command, args = [], timeoutMs = 30000, r
|
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
else {
|
|
94
|
-
|
|
95
|
+
logger.consoleLog(`š Retry attempt ${retryCount}/${maxRetries}: ${commandString}`);
|
|
95
96
|
}
|
|
96
97
|
return new Promise((resolve, reject) => {
|
|
97
98
|
const child = spawn(command, args, {
|
|
@@ -154,18 +155,18 @@ async function runShellCommandWithRetry(command, args = [], timeoutMs = 30000, r
|
|
|
154
155
|
}
|
|
155
156
|
async function analyzeTimeoutAndRetry(command, args = [], originalTimeout, retryCount) {
|
|
156
157
|
const commandString = `${command} ${args.join(' ')}`;
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
logger.consoleLog(`ā±ļø Command timed out after ${originalTimeout}ms: ${commandString}`);
|
|
159
|
+
logger.consoleLog(`š Analyzing timeout cause and preparing retry...`);
|
|
159
160
|
// Analyze the command to understand why it might have timed out
|
|
160
161
|
const analysis = analyzeCommandForTimeout(command, args);
|
|
161
|
-
|
|
162
|
+
logger.consoleLog(`š” Timeout analysis: ${analysis.reason}`);
|
|
162
163
|
if (analysis.suggestedArgs.length > 0) {
|
|
163
|
-
|
|
164
|
+
logger.consoleLog(`š§ Suggested fix: ${command} ${analysis.suggestedArgs.join(' ')}`);
|
|
164
165
|
// Try with suggested arguments
|
|
165
166
|
return await runShellCommandWithRetry(command, analysis.suggestedArgs, analysis.suggestedTimeout, retryCount + 1);
|
|
166
167
|
}
|
|
167
168
|
else if (analysis.suggestedTimeout > originalTimeout) {
|
|
168
|
-
|
|
169
|
+
logger.consoleLog(`ā° Increasing timeout to ${analysis.suggestedTimeout}ms for long-running operation`);
|
|
169
170
|
// Try with longer timeout
|
|
170
171
|
return await runShellCommandWithRetry(command, args, analysis.suggestedTimeout, retryCount + 1);
|
|
171
172
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Conversation history compaction utilities
|
|
3
3
|
*/
|
|
4
|
+
import { logger } from './logger.js';
|
|
4
5
|
/**
|
|
5
6
|
* Provides the system prompt for the history compression process.
|
|
6
7
|
* This prompt instructs the model to act as a specialized state manager,
|
|
@@ -69,14 +70,14 @@ The structure MUST be as follows:
|
|
|
69
70
|
* Compact conversation history by creating a summary
|
|
70
71
|
*/
|
|
71
72
|
export async function compactConversation(client, model, messages, recentMessagesToKeep = 5) {
|
|
72
|
-
|
|
73
|
+
logger.consoleLog('\nšļø Compacting conversation history to manage context window...');
|
|
73
74
|
// Keep the system message and recent messages
|
|
74
75
|
const systemMessage = messages.find(m => m.role === 'system');
|
|
75
76
|
const recentMessages = messages.slice(-recentMessagesToKeep);
|
|
76
77
|
// Get the history to compress (excluding system and recent messages)
|
|
77
78
|
const historyToCompress = messages.slice(systemMessage ? 1 : 0, -recentMessagesToKeep);
|
|
78
79
|
if (historyToCompress.length === 0) {
|
|
79
|
-
|
|
80
|
+
logger.consoleLog(' No history to compress, keeping current messages');
|
|
80
81
|
return messages;
|
|
81
82
|
}
|
|
82
83
|
try {
|
|
@@ -100,7 +101,7 @@ export async function compactConversation(client, model, messages, recentMessage
|
|
|
100
101
|
});
|
|
101
102
|
const compressionSummary = response.choices[0]?.message?.content;
|
|
102
103
|
if (!compressionSummary) {
|
|
103
|
-
|
|
104
|
+
logger.consoleLog(' Failed to generate compression summary, keeping original messages');
|
|
104
105
|
return messages;
|
|
105
106
|
}
|
|
106
107
|
// Create the compressed history message
|
|
@@ -118,12 +119,12 @@ export async function compactConversation(client, model, messages, recentMessage
|
|
|
118
119
|
compactedMessages.push(compressedMessage);
|
|
119
120
|
// Add recent messages
|
|
120
121
|
compactedMessages.push(...recentMessages);
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
logger.consoleLog(` ā
Compressed ${historyToCompress.length} messages into summary`);
|
|
123
|
+
logger.consoleLog(` š Keeping ${recentMessages.length} recent messages`);
|
|
123
124
|
return compactedMessages;
|
|
124
125
|
}
|
|
125
126
|
catch (error) {
|
|
126
|
-
|
|
127
|
+
logger.consoleLog(` ā Compression failed: ${error}. Keeping original messages.`);
|
|
127
128
|
return messages;
|
|
128
129
|
}
|
|
129
130
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cost tracking and token counting utilities
|
|
3
3
|
*/
|
|
4
|
+
import { logger } from './logger.js';
|
|
4
5
|
/**
|
|
5
6
|
* Rough token estimation for OpenAI models
|
|
6
7
|
* This is approximate - actual tokenization may vary
|
|
@@ -72,11 +73,11 @@ export function getContextInfo(messages, modelConfig) {
|
|
|
72
73
|
* Log usage and cost information
|
|
73
74
|
*/
|
|
74
75
|
export function logUsageInfo(usage, contextInfo, modelConfig) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
logger.consoleLog(`š° Usage: ${usage.inputTokens} in + ${usage.outputTokens} out = ${usage.totalTokens} tokens`);
|
|
77
|
+
logger.consoleLog(`šø Estimated cost: $${usage.estimatedCost.toFixed(6)}`);
|
|
78
|
+
logger.consoleLog(`š Context: ${contextInfo.currentTokens}/${contextInfo.maxTokens} tokens (${contextInfo.utilizationPercentage.toFixed(1)}%)`);
|
|
78
79
|
if (contextInfo.needsCompaction) {
|
|
79
|
-
|
|
80
|
+
logger.consoleLog(`ā ļø Context approaching limit - automatic compaction will trigger soon`);
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
/**
|
|
@@ -33,21 +33,9 @@ export async function requestFileOperationApproval(context) {
|
|
|
33
33
|
return true;
|
|
34
34
|
}
|
|
35
35
|
// Show detailed information about the operation
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (contentPreview) {
|
|
40
|
-
console.log(`\nš Content Preview:`);
|
|
41
|
-
const lines = contentPreview.split('\n');
|
|
42
|
-
if (lines.length > 10) {
|
|
43
|
-
console.log(lines.slice(0, 5).join('\n'));
|
|
44
|
-
console.log(`... (${lines.length - 10} more lines) ...`);
|
|
45
|
-
console.log(lines.slice(-5).join('\n'));
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
console.log(contentPreview);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
36
|
+
logger.consoleLog(`\nš File Operation Requested: ${operation.toUpperCase()}`);
|
|
37
|
+
logger.consoleLog(`š File: ${filePath}`);
|
|
38
|
+
logger.consoleLog(`š Description: ${description}`);
|
|
51
39
|
// Ask for user approval with enhanced options
|
|
52
40
|
const { choice } = await inquirer.prompt([
|
|
53
41
|
{
|
|
@@ -89,43 +77,43 @@ export async function requestFileOperationApproval(context) {
|
|
|
89
77
|
*/
|
|
90
78
|
async function showFileOperationDetails(context) {
|
|
91
79
|
const { operation, filePath, description, contentPreview } = context;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
80
|
+
logger.consoleLog(`\nš Detailed File Operation Information:`);
|
|
81
|
+
logger.consoleLog(`āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā`);
|
|
82
|
+
logger.consoleLog(`š§ Operation Type: ${operation.toUpperCase()}`);
|
|
83
|
+
logger.consoleLog(`š Target File: ${filePath}`);
|
|
84
|
+
logger.consoleLog(`š Description: ${description}`);
|
|
85
|
+
logger.consoleLog(`š Working Directory: ${process.cwd()}`);
|
|
98
86
|
// Show file status
|
|
99
87
|
try {
|
|
100
88
|
const fs = await import('fs/promises');
|
|
101
89
|
const stats = await fs.stat(filePath);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
90
|
+
logger.consoleLog(`š File exists: YES`);
|
|
91
|
+
logger.consoleLog(`š
Last modified: ${stats.mtime.toLocaleString()}`);
|
|
92
|
+
logger.consoleLog(`š File size: ${stats.size} bytes`);
|
|
105
93
|
}
|
|
106
94
|
catch (error) {
|
|
107
|
-
|
|
95
|
+
logger.consoleLog(`š File exists: NO (will be created)`);
|
|
108
96
|
}
|
|
109
97
|
// Show full content if available
|
|
110
98
|
if (contentPreview) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
99
|
+
logger.consoleLog(`\nš Full Content Preview:`);
|
|
100
|
+
logger.consoleLog(`āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā`);
|
|
101
|
+
logger.consoleLog(contentPreview);
|
|
102
|
+
logger.consoleLog(`āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā`);
|
|
115
103
|
}
|
|
116
104
|
// Show security information
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
105
|
+
logger.consoleLog(`\nš Security Information:`);
|
|
106
|
+
logger.consoleLog(`⢠File path is validated and restricted to working directory`);
|
|
107
|
+
logger.consoleLog(`⢠Operation will be performed atomically with backup`);
|
|
108
|
+
logger.consoleLog(`⢠You can undo changes using git if needed`);
|
|
109
|
+
logger.consoleLog(`\nš” Recommendation:`);
|
|
122
110
|
if (operation === 'write' && contentPreview) {
|
|
123
|
-
|
|
124
|
-
|
|
111
|
+
logger.consoleLog(`⢠WRITE operation will ${contentPreview.includes('export') ? 'create a new file' : 'replace file content'}`);
|
|
112
|
+
logger.consoleLog(`⢠Consider approving if the content looks correct`);
|
|
125
113
|
}
|
|
126
114
|
else if (operation === 'edit') {
|
|
127
|
-
|
|
128
|
-
|
|
115
|
+
logger.consoleLog(`⢠EDIT operation will make targeted changes to existing file`);
|
|
116
|
+
logger.consoleLog(`⢠Generally safer than write operations`);
|
|
129
117
|
}
|
|
130
118
|
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
131
119
|
}
|
|
@@ -151,13 +139,13 @@ export function clearSessionApprovals() {
|
|
|
151
139
|
*/
|
|
152
140
|
export function showApprovalStatus() {
|
|
153
141
|
const status = getSessionApprovalStatus();
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
142
|
+
logger.consoleLog('\nš Current File Operation Approval Status:');
|
|
143
|
+
logger.consoleLog(`āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā`);
|
|
144
|
+
logger.consoleLog(`š Dangerous Mode (auto-approve all): ${status.dangerousMode ? 'ā
ENABLED' : 'ā Disabled'}`);
|
|
145
|
+
logger.consoleLog(`āļø Edit operations for session: ${status.editApproved ? 'ā
Auto-approved' : 'ā Require approval'}`);
|
|
146
|
+
logger.consoleLog(`š Write operations for session: ${status.writeApproved ? 'ā
Auto-approved' : 'ā Require approval'}`);
|
|
159
147
|
if (status.dangerousMode) {
|
|
160
|
-
|
|
148
|
+
logger.consoleLog(`\nā ļø WARNING: Dangerous mode is enabled - all file operations will be auto-approved!`);
|
|
161
149
|
}
|
|
162
|
-
|
|
150
|
+
logger.consoleLog(`āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n`);
|
|
163
151
|
}
|