codeep 1.0.28 → 1.0.31

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/README.md CHANGED
@@ -497,11 +497,14 @@ With write access enabled:
497
497
  | Setting | Default | Description |
498
498
  |---------|---------|-------------|
499
499
  | Temperature | 0.7 | Response creativity (0.0 - 2.0) |
500
- | Max Tokens | 4096 | Maximum response length |
501
- | API Timeout | 30000ms | Request timeout |
500
+ | Max Tokens | 8192 | Maximum response length |
501
+ | API Timeout | 60000ms | Request timeout |
502
502
  | API Rate Limit | 30/min | Max API calls per minute |
503
503
  | Command Rate Limit | 100/min | Max commands per minute |
504
504
  | Agent Mode | Auto | `Auto` = agent runs on every message, `Manual` = use /agent |
505
+ | Agent API Timeout | 180000ms | Timeout per agent API call (auto-adjusted for complexity) |
506
+ | Agent Max Duration | 20 min | Maximum time for agent to run (5-60 min) |
507
+ | Agent Max Iterations | 100 | Maximum agent iterations (10-200) |
505
508
  | Agent Confirmation | Dangerous | `Never`, `Dangerous` (default), or `Always` |
506
509
  | Agent Auto-Commit | Off | Automatically commit after agent completes |
507
510
  | Agent Branch | Off | Create new branch for agent commits |
package/dist/app.js CHANGED
@@ -266,8 +266,7 @@ export const App = () => {
266
266
  setAbortController(controller);
267
267
  try {
268
268
  const result = await runAgent(prompt, projectContext, {
269
- maxIterations: 50, // Increased for complex tasks
270
- maxDuration: 5 * 60 * 1000, // 5 minutes
269
+ // Use config values - no hardcoded limits
271
270
  dryRun,
272
271
  onIteration: (iteration, message) => {
273
272
  setAgentIteration(iteration);
@@ -33,6 +33,7 @@ export const ChatInput = ({ onSubmit, disabled, history = [], clearTrigger = 0 }
33
33
  const [selectedIndex, setSelectedIndex] = useState(0);
34
34
  const [isSelectingCommand, setIsSelectingCommand] = useState(false);
35
35
  const [pasteInfo, setPasteInfo] = useState(null);
36
+ const [fullPasteText, setFullPasteText] = useState(null);
36
37
  // Clear input when clearTrigger changes
37
38
  useEffect(() => {
38
39
  if (clearTrigger > 0) {
@@ -40,6 +41,7 @@ export const ChatInput = ({ onSubmit, disabled, history = [], clearTrigger = 0 }
40
41
  setSelectedIndex(0);
41
42
  setIsSelectingCommand(false);
42
43
  setPasteInfo(null);
44
+ setFullPasteText(null);
43
45
  }
44
46
  }, [clearTrigger]);
45
47
  // Filter commands based on input
@@ -66,15 +68,29 @@ export const ChatInput = ({ onSubmit, disabled, history = [], clearTrigger = 0 }
66
68
  if (key.ctrl && input === 'v') {
67
69
  try {
68
70
  const clipboardText = await clipboard.read();
69
- // Replace newlines with spaces to prevent multi-line issues
70
- const sanitized = clipboardText.replace(/\r?\n/g, ' ').trim();
71
- // Store full text
72
- setValue(prev => prev + sanitized);
73
- // Show paste info if long text
74
- if (sanitized.length > 100) {
75
- setPasteInfo(`[pasted ${sanitized.length} chars]`);
76
- // Clear info after 2 seconds
77
- setTimeout(() => setPasteInfo(null), 2000);
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 });
88
+ }
89
+ else {
90
+ // Short single-line paste - just insert directly
91
+ setValue(prev => prev + trimmed);
92
+ setFullPasteText(null);
93
+ setPasteInfo(null);
78
94
  }
79
95
  }
80
96
  catch (error) {
@@ -105,11 +121,18 @@ export const ChatInput = ({ onSubmit, disabled, history = [], clearTrigger = 0 }
105
121
  };
106
122
  const handleSubmit = () => {
107
123
  if (value.trim() && !disabled) {
108
- onSubmit(value.trim());
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);
109
131
  setValue('');
110
132
  setPasteInfo(null);
133
+ setFullPasteText(null);
111
134
  setIsSelectingCommand(false);
112
135
  }
113
136
  };
114
- 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 && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "green", children: pasteInfo }) }))] })] }));
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] })] }))] })] }));
115
138
  };
@@ -78,6 +78,24 @@ const SETTINGS = [
78
78
  max: 300000,
79
79
  step: 10000,
80
80
  },
81
+ {
82
+ key: 'agentMaxDuration',
83
+ label: 'Agent Max Duration (min)',
84
+ value: () => config.get('agentMaxDuration'),
85
+ type: 'number',
86
+ min: 5,
87
+ max: 60,
88
+ step: 5,
89
+ },
90
+ {
91
+ key: 'agentMaxIterations',
92
+ label: 'Agent Max Iterations',
93
+ value: () => config.get('agentMaxIterations'),
94
+ type: 'number',
95
+ min: 10,
96
+ max: 200,
97
+ step: 10,
98
+ },
81
99
  ];
82
100
  export const Settings = ({ onClose, notify }) => {
83
101
  const [selected, setSelected] = useState(0);
@@ -343,7 +343,7 @@ async function agentChatFallback(messages, systemPrompt, onChunk, abortSignal, d
343
343
  ],
344
344
  stream: Boolean(onChunk),
345
345
  temperature: config.get('temperature'),
346
- max_tokens: config.get('maxTokens'),
346
+ max_tokens: Math.max(config.get('maxTokens'), 16384), // Ensure enough tokens for large file generation
347
347
  };
348
348
  }
349
349
  else {
@@ -357,7 +357,7 @@ async function agentChatFallback(messages, systemPrompt, onChunk, abortSignal, d
357
357
  ],
358
358
  stream: Boolean(onChunk),
359
359
  temperature: config.get('temperature'),
360
- max_tokens: config.get('maxTokens'),
360
+ max_tokens: Math.max(config.get('maxTokens'), 16384), // Ensure enough tokens for large file generation
361
361
  };
362
362
  }
363
363
  const response = await fetch(endpoint, {
@@ -226,7 +226,12 @@ export function parseOpenAIToolCalls(toolCalls) {
226
226
  }
227
227
  }
228
228
  // Validate required parameters for specific tools
229
- if (toolName === 'write_file' && (!parameters.path || parameters.content === undefined)) {
229
+ // For write_file, we need at least a path (content can be empty string or placeholder)
230
+ 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
+ }
230
235
  continue;
231
236
  }
232
237
  if (toolName === 'read_file' && !parameters.path) {
@@ -252,21 +257,29 @@ function extractPartialToolParams(toolName, rawArgs) {
252
257
  // For write_file, try to extract path and content
253
258
  if (toolName === 'write_file') {
254
259
  const pathMatch = rawArgs.match(/"path"\s*:\s*"([^"]+)"/);
255
- const contentMatch = rawArgs.match(/"content"\s*:\s*"([\s\S]*?)(?:"|$)/);
256
- if (pathMatch && contentMatch) {
257
- // Unescape the content
258
- let content = contentMatch[1];
259
- content = content
260
- .replace(/\\n/g, '\n')
261
- .replace(/\\t/g, '\t')
262
- .replace(/\\r/g, '\r')
263
- .replace(/\\"/g, '"')
264
- .replace(/\\\\/g, '\\');
265
- // If content appears truncated (doesn't end properly), add a comment
266
- if (!content.endsWith('\n') && !content.endsWith('}') && !content.endsWith(';') && !content.endsWith('>')) {
267
- content += '\n<!-- Content may be truncated -->\n';
268
- }
269
- return { path: pathMatch[1], content };
260
+ if (pathMatch) {
261
+ // Try to extract content - it may be truncated
262
+ const contentMatch = rawArgs.match(/"content"\s*:\s*"([\s\S]*?)(?:"|$)/);
263
+ if (contentMatch) {
264
+ // Unescape the content
265
+ let content = contentMatch[1];
266
+ content = content
267
+ .replace(/\\n/g, '\n')
268
+ .replace(/\\t/g, '\t')
269
+ .replace(/\\r/g, '\r')
270
+ .replace(/\\"/g, '"')
271
+ .replace(/\\\\/g, '\\');
272
+ // If content appears truncated (doesn't end properly), add a comment
273
+ if (!content.endsWith('\n') && !content.endsWith('}') && !content.endsWith(';') && !content.endsWith('>')) {
274
+ content += '\n<!-- Content may be truncated -->\n';
275
+ }
276
+ return { path: pathMatch[1], content };
277
+ }
278
+ else {
279
+ // Path found but content completely missing or malformed
280
+ // Return with empty content placeholder so the file is at least created
281
+ return { path: pathMatch[1], content: '<!-- Content was truncated by API -->\n' };
282
+ }
270
283
  }
271
284
  }
272
285
  // For read_file, just need path
@@ -553,7 +566,16 @@ function tryParseToolCall(str) {
553
566
  * Validate path is within project
554
567
  */
555
568
  function validatePath(path, projectRoot) {
556
- const absolutePath = isAbsolute(path) ? path : resolve(projectRoot, path);
569
+ // If path is absolute but starts with projectRoot, convert to relative
570
+ let normalizedPath = path;
571
+ if (isAbsolute(path) && path.startsWith(projectRoot)) {
572
+ normalizedPath = relative(projectRoot, path);
573
+ }
574
+ // If still absolute and doesn't match projectRoot, reject it
575
+ if (isAbsolute(normalizedPath)) {
576
+ return { valid: false, absolutePath: normalizedPath, error: `Absolute path '${path}' not allowed. Use relative paths.` };
577
+ }
578
+ const absolutePath = resolve(projectRoot, normalizedPath);
557
579
  const relativePath = relative(projectRoot, absolutePath);
558
580
  if (relativePath.startsWith('..')) {
559
581
  return { valid: false, absolutePath, error: `Path '${path}' is outside project directory` };
@@ -594,12 +616,13 @@ export function executeTool(toolCall, projectRoot) {
594
616
  }
595
617
  case 'write_file': {
596
618
  const path = parameters.path;
597
- const content = parameters.content;
619
+ let content = parameters.content;
598
620
  if (!path) {
599
621
  return { success: false, output: '', error: 'Missing required parameter: path', tool, parameters };
600
622
  }
601
- if (content === undefined) {
602
- return { success: false, output: '', error: 'Missing required parameter: content', tool, parameters };
623
+ // Allow empty content or provide placeholder for truncated responses
624
+ if (content === undefined || content === null) {
625
+ content = '<!-- Content was not provided -->\n';
603
626
  }
604
627
  const validation = validatePath(path, projectRoot);
605
628
  if (!validation.valid) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "1.0.28",
3
+ "version": "1.0.31",
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",