codeep 1.2.12 → 1.2.14
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/bin/codeep.js +14 -0
- package/dist/api/index.js +7 -7
- package/dist/config/index.js +3 -3
- package/dist/config/providers.test.js +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/useAgent.js +3 -3
- package/dist/renderer/App.d.ts +1 -0
- package/dist/renderer/App.js +17 -13
- package/dist/renderer/ChatUI.js +3 -3
- package/dist/renderer/Input.js +9 -0
- package/dist/renderer/Screen.js +1 -1
- package/dist/renderer/components/Export.js +1 -1
- package/dist/renderer/components/Help.js +1 -1
- package/dist/renderer/components/Intro.js +1 -1
- package/dist/renderer/components/Login.js +3 -3
- package/dist/renderer/components/Logout.js +1 -1
- package/dist/renderer/components/Modal.js +2 -2
- package/dist/renderer/components/Permission.js +2 -2
- package/dist/renderer/components/Search.js +1 -1
- package/dist/renderer/components/SelectScreen.js +1 -1
- package/dist/renderer/components/Settings.js +3 -3
- package/dist/renderer/components/Status.js +1 -1
- package/dist/renderer/demo-app.js +1 -1
- package/dist/renderer/demo.js +1 -1
- package/dist/renderer/index.js +9 -9
- package/dist/renderer/main.js +32 -31
- package/dist/utils/agent.d.ts +13 -0
- package/dist/utils/agent.js +53 -8
- package/dist/utils/agent.test.js +66 -1
- package/dist/utils/codeReview.js +1 -1
- package/dist/utils/context.js +1 -1
- package/dist/utils/git.test.js +1 -1
- package/dist/utils/gitignore.test.js +1 -1
- package/dist/utils/keychain.js +1 -1
- package/dist/utils/project.test.js +1 -1
- package/dist/utils/ratelimit.js +1 -1
- package/dist/utils/ratelimit.test.js +1 -1
- package/dist/utils/retry.test.js +1 -1
- package/dist/utils/smartContext.js +1 -1
- package/dist/utils/smartContext.test.js +1 -1
- package/dist/utils/taskPlanner.js +2 -2
- package/dist/utils/tools.js +5 -5
- package/dist/utils/tools.test.js +13 -8
- package/dist/utils/validation.test.js +1 -1
- package/dist/utils/verify.js +1 -1
- package/package.json +2 -1
package/bin/codeep.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const main = join(__dirname, '..', 'dist', 'renderer', 'main.js');
|
|
8
|
+
|
|
9
|
+
const child = spawn(process.execPath, [main, ...process.argv.slice(2)], {
|
|
10
|
+
stdio: 'inherit',
|
|
11
|
+
env: process.env,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
package/dist/api/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { config, getApiKey } from '../config/index';
|
|
2
|
-
import { withRetry, isNetworkError, isTimeoutError } from '../utils/retry';
|
|
3
|
-
import { getProvider, getProviderBaseUrl, getProviderAuthHeader } from '../config/providers';
|
|
4
|
-
import { logApiRequest, logApiResponse } from '../utils/logger';
|
|
5
|
-
import { loadProjectIntelligence, generateContextFromIntelligence } from '../utils/projectIntelligence';
|
|
6
|
-
import { loadProjectRules } from '../utils/agent';
|
|
7
|
-
import { recordTokenUsage, extractOpenAIUsage, extractAnthropicUsage } from '../utils/tokenTracker';
|
|
1
|
+
import { config, getApiKey } from '../config/index.js';
|
|
2
|
+
import { withRetry, isNetworkError, isTimeoutError } from '../utils/retry.js';
|
|
3
|
+
import { getProvider, getProviderBaseUrl, getProviderAuthHeader } from '../config/providers.js';
|
|
4
|
+
import { logApiRequest, logApiResponse } from '../utils/logger.js';
|
|
5
|
+
import { loadProjectIntelligence, generateContextFromIntelligence } from '../utils/projectIntelligence.js';
|
|
6
|
+
import { loadProjectRules } from '../utils/agent.js';
|
|
7
|
+
import { recordTokenUsage, extractOpenAIUsage, extractAnthropicUsage } from '../utils/tokenTracker.js';
|
|
8
8
|
// Error messages by language
|
|
9
9
|
const ERROR_MESSAGES = {
|
|
10
10
|
en: {
|
package/dist/config/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import Conf from 'conf';
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync, statSync } from 'fs';
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
|
-
import { PROVIDERS, getProvider } from './providers';
|
|
5
|
-
import { logSession } from '../utils/logger';
|
|
4
|
+
import { PROVIDERS, getProvider } from './providers.js';
|
|
5
|
+
import { logSession } from '../utils/logger.js';
|
|
6
6
|
// We'll initialize GLOBAL_SESSIONS_DIR after config is created (to use config.path)
|
|
7
7
|
/**
|
|
8
8
|
* Get sessions directory - local .codeep/sessions/ if in project, otherwise global
|
|
@@ -422,7 +422,7 @@ export function getModelsForCurrentProvider() {
|
|
|
422
422
|
return models;
|
|
423
423
|
}
|
|
424
424
|
// Re-export PROVIDERS for convenience
|
|
425
|
-
export { PROVIDERS } from './providers';
|
|
425
|
+
export { PROVIDERS } from './providers.js';
|
|
426
426
|
// Generate unique session ID
|
|
427
427
|
function generateSessionId() {
|
|
428
428
|
const now = new Date();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { PROVIDERS, getProvider, getProviderList, getProviderModels, getProviderBaseUrl, getProviderAuthHeader, getProviderMcpEndpoints, } from './providers';
|
|
2
|
+
import { PROVIDERS, getProvider, getProviderList, getProviderModels, getProviderBaseUrl, getProviderAuthHeader, getProviderMcpEndpoints, } from './providers.js';
|
|
3
3
|
describe('providers', () => {
|
|
4
4
|
describe('PROVIDERS constant', () => {
|
|
5
5
|
it('should have z.ai provider', () => {
|
package/dist/hooks/index.js
CHANGED
package/dist/hooks/useAgent.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Reduces re-renders in App component when agent state changes
|
|
4
4
|
*/
|
|
5
5
|
import { useState, useCallback, useRef } from 'react';
|
|
6
|
-
import { runAgent, formatAgentResult } from '../utils/agent';
|
|
7
|
-
import { createActionLog } from '../utils/tools';
|
|
8
|
-
import { autoSaveSession } from '../config/index';
|
|
6
|
+
import { runAgent, formatAgentResult } from '../utils/agent.js';
|
|
7
|
+
import { createActionLog } from '../utils/tools.js';
|
|
8
|
+
import { autoSaveSession } from '../config/index.js';
|
|
9
9
|
export function useAgent({ projectContext, hasWriteAccess, messages, projectPath, onMessageAdd, notify, }) {
|
|
10
10
|
const [isAgentRunning, setIsAgentRunning] = useState(false);
|
|
11
11
|
const [agentIteration, setAgentIteration] = useState(0);
|
package/dist/renderer/App.d.ts
CHANGED
package/dist/renderer/App.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Main Application using custom renderer
|
|
3
3
|
* Replaces Ink-based App
|
|
4
4
|
*/
|
|
5
|
-
import { Screen } from './Screen';
|
|
6
|
-
import { Input, LineEditor } from './Input';
|
|
7
|
-
import { fg, style, stringWidth } from './ansi';
|
|
5
|
+
import { Screen } from './Screen.js';
|
|
6
|
+
import { Input, LineEditor } from './Input.js';
|
|
7
|
+
import { fg, style, stringWidth } from './ansi.js';
|
|
8
8
|
import clipboardy from 'clipboardy';
|
|
9
9
|
import { spawn } from 'child_process';
|
|
10
10
|
// Primary color: #f02a30 (Codeep red)
|
|
@@ -222,11 +222,11 @@ const COMMAND_DESCRIPTIONS = {
|
|
|
222
222
|
'context-clear': 'Clear saved context',
|
|
223
223
|
'learn': 'Learn code preferences',
|
|
224
224
|
};
|
|
225
|
-
import { helpCategories, keyboardShortcuts } from './components/Help';
|
|
226
|
-
import { handleSettingsKey, SETTINGS } from './components/Settings';
|
|
227
|
-
import { renderExportPanel, handleExportKey as handleExportKeyComponent } from './components/Export';
|
|
228
|
-
import { renderLogoutPanel, handleLogoutKey as handleLogoutKeyComponent } from './components/Logout';
|
|
229
|
-
import { renderSearchPanel, handleSearchKey as handleSearchKeyComponent } from './components/Search';
|
|
225
|
+
import { helpCategories, keyboardShortcuts } from './components/Help.js';
|
|
226
|
+
import { handleSettingsKey, SETTINGS } from './components/Settings.js';
|
|
227
|
+
import { renderExportPanel, handleExportKey as handleExportKeyComponent } from './components/Export.js';
|
|
228
|
+
import { renderLogoutPanel, handleLogoutKey as handleLogoutKeyComponent } from './components/Logout.js';
|
|
229
|
+
import { renderSearchPanel, handleSearchKey as handleSearchKeyComponent } from './components/Search.js';
|
|
230
230
|
export class App {
|
|
231
231
|
screen;
|
|
232
232
|
input;
|
|
@@ -250,6 +250,7 @@ export class App {
|
|
|
250
250
|
// Paste detection state
|
|
251
251
|
pasteInfo = null;
|
|
252
252
|
pasteInfoOpen = false;
|
|
253
|
+
codeBlockCounter = 0; // Global code block counter for /copy numbering
|
|
253
254
|
// Inline help state
|
|
254
255
|
helpOpen = false;
|
|
255
256
|
helpScrollIndex = 0;
|
|
@@ -2402,6 +2403,7 @@ export class App {
|
|
|
2402
2403
|
*/
|
|
2403
2404
|
getVisibleMessages(height, width) {
|
|
2404
2405
|
const allLines = [];
|
|
2406
|
+
this.codeBlockCounter = 0; // Reset block counter for each render pass
|
|
2405
2407
|
// Logo at the top, scrolls with content
|
|
2406
2408
|
if (height >= 20) {
|
|
2407
2409
|
const logoWidth = LOGO_LINES[0].length;
|
|
@@ -2455,6 +2457,7 @@ export class App {
|
|
|
2455
2457
|
isFirstLine = false;
|
|
2456
2458
|
}
|
|
2457
2459
|
// Add code block with syntax highlighting
|
|
2460
|
+
this.codeBlockCounter++;
|
|
2458
2461
|
const rawLang = (match[1] || 'text').trim();
|
|
2459
2462
|
// Handle filepath:name.ext format - extract extension as language
|
|
2460
2463
|
let lang = rawLang;
|
|
@@ -2463,7 +2466,7 @@ export class App {
|
|
|
2463
2466
|
lang = ext;
|
|
2464
2467
|
}
|
|
2465
2468
|
const code = match[2];
|
|
2466
|
-
const codeLines = this.formatCodeBlock(code, lang, maxWidth);
|
|
2469
|
+
const codeLines = this.formatCodeBlock(code, lang, maxWidth, this.codeBlockCounter);
|
|
2467
2470
|
lines.push(...codeLines);
|
|
2468
2471
|
lastIndex = match.index + match[0].length;
|
|
2469
2472
|
isFirstLine = false;
|
|
@@ -2623,16 +2626,17 @@ export class App {
|
|
|
2623
2626
|
/**
|
|
2624
2627
|
* Format code block with syntax highlighting (no border)
|
|
2625
2628
|
*/
|
|
2626
|
-
formatCodeBlock(code, lang, maxWidth) {
|
|
2629
|
+
formatCodeBlock(code, lang, maxWidth, blockNum) {
|
|
2627
2630
|
const lines = [];
|
|
2628
2631
|
const codeLines = code.split('\n');
|
|
2629
2632
|
// Remove trailing empty line if exists
|
|
2630
2633
|
if (codeLines.length > 0 && codeLines[codeLines.length - 1] === '') {
|
|
2631
2634
|
codeLines.pop();
|
|
2632
2635
|
}
|
|
2633
|
-
// Language label
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
+
// Language label with block number for /copy
|
|
2637
|
+
const label = blockNum ? (lang ? ` ${lang} [${blockNum}]` : ` [${blockNum}]`) : (lang ? ' ' + lang : '');
|
|
2638
|
+
if (label) {
|
|
2639
|
+
lines.push({ text: label, style: SYNTAX.codeLang, raw: false });
|
|
2636
2640
|
}
|
|
2637
2641
|
// Code lines with highlighting and indent
|
|
2638
2642
|
for (const codeLine of codeLines) {
|
package/dist/renderer/ChatUI.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Simple Chat UI - Proof of Concept
|
|
3
3
|
* Demonstrates custom renderer without Ink
|
|
4
4
|
*/
|
|
5
|
-
import { Screen } from './Screen';
|
|
6
|
-
import { Input, LineEditor } from './Input';
|
|
7
|
-
import { fg } from './ansi';
|
|
5
|
+
import { Screen } from './Screen.js';
|
|
6
|
+
import { Input, LineEditor } from './Input.js';
|
|
7
|
+
import { fg } from './ansi.js';
|
|
8
8
|
export class ChatUI {
|
|
9
9
|
screen;
|
|
10
10
|
input;
|
package/dist/renderer/Input.js
CHANGED
|
@@ -101,6 +101,15 @@ export class Input {
|
|
|
101
101
|
event.key = 'enter';
|
|
102
102
|
return event;
|
|
103
103
|
}
|
|
104
|
+
// Bracketed paste mode: terminal wraps Cmd+V paste in \x1b[200~ ... \x1b[201~
|
|
105
|
+
if (data.includes('\x1b[200~') || data.includes('\x1b[201~')) {
|
|
106
|
+
const pasteContent = data.replace(/\x1b\[200~/g, '').replace(/\x1b\[201~/g, '');
|
|
107
|
+
if (pasteContent.length > 0) {
|
|
108
|
+
event.key = pasteContent;
|
|
109
|
+
event.isPaste = true;
|
|
110
|
+
return event;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
104
113
|
// Detect paste: multiple printable characters at once (not escape sequences)
|
|
105
114
|
if (data.length > 1 && !data.startsWith('\x1b')) {
|
|
106
115
|
// Check if it's all printable characters (paste event)
|
package/dist/renderer/Screen.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Screen buffer with diff-based rendering
|
|
3
3
|
* Only writes changes to terminal - minimizes flickering
|
|
4
4
|
*/
|
|
5
|
-
import { cursor, screen, style, visibleLength, charWidth } from './ansi';
|
|
5
|
+
import { cursor, screen, style, visibleLength, charWidth } from './ansi.js';
|
|
6
6
|
export class Screen {
|
|
7
7
|
width;
|
|
8
8
|
height;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Login screen for API key setup
|
|
3
3
|
*/
|
|
4
|
-
import { LineEditor } from '../Input';
|
|
5
|
-
import { fg, style } from '../ansi';
|
|
6
|
-
import { createBox, centerBox } from './Box';
|
|
4
|
+
import { LineEditor } from '../Input.js';
|
|
5
|
+
import { fg, style } from '../ansi.js';
|
|
6
|
+
import { createBox, centerBox } from './Box.js';
|
|
7
7
|
import { spawn } from 'child_process';
|
|
8
8
|
import clipboardy from 'clipboardy';
|
|
9
9
|
// Primary color: #f02a30 (Codeep red)
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Modal overlay component
|
|
3
3
|
* Renders a box with content on top of existing screen
|
|
4
4
|
*/
|
|
5
|
-
import { fg, style } from '../ansi';
|
|
6
|
-
import { createBox, centerBox } from './Box';
|
|
5
|
+
import { fg, style } from '../ansi.js';
|
|
6
|
+
import { createBox, centerBox } from './Box.js';
|
|
7
7
|
// Primary color: #f02a30 (Codeep red)
|
|
8
8
|
const PRIMARY_COLOR = fg.rgb(240, 42, 48);
|
|
9
9
|
const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Permission screen for granting folder access
|
|
3
3
|
*/
|
|
4
|
-
import { fg, style } from '../ansi';
|
|
5
|
-
import { createBox, centerBox } from './Box';
|
|
4
|
+
import { fg, style } from '../ansi.js';
|
|
5
|
+
import { createBox, centerBox } from './Box.js';
|
|
6
6
|
// Primary color: #f02a30 (Codeep red)
|
|
7
7
|
const PRIMARY_COLOR = fg.rgb(240, 42, 48);
|
|
8
8
|
const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Generic fullscreen selection component
|
|
3
3
|
* Used for Language, Provider, Model, Protocol selection
|
|
4
4
|
*/
|
|
5
|
-
import { fg, style } from '../ansi';
|
|
5
|
+
import { fg, style } from '../ansi.js';
|
|
6
6
|
// Primary color: #f02a30 (Codeep red)
|
|
7
7
|
const PRIMARY_COLOR = fg.rgb(240, 42, 48);
|
|
8
8
|
const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Settings screen component
|
|
3
3
|
*/
|
|
4
|
-
import { fg, style } from '../ansi';
|
|
5
|
-
import { config } from '../../config/index';
|
|
6
|
-
import { updateRateLimits } from '../../utils/ratelimit';
|
|
4
|
+
import { fg, style } from '../ansi.js';
|
|
5
|
+
import { config } from '../../config/index.js';
|
|
6
|
+
import { updateRateLimits } from '../../utils/ratelimit.js';
|
|
7
7
|
// Primary color: #f02a30 (Codeep red)
|
|
8
8
|
const PRIMARY_COLOR = fg.rgb(240, 42, 48);
|
|
9
9
|
const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
|
package/dist/renderer/demo.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Demo/Test for custom renderer
|
|
4
4
|
* Run with: npx ts-node src/renderer/demo.ts
|
|
5
5
|
*/
|
|
6
|
-
import { ChatUI } from './ChatUI';
|
|
6
|
+
import { ChatUI } from './ChatUI.js';
|
|
7
7
|
// Simulate streaming response
|
|
8
8
|
function simulateStreaming(ui, text) {
|
|
9
9
|
return new Promise((resolve) => {
|
package/dist/renderer/index.js
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
* Uses direct ANSI escape codes and a virtual screen buffer
|
|
6
6
|
* with diff-based rendering for flicker-free updates.
|
|
7
7
|
*/
|
|
8
|
-
export { cursor, screen, fg, bg, style, styled, stripAnsi, visibleLength, truncate, wordWrap } from './ansi';
|
|
9
|
-
export { Screen } from './Screen';
|
|
10
|
-
export { Input, LineEditor } from './Input';
|
|
11
|
-
export { ChatUI } from './ChatUI';
|
|
12
|
-
export { App } from './App';
|
|
8
|
+
export { cursor, screen, fg, bg, style, styled, stripAnsi, visibleLength, truncate, wordWrap } from './ansi.js';
|
|
9
|
+
export { Screen } from './Screen.js';
|
|
10
|
+
export { Input, LineEditor } from './Input.js';
|
|
11
|
+
export { ChatUI } from './ChatUI.js';
|
|
12
|
+
export { App } from './App.js';
|
|
13
13
|
// Components
|
|
14
|
-
export { createBox, centerBox } from './components/Box';
|
|
15
|
-
export { renderModal, renderHelpModal, renderListModal } from './components/Modal';
|
|
16
|
-
export { renderHelpScreen, helpCategories, keyboardShortcuts } from './components/Help';
|
|
17
|
-
export { renderStatusScreen } from './components/Status';
|
|
14
|
+
export { createBox, centerBox } from './components/Box.js';
|
|
15
|
+
export { renderModal, renderHelpModal, renderListModal } from './components/Modal.js';
|
|
16
|
+
export { renderHelpScreen, helpCategories, keyboardShortcuts } from './components/Help.js';
|
|
17
|
+
export { renderStatusScreen } from './components/Status.js';
|
package/dist/renderer/main.js
CHANGED
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
* Codeep with Custom Renderer
|
|
4
4
|
* Main entry point using the new ANSI-based renderer instead of Ink
|
|
5
5
|
*/
|
|
6
|
-
import { App } from './App';
|
|
7
|
-
import { Screen } from './Screen';
|
|
8
|
-
import { Input } from './Input';
|
|
9
|
-
import { LoginScreen, renderProviderSelect } from './components/Login';
|
|
10
|
-
import { renderPermissionScreen, getPermissionOptions } from './components/Permission';
|
|
6
|
+
import { App } from './App.js';
|
|
7
|
+
import { Screen } from './Screen.js';
|
|
8
|
+
import { Input } from './Input.js';
|
|
9
|
+
import { LoginScreen, renderProviderSelect } from './components/Login.js';
|
|
10
|
+
import { renderPermissionScreen, getPermissionOptions } from './components/Permission.js';
|
|
11
11
|
// Intro animation is now handled by App.startIntro()
|
|
12
|
-
import { chat, setProjectContext } from '../api/index';
|
|
13
|
-
import { runAgent } from '../utils/agent';
|
|
14
|
-
import { config, loadApiKey, loadAllApiKeys, getCurrentProvider, getModelsForCurrentProvider, PROTOCOLS, LANGUAGES, setProvider, setApiKey, clearApiKey, getApiKey, autoSaveSession, saveSession, startNewSession, getCurrentSessionId, loadSession, listSessionsWithInfo, deleteSession, renameSession, hasReadPermission, hasWritePermission, setProjectPermission, initializeAsProject, isManuallyInitializedProject, } from '../config/index';
|
|
15
|
-
import { isProjectDirectory, getProjectContext } from '../utils/project';
|
|
16
|
-
import { getCurrentVersion } from '../utils/update';
|
|
17
|
-
import { getProviderList, getProvider } from '../config/providers';
|
|
18
|
-
import { getSessionStats } from '../utils/tokenTracker';
|
|
12
|
+
import { chat, setProjectContext } from '../api/index.js';
|
|
13
|
+
import { runAgent } from '../utils/agent.js';
|
|
14
|
+
import { config, loadApiKey, loadAllApiKeys, getCurrentProvider, getModelsForCurrentProvider, PROTOCOLS, LANGUAGES, setProvider, setApiKey, clearApiKey, getApiKey, autoSaveSession, saveSession, startNewSession, getCurrentSessionId, loadSession, listSessionsWithInfo, deleteSession, renameSession, hasReadPermission, hasWritePermission, setProjectPermission, initializeAsProject, isManuallyInitializedProject, } from '../config/index.js';
|
|
15
|
+
import { isProjectDirectory, getProjectContext } from '../utils/project.js';
|
|
16
|
+
import { getCurrentVersion } from '../utils/update.js';
|
|
17
|
+
import { getProviderList, getProvider } from '../config/providers.js';
|
|
18
|
+
import { getSessionStats } from '../utils/tokenTracker.js';
|
|
19
19
|
// State
|
|
20
20
|
let projectPath = process.cwd();
|
|
21
21
|
let projectContext = null;
|
|
@@ -67,7 +67,7 @@ function formatAddedFilesContext() {
|
|
|
67
67
|
async function handleSubmit(message) {
|
|
68
68
|
// Check if we're waiting for interactive mode answers
|
|
69
69
|
if (pendingInteractiveContext) {
|
|
70
|
-
const { parseAnswers, enhancePromptWithAnswers } = await import('../utils/interactive');
|
|
70
|
+
const { parseAnswers, enhancePromptWithAnswers } = await import('../utils/interactive.js');
|
|
71
71
|
const answers = parseAnswers(message, pendingInteractiveContext.context);
|
|
72
72
|
// Enhance the original prompt with user's answers
|
|
73
73
|
const enhancedTask = enhancePromptWithAnswers(pendingInteractiveContext.context, answers);
|
|
@@ -213,7 +213,7 @@ async function runAgentTask(task, dryRun = false) {
|
|
|
213
213
|
const interactiveMode = config.get('agentInteractive') !== false;
|
|
214
214
|
if (interactiveMode) {
|
|
215
215
|
// Analyze task for ambiguity
|
|
216
|
-
const { analyzeForClarification, formatQuestions } = await import('../utils/interactive');
|
|
216
|
+
const { analyzeForClarification, formatQuestions } = await import('../utils/interactive.js');
|
|
217
217
|
const interactiveContext = analyzeForClarification(task);
|
|
218
218
|
if (interactiveContext.needsClarification) {
|
|
219
219
|
// Store context for follow-up
|
|
@@ -317,6 +317,7 @@ async function executeAgentTask(task, dryRun = false) {
|
|
|
317
317
|
const enrichedTask = fileContext ? fileContext + task : task;
|
|
318
318
|
const result = await runAgent(enrichedTask, context, {
|
|
319
319
|
dryRun,
|
|
320
|
+
chatHistory: app.getChatHistory(),
|
|
320
321
|
onIteration: (iteration) => {
|
|
321
322
|
app.updateAgentProgress(iteration);
|
|
322
323
|
},
|
|
@@ -429,7 +430,7 @@ async function executeAgentTask(task, dryRun = false) {
|
|
|
429
430
|
// Auto-commit if enabled and there were file changes
|
|
430
431
|
if (!dryRun && config.get('agentAutoCommit') && result.actions.length > 0) {
|
|
431
432
|
try {
|
|
432
|
-
const { autoCommitAgentChanges, createBranchAndCommit } = await import('../utils/git');
|
|
433
|
+
const { autoCommitAgentChanges, createBranchAndCommit } = await import('../utils/git.js');
|
|
433
434
|
const useBranch = config.get('agentAutoCommitBranch');
|
|
434
435
|
if (useBranch) {
|
|
435
436
|
const commitResult = createBranchAndCommit(task, result.actions, context.root);
|
|
@@ -482,13 +483,13 @@ async function executeAgentTask(task, dryRun = false) {
|
|
|
482
483
|
* Wires the skill execution engine to App's UI.
|
|
483
484
|
*/
|
|
484
485
|
async function runSkill(nameOrShortcut, args) {
|
|
485
|
-
const { findSkill, parseSkillArgs, executeSkill, trackSkillUsage } = await import('../utils/skills');
|
|
486
|
+
const { findSkill, parseSkillArgs, executeSkill, trackSkillUsage } = await import('../utils/skills.js');
|
|
486
487
|
const skill = findSkill(nameOrShortcut);
|
|
487
488
|
if (!skill)
|
|
488
489
|
return false;
|
|
489
490
|
// Pre-flight checks
|
|
490
491
|
if (skill.requiresGit) {
|
|
491
|
-
const { getGitStatus } = await import('../utils/git');
|
|
492
|
+
const { getGitStatus } = await import('../utils/git.js');
|
|
492
493
|
if (!projectPath || !getGitStatus(projectPath).isRepo) {
|
|
493
494
|
app.notify('This skill requires a git repository');
|
|
494
495
|
return true;
|
|
@@ -732,7 +733,7 @@ function handleCommand(command, args) {
|
|
|
732
733
|
const staged = args.includes('--staged') || args.includes('-s');
|
|
733
734
|
app.notify(staged ? 'Getting staged diff...' : 'Getting diff...');
|
|
734
735
|
// Import dynamically to avoid circular deps
|
|
735
|
-
import('../utils/git').then(({ getGitDiff, formatDiffForDisplay }) => {
|
|
736
|
+
import('../utils/git.js').then(({ getGitDiff, formatDiffForDisplay }) => {
|
|
736
737
|
const result = getGitDiff(staged, projectPath);
|
|
737
738
|
if (!result.success || !result.diff) {
|
|
738
739
|
app.notify(result.error || 'No changes');
|
|
@@ -746,14 +747,14 @@ function handleCommand(command, args) {
|
|
|
746
747
|
break;
|
|
747
748
|
}
|
|
748
749
|
case 'undo': {
|
|
749
|
-
import('../utils/agent').then(({ undoLastAction }) => {
|
|
750
|
+
import('../utils/agent.js').then(({ undoLastAction }) => {
|
|
750
751
|
const result = undoLastAction();
|
|
751
752
|
app.notify(result.success ? `Undo: ${result.message}` : `Cannot undo: ${result.message}`);
|
|
752
753
|
});
|
|
753
754
|
break;
|
|
754
755
|
}
|
|
755
756
|
case 'undo-all': {
|
|
756
|
-
import('../utils/agent').then(({ undoAllActions }) => {
|
|
757
|
+
import('../utils/agent.js').then(({ undoAllActions }) => {
|
|
757
758
|
const result = undoAllActions();
|
|
758
759
|
app.notify(result.success ? `Undone ${result.results.length} action(s)` : 'Nothing to undo');
|
|
759
760
|
});
|
|
@@ -765,7 +766,7 @@ function handleCommand(command, args) {
|
|
|
765
766
|
return;
|
|
766
767
|
}
|
|
767
768
|
app.notify('Scanning project...');
|
|
768
|
-
import('../utils/projectIntelligence').then(({ scanProject, saveProjectIntelligence, generateContextFromIntelligence }) => {
|
|
769
|
+
import('../utils/projectIntelligence.js').then(({ scanProject, saveProjectIntelligence, generateContextFromIntelligence }) => {
|
|
769
770
|
scanProject(projectContext.root).then(intelligence => {
|
|
770
771
|
saveProjectIntelligence(projectContext.root, intelligence);
|
|
771
772
|
const context = generateContextFromIntelligence(intelligence);
|
|
@@ -785,7 +786,7 @@ function handleCommand(command, args) {
|
|
|
785
786
|
app.notify('No project context');
|
|
786
787
|
return;
|
|
787
788
|
}
|
|
788
|
-
import('../utils/codeReview').then(({ performCodeReview, formatReviewResult }) => {
|
|
789
|
+
import('../utils/codeReview.js').then(({ performCodeReview, formatReviewResult }) => {
|
|
789
790
|
const reviewFiles = args.length > 0 ? args : undefined;
|
|
790
791
|
const result = performCodeReview(projectContext, reviewFiles);
|
|
791
792
|
app.addMessage({
|
|
@@ -797,7 +798,7 @@ function handleCommand(command, args) {
|
|
|
797
798
|
}
|
|
798
799
|
case 'update': {
|
|
799
800
|
app.notify('Checking for updates...');
|
|
800
|
-
import('../utils/update').then(({ checkForUpdates, formatVersionInfo }) => {
|
|
801
|
+
import('../utils/update.js').then(({ checkForUpdates, formatVersionInfo }) => {
|
|
801
802
|
checkForUpdates().then(info => {
|
|
802
803
|
const message = formatVersionInfo(info);
|
|
803
804
|
app.notify(message.split('\n')[0], 5000);
|
|
@@ -1017,7 +1018,7 @@ function handleCommand(command, args) {
|
|
|
1017
1018
|
app.notify(`Invalid block number. Available: 1-${codeBlocks.length}`);
|
|
1018
1019
|
return;
|
|
1019
1020
|
}
|
|
1020
|
-
import('../utils/clipboard').then(({ copyToClipboard }) => {
|
|
1021
|
+
import('../utils/clipboard.js').then(({ copyToClipboard }) => {
|
|
1021
1022
|
if (copyToClipboard(codeBlocks[index])) {
|
|
1022
1023
|
app.notify(`Copied block ${index + 1} to clipboard`);
|
|
1023
1024
|
}
|
|
@@ -1219,7 +1220,7 @@ function handleCommand(command, args) {
|
|
|
1219
1220
|
}
|
|
1220
1221
|
// Agent history and changes
|
|
1221
1222
|
case 'history': {
|
|
1222
|
-
import('../utils/agent').then(({ getAgentHistory }) => {
|
|
1223
|
+
import('../utils/agent.js').then(({ getAgentHistory }) => {
|
|
1223
1224
|
const history = getAgentHistory();
|
|
1224
1225
|
if (history.length === 0) {
|
|
1225
1226
|
app.notify('No agent history');
|
|
@@ -1239,7 +1240,7 @@ function handleCommand(command, args) {
|
|
|
1239
1240
|
break;
|
|
1240
1241
|
}
|
|
1241
1242
|
case 'changes': {
|
|
1242
|
-
import('../utils/agent').then(({ getCurrentSessionActions }) => {
|
|
1243
|
+
import('../utils/agent.js').then(({ getCurrentSessionActions }) => {
|
|
1243
1244
|
const actions = getCurrentSessionActions();
|
|
1244
1245
|
if (actions.length === 0) {
|
|
1245
1246
|
app.notify('No changes in current session');
|
|
@@ -1286,7 +1287,7 @@ function handleCommand(command, args) {
|
|
|
1286
1287
|
// Learning mode
|
|
1287
1288
|
case 'learn': {
|
|
1288
1289
|
if (args[0] === 'status') {
|
|
1289
|
-
import('../utils/learning').then(({ getLearningStatus }) => {
|
|
1290
|
+
import('../utils/learning.js').then(({ getLearningStatus }) => {
|
|
1290
1291
|
const status = getLearningStatus(projectPath);
|
|
1291
1292
|
app.addMessage({
|
|
1292
1293
|
role: 'system',
|
|
@@ -1298,7 +1299,7 @@ function handleCommand(command, args) {
|
|
|
1298
1299
|
return;
|
|
1299
1300
|
}
|
|
1300
1301
|
if (args[0] === 'rule' && args.length > 1) {
|
|
1301
|
-
import('../utils/learning').then(({ addCustomRule }) => {
|
|
1302
|
+
import('../utils/learning.js').then(({ addCustomRule }) => {
|
|
1302
1303
|
addCustomRule(projectPath, args.slice(1).join(' '));
|
|
1303
1304
|
app.notify('Custom rule added');
|
|
1304
1305
|
}).catch(() => {
|
|
@@ -1311,7 +1312,7 @@ function handleCommand(command, args) {
|
|
|
1311
1312
|
return;
|
|
1312
1313
|
}
|
|
1313
1314
|
app.notify('Learning from project...');
|
|
1314
|
-
import('../utils/learning').then(({ learnFromProject, formatPreferencesForPrompt }) => {
|
|
1315
|
+
import('../utils/learning.js').then(({ learnFromProject, formatPreferencesForPrompt }) => {
|
|
1315
1316
|
// Get some source files to learn from
|
|
1316
1317
|
import('fs').then(fs => {
|
|
1317
1318
|
import('path').then(path => {
|
|
@@ -1394,7 +1395,7 @@ function handleCommand(command, args) {
|
|
|
1394
1395
|
break;
|
|
1395
1396
|
}
|
|
1396
1397
|
case 'skills': {
|
|
1397
|
-
import('../utils/skills').then(({ getAllSkills, searchSkills, formatSkillsList, getSkillStats }) => {
|
|
1398
|
+
import('../utils/skills.js').then(({ getAllSkills, searchSkills, formatSkillsList, getSkillStats }) => {
|
|
1398
1399
|
const query = args.join(' ').toLowerCase();
|
|
1399
1400
|
// Check for stats subcommand
|
|
1400
1401
|
if (query === 'stats') {
|
|
@@ -1418,7 +1419,7 @@ function handleCommand(command, args) {
|
|
|
1418
1419
|
break;
|
|
1419
1420
|
}
|
|
1420
1421
|
case 'skill': {
|
|
1421
|
-
import('../utils/skills').then(({ findSkill, formatSkillHelp, createSkillTemplate, saveCustomSkill, deleteCustomSkill }) => {
|
|
1422
|
+
import('../utils/skills.js').then(({ findSkill, formatSkillHelp, createSkillTemplate, saveCustomSkill, deleteCustomSkill }) => {
|
|
1422
1423
|
const subCommand = args[0]?.toLowerCase();
|
|
1423
1424
|
const skillName = args[1];
|
|
1424
1425
|
if (!subCommand) {
|
package/dist/utils/agent.d.ts
CHANGED
|
@@ -21,6 +21,10 @@ export interface AgentOptions {
|
|
|
21
21
|
autoVerify?: boolean;
|
|
22
22
|
maxFixAttempts?: number;
|
|
23
23
|
usePlanning?: boolean;
|
|
24
|
+
chatHistory?: Array<{
|
|
25
|
+
role: 'user' | 'assistant';
|
|
26
|
+
content: string;
|
|
27
|
+
}>;
|
|
24
28
|
}
|
|
25
29
|
export interface AgentResult {
|
|
26
30
|
success: boolean;
|
|
@@ -35,6 +39,15 @@ export interface AgentResult {
|
|
|
35
39
|
* Returns the rules content formatted for system prompt, or empty string if no rules found
|
|
36
40
|
*/
|
|
37
41
|
export declare function loadProjectRules(projectRoot: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Format chat session history for inclusion in agent system prompt.
|
|
44
|
+
* Keeps the most recent messages within a character budget so the agent
|
|
45
|
+
* has conversational context without overwhelming the context window.
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatChatHistoryForAgent(history?: Array<{
|
|
48
|
+
role: 'user' | 'assistant';
|
|
49
|
+
content: string;
|
|
50
|
+
}>, maxChars?: number): string;
|
|
38
51
|
/**
|
|
39
52
|
* Run the agent loop
|
|
40
53
|
*/
|
package/dist/utils/agent.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { existsSync, readFileSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
|
-
import { recordTokenUsage, extractOpenAIUsage, extractAnthropicUsage } from './tokenTracker';
|
|
6
|
+
import { recordTokenUsage, extractOpenAIUsage, extractAnthropicUsage } from './tokenTracker.js';
|
|
7
7
|
// Debug logging helper - only logs when CODEEP_DEBUG=1
|
|
8
8
|
const debug = (...args) => {
|
|
9
9
|
if (process.env.CODEEP_DEBUG === '1') {
|
|
@@ -38,13 +38,13 @@ function calculateDynamicTimeout(prompt, iteration, baseTimeout) {
|
|
|
38
38
|
const calculatedTimeout = baseTimeout * multiplier;
|
|
39
39
|
return Math.min(Math.max(calculatedTimeout, 120000), 300000);
|
|
40
40
|
}
|
|
41
|
-
import { parseToolCalls, executeTool, createActionLog, formatToolDefinitions, getOpenAITools, getAnthropicTools, parseOpenAIToolCalls, parseAnthropicToolCalls } from './tools';
|
|
42
|
-
import { config, getApiKey } from '../config/index';
|
|
43
|
-
import { getProviderBaseUrl, getProviderAuthHeader, supportsNativeTools } from '../config/providers';
|
|
44
|
-
import { startSession, endSession, undoLastAction, undoAllActions, getCurrentSession, getRecentSessions, formatSession } from './history';
|
|
45
|
-
import { runAllVerifications, formatErrorsForAgent, hasVerificationErrors, getVerificationSummary } from './verify';
|
|
46
|
-
import { gatherSmartContext, formatSmartContext, extractTargetFile } from './smartContext';
|
|
47
|
-
import { planTasks, formatTaskPlan } from './taskPlanner';
|
|
41
|
+
import { parseToolCalls, executeTool, createActionLog, formatToolDefinitions, getOpenAITools, getAnthropicTools, parseOpenAIToolCalls, parseAnthropicToolCalls } from './tools.js';
|
|
42
|
+
import { config, getApiKey } from '../config/index.js';
|
|
43
|
+
import { getProviderBaseUrl, getProviderAuthHeader, supportsNativeTools } from '../config/providers.js';
|
|
44
|
+
import { startSession, endSession, undoLastAction, undoAllActions, getCurrentSession, getRecentSessions, formatSession } from './history.js';
|
|
45
|
+
import { runAllVerifications, formatErrorsForAgent, hasVerificationErrors, getVerificationSummary } from './verify.js';
|
|
46
|
+
import { gatherSmartContext, formatSmartContext, extractTargetFile } from './smartContext.js';
|
|
47
|
+
import { planTasks, formatTaskPlan } from './taskPlanner.js';
|
|
48
48
|
const DEFAULT_OPTIONS = {
|
|
49
49
|
maxIterations: 100, // Increased for large tasks
|
|
50
50
|
maxDuration: 20 * 60 * 1000, // 20 minutes
|
|
@@ -75,6 +75,46 @@ export function loadProjectRules(projectRoot) {
|
|
|
75
75
|
}
|
|
76
76
|
return '';
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Format chat session history for inclusion in agent system prompt.
|
|
80
|
+
* Keeps the most recent messages within a character budget so the agent
|
|
81
|
+
* has conversational context without overwhelming the context window.
|
|
82
|
+
*/
|
|
83
|
+
export function formatChatHistoryForAgent(history, maxChars = 8000) {
|
|
84
|
+
if (!history || history.length === 0)
|
|
85
|
+
return '';
|
|
86
|
+
// Filter out agent execution messages
|
|
87
|
+
const filtered = history.filter(m => {
|
|
88
|
+
const content = m.content.trimStart();
|
|
89
|
+
if (content.startsWith('[AGENT]') || content.startsWith('[DRY RUN]'))
|
|
90
|
+
return false;
|
|
91
|
+
if (content.startsWith('Agent completed') || content.startsWith('Agent failed') || content.startsWith('Agent stopped'))
|
|
92
|
+
return false;
|
|
93
|
+
return true;
|
|
94
|
+
});
|
|
95
|
+
if (filtered.length === 0)
|
|
96
|
+
return '';
|
|
97
|
+
// Walk backward (newest first) and accumulate within budget
|
|
98
|
+
const selected = [];
|
|
99
|
+
let totalChars = 0;
|
|
100
|
+
for (let i = filtered.length - 1; i >= 0; i--) {
|
|
101
|
+
const msg = filtered[i];
|
|
102
|
+
const entry = `${msg.role === 'user' ? 'User' : 'Assistant'}: ${msg.content}`;
|
|
103
|
+
if (totalChars + entry.length > maxChars && selected.length > 0)
|
|
104
|
+
break;
|
|
105
|
+
// If single message exceeds budget, truncate it
|
|
106
|
+
if (entry.length > maxChars) {
|
|
107
|
+
selected.unshift({ role: msg.role, content: msg.content.slice(0, maxChars - 100) + '\n[truncated]' });
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
selected.unshift(msg);
|
|
111
|
+
totalChars += entry.length;
|
|
112
|
+
}
|
|
113
|
+
if (selected.length === 0)
|
|
114
|
+
return '';
|
|
115
|
+
const lines = selected.map(m => `**${m.role === 'user' ? 'User' : 'Assistant'}:** ${m.content}`).join('\n\n');
|
|
116
|
+
return `\n\n## Prior Conversation Context\nThe following is the recent chat history from this session. Use it as background context to understand the user's intent, but focus on completing the current task.\n\n${lines}`;
|
|
117
|
+
}
|
|
78
118
|
/**
|
|
79
119
|
* Generate system prompt for agent mode (used with native tool calling)
|
|
80
120
|
*/
|
|
@@ -753,6 +793,11 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
753
793
|
if (smartContextStr) {
|
|
754
794
|
systemPrompt += '\n\n' + smartContextStr;
|
|
755
795
|
}
|
|
796
|
+
// Inject prior chat session context
|
|
797
|
+
const chatHistoryStr = formatChatHistoryForAgent(opts.chatHistory);
|
|
798
|
+
if (chatHistoryStr) {
|
|
799
|
+
systemPrompt += chatHistoryStr;
|
|
800
|
+
}
|
|
756
801
|
// Initial user message with optional task plan
|
|
757
802
|
let initialPrompt = prompt;
|
|
758
803
|
if (taskPlan) {
|
package/dist/utils/agent.test.js
CHANGED
|
@@ -11,7 +11,7 @@ vi.mock('fs', async (importOriginal) => {
|
|
|
11
11
|
});
|
|
12
12
|
import { existsSync, readFileSync } from 'fs';
|
|
13
13
|
import { join } from 'path';
|
|
14
|
-
import { loadProjectRules, formatAgentResult } from './agent';
|
|
14
|
+
import { loadProjectRules, formatAgentResult, formatChatHistoryForAgent } from './agent.js';
|
|
15
15
|
// Cast mocked functions for convenience
|
|
16
16
|
const mockExistsSync = existsSync;
|
|
17
17
|
const mockReadFileSync = readFileSync;
|
|
@@ -248,3 +248,68 @@ describe('formatAgentResult', () => {
|
|
|
248
248
|
}
|
|
249
249
|
});
|
|
250
250
|
});
|
|
251
|
+
describe('formatChatHistoryForAgent', () => {
|
|
252
|
+
it('should return empty string for undefined input', () => {
|
|
253
|
+
expect(formatChatHistoryForAgent(undefined)).toBe('');
|
|
254
|
+
});
|
|
255
|
+
it('should return empty string for empty array', () => {
|
|
256
|
+
expect(formatChatHistoryForAgent([])).toBe('');
|
|
257
|
+
});
|
|
258
|
+
it('should format simple chat history', () => {
|
|
259
|
+
const history = [
|
|
260
|
+
{ role: 'user', content: 'How do I fix the login bug?' },
|
|
261
|
+
{ role: 'assistant', content: 'Check the auth middleware in src/auth.ts' },
|
|
262
|
+
];
|
|
263
|
+
const result = formatChatHistoryForAgent(history);
|
|
264
|
+
expect(result).toContain('## Prior Conversation Context');
|
|
265
|
+
expect(result).toContain('**User:** How do I fix the login bug?');
|
|
266
|
+
expect(result).toContain('**Assistant:** Check the auth middleware in src/auth.ts');
|
|
267
|
+
});
|
|
268
|
+
it('should filter out [AGENT] messages', () => {
|
|
269
|
+
const history = [
|
|
270
|
+
{ role: 'user', content: 'Hello' },
|
|
271
|
+
{ role: 'user', content: '[AGENT] fix the bug' },
|
|
272
|
+
{ role: 'assistant', content: 'Agent completed in 3 iteration(s)' },
|
|
273
|
+
{ role: 'user', content: 'Thanks' },
|
|
274
|
+
];
|
|
275
|
+
const result = formatChatHistoryForAgent(history);
|
|
276
|
+
expect(result).toContain('**User:** Hello');
|
|
277
|
+
expect(result).toContain('**User:** Thanks');
|
|
278
|
+
expect(result).not.toContain('[AGENT]');
|
|
279
|
+
expect(result).not.toContain('Agent completed');
|
|
280
|
+
});
|
|
281
|
+
it('should filter out [DRY RUN] messages', () => {
|
|
282
|
+
const history = [
|
|
283
|
+
{ role: 'user', content: '[DRY RUN] test task' },
|
|
284
|
+
];
|
|
285
|
+
const result = formatChatHistoryForAgent(history);
|
|
286
|
+
expect(result).toBe('');
|
|
287
|
+
});
|
|
288
|
+
it('should filter out Agent failed/stopped messages', () => {
|
|
289
|
+
const history = [
|
|
290
|
+
{ role: 'assistant', content: 'Agent failed: timeout' },
|
|
291
|
+
{ role: 'assistant', content: 'Agent stopped by user' },
|
|
292
|
+
];
|
|
293
|
+
const result = formatChatHistoryForAgent(history);
|
|
294
|
+
expect(result).toBe('');
|
|
295
|
+
});
|
|
296
|
+
it('should respect character budget and keep newest messages', () => {
|
|
297
|
+
const history = [
|
|
298
|
+
{ role: 'user', content: 'A'.repeat(5000) },
|
|
299
|
+
{ role: 'assistant', content: 'B'.repeat(5000) },
|
|
300
|
+
{ role: 'user', content: 'Most recent message' },
|
|
301
|
+
];
|
|
302
|
+
const result = formatChatHistoryForAgent(history, 6000);
|
|
303
|
+
expect(result).toContain('Most recent message');
|
|
304
|
+
// The first 5000-char message should be dropped due to budget
|
|
305
|
+
expect(result).not.toContain('AAAAA');
|
|
306
|
+
});
|
|
307
|
+
it('should truncate a single very long message', () => {
|
|
308
|
+
const history = [
|
|
309
|
+
{ role: 'user', content: 'X'.repeat(20000) },
|
|
310
|
+
];
|
|
311
|
+
const result = formatChatHistoryForAgent(history, 8000);
|
|
312
|
+
expect(result).toContain('[truncated]');
|
|
313
|
+
expect(result.length).toBeLessThan(9000);
|
|
314
|
+
});
|
|
315
|
+
});
|
package/dist/utils/codeReview.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
5
5
|
import { join, extname, relative } from 'path';
|
|
6
|
-
import { getChangedFiles } from './git';
|
|
6
|
+
import { getChangedFiles } from './git.js';
|
|
7
7
|
// Common code patterns that indicate issues
|
|
8
8
|
const CODE_PATTERNS = [
|
|
9
9
|
// Security issues
|
package/dist/utils/context.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from 'fs';
|
|
5
5
|
import { join, basename } from 'path';
|
|
6
6
|
import { homedir } from 'os';
|
|
7
|
-
import { logger } from './logger';
|
|
7
|
+
import { logger } from './logger.js';
|
|
8
8
|
// Context storage directory
|
|
9
9
|
const CONTEXT_DIR = join(homedir(), '.codeep', 'contexts');
|
|
10
10
|
/**
|
package/dist/utils/git.test.js
CHANGED
|
@@ -3,7 +3,7 @@ import { execSync } from 'child_process';
|
|
|
3
3
|
import { mkdirSync, rmSync, writeFileSync } from 'fs';
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { tmpdir } from 'os';
|
|
6
|
-
import { isGitRepository, getGitStatus, getGitDiff, getChangedFiles, suggestCommitMessage, createCommit, stageAll, formatDiffForDisplay, } from './git';
|
|
6
|
+
import { isGitRepository, getGitStatus, getGitDiff, getChangedFiles, suggestCommitMessage, createCommit, stageAll, formatDiffForDisplay, } from './git.js';
|
|
7
7
|
// Create a temp directory for git tests
|
|
8
8
|
const TEST_DIR = join(tmpdir(), 'codeep-git-test-' + Date.now());
|
|
9
9
|
const NON_GIT_DIR = join(tmpdir(), 'codeep-non-git-test-' + Date.now());
|
|
@@ -5,7 +5,7 @@ vi.mock('fs', () => ({
|
|
|
5
5
|
readFileSync: vi.fn(),
|
|
6
6
|
}));
|
|
7
7
|
import { existsSync, readFileSync } from 'fs';
|
|
8
|
-
import { loadIgnoreRules, isIgnored } from './gitignore';
|
|
8
|
+
import { loadIgnoreRules, isIgnored } from './gitignore.js';
|
|
9
9
|
const mockExistsSync = existsSync;
|
|
10
10
|
const mockReadFileSync = readFileSync;
|
|
11
11
|
beforeEach(() => {
|
package/dist/utils/keychain.js
CHANGED
|
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
|
2
2
|
import { mkdirSync, rmSync, writeFileSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { tmpdir } from 'os';
|
|
5
|
-
import { isProjectDirectory, getProjectType, scanDirectory, generateTreeStructure, readProjectFile, deleteProjectFile, writeProjectFile, } from './project';
|
|
5
|
+
import { isProjectDirectory, getProjectType, scanDirectory, generateTreeStructure, readProjectFile, deleteProjectFile, writeProjectFile, } from './project.js';
|
|
6
6
|
const TEST_DIR = join(tmpdir(), 'codeep-project-test-' + Date.now());
|
|
7
7
|
describe('project utilities', () => {
|
|
8
8
|
beforeEach(() => {
|
package/dist/utils/ratelimit.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { checkApiRateLimit, checkCommandRateLimit, resetRateLimits, getRateLimitStatus, } from './ratelimit';
|
|
2
|
+
import { checkApiRateLimit, checkCommandRateLimit, resetRateLimits, getRateLimitStatus, } from './ratelimit.js';
|
|
3
3
|
describe('ratelimit utilities', () => {
|
|
4
4
|
beforeEach(() => {
|
|
5
5
|
// Reset rate limiters before each test
|
package/dist/utils/retry.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { withRetry, isNetworkError, isTimeoutError, fetchWithTimeout, } from './retry';
|
|
2
|
+
import { withRetry, isNetworkError, isTimeoutError, fetchWithTimeout, } from './retry.js';
|
|
3
3
|
describe('retry utilities', () => {
|
|
4
4
|
describe('isNetworkError', () => {
|
|
5
5
|
it('should detect fetch TypeError', () => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { existsSync, readFileSync, statSync } from 'fs';
|
|
5
5
|
import { join, dirname, basename, extname, relative } from 'path';
|
|
6
|
-
import { loadIgnoreRules, isIgnored } from './gitignore';
|
|
6
|
+
import { loadIgnoreRules, isIgnored } from './gitignore.js';
|
|
7
7
|
// Max context size (characters)
|
|
8
8
|
const MAX_CONTEXT_SIZE = 50000;
|
|
9
9
|
const MAX_FILES = 15;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { extractTargetFile, formatSmartContext, } from './smartContext';
|
|
2
|
+
import { extractTargetFile, formatSmartContext, } from './smartContext.js';
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
// Helper to build a SmartContextResult quickly
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Task Planning - breaks down complex tasks into subtasks
|
|
3
3
|
*/
|
|
4
|
-
import { config, getApiKey } from '../config/index';
|
|
5
|
-
import { getProviderBaseUrl, getProviderAuthHeader } from '../config/providers';
|
|
4
|
+
import { config, getApiKey } from '../config/index.js';
|
|
5
|
+
import { getProviderBaseUrl, getProviderAuthHeader } from '../config/providers.js';
|
|
6
6
|
/**
|
|
7
7
|
* Ask AI to break down a complex task into subtasks
|
|
8
8
|
*/
|
package/dist/utils/tools.js
CHANGED
|
@@ -9,11 +9,11 @@ const debug = (...args) => {
|
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
11
|
import { join, dirname, relative, resolve, isAbsolute } from 'path';
|
|
12
|
-
import { executeCommand } from './shell';
|
|
13
|
-
import { recordWrite, recordEdit, recordDelete, recordMkdir, recordCommand } from './history';
|
|
14
|
-
import { loadIgnoreRules, isIgnored } from './gitignore';
|
|
15
|
-
import { config, getApiKey } from '../config/index';
|
|
16
|
-
import { getProviderMcpEndpoints } from '../config/providers';
|
|
12
|
+
import { executeCommand } from './shell.js';
|
|
13
|
+
import { recordWrite, recordEdit, recordDelete, recordMkdir, recordCommand } from './history.js';
|
|
14
|
+
import { loadIgnoreRules, isIgnored } from './gitignore.js';
|
|
15
|
+
import { config, getApiKey } from '../config/index.js';
|
|
16
|
+
import { getProviderMcpEndpoints } from '../config/providers.js';
|
|
17
17
|
// Z.AI MCP tool names (available when user has any Z.AI API key)
|
|
18
18
|
const ZAI_MCP_TOOLS = ['web_search', 'web_read', 'github_read'];
|
|
19
19
|
// Z.AI provider IDs that have MCP endpoints
|
package/dist/utils/tools.test.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { getOpenAITools, getAnthropicTools, parseToolCalls, parseOpenAIToolCalls, parseAnthropicToolCalls, createActionLog, AGENT_TOOLS, } from './tools';
|
|
2
|
+
import { getOpenAITools, getAnthropicTools, parseToolCalls, parseOpenAIToolCalls, parseAnthropicToolCalls, createActionLog, AGENT_TOOLS, } from './tools.js';
|
|
3
3
|
// ─── CONSTANTS ───────────────────────────────────────────────────────────────
|
|
4
4
|
const ALL_TOOL_NAMES = Object.keys(AGENT_TOOLS);
|
|
5
|
+
// MCP tools are filtered out when no Z.AI API key is configured (e.g. in tests)
|
|
6
|
+
const ZAI_MCP_TOOLS = ['web_search', 'web_read', 'github_read'];
|
|
7
|
+
const CORE_TOOL_NAMES = ALL_TOOL_NAMES.filter(n => !ZAI_MCP_TOOLS.includes(n));
|
|
5
8
|
// ─── getOpenAITools ──────────────────────────────────────────────────────────
|
|
6
9
|
describe('getOpenAITools', () => {
|
|
7
10
|
it('should return one entry per AGENT_TOOLS definition', () => {
|
|
8
11
|
const tools = getOpenAITools();
|
|
9
|
-
|
|
12
|
+
// MCP tools are excluded when no Z.AI API key is configured
|
|
13
|
+
expect(tools).toHaveLength(CORE_TOOL_NAMES.length);
|
|
10
14
|
});
|
|
11
15
|
it('should wrap every tool in the OpenAI function-calling envelope', () => {
|
|
12
16
|
const tools = getOpenAITools();
|
|
@@ -20,10 +24,10 @@ describe('getOpenAITools', () => {
|
|
|
20
24
|
expect(Array.isArray(tool.function.parameters.required)).toBe(true);
|
|
21
25
|
}
|
|
22
26
|
});
|
|
23
|
-
it('should include all tool names from AGENT_TOOLS', () => {
|
|
27
|
+
it('should include all core tool names from AGENT_TOOLS', () => {
|
|
24
28
|
const tools = getOpenAITools();
|
|
25
29
|
const names = tools.map(t => t.function.name);
|
|
26
|
-
for (const name of
|
|
30
|
+
for (const name of CORE_TOOL_NAMES) {
|
|
27
31
|
expect(names).toContain(name);
|
|
28
32
|
}
|
|
29
33
|
});
|
|
@@ -71,7 +75,8 @@ describe('getOpenAITools', () => {
|
|
|
71
75
|
describe('getAnthropicTools', () => {
|
|
72
76
|
it('should return one entry per AGENT_TOOLS definition', () => {
|
|
73
77
|
const tools = getAnthropicTools();
|
|
74
|
-
|
|
78
|
+
// MCP tools are excluded when no Z.AI API key is configured
|
|
79
|
+
expect(tools).toHaveLength(CORE_TOOL_NAMES.length);
|
|
75
80
|
});
|
|
76
81
|
it('should use Anthropic tool-use shape (name, description, input_schema)', () => {
|
|
77
82
|
const tools = getAnthropicTools();
|
|
@@ -84,17 +89,17 @@ describe('getAnthropicTools', () => {
|
|
|
84
89
|
expect(Array.isArray(tool.input_schema.required)).toBe(true);
|
|
85
90
|
}
|
|
86
91
|
});
|
|
87
|
-
it('should include all tool names from AGENT_TOOLS', () => {
|
|
92
|
+
it('should include all core tool names from AGENT_TOOLS', () => {
|
|
88
93
|
const tools = getAnthropicTools();
|
|
89
94
|
const names = tools.map(t => t.name);
|
|
90
|
-
for (const name of
|
|
95
|
+
for (const name of CORE_TOOL_NAMES) {
|
|
91
96
|
expect(names).toContain(name);
|
|
92
97
|
}
|
|
93
98
|
});
|
|
94
99
|
it('should have same required params as OpenAI format', () => {
|
|
95
100
|
const openai = getOpenAITools();
|
|
96
101
|
const anthropic = getAnthropicTools();
|
|
97
|
-
for (const name of
|
|
102
|
+
for (const name of CORE_TOOL_NAMES) {
|
|
98
103
|
const oTool = openai.find(t => t.function.name === name);
|
|
99
104
|
const aTool = anthropic.find(t => t.name === name);
|
|
100
105
|
expect(aTool.input_schema.required).toEqual(oTool.function.parameters.required);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { validateInput, validateApiKey, validateCommandArgs, validateFilePath, sanitizeOutput, } from './validation';
|
|
2
|
+
import { validateInput, validateApiKey, validateCommandArgs, validateFilePath, sanitizeOutput, } from './validation.js';
|
|
3
3
|
describe('validation utilities', () => {
|
|
4
4
|
describe('validateInput', () => {
|
|
5
5
|
it('should reject empty input', () => {
|
package/dist/utils/verify.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.14",
|
|
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",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"dev": "node --import tsx src/renderer/main.ts",
|
|
12
|
+
"prepack": "tsc; node scripts/fix-imports.js",
|
|
12
13
|
"build": "tsc && node scripts/fix-imports.js",
|
|
13
14
|
"start": "node dist/renderer/main.js",
|
|
14
15
|
"demo:renderer": "node --import tsx src/renderer/demo.ts",
|