codeep 1.0.31 → 1.0.34
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/components/Input.js +209 -62
- package/dist/utils/agent.js +19 -0
- package/dist/utils/tools.js +17 -4
- package/package.json +1 -1
package/dist/components/Input.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useMemo, useEffect } from 'react';
|
|
2
|
+
import { useState, useMemo, useEffect, useRef } from 'react';
|
|
3
3
|
import { Text, Box, useInput } from 'ink';
|
|
4
|
-
import TextInput from 'ink-text-input';
|
|
5
4
|
import clipboard from 'clipboardy';
|
|
6
5
|
const COMMANDS = [
|
|
7
6
|
{ cmd: '/help', desc: 'Show help' },
|
|
@@ -30,18 +29,20 @@ const COMMANDS = [
|
|
|
30
29
|
];
|
|
31
30
|
export const ChatInput = ({ onSubmit, disabled, history = [], clearTrigger = 0 }) => {
|
|
32
31
|
const [value, setValue] = useState('');
|
|
32
|
+
const [cursorPos, setCursorPos] = useState(0);
|
|
33
33
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
34
|
-
const [isSelectingCommand, setIsSelectingCommand] = useState(false);
|
|
35
34
|
const [pasteInfo, setPasteInfo] = useState(null);
|
|
36
|
-
const [
|
|
35
|
+
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
36
|
+
const lastInputTime = useRef(0);
|
|
37
|
+
const inputBuffer = useRef('');
|
|
37
38
|
// Clear input when clearTrigger changes
|
|
38
39
|
useEffect(() => {
|
|
39
40
|
if (clearTrigger > 0) {
|
|
40
41
|
setValue('');
|
|
42
|
+
setCursorPos(0);
|
|
41
43
|
setSelectedIndex(0);
|
|
42
|
-
setIsSelectingCommand(false);
|
|
43
44
|
setPasteInfo(null);
|
|
44
|
-
|
|
45
|
+
setHistoryIndex(-1);
|
|
45
46
|
}
|
|
46
47
|
}, [clearTrigger]);
|
|
47
48
|
// Filter commands based on input
|
|
@@ -54,85 +55,231 @@ export const ChatInput = ({ onSubmit, disabled, history = [], clearTrigger = 0 }
|
|
|
54
55
|
useEffect(() => {
|
|
55
56
|
if (suggestions.length > 0) {
|
|
56
57
|
setSelectedIndex(0);
|
|
57
|
-
|
|
58
|
+
}
|
|
59
|
+
}, [suggestions.length]);
|
|
60
|
+
// Detect paste by checking for rapid input (multiple chars in < 50ms)
|
|
61
|
+
const detectPaste = (newChars) => {
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
const timeDiff = now - lastInputTime.current;
|
|
64
|
+
// If multiple characters arrive very quickly, it's likely a paste
|
|
65
|
+
if (timeDiff < 50 && newChars.length > 0) {
|
|
66
|
+
inputBuffer.current += newChars;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
// Process any buffered paste
|
|
70
|
+
if (inputBuffer.current.length > 5) {
|
|
71
|
+
// This was a paste that just finished
|
|
72
|
+
const pastedText = inputBuffer.current;
|
|
73
|
+
inputBuffer.current = '';
|
|
74
|
+
handlePastedText(pastedText);
|
|
75
|
+
lastInputTime.current = now;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
inputBuffer.current = newChars;
|
|
79
|
+
lastInputTime.current = now;
|
|
80
|
+
return false;
|
|
81
|
+
};
|
|
82
|
+
const handlePastedText = (text) => {
|
|
83
|
+
const trimmed = text.trim();
|
|
84
|
+
if (!trimmed)
|
|
85
|
+
return;
|
|
86
|
+
const lines = trimmed.split(/\r?\n/);
|
|
87
|
+
const lineCount = lines.length;
|
|
88
|
+
const charCount = trimmed.length;
|
|
89
|
+
// For multi-line or long pastes, store info
|
|
90
|
+
if (lineCount > 1 || charCount > 100) {
|
|
91
|
+
const firstLine = lines[0].substring(0, 60);
|
|
92
|
+
const preview = firstLine + (lines[0].length > 60 ? '...' : '');
|
|
93
|
+
setPasteInfo({
|
|
94
|
+
lines: lineCount,
|
|
95
|
+
chars: charCount,
|
|
96
|
+
preview,
|
|
97
|
+
fullText: trimmed,
|
|
98
|
+
});
|
|
99
|
+
// Show only indicator in input field, NOT the actual pasted text
|
|
100
|
+
const indicator = `📋 [${lineCount} lines, ${charCount} chars]`;
|
|
101
|
+
// Replace entire value with just the indicator (don't append pasted text)
|
|
102
|
+
setValue(indicator);
|
|
103
|
+
setCursorPos(indicator.length);
|
|
58
104
|
}
|
|
59
105
|
else {
|
|
60
|
-
|
|
106
|
+
// Short paste - insert directly
|
|
107
|
+
setValue(prev => prev + trimmed);
|
|
108
|
+
setCursorPos(prev => prev + trimmed.length);
|
|
109
|
+
setPasteInfo(null);
|
|
61
110
|
}
|
|
62
|
-
}
|
|
63
|
-
//
|
|
64
|
-
useInput(
|
|
111
|
+
};
|
|
112
|
+
// Main input handler
|
|
113
|
+
useInput((input, key) => {
|
|
65
114
|
if (disabled)
|
|
66
115
|
return;
|
|
67
|
-
// Handle
|
|
116
|
+
// Handle Ctrl+V paste
|
|
68
117
|
if (key.ctrl && input === 'v') {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (!trimmed)
|
|
73
|
-
return;
|
|
74
|
-
const lines = trimmed.split(/\r?\n/);
|
|
75
|
-
const lineCount = lines.length;
|
|
76
|
-
const charCount = trimmed.length;
|
|
77
|
-
// For multi-line or long pastes, show summary and store full text
|
|
78
|
-
if (lineCount > 1 || charCount > 200) {
|
|
79
|
-
// Store the full text for submission
|
|
80
|
-
setFullPasteText(trimmed);
|
|
81
|
-
// Create a short preview (first line, truncated)
|
|
82
|
-
const firstLine = lines[0].substring(0, 50);
|
|
83
|
-
const preview = firstLine + (lines[0].length > 50 ? '...' : '');
|
|
84
|
-
// Show compact indicator in input
|
|
85
|
-
setValue(prev => prev + `[paste: ${lineCount} lines, ${charCount} chars]`);
|
|
86
|
-
// Show detailed info below
|
|
87
|
-
setPasteInfo({ lines: lineCount, chars: charCount, preview });
|
|
118
|
+
clipboard.read().then(text => {
|
|
119
|
+
if (text) {
|
|
120
|
+
handlePastedText(text);
|
|
88
121
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
122
|
+
}).catch(() => { });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
// Handle Enter - submit
|
|
126
|
+
if (key.return) {
|
|
127
|
+
if (value.trim()) {
|
|
128
|
+
let submitValue = value.trim();
|
|
129
|
+
// Replace paste indicator with actual content
|
|
130
|
+
if (pasteInfo && submitValue.includes('📋 [')) {
|
|
131
|
+
submitValue = submitValue.replace(/📋 \[\d+ lines, \d+ chars\]/, pasteInfo.fullText);
|
|
94
132
|
}
|
|
133
|
+
onSubmit(submitValue);
|
|
134
|
+
setValue('');
|
|
135
|
+
setCursorPos(0);
|
|
136
|
+
setPasteInfo(null);
|
|
137
|
+
setHistoryIndex(-1);
|
|
95
138
|
}
|
|
96
|
-
|
|
97
|
-
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Handle Escape - clear paste info or input
|
|
142
|
+
if (key.escape) {
|
|
143
|
+
if (pasteInfo) {
|
|
144
|
+
// Remove paste indicator from value
|
|
145
|
+
setValue(prev => prev.replace(/📋 \[\d+ lines, \d+ chars\]/, ''));
|
|
146
|
+
setPasteInfo(null);
|
|
147
|
+
}
|
|
148
|
+
else if (value) {
|
|
149
|
+
setValue('');
|
|
150
|
+
setCursorPos(0);
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
// Handle Backspace
|
|
155
|
+
if (key.backspace || key.delete) {
|
|
156
|
+
if (cursorPos > 0) {
|
|
157
|
+
setValue(prev => prev.slice(0, cursorPos - 1) + prev.slice(cursorPos));
|
|
158
|
+
setCursorPos(prev => prev - 1);
|
|
159
|
+
// Clear paste info if we deleted the indicator
|
|
160
|
+
if (pasteInfo && !value.includes('📋 [')) {
|
|
161
|
+
setPasteInfo(null);
|
|
162
|
+
}
|
|
98
163
|
}
|
|
99
164
|
return;
|
|
100
165
|
}
|
|
101
|
-
|
|
166
|
+
// Handle Tab - autocomplete command
|
|
167
|
+
if (key.tab && suggestions.length > 0) {
|
|
168
|
+
setValue(suggestions[selectedIndex].cmd + ' ');
|
|
169
|
+
setCursorPos(suggestions[selectedIndex].cmd.length + 1);
|
|
102
170
|
return;
|
|
103
|
-
|
|
171
|
+
}
|
|
172
|
+
// Handle Up Arrow - navigate suggestions or history
|
|
104
173
|
if (key.upArrow) {
|
|
105
|
-
|
|
174
|
+
if (suggestions.length > 0) {
|
|
175
|
+
setSelectedIndex(i => Math.max(0, i - 1));
|
|
176
|
+
}
|
|
177
|
+
else if (history.length > 0) {
|
|
178
|
+
const newIndex = historyIndex < history.length - 1 ? historyIndex + 1 : historyIndex;
|
|
179
|
+
setHistoryIndex(newIndex);
|
|
180
|
+
if (newIndex >= 0 && history[history.length - 1 - newIndex]) {
|
|
181
|
+
const historyValue = history[history.length - 1 - newIndex];
|
|
182
|
+
setValue(historyValue);
|
|
183
|
+
setCursorPos(historyValue.length);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
106
186
|
return;
|
|
107
187
|
}
|
|
188
|
+
// Handle Down Arrow - navigate suggestions or history
|
|
108
189
|
if (key.downArrow) {
|
|
109
|
-
|
|
190
|
+
if (suggestions.length > 0) {
|
|
191
|
+
setSelectedIndex(i => Math.min(suggestions.length - 1, i + 1));
|
|
192
|
+
}
|
|
193
|
+
else if (historyIndex > 0) {
|
|
194
|
+
const newIndex = historyIndex - 1;
|
|
195
|
+
setHistoryIndex(newIndex);
|
|
196
|
+
const historyValue = history[history.length - 1 - newIndex];
|
|
197
|
+
setValue(historyValue);
|
|
198
|
+
setCursorPos(historyValue.length);
|
|
199
|
+
}
|
|
200
|
+
else if (historyIndex === 0) {
|
|
201
|
+
setHistoryIndex(-1);
|
|
202
|
+
setValue('');
|
|
203
|
+
setCursorPos(0);
|
|
204
|
+
}
|
|
110
205
|
return;
|
|
111
206
|
}
|
|
112
|
-
//
|
|
113
|
-
if (key.
|
|
114
|
-
|
|
115
|
-
setIsSelectingCommand(false);
|
|
207
|
+
// Handle Left Arrow
|
|
208
|
+
if (key.leftArrow) {
|
|
209
|
+
setCursorPos(prev => Math.max(0, prev - 1));
|
|
116
210
|
return;
|
|
117
211
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
212
|
+
// Handle Right Arrow
|
|
213
|
+
if (key.rightArrow) {
|
|
214
|
+
setCursorPos(prev => Math.min(value.length, prev + 1));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// Handle Ctrl+A - go to beginning
|
|
218
|
+
if (key.ctrl && input === 'a') {
|
|
219
|
+
setCursorPos(0);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
// Handle Ctrl+E - go to end
|
|
223
|
+
if (key.ctrl && input === 'e') {
|
|
224
|
+
setCursorPos(value.length);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// Handle Ctrl+U - clear line
|
|
228
|
+
if (key.ctrl && input === 'u') {
|
|
131
229
|
setValue('');
|
|
230
|
+
setCursorPos(0);
|
|
132
231
|
setPasteInfo(null);
|
|
133
|
-
|
|
134
|
-
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Handle Ctrl+W - delete word
|
|
235
|
+
if (key.ctrl && input === 'w') {
|
|
236
|
+
const beforeCursor = value.slice(0, cursorPos);
|
|
237
|
+
const afterCursor = value.slice(cursorPos);
|
|
238
|
+
const lastSpace = beforeCursor.trimEnd().lastIndexOf(' ');
|
|
239
|
+
const newBefore = lastSpace >= 0 ? beforeCursor.slice(0, lastSpace + 1) : '';
|
|
240
|
+
setValue(newBefore + afterCursor);
|
|
241
|
+
setCursorPos(newBefore.length);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// Regular character input
|
|
245
|
+
if (input && !key.ctrl && !key.meta) {
|
|
246
|
+
// If we have paste info, clear it when user starts typing
|
|
247
|
+
if (pasteInfo) {
|
|
248
|
+
setPasteInfo(null);
|
|
249
|
+
setValue('');
|
|
250
|
+
setCursorPos(0);
|
|
251
|
+
}
|
|
252
|
+
// Normal single character input
|
|
253
|
+
setValue(prev => prev.slice(0, cursorPos) + input + prev.slice(cursorPos));
|
|
254
|
+
setCursorPos(prev => prev + input.length);
|
|
255
|
+
}
|
|
256
|
+
}, { isActive: !disabled });
|
|
257
|
+
// Process any remaining buffered input after a delay
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
const timer = setTimeout(() => {
|
|
260
|
+
if (inputBuffer.current.length > 0) {
|
|
261
|
+
const buffered = inputBuffer.current;
|
|
262
|
+
inputBuffer.current = '';
|
|
263
|
+
if (buffered.length > 5) {
|
|
264
|
+
handlePastedText(buffered);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
setValue(prev => prev + buffered);
|
|
268
|
+
setCursorPos(prev => prev + buffered.length);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}, 100);
|
|
272
|
+
return () => clearTimeout(timer);
|
|
273
|
+
}, [value]);
|
|
274
|
+
// Render input with cursor
|
|
275
|
+
const renderInput = () => {
|
|
276
|
+
if (!value) {
|
|
277
|
+
return _jsx(Text, { color: "gray", children: "Type a message or /command..." });
|
|
135
278
|
}
|
|
279
|
+
const before = value.slice(0, cursorPos);
|
|
280
|
+
const cursor = value[cursorPos] || ' ';
|
|
281
|
+
const after = value.slice(cursorPos + 1);
|
|
282
|
+
return (_jsxs(Text, { children: [before, _jsx(Text, { backgroundColor: "white", color: "black", children: cursor }), after] }));
|
|
136
283
|
};
|
|
137
|
-
return (_jsxs(Box, { flexDirection: "column", children: [suggestions.length > 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [suggestions.map((s, i) => (_jsxs(Text, { children: [i === selectedIndex ? _jsx(Text, { color: "#f02a30", children: "\u25B8 " }) : ' ', _jsx(Text, { color: i === selectedIndex ? '#f02a30' : undefined, bold: i === selectedIndex, children: s.cmd }), _jsxs(Text, { color: i === selectedIndex ? undefined : 'gray', children: [" - ", s.desc] })] }, s.cmd))),
|
|
284
|
+
return (_jsxs(Box, { flexDirection: "column", children: [suggestions.length > 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [suggestions.slice(0, 8).map((s, i) => (_jsxs(Text, { children: [i === selectedIndex ? _jsx(Text, { color: "#f02a30", children: "\u25B8 " }) : ' ', _jsx(Text, { color: i === selectedIndex ? '#f02a30' : undefined, bold: i === selectedIndex, children: s.cmd }), _jsxs(Text, { color: i === selectedIndex ? undefined : 'gray', children: [" - ", s.desc] })] }, s.cmd))), _jsx(Text, { color: "gray", dimColor: true, children: "\u2191\u2193 navigate \u2022 Tab complete \u2022 Esc cancel" })] })), pasteInfo && (_jsxs(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "\uD83D\uDCCB Pasted Content" }), _jsxs(Text, { children: [_jsx(Text, { color: "white", bold: true, children: pasteInfo.lines }), _jsxs(Text, { color: "gray", children: [" ", pasteInfo.lines === 1 ? 'line' : 'lines', " \u2022 "] }), _jsx(Text, { color: "white", bold: true, children: pasteInfo.chars }), _jsx(Text, { color: "gray", children: " characters" })] }), _jsx(Text, { color: "gray", dimColor: true, wrap: "truncate", children: pasteInfo.preview }), _jsx(Text, { color: "gray", dimColor: true, children: "Press Enter to send \u2022 Esc to remove" })] })), _jsxs(Box, { children: [_jsx(Text, { color: "#f02a30", bold: true, children: '> ' }), disabled ? (_jsx(Text, { color: "yellow", children: "Agent working... (Esc to stop)" })) : (renderInput())] })] }));
|
|
138
285
|
};
|
package/dist/utils/agent.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent loop - autonomous task execution
|
|
3
3
|
*/
|
|
4
|
+
// Debug logging helper - only logs when CODEEP_DEBUG=1
|
|
5
|
+
const debug = (...args) => {
|
|
6
|
+
if (process.env.CODEEP_DEBUG === '1') {
|
|
7
|
+
console.error('[DEBUG]', ...args);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
4
10
|
/**
|
|
5
11
|
* Custom error class for timeout - allows distinguishing from user abort
|
|
6
12
|
*/
|
|
@@ -236,15 +242,21 @@ async function agentChat(messages, systemPrompt, onChunk, abortSignal, dynamicTi
|
|
|
236
242
|
throw new Error(`API error: ${response.status} - ${errorText}`);
|
|
237
243
|
}
|
|
238
244
|
const data = await response.json();
|
|
245
|
+
debug('Raw API response:', JSON.stringify(data, null, 2).substring(0, 1500));
|
|
239
246
|
if (protocol === 'openai') {
|
|
240
247
|
const message = data.choices?.[0]?.message;
|
|
241
248
|
const content = message?.content || '';
|
|
242
249
|
const rawToolCalls = message?.tool_calls || [];
|
|
250
|
+
debug('Raw tool_calls:', JSON.stringify(rawToolCalls, null, 2));
|
|
243
251
|
const toolCalls = parseOpenAIToolCalls(rawToolCalls);
|
|
252
|
+
debug('Parsed tool calls:', toolCalls.length, toolCalls.map(t => t.tool));
|
|
244
253
|
// If no native tool calls, try parsing from content (some models return text-based)
|
|
245
254
|
if (toolCalls.length === 0 && content) {
|
|
255
|
+
debug('No native tool calls, checking content for text-based calls...');
|
|
256
|
+
debug('Content preview:', content.substring(0, 300));
|
|
246
257
|
const textToolCalls = parseToolCalls(content);
|
|
247
258
|
if (textToolCalls.length > 0) {
|
|
259
|
+
debug('Found text-based tool calls:', textToolCalls.length);
|
|
248
260
|
return { content, toolCalls: textToolCalls, usedNativeTools: false };
|
|
249
261
|
}
|
|
250
262
|
}
|
|
@@ -534,6 +546,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
534
546
|
}
|
|
535
547
|
// Check abort signal
|
|
536
548
|
if (opts.abortSignal?.aborted) {
|
|
549
|
+
debug('Agent aborted at iteration', iteration);
|
|
537
550
|
result = {
|
|
538
551
|
success: false,
|
|
539
552
|
iterations: iteration,
|
|
@@ -545,8 +558,10 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
545
558
|
}
|
|
546
559
|
iteration++;
|
|
547
560
|
opts.onIteration?.(iteration, `Iteration ${iteration}/${opts.maxIterations}`);
|
|
561
|
+
debug(`Starting iteration ${iteration}/${opts.maxIterations}, actions: ${actions.length}`);
|
|
548
562
|
// Calculate dynamic timeout based on task complexity
|
|
549
563
|
const dynamicTimeout = calculateDynamicTimeout(prompt, iteration, baseTimeout);
|
|
564
|
+
debug(`Using timeout: ${dynamicTimeout}ms (base: ${baseTimeout}ms)`);
|
|
550
565
|
// Get AI response with retry logic for timeouts
|
|
551
566
|
let chatResponse;
|
|
552
567
|
let retryCount = 0;
|
|
@@ -574,6 +589,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
574
589
|
if (err.name === 'TimeoutError') {
|
|
575
590
|
retryCount++;
|
|
576
591
|
consecutiveTimeouts++;
|
|
592
|
+
debug(`Timeout occurred (retry ${retryCount}/${maxTimeoutRetries}, consecutive: ${consecutiveTimeouts})`);
|
|
577
593
|
opts.onIteration?.(iteration, `API timeout, retrying (${retryCount}/${maxTimeoutRetries})...`);
|
|
578
594
|
if (retryCount >= maxTimeoutRetries) {
|
|
579
595
|
// Too many retries for this iteration
|
|
@@ -617,6 +633,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
617
633
|
}
|
|
618
634
|
// If no tool calls, check if model wants to continue or is really done
|
|
619
635
|
if (toolCalls.length === 0) {
|
|
636
|
+
debug(`No tool calls at iteration ${iteration}, content length: ${content.length}`);
|
|
620
637
|
// Remove <think>...</think> tags from response (some models include thinking)
|
|
621
638
|
finalResponse = content.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
|
|
622
639
|
// Check if model indicates it wants to continue (incomplete response)
|
|
@@ -631,6 +648,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
631
648
|
// by looking for incomplete actions (e.g., write_file without content)
|
|
632
649
|
const hasIncompleteWork = iteration < 10 && wantsToContinue && finalResponse.length < 500;
|
|
633
650
|
if (hasIncompleteWork) {
|
|
651
|
+
debug('Model wants to continue, prompting for next action');
|
|
634
652
|
messages.push({ role: 'assistant', content });
|
|
635
653
|
messages.push({
|
|
636
654
|
role: 'user',
|
|
@@ -639,6 +657,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
639
657
|
continue;
|
|
640
658
|
}
|
|
641
659
|
// Model is done
|
|
660
|
+
debug(`Agent finished at iteration ${iteration}`);
|
|
642
661
|
break;
|
|
643
662
|
}
|
|
644
663
|
// Add assistant response to history
|
package/dist/utils/tools.js
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* Agent tools - definitions and execution
|
|
3
3
|
*/
|
|
4
4
|
import { existsSync, readdirSync, statSync, readFileSync, writeFileSync, unlinkSync, mkdirSync, rmSync } from 'fs';
|
|
5
|
+
// Debug logging helper - only logs when CODEEP_DEBUG=1
|
|
6
|
+
const debug = (...args) => {
|
|
7
|
+
if (process.env.CODEEP_DEBUG === '1') {
|
|
8
|
+
console.error('[DEBUG]', ...args);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
5
11
|
import { join, dirname, relative, resolve, isAbsolute } from 'path';
|
|
6
12
|
import { executeCommand } from './shell.js';
|
|
7
13
|
import { recordWrite, recordEdit, recordDelete, recordMkdir, recordCommand } from './history.js';
|
|
@@ -217,27 +223,30 @@ export function parseOpenAIToolCalls(toolCalls) {
|
|
|
217
223
|
catch (e) {
|
|
218
224
|
// JSON parsing failed - likely truncated response
|
|
219
225
|
// Try to extract what we can from partial JSON
|
|
226
|
+
debug(`Failed to parse tool arguments for ${toolName}, attempting partial extraction...`);
|
|
227
|
+
debug('Raw args preview:', rawArgs.substring(0, 200));
|
|
220
228
|
const partialParams = extractPartialToolParams(toolName, rawArgs);
|
|
221
229
|
if (partialParams) {
|
|
230
|
+
debug(`Successfully extracted partial params for ${toolName}:`, Object.keys(partialParams));
|
|
222
231
|
parameters = partialParams;
|
|
223
232
|
}
|
|
224
233
|
else {
|
|
234
|
+
debug(`Could not extract params, skipping ${toolName}`);
|
|
225
235
|
continue;
|
|
226
236
|
}
|
|
227
237
|
}
|
|
228
238
|
// Validate required parameters for specific tools
|
|
229
239
|
// For write_file, we need at least a path (content can be empty string or placeholder)
|
|
230
240
|
if (toolName === 'write_file' && !parameters.path) {
|
|
231
|
-
|
|
232
|
-
if (process.env.CODEEP_DEBUG) {
|
|
233
|
-
console.error(`[WARN] write_file missing path, raw args: ${rawArgs.substring(0, 200)}`);
|
|
234
|
-
}
|
|
241
|
+
debug(`Skipping write_file - missing path. Raw args:`, rawArgs.substring(0, 200));
|
|
235
242
|
continue;
|
|
236
243
|
}
|
|
237
244
|
if (toolName === 'read_file' && !parameters.path) {
|
|
245
|
+
debug(`Skipping read_file - missing path`);
|
|
238
246
|
continue;
|
|
239
247
|
}
|
|
240
248
|
if (toolName === 'edit_file' && (!parameters.path || parameters.old_text === undefined || parameters.new_text === undefined)) {
|
|
249
|
+
debug(`Skipping edit_file - missing required params`);
|
|
241
250
|
continue;
|
|
242
251
|
}
|
|
243
252
|
parsed.push({
|
|
@@ -334,6 +343,7 @@ function extractPartialToolParams(toolName, rawArgs) {
|
|
|
334
343
|
return null;
|
|
335
344
|
}
|
|
336
345
|
catch (e) {
|
|
346
|
+
debug('Error in extractPartialToolParams:', e);
|
|
337
347
|
return null;
|
|
338
348
|
}
|
|
339
349
|
}
|
|
@@ -589,6 +599,7 @@ export function executeTool(toolCall, projectRoot) {
|
|
|
589
599
|
// Normalize tool name to handle case variations (WRITE_FILE -> write_file)
|
|
590
600
|
const tool = normalizeToolName(toolCall.tool);
|
|
591
601
|
const parameters = toolCall.parameters;
|
|
602
|
+
debug(`Executing tool: ${tool}`, parameters.path || parameters.command || '');
|
|
592
603
|
try {
|
|
593
604
|
switch (tool) {
|
|
594
605
|
case 'read_file': {
|
|
@@ -618,10 +629,12 @@ export function executeTool(toolCall, projectRoot) {
|
|
|
618
629
|
const path = parameters.path;
|
|
619
630
|
let content = parameters.content;
|
|
620
631
|
if (!path) {
|
|
632
|
+
debug('write_file failed: missing path');
|
|
621
633
|
return { success: false, output: '', error: 'Missing required parameter: path', tool, parameters };
|
|
622
634
|
}
|
|
623
635
|
// Allow empty content or provide placeholder for truncated responses
|
|
624
636
|
if (content === undefined || content === null) {
|
|
637
|
+
debug('write_file: content was undefined, using placeholder');
|
|
625
638
|
content = '<!-- Content was not provided -->\n';
|
|
626
639
|
}
|
|
627
640
|
const validation = validatePath(path, projectRoot);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.34",
|
|
4
4
|
"description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|