codeep 1.0.31 → 1.0.33

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,227 @@ 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 indicator in input field
100
+ const indicator = `📋 [${lineCount} lines, ${charCount} chars]`;
101
+ setValue(prev => prev + indicator);
102
+ setCursorPos(prev => prev + indicator.length);
58
103
  }
59
104
  else {
60
- setIsSelectingCommand(false);
105
+ // Short paste - insert directly
106
+ setValue(prev => prev + trimmed);
107
+ setCursorPos(prev => prev + trimmed.length);
108
+ setPasteInfo(null);
61
109
  }
62
- }, [suggestions.length]);
63
- // Handle keyboard navigation for command suggestions and paste
64
- useInput(async (input, key) => {
110
+ };
111
+ // Main input handler
112
+ useInput((input, key) => {
65
113
  if (disabled)
66
114
  return;
67
- // Handle paste (Ctrl+V) - read from clipboard and insert
115
+ // Handle Ctrl+V paste
68
116
  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 });
117
+ clipboard.read().then(text => {
118
+ if (text) {
119
+ handlePastedText(text);
88
120
  }
89
- else {
90
- // Short single-line paste - just insert directly
91
- setValue(prev => prev + trimmed);
92
- setFullPasteText(null);
93
- setPasteInfo(null);
121
+ }).catch(() => { });
122
+ return;
123
+ }
124
+ // Handle Enter - submit
125
+ if (key.return) {
126
+ if (value.trim()) {
127
+ let submitValue = value.trim();
128
+ // Replace paste indicator with actual content
129
+ if (pasteInfo && submitValue.includes('📋 [')) {
130
+ submitValue = submitValue.replace(/📋 \[\d+ lines, \d+ chars\]/, pasteInfo.fullText);
94
131
  }
132
+ onSubmit(submitValue);
133
+ setValue('');
134
+ setCursorPos(0);
135
+ setPasteInfo(null);
136
+ setHistoryIndex(-1);
95
137
  }
96
- catch (error) {
97
- // Clipboard read failed, ignore
138
+ return;
139
+ }
140
+ // Handle Escape - clear paste info or input
141
+ if (key.escape) {
142
+ if (pasteInfo) {
143
+ // Remove paste indicator from value
144
+ setValue(prev => prev.replace(/📋 \[\d+ lines, \d+ chars\]/, ''));
145
+ setPasteInfo(null);
146
+ }
147
+ else if (value) {
148
+ setValue('');
149
+ setCursorPos(0);
150
+ }
151
+ return;
152
+ }
153
+ // Handle Backspace
154
+ if (key.backspace || key.delete) {
155
+ if (cursorPos > 0) {
156
+ setValue(prev => prev.slice(0, cursorPos - 1) + prev.slice(cursorPos));
157
+ setCursorPos(prev => prev - 1);
158
+ // Clear paste info if we deleted the indicator
159
+ if (pasteInfo && !value.includes('📋 [')) {
160
+ setPasteInfo(null);
161
+ }
98
162
  }
99
163
  return;
100
164
  }
101
- if (suggestions.length === 0)
165
+ // Handle Tab - autocomplete command
166
+ if (key.tab && suggestions.length > 0) {
167
+ setValue(suggestions[selectedIndex].cmd + ' ');
168
+ setCursorPos(suggestions[selectedIndex].cmd.length + 1);
102
169
  return;
103
- // Navigate suggestions with up/down arrows
170
+ }
171
+ // Handle Up Arrow - navigate suggestions or history
104
172
  if (key.upArrow) {
105
- setSelectedIndex(i => Math.max(0, i - 1));
173
+ if (suggestions.length > 0) {
174
+ setSelectedIndex(i => Math.max(0, i - 1));
175
+ }
176
+ else if (history.length > 0) {
177
+ const newIndex = historyIndex < history.length - 1 ? historyIndex + 1 : historyIndex;
178
+ setHistoryIndex(newIndex);
179
+ if (newIndex >= 0 && history[history.length - 1 - newIndex]) {
180
+ const historyValue = history[history.length - 1 - newIndex];
181
+ setValue(historyValue);
182
+ setCursorPos(historyValue.length);
183
+ }
184
+ }
106
185
  return;
107
186
  }
187
+ // Handle Down Arrow - navigate suggestions or history
108
188
  if (key.downArrow) {
109
- setSelectedIndex(i => Math.min(suggestions.length - 1, i + 1));
189
+ if (suggestions.length > 0) {
190
+ setSelectedIndex(i => Math.min(suggestions.length - 1, i + 1));
191
+ }
192
+ else if (historyIndex > 0) {
193
+ const newIndex = historyIndex - 1;
194
+ setHistoryIndex(newIndex);
195
+ const historyValue = history[history.length - 1 - newIndex];
196
+ setValue(historyValue);
197
+ setCursorPos(historyValue.length);
198
+ }
199
+ else if (historyIndex === 0) {
200
+ setHistoryIndex(-1);
201
+ setValue('');
202
+ setCursorPos(0);
203
+ }
110
204
  return;
111
205
  }
112
- // Tab to autocomplete selected command
113
- if (key.tab) {
114
- setValue(suggestions[selectedIndex].cmd);
115
- setIsSelectingCommand(false);
206
+ // Handle Left Arrow
207
+ if (key.leftArrow) {
208
+ setCursorPos(prev => Math.max(0, prev - 1));
116
209
  return;
117
210
  }
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);
211
+ // Handle Right Arrow
212
+ if (key.rightArrow) {
213
+ setCursorPos(prev => Math.min(value.length, prev + 1));
214
+ return;
215
+ }
216
+ // Handle Ctrl+A - go to beginning
217
+ if (key.ctrl && input === 'a') {
218
+ setCursorPos(0);
219
+ return;
220
+ }
221
+ // Handle Ctrl+E - go to end
222
+ if (key.ctrl && input === 'e') {
223
+ setCursorPos(value.length);
224
+ return;
225
+ }
226
+ // Handle Ctrl+U - clear line
227
+ if (key.ctrl && input === 'u') {
131
228
  setValue('');
229
+ setCursorPos(0);
132
230
  setPasteInfo(null);
133
- setFullPasteText(null);
134
- setIsSelectingCommand(false);
231
+ return;
232
+ }
233
+ // Handle Ctrl+W - delete word
234
+ if (key.ctrl && input === 'w') {
235
+ const beforeCursor = value.slice(0, cursorPos);
236
+ const afterCursor = value.slice(cursorPos);
237
+ const lastSpace = beforeCursor.trimEnd().lastIndexOf(' ');
238
+ const newBefore = lastSpace >= 0 ? beforeCursor.slice(0, lastSpace + 1) : '';
239
+ setValue(newBefore + afterCursor);
240
+ setCursorPos(newBefore.length);
241
+ return;
242
+ }
243
+ // Regular character input
244
+ if (input && !key.ctrl && !key.meta) {
245
+ // Check if this might be part of a paste
246
+ if (!detectPaste(input)) {
247
+ // Normal single character input
248
+ setValue(prev => prev.slice(0, cursorPos) + input + prev.slice(cursorPos));
249
+ setCursorPos(prev => prev + input.length);
250
+ }
251
+ }
252
+ }, { isActive: !disabled });
253
+ // Process any remaining buffered input after a delay
254
+ useEffect(() => {
255
+ const timer = setTimeout(() => {
256
+ if (inputBuffer.current.length > 0) {
257
+ const buffered = inputBuffer.current;
258
+ inputBuffer.current = '';
259
+ if (buffered.length > 5) {
260
+ handlePastedText(buffered);
261
+ }
262
+ else {
263
+ setValue(prev => prev + buffered);
264
+ setCursorPos(prev => prev + buffered.length);
265
+ }
266
+ }
267
+ }, 100);
268
+ return () => clearTimeout(timer);
269
+ }, [value]);
270
+ // Render input with cursor
271
+ const renderInput = () => {
272
+ if (!value) {
273
+ return _jsx(Text, { color: "gray", children: "Type a message or /command..." });
135
274
  }
275
+ const before = value.slice(0, cursorPos);
276
+ const cursor = value[cursorPos] || ' ';
277
+ const after = value.slice(cursorPos + 1);
278
+ return (_jsxs(Text, { children: [before, _jsx(Text, { backgroundColor: "white", color: "black", children: cursor }), after] }));
136
279
  };
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] })] }))] })] }));
280
+ 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: "gray", children: "..." })) : (renderInput())] })] }));
138
281
  };
@@ -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.33",
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",