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.
@@ -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 [fullPasteText, setFullPasteText] = useState(null);
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
- setFullPasteText(null);
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
- setIsSelectingCommand(true);
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
- setIsSelectingCommand(false);
106
+ // Short paste - insert directly
107
+ setValue(prev => prev + trimmed);
108
+ setCursorPos(prev => prev + trimmed.length);
109
+ setPasteInfo(null);
61
110
  }
62
- }, [suggestions.length]);
63
- // Handle keyboard navigation for command suggestions and paste
64
- useInput(async (input, key) => {
111
+ };
112
+ // Main input handler
113
+ useInput((input, key) => {
65
114
  if (disabled)
66
115
  return;
67
- // Handle paste (Ctrl+V) - read from clipboard and insert
116
+ // Handle Ctrl+V paste
68
117
  if (key.ctrl && input === 'v') {
69
- try {
70
- const clipboardText = await clipboard.read();
71
- const trimmed = clipboardText.trim();
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
- else {
90
- // Short single-line paste - just insert directly
91
- setValue(prev => prev + trimmed);
92
- setFullPasteText(null);
93
- setPasteInfo(null);
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
- catch (error) {
97
- // Clipboard read failed, ignore
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
- if (suggestions.length === 0)
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
- // Navigate suggestions with up/down arrows
171
+ }
172
+ // Handle Up Arrow - navigate suggestions or history
104
173
  if (key.upArrow) {
105
- setSelectedIndex(i => Math.max(0, i - 1));
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
- setSelectedIndex(i => Math.min(suggestions.length - 1, i + 1));
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
- // Tab to autocomplete selected command
113
- if (key.tab) {
114
- setValue(suggestions[selectedIndex].cmd);
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
- }, { isActive: !disabled });
119
- const handleChange = (newValue) => {
120
- setValue(newValue);
121
- };
122
- const handleSubmit = () => {
123
- if (value.trim() && !disabled) {
124
- // If we have stored paste text, replace the placeholder with actual content
125
- let submitValue = value.trim();
126
- if (fullPasteText && submitValue.includes('[paste:')) {
127
- // Replace the paste placeholder with actual content
128
- submitValue = submitValue.replace(/\[paste: \d+ lines, \d+ chars\]/, fullPasteText);
129
- }
130
- onSubmit(submitValue);
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
- setFullPasteText(null);
134
- setIsSelectingCommand(false);
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))), _jsxs(Text, { color: "gray", children: ["\u2191\u2193 Navigate \u2022 Tab Complete \u2022 ", suggestions.length, " ", suggestions.length === 1 ? 'command' : 'commands'] })] })), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "#f02a30", bold: true, children: '> ' }), disabled ? (_jsx(Text, { children: "..." })) : (_jsx(TextInput, { value: value, onChange: handleChange, onSubmit: handleSubmit, placeholder: "Type a message or /command..." }))] }), pasteInfo && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [_jsxs(Text, { color: "cyan", children: ["\uD83D\uDCCB Pasted: ", _jsx(Text, { bold: true, children: pasteInfo.lines }), " ", pasteInfo.lines === 1 ? 'line' : 'lines', ", ", _jsx(Text, { bold: true, children: pasteInfo.chars }), " chars"] }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Preview: ", pasteInfo.preview] })] }))] })] }));
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
  };
@@ -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
@@ -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
- // Log for debugging write_file issues
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.31",
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",