codeep 1.2.87 → 1.2.89

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.
@@ -190,12 +190,17 @@ export async function handleCommand(input, session, onChunk, abortSignal) {
190
190
  return { handled: true, response: `Write access granted for \`${ctx?.name || session.workspaceRoot}\`` };
191
191
  }
192
192
  case 'lang': {
193
+ const validLangs = ['auto', 'en', 'zh', 'es', 'hi', 'ar', 'pt', 'fr', 'de', 'ja', 'ru', 'hr'];
193
194
  if (!args.length) {
194
195
  const current = config.get('language') || 'auto';
195
- return { handled: true, response: `Current language: \`${current}\`. Usage: \`/lang <code>\` (e.g. \`en\`, \`hr\`, \`auto\`)` };
196
+ return { handled: true, response: `Current language: \`${current}\`. Usage: \`/lang <code>\` (${validLangs.join(', ')})` };
196
197
  }
197
- config.set('language', args[0]);
198
- return { handled: true, response: `Language set to \`${args[0]}\`` };
198
+ const lang = args[0].toLowerCase();
199
+ if (!validLangs.includes(lang)) {
200
+ return { handled: true, response: `Invalid language \`${args[0]}\`. Valid: ${validLangs.join(', ')}` };
201
+ }
202
+ config.set('language', lang);
203
+ return { handled: true, response: `Language set to \`${lang}\`` };
199
204
  }
200
205
  // ─── File context ──────────────────────────────────────────────────────────
201
206
  case 'add': {
@@ -211,7 +216,11 @@ export async function handleCommand(input, session, onChunk, abortSignal) {
211
216
  const added = [];
212
217
  const errors = [];
213
218
  for (const filePath of args) {
214
- const fullPath = pathMod.isAbsolute(filePath) ? filePath : pathMod.join(root, filePath);
219
+ const fullPath = pathMod.resolve(root, filePath);
220
+ if (!fullPath.startsWith(root + pathMod.sep) && fullPath !== root) {
221
+ errors.push(`\`${filePath}\`: path outside workspace`);
222
+ continue;
223
+ }
215
224
  const relativePath = pathMod.relative(root, fullPath);
216
225
  try {
217
226
  const stat = await fs.stat(fullPath);
@@ -248,7 +257,9 @@ export async function handleCommand(input, session, onChunk, abortSignal) {
248
257
  const root = session.workspaceRoot;
249
258
  let dropped = 0;
250
259
  for (const filePath of args) {
251
- const fullPath = pathMod.isAbsolute(filePath) ? filePath : pathMod.join(root, filePath);
260
+ const fullPath = pathMod.resolve(root, filePath);
261
+ if (!fullPath.startsWith(root + pathMod.sep) && fullPath !== root)
262
+ continue;
252
263
  if (session.addedFiles.delete(fullPath))
253
264
  dropped++;
254
265
  }
@@ -1,5 +1,7 @@
1
1
  // acp/transport.ts
2
2
  // Newline-delimited JSON-RPC over stdio
3
+ const MAX_BUFFER_SIZE = 10 * 1024 * 1024; // 10MB
4
+ const REQUEST_TIMEOUT_MS = 30_000; // 30s
3
5
  export class StdioTransport {
4
6
  buffer = '';
5
7
  handler = null;
@@ -13,6 +15,10 @@ export class StdioTransport {
13
15
  }
14
16
  onData(chunk) {
15
17
  this.buffer += chunk;
18
+ if (this.buffer.length > MAX_BUFFER_SIZE) {
19
+ this.buffer = '';
20
+ return;
21
+ }
16
22
  const lines = this.buffer.split('\n');
17
23
  this.buffer = lines.pop() ?? '';
18
24
  for (const line of lines) {
@@ -59,6 +65,11 @@ export class StdioTransport {
59
65
  return new Promise((resolve) => {
60
66
  this.pendingRequests.set(id, resolve);
61
67
  process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
68
+ setTimeout(() => {
69
+ if (this.pendingRequests.delete(id)) {
70
+ resolve(null);
71
+ }
72
+ }, REQUEST_TIMEOUT_MS);
62
73
  });
63
74
  }
64
75
  }
@@ -38,6 +38,7 @@ interface ConfigSchema {
38
38
  agentMaxIterations: number;
39
39
  agentMaxDuration: number;
40
40
  agentApiTimeout: number;
41
+ agentInteractive: boolean;
41
42
  projectPermissions: ProjectPermission[];
42
43
  providerApiKeys: ProviderApiKey[];
43
44
  }
@@ -148,9 +148,10 @@ function createConfig() {
148
148
  agentAutoCommitBranch: false,
149
149
  agentAutoVerify: 'off',
150
150
  agentMaxFixAttempts: 3,
151
- agentMaxIterations: 200,
152
- agentMaxDuration: 20,
153
- agentApiTimeout: 180000,
151
+ agentMaxIterations: 10000,
152
+ agentMaxDuration: 480,
153
+ agentApiTimeout: 600000,
154
+ agentInteractive: true,
154
155
  protocol: 'openai',
155
156
  plan: 'lite',
156
157
  language: 'en',
@@ -159,8 +160,8 @@ function createConfig() {
159
160
  temperature: 0.7,
160
161
  maxTokens: 8192,
161
162
  apiTimeout: 60000,
162
- rateLimitApi: 30,
163
- rateLimitCommands: 100,
163
+ rateLimitApi: 10000,
164
+ rateLimitCommands: 10000,
164
165
  projectPermissions: [],
165
166
  providerApiKeys: [],
166
167
  };
@@ -41,8 +41,8 @@ export const SETTINGS = [
41
41
  getValue: () => config.get('rateLimitApi'),
42
42
  type: 'number',
43
43
  min: 1,
44
- max: 300,
45
- step: 5,
44
+ max: 10000,
45
+ step: 50,
46
46
  },
47
47
  {
48
48
  key: 'rateLimitCommands',
@@ -50,8 +50,8 @@ export const SETTINGS = [
50
50
  getValue: () => config.get('rateLimitCommands'),
51
51
  type: 'number',
52
52
  min: 10,
53
- max: 1000,
54
- step: 10,
53
+ max: 10000,
54
+ step: 100,
55
55
  },
56
56
  {
57
57
  key: 'autoSave',
@@ -91,8 +91,8 @@ export const SETTINGS = [
91
91
  getValue: () => config.get('agentApiTimeout'),
92
92
  type: 'number',
93
93
  min: 30000,
94
- max: 300000,
95
- step: 10000,
94
+ max: 3600000,
95
+ step: 30000,
96
96
  },
97
97
  {
98
98
  key: 'agentMaxDuration',
@@ -100,8 +100,8 @@ export const SETTINGS = [
100
100
  getValue: () => config.get('agentMaxDuration'),
101
101
  type: 'number',
102
102
  min: 5,
103
- max: 60,
104
- step: 5,
103
+ max: 1440,
104
+ step: 30,
105
105
  },
106
106
  {
107
107
  key: 'agentMaxIterations',
@@ -109,8 +109,8 @@ export const SETTINGS = [
109
109
  getValue: () => config.get('agentMaxIterations'),
110
110
  type: 'number',
111
111
  min: 10,
112
- max: 500,
113
- step: 10,
112
+ max: 100000,
113
+ step: 100,
114
114
  },
115
115
  {
116
116
  key: 'agentAutoVerify',
@@ -4,9 +4,9 @@
4
4
  * Private chat/stream logic lives in agentChat.ts and agentStream.ts.
5
5
  */
6
6
  import { ProjectContext } from './project';
7
- import { loadProjectRules, formatChatHistoryForAgent } from './agentChat';
7
+ import { loadProjectRules, loadProgressLog, writeProgressLog, formatChatHistoryForAgent } from './agentChat';
8
8
  import type { AgentChatResponse } from './agentChat';
9
- export { loadProjectRules, formatChatHistoryForAgent };
9
+ export { loadProjectRules, loadProgressLog, writeProgressLog, formatChatHistoryForAgent };
10
10
  export type { AgentChatResponse };
11
11
  import { ToolCall, ToolResult, ActionLog } from './tools';
12
12
  import { undoLastAction, undoAllActions, getCurrentSession, getRecentSessions, formatSession, ActionSession } from './history';
@@ -10,9 +10,9 @@ const debug = (...args) => {
10
10
  }
11
11
  };
12
12
  // Import chat layer (prompt building + API calls)
13
- import { agentChat, getAgentSystemPrompt, getFallbackSystemPrompt, loadProjectRules, formatChatHistoryForAgent, } from './agentChat.js';
13
+ import { agentChat, getAgentSystemPrompt, getFallbackSystemPrompt, loadProjectRules, loadProgressLog, writeProgressLog, formatChatHistoryForAgent, } from './agentChat.js';
14
14
  import { ApiError } from '../api/index.js';
15
- export { loadProjectRules, formatChatHistoryForAgent };
15
+ export { loadProjectRules, loadProgressLog, writeProgressLog, formatChatHistoryForAgent };
16
16
  /**
17
17
  * Calculate dynamic timeout based on task complexity
18
18
  * Complex tasks (creating pages, multiple files) need more time
@@ -28,9 +28,9 @@ function calculateDynamicTimeout(iteration, baseTimeout) {
28
28
  if (iteration > 8) {
29
29
  multiplier = 1.5;
30
30
  }
31
- // Minimum 120 seconds, maximum 5 minutes for a single API call
31
+ // Minimum 120 seconds, no hard upper cap let agentApiTimeout setting be the real ceiling
32
32
  const calculatedTimeout = baseTimeout * multiplier;
33
- return Math.min(Math.max(calculatedTimeout, 120000), 300000);
33
+ return Math.max(calculatedTimeout, 120000);
34
34
  }
35
35
  import { parseToolCalls, executeTool, createActionLog } from './tools.js';
36
36
  import { config } from '../config/index.js';
@@ -157,6 +157,11 @@ export async function runAgent(prompt, projectContext, options = {}) {
157
157
  if (projectRules) {
158
158
  systemPrompt += projectRules;
159
159
  }
160
+ // Inject previous session progress (from .codeep/progress.md)
161
+ const progressLog = loadProgressLog(projectContext.root);
162
+ if (progressLog) {
163
+ systemPrompt += progressLog;
164
+ }
160
165
  if (smartContextStr) {
161
166
  systemPrompt += '\n\n' + smartContextStr;
162
167
  }
@@ -176,13 +181,13 @@ export async function runAgent(prompt, projectContext, options = {}) {
176
181
  let result;
177
182
  let consecutiveTimeouts = 0;
178
183
  let incompleteWorkRetries = 0;
179
- const maxIncompleteWorkRetries = 2;
184
+ const maxIncompleteWorkRetries = 5;
180
185
  // Track tools permanently allowed this session via allow_always
181
186
  const alwaysAllowedTools = new Set();
182
187
  // Tools that require permission when onRequestPermission is set
183
188
  const dangerousTools = new Set(['delete_file', 'execute_command']);
184
189
  const maxTimeoutRetries = 3;
185
- const maxConsecutiveTimeouts = 9; // Allow more consecutive timeouts before giving up
190
+ const maxConsecutiveTimeouts = 30; // Allow more consecutive timeouts before giving up
186
191
  const baseTimeout = config.get('agentApiTimeout');
187
192
  // Infinite loop detection: track last write hash per file path
188
193
  const lastWriteHashByPath = new Map();
@@ -208,6 +213,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
208
213
  finalResponse: partialLines.join('\n'),
209
214
  error: `Exceeded maximum duration of ${durationMin} min`,
210
215
  };
216
+ writeProgressLog(projectContext.root || '', prompt, result);
211
217
  return result;
212
218
  }
213
219
  // Check abort signal
@@ -519,7 +525,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
519
525
  // Add tool results to messages
520
526
  messages.push({
521
527
  role: 'user',
522
- content: `Tool results:\n\n${toolResults.join('\n\n')}\n\nContinue with the task. If this subtask is complete, provide a summary without tool calls.`,
528
+ content: `Tool results:\n\n${toolResults.join('\n\n')}\n\nContinue with the task. Keep working until everything is fully done.`,
523
529
  });
524
530
  }
525
531
  // Check if we hit max iterations — build partial summary from actions log
@@ -538,6 +544,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
538
544
  finalResponse: partialLines.join('\n'),
539
545
  error: `Exceeded maximum of ${opts.maxIterations} iterations`,
540
546
  };
547
+ writeProgressLog(projectContext.root || '', prompt, result);
541
548
  return result;
542
549
  }
543
550
  // Self-verification: Run build/test and fix errors if needed
@@ -669,6 +676,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
669
676
  actions,
670
677
  finalResponse,
671
678
  };
679
+ writeProgressLog(projectContext.root || '', prompt, result);
672
680
  return result;
673
681
  }
674
682
  catch (error) {
@@ -25,6 +25,24 @@ export declare class TimeoutError extends Error {
25
25
  * Load project rules from .codeep/rules.md or CODEEP.md
26
26
  */
27
27
  export declare function loadProjectRules(projectRoot: string): string;
28
+ /**
29
+ * Load agent progress log from .codeep/progress.md
30
+ * Injected into system prompt so agent knows what was previously done.
31
+ */
32
+ export declare function loadProgressLog(projectRoot: string): string;
33
+ /**
34
+ * Write agent progress log to .codeep/progress.md
35
+ * Called after each agent run so the next session has context.
36
+ */
37
+ export declare function writeProgressLog(projectRoot: string, prompt: string, result: {
38
+ success: boolean;
39
+ iterations: number;
40
+ actions: Array<{
41
+ type: string;
42
+ target: string;
43
+ }>;
44
+ finalResponse: string;
45
+ }): void;
28
46
  /**
29
47
  * Format chat session history for inclusion in agent system prompt.
30
48
  * Keeps the most recent messages within a character budget.
@@ -11,7 +11,7 @@
11
11
  * AgentChatResponse — response type (re-export from agentStream)
12
12
  * TimeoutError — distinguishes timeout from user abort
13
13
  */
14
- import { existsSync, readFileSync } from 'fs';
14
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
15
15
  import { join } from 'path';
16
16
  import { config, getApiKey } from '../config/index.js';
17
17
  import { getProviderBaseUrl, getProviderAuthHeader, supportsNativeTools, getEffectiveMaxTokens } from '../config/providers.js';
@@ -57,6 +57,85 @@ export function loadProjectRules(projectRoot) {
57
57
  }
58
58
  return '';
59
59
  }
60
+ /**
61
+ * Load agent progress log from .codeep/progress.md
62
+ * Injected into system prompt so agent knows what was previously done.
63
+ */
64
+ export function loadProgressLog(projectRoot) {
65
+ if (!projectRoot)
66
+ return '';
67
+ const progressFile = join(projectRoot, '.codeep', 'progress.md');
68
+ if (!existsSync(progressFile))
69
+ return '';
70
+ try {
71
+ const content = readFileSync(progressFile, 'utf-8').trim();
72
+ if (content) {
73
+ return `\n\n## Previous Session Progress\nThe agent has previously worked on this project. Read this to understand what was already done and what still needs to be done:\n\n${content}`;
74
+ }
75
+ }
76
+ catch (err) {
77
+ debug('Failed to read progress log from', progressFile, err);
78
+ }
79
+ return '';
80
+ }
81
+ /**
82
+ * Write agent progress log to .codeep/progress.md
83
+ * Called after each agent run so the next session has context.
84
+ */
85
+ export function writeProgressLog(projectRoot, prompt, result) {
86
+ if (!projectRoot)
87
+ return;
88
+ const codeepDir = join(projectRoot, '.codeep');
89
+ if (!existsSync(codeepDir))
90
+ return; // Only write if .codeep already exists (initialized project)
91
+ try {
92
+ const now = new Date().toISOString();
93
+ const fileWrites = result.actions.filter(a => a.type === 'write' || a.type === 'edit');
94
+ const fileDeletes = result.actions.filter(a => a.type === 'delete');
95
+ const commands = result.actions.filter(a => a.type === 'command');
96
+ const lines = [
97
+ `# Agent Progress Log`,
98
+ ``,
99
+ `## Last Session: ${now}`,
100
+ ``,
101
+ `### Task`,
102
+ `${prompt}`,
103
+ ``,
104
+ `### Status`,
105
+ result.success ? `✓ Completed (${result.iterations} iterations)` : `⚠ Incomplete — task may need to be continued`,
106
+ ``,
107
+ ];
108
+ if (fileWrites.length > 0) {
109
+ lines.push(`### Files Written/Edited`);
110
+ [...new Set(fileWrites.map(a => a.target))].forEach(f => lines.push(`- ${f}`));
111
+ lines.push('');
112
+ }
113
+ if (fileDeletes.length > 0) {
114
+ lines.push(`### Files Deleted`);
115
+ [...new Set(fileDeletes.map(a => a.target))].forEach(f => lines.push(`- ${f}`));
116
+ lines.push('');
117
+ }
118
+ if (commands.length > 0) {
119
+ lines.push(`### Commands Run`);
120
+ commands.forEach(a => lines.push(`- ${a.target}`));
121
+ lines.push('');
122
+ }
123
+ if (result.finalResponse) {
124
+ lines.push(`### Summary`);
125
+ lines.push(result.finalResponse.slice(0, 3000));
126
+ lines.push('');
127
+ }
128
+ if (!result.success) {
129
+ lines.push(`### What Still Needs to Be Done`);
130
+ lines.push(`The task was not fully completed in the last session. Run the agent again on the same project to continue from where it left off.`);
131
+ lines.push('');
132
+ }
133
+ writeFileSync(join(codeepDir, 'progress.md'), lines.join('\n'), 'utf-8');
134
+ }
135
+ catch (err) {
136
+ debug('Failed to write progress log:', err);
137
+ }
138
+ }
60
139
  /**
61
140
  * Format chat session history for inclusion in agent system prompt.
62
141
  * Keeps the most recent messages within a character budget.
@@ -76,11 +76,11 @@ class RateLimiter {
76
76
  }
77
77
  // Default rate limiters - configs are loaded from user settings
78
78
  let apiRateLimiter = new RateLimiter({
79
- maxRequests: config.get('rateLimitApi') || 30,
79
+ maxRequests: config.get('rateLimitApi') || 1000000,
80
80
  windowMs: 60 * 1000, // per minute
81
81
  });
82
82
  let commandRateLimiter = new RateLimiter({
83
- maxRequests: config.get('rateLimitCommands') || 100,
83
+ maxRequests: config.get('rateLimitCommands') || 10000,
84
84
  windowMs: 60 * 1000, // per minute
85
85
  });
86
86
  /**
@@ -88,11 +88,11 @@ let commandRateLimiter = new RateLimiter({
88
88
  */
89
89
  export function updateRateLimits() {
90
90
  apiRateLimiter = new RateLimiter({
91
- maxRequests: config.get('rateLimitApi') || 30,
91
+ maxRequests: config.get('rateLimitApi') || 1000000,
92
92
  windowMs: 60 * 1000,
93
93
  });
94
94
  commandRateLimiter = new RateLimiter({
95
- maxRequests: config.get('rateLimitCommands') || 100,
95
+ maxRequests: config.get('rateLimitCommands') || 10000,
96
96
  windowMs: 60 * 1000,
97
97
  });
98
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "1.2.87",
3
+ "version": "1.2.89",
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",