protoagent 0.0.3 → 0.0.4
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 +179 -121
- package/dist/config/client.js +11 -1
- package/dist/config/commands.js +15 -1
- package/dist/config/providers.js +59 -0
- package/dist/config/setup.js +35 -2
- package/dist/config/system-prompt.js +55 -21
- package/dist/tools/edit-file.js +43 -6
- package/dist/tools/index.js +14 -13
- package/dist/tools/run-shell-command.js +431 -26
- package/dist/tools/task-complete.js +26 -0
- package/dist/tools/write-file.js +15 -7
- package/dist/utils/enhanced-prompt.js +23 -0
- package/dist/utils/file-operations-approval.js +247 -25
- package/dist/utils/interrupt-handler.js +127 -0
- package/dist/utils/logger.js +1 -1
- package/dist/utils/user-cancellation.js +34 -0
- package/package.json +2 -2
|
@@ -175,38 +175,66 @@ When users ask you to work with files or run commands, you should:
|
|
|
175
175
|
|
|
176
176
|
**MANDATORY: If a user mentions specific codebases or asks you to analyze source code, you MUST start using tools immediately to explore and understand the code before providing any analysis.**
|
|
177
177
|
|
|
178
|
-
##
|
|
178
|
+
## WORK TRACKING - MANDATORY REQUIREMENT:
|
|
179
179
|
|
|
180
|
-
**YOU MUST ALWAYS
|
|
180
|
+
**YOU MUST ALWAYS CREATE AND MAINTAIN A PROTOAGENT_TODO.md FILE FOR ANY NON-TRIVIAL TASK:**
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
1.
|
|
184
|
-
2.
|
|
185
|
-
3.
|
|
186
|
-
4. Keep the
|
|
182
|
+
For any complex task (more than a single file operation), you MUST:
|
|
183
|
+
1. **Create or update PROTOAGENT_TODO.md** in the project root using write_file or edit_file
|
|
184
|
+
2. **Document your work plan** before starting any implementation
|
|
185
|
+
3. **Update progress continuously** as you complete each step
|
|
186
|
+
4. **Keep the file current** throughout the entire process so users can check in anytime
|
|
187
187
|
|
|
188
|
-
**
|
|
189
|
-
- Use clear
|
|
188
|
+
**PROTOAGENT_TODO.md Format Requirements:**
|
|
189
|
+
- Use clear markdown with checkboxes: [ ] for incomplete, [x] for complete
|
|
190
190
|
- Include relevant file paths and specific changes needed
|
|
191
191
|
- Break down complex tasks into smaller, manageable steps
|
|
192
192
|
- Add context and rationale for decisions
|
|
193
|
-
-
|
|
193
|
+
- Include current status and next steps
|
|
194
|
+
- Add timestamps for major updates
|
|
194
195
|
|
|
195
|
-
**
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
196
|
+
**Structure your PROTOAGENT_TODO.md like this:**
|
|
197
|
+
|
|
198
|
+
# ProtoAgent Work Session
|
|
199
|
+
|
|
200
|
+
## Current Task: [Brief Description]
|
|
201
|
+
**Started:** [timestamp]
|
|
202
|
+
**Last Updated:** [timestamp]
|
|
203
|
+
|
|
204
|
+
## Work Plan:
|
|
205
|
+
- [ ] Step 1: Description with file paths
|
|
206
|
+
- [ ] Step 2: Specific changes needed
|
|
207
|
+
- [ ] Step 3: Testing/validation steps
|
|
208
|
+
|
|
209
|
+
## Progress:
|
|
210
|
+
- [x] Completed step with details
|
|
211
|
+
- [ ] Current step being worked on
|
|
212
|
+
- [ ] Remaining steps
|
|
213
|
+
|
|
214
|
+
## Files Modified:
|
|
215
|
+
- path/to/file1.js - Description of changes
|
|
216
|
+
- path/to/file2.ts - Description of changes
|
|
217
|
+
|
|
218
|
+
## Notes:
|
|
219
|
+
- Important decisions made
|
|
220
|
+
- Issues encountered and solutions
|
|
221
|
+
- Next session pickup points
|
|
222
|
+
|
|
223
|
+
**Examples of tasks that REQUIRE PROTOAGENT_TODO.md tracking:**
|
|
224
|
+
- Implementing new features across multiple files
|
|
225
|
+
- Refactoring code or project structure
|
|
226
|
+
- Setting up development environments
|
|
199
227
|
- Debugging complex issues
|
|
200
228
|
- Any task involving more than 2 file operations
|
|
229
|
+
- Multi-step processes like build setup, testing, deployment
|
|
201
230
|
|
|
202
|
-
**
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
## Notes: Using JWT tokens with 24h expiration
|
|
231
|
+
**CRITICAL: The PROTOAGENT_TODO.md file serves as:**
|
|
232
|
+
1. **Communication tool** - Users can read it anytime to understand progress
|
|
233
|
+
2. **Session continuity** - Next sessions can pick up exactly where you left off
|
|
234
|
+
3. **Accountability** - Clear tracking of what was planned vs completed
|
|
235
|
+
4. **Context preservation** - Maintains reasoning and decision history
|
|
208
236
|
|
|
209
|
-
**FAILURE TO
|
|
237
|
+
**FAILURE TO CREATE AND MAINTAIN PROTOAGENT_TODO.md WILL RESULT IN POOR TASK COMPLETION AND USER CONFUSION.**
|
|
210
238
|
|
|
211
239
|
## File Operation Guidelines - CRITICAL:
|
|
212
240
|
|
|
@@ -262,6 +290,9 @@ Shell Command Non-Interactive Mode:
|
|
|
262
290
|
|
|
263
291
|
${generateToolDescriptions()}
|
|
264
292
|
|
|
293
|
+
**CRITICAL TOOL USAGE NOTE:**
|
|
294
|
+
When using edit_file and it reports "Found X occurrences of the old string", you MUST include the expected_replacements parameter to specify how many replacements you want to make. Without this parameter, the edit will fail.
|
|
295
|
+
|
|
265
296
|
Be helpful, accurate, and always explain what you're doing with the files and commands.
|
|
266
297
|
|
|
267
298
|
---
|
|
@@ -360,4 +391,7 @@ Shell Command Non-Interactive Mode:
|
|
|
360
391
|
|
|
361
392
|
${generateToolDescriptions()}
|
|
362
393
|
|
|
394
|
+
**CRITICAL TOOL USAGE NOTE:**
|
|
395
|
+
When using edit_file and it reports "Found X occurrences of the old string", you MUST include the expected_replacements parameter to specify how many replacements you want to make. Without this parameter, the edit will fail.
|
|
396
|
+
|
|
363
397
|
Be helpful, accurate, and always explain what you're doing with the files and commands.`;
|
package/dist/tools/edit-file.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'fs/promises';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { randomBytes } from 'crypto';
|
|
4
4
|
import { requestFileOperationApproval } from '../utils/file-operations-approval.js';
|
|
5
|
+
import { isUserCancellation } from '../utils/user-cancellation.js';
|
|
5
6
|
import { logger } from '../utils/logger.js';
|
|
6
7
|
// Current working directory for file operations
|
|
7
8
|
const workingDirectory = process.cwd();
|
|
@@ -72,18 +73,28 @@ export async function editFile(filePath, oldString, newString, expectedReplaceme
|
|
|
72
73
|
if (updatedContent === currentContent) {
|
|
73
74
|
throw new Error(`No changes were made to the file. The old_string and new_string appear to be identical.`);
|
|
74
75
|
}
|
|
76
|
+
// Calculate change context for better preview
|
|
77
|
+
const oldLines = currentContent.split('\n');
|
|
78
|
+
const newLines = updatedContent.split('\n');
|
|
79
|
+
const changeContext = {
|
|
80
|
+
linesAdded: newLines.length - oldLines.length,
|
|
81
|
+
linesRemoved: oldLines.length > newLines.length ? oldLines.length - newLines.length : 0,
|
|
82
|
+
totalLines: newLines.length,
|
|
83
|
+
affectedLineNumbers: findAffectedLineNumbers(oldString, currentContent)
|
|
84
|
+
};
|
|
75
85
|
// Request user approval for edit operation
|
|
76
86
|
const replacementCount = expectedReplacements || 1;
|
|
77
87
|
const approvalContext = {
|
|
78
88
|
operation: 'edit',
|
|
79
89
|
filePath: filePath,
|
|
80
|
-
description: `Replace ${replacementCount} occurrence${replacementCount === 1 ? '' : 's'} of text
|
|
81
|
-
contentPreview:
|
|
90
|
+
description: `Replace ${replacementCount} occurrence${replacementCount === 1 ? '' : 's'} of text (${oldString.length} → ${newString.length} chars)`,
|
|
91
|
+
contentPreview: createEditPreview(oldString, newString),
|
|
92
|
+
oldContent: currentContent,
|
|
93
|
+
newContent: updatedContent,
|
|
94
|
+
changeContext
|
|
82
95
|
};
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return `Edit operation cancelled by user: ${filePath}`;
|
|
86
|
-
}
|
|
96
|
+
// Request user approval for edit operation (throws UserCancellationError if cancelled)
|
|
97
|
+
await requestFileOperationApproval(approvalContext);
|
|
87
98
|
logger.debug(`✏️ Editing file: ${filePath} (${replacementCount} replacement${replacementCount === 1 ? '' : 's'})`, { component: 'EditFile', operation: 'editFile' });
|
|
88
99
|
// Write the updated content using atomic operation
|
|
89
100
|
const tempPath = `${validPath}.${randomBytes(16).toString('hex')}.tmp`;
|
|
@@ -102,6 +113,10 @@ export async function editFile(filePath, oldString, newString, expectedReplaceme
|
|
|
102
113
|
return `Successfully edited ${filePath} - replaced ${replacementCount} occurrence${replacementCount === 1 ? '' : 's'} of the specified text`;
|
|
103
114
|
}
|
|
104
115
|
catch (error) {
|
|
116
|
+
// Re-throw UserCancellationError without modification
|
|
117
|
+
if (isUserCancellation(error)) {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
105
120
|
if (error instanceof Error) {
|
|
106
121
|
throw new Error(`Failed to edit file: ${error.message}`);
|
|
107
122
|
}
|
|
@@ -112,6 +127,28 @@ export async function editFile(filePath, oldString, newString, expectedReplaceme
|
|
|
112
127
|
function escapeRegExp(string) {
|
|
113
128
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
114
129
|
}
|
|
130
|
+
// Helper function to find affected line numbers
|
|
131
|
+
function findAffectedLineNumbers(searchString, content) {
|
|
132
|
+
const lines = content.split('\n');
|
|
133
|
+
const affectedLines = [];
|
|
134
|
+
lines.forEach((line, index) => {
|
|
135
|
+
if (line.includes(searchString)) {
|
|
136
|
+
affectedLines.push(index + 1); // Line numbers are 1-based
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return affectedLines;
|
|
140
|
+
}
|
|
141
|
+
// Helper function to create a concise edit preview
|
|
142
|
+
function createEditPreview(oldText, newText) {
|
|
143
|
+
const maxLength = 150;
|
|
144
|
+
const oldPreview = oldText.length > maxLength
|
|
145
|
+
? `${oldText.substring(0, maxLength)}...`
|
|
146
|
+
: oldText;
|
|
147
|
+
const newPreview = newText.length > maxLength
|
|
148
|
+
? `${newText.substring(0, maxLength)}...`
|
|
149
|
+
: newText;
|
|
150
|
+
return `REMOVE: "${oldPreview}"\nADD: "${newPreview}"`;
|
|
151
|
+
}
|
|
115
152
|
// Tool definition
|
|
116
153
|
export const editFileTool = {
|
|
117
154
|
type: 'function',
|
package/dist/tools/index.js
CHANGED
|
@@ -7,7 +7,6 @@ export { createDirectory, createDirectoryTool } from './create-directory.js';
|
|
|
7
7
|
export { viewDirectoryTree, viewDirectoryTreeTool } from './view-directory-tree.js';
|
|
8
8
|
export { searchFiles, searchFilesTool } from './search-files.js';
|
|
9
9
|
export { runShellCommand, setShellConfig, setDangerouslyAcceptAll, runShellCommandTool } from './run-shell-command.js';
|
|
10
|
-
export { handleTodoTool, todoReadTool, todoWriteTool } from './todo.js';
|
|
11
10
|
export { setFileOperationConfig, setDangerouslyAcceptAllFileOps, getSessionApprovalStatus, clearSessionApprovals, showApprovalStatus } from '../utils/file-operations-approval.js';
|
|
12
11
|
// Import all tool definitions
|
|
13
12
|
import { readFileTool } from './read-file.js';
|
|
@@ -18,7 +17,6 @@ import { createDirectoryTool } from './create-directory.js';
|
|
|
18
17
|
import { viewDirectoryTreeTool } from './view-directory-tree.js';
|
|
19
18
|
import { searchFilesTool } from './search-files.js';
|
|
20
19
|
import { runShellCommandTool } from './run-shell-command.js';
|
|
21
|
-
import { todoReadTool, todoWriteTool } from './todo.js';
|
|
22
20
|
// Import all tool implementations
|
|
23
21
|
import { readFile } from './read-file.js';
|
|
24
22
|
import { writeFile } from './write-file.js';
|
|
@@ -28,8 +26,8 @@ import { createDirectory } from './create-directory.js';
|
|
|
28
26
|
import { viewDirectoryTree } from './view-directory-tree.js';
|
|
29
27
|
import { searchFiles } from './search-files.js';
|
|
30
28
|
import { runShellCommand } from './run-shell-command.js';
|
|
31
|
-
import { handleTodoTool } from './todo.js';
|
|
32
29
|
import { logger } from '../utils/logger.js';
|
|
30
|
+
import { isUserCancellation } from '../utils/user-cancellation.js';
|
|
33
31
|
// Consolidated tool definitions
|
|
34
32
|
export const tools = [
|
|
35
33
|
readFileTool,
|
|
@@ -39,9 +37,7 @@ export const tools = [
|
|
|
39
37
|
createDirectoryTool,
|
|
40
38
|
viewDirectoryTreeTool,
|
|
41
39
|
searchFilesTool,
|
|
42
|
-
runShellCommandTool
|
|
43
|
-
todoReadTool,
|
|
44
|
-
todoWriteTool
|
|
40
|
+
runShellCommandTool
|
|
45
41
|
];
|
|
46
42
|
// Tool handler function
|
|
47
43
|
export async function handleToolCall(toolName, args) {
|
|
@@ -105,14 +101,10 @@ export async function handleToolCall(toolName, args) {
|
|
|
105
101
|
component: 'ToolHandler',
|
|
106
102
|
command: args.command,
|
|
107
103
|
argsCount: args.args?.length || 0,
|
|
108
|
-
timeout: args.timeout_ms || 30000
|
|
104
|
+
timeout: args.timeout_ms || 30000,
|
|
105
|
+
directory: args.directory || 'current'
|
|
109
106
|
});
|
|
110
|
-
result = await runShellCommand(args.command, args.args || [], args.timeout_ms || 30000);
|
|
111
|
-
break;
|
|
112
|
-
case 'todo_read':
|
|
113
|
-
case 'todo_write':
|
|
114
|
-
logger.debug('📋 Executing todo tool', { component: 'ToolHandler', toolName });
|
|
115
|
-
result = await handleTodoTool(toolName, args);
|
|
107
|
+
result = await runShellCommand(args.command, args.args || [], args.timeout_ms || 30000, args.directory);
|
|
116
108
|
break;
|
|
117
109
|
default:
|
|
118
110
|
logger.error('❌ Unknown tool requested', { component: 'ToolHandler', toolName });
|
|
@@ -129,6 +121,15 @@ export async function handleToolCall(toolName, args) {
|
|
|
129
121
|
return result;
|
|
130
122
|
}
|
|
131
123
|
catch (error) {
|
|
124
|
+
// Re-throw UserCancellationError so it can be handled properly by the agentic loop
|
|
125
|
+
if (isUserCancellation(error)) {
|
|
126
|
+
logger.debug('🚪 Re-throwing UserCancellationError for proper handling', {
|
|
127
|
+
component: 'ToolHandler',
|
|
128
|
+
toolName,
|
|
129
|
+
cancellationReason: error.message
|
|
130
|
+
});
|
|
131
|
+
throw error; // Re-throw to let the agentic loop handle it
|
|
132
|
+
}
|
|
132
133
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
133
134
|
logger.error('❌ Tool execution failed with exception', {
|
|
134
135
|
component: 'ToolHandler',
|