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.
Files changed (46) hide show
  1. package/bin/codeep.js +14 -0
  2. package/dist/api/index.js +7 -7
  3. package/dist/config/index.js +3 -3
  4. package/dist/config/providers.test.js +1 -1
  5. package/dist/hooks/index.js +1 -1
  6. package/dist/hooks/useAgent.js +3 -3
  7. package/dist/renderer/App.d.ts +1 -0
  8. package/dist/renderer/App.js +17 -13
  9. package/dist/renderer/ChatUI.js +3 -3
  10. package/dist/renderer/Input.js +9 -0
  11. package/dist/renderer/Screen.js +1 -1
  12. package/dist/renderer/components/Export.js +1 -1
  13. package/dist/renderer/components/Help.js +1 -1
  14. package/dist/renderer/components/Intro.js +1 -1
  15. package/dist/renderer/components/Login.js +3 -3
  16. package/dist/renderer/components/Logout.js +1 -1
  17. package/dist/renderer/components/Modal.js +2 -2
  18. package/dist/renderer/components/Permission.js +2 -2
  19. package/dist/renderer/components/Search.js +1 -1
  20. package/dist/renderer/components/SelectScreen.js +1 -1
  21. package/dist/renderer/components/Settings.js +3 -3
  22. package/dist/renderer/components/Status.js +1 -1
  23. package/dist/renderer/demo-app.js +1 -1
  24. package/dist/renderer/demo.js +1 -1
  25. package/dist/renderer/index.js +9 -9
  26. package/dist/renderer/main.js +32 -31
  27. package/dist/utils/agent.d.ts +13 -0
  28. package/dist/utils/agent.js +53 -8
  29. package/dist/utils/agent.test.js +66 -1
  30. package/dist/utils/codeReview.js +1 -1
  31. package/dist/utils/context.js +1 -1
  32. package/dist/utils/git.test.js +1 -1
  33. package/dist/utils/gitignore.test.js +1 -1
  34. package/dist/utils/keychain.js +1 -1
  35. package/dist/utils/project.test.js +1 -1
  36. package/dist/utils/ratelimit.js +1 -1
  37. package/dist/utils/ratelimit.test.js +1 -1
  38. package/dist/utils/retry.test.js +1 -1
  39. package/dist/utils/smartContext.js +1 -1
  40. package/dist/utils/smartContext.test.js +1 -1
  41. package/dist/utils/taskPlanner.js +2 -2
  42. package/dist/utils/tools.js +5 -5
  43. package/dist/utils/tools.test.js +13 -8
  44. package/dist/utils/validation.test.js +1 -1
  45. package/dist/utils/verify.js +1 -1
  46. 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: {
@@ -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', () => {
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Custom hooks for Codeep
3
3
  */
4
- export { useAgent } from './useAgent';
4
+ export { useAgent } from './useAgent.js';
@@ -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);
@@ -45,6 +45,7 @@ export declare class App {
45
45
  private agentThinking;
46
46
  private pasteInfo;
47
47
  private pasteInfoOpen;
48
+ private codeBlockCounter;
48
49
  private helpOpen;
49
50
  private helpScrollIndex;
50
51
  private statusOpen;
@@ -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 (if present)
2634
- if (lang) {
2635
- lines.push({ text: ' ' + lang, style: SYNTAX.codeLang, raw: false });
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) {
@@ -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;
@@ -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)
@@ -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,7 +1,7 @@
1
1
  /**
2
2
  * Export panel component
3
3
  */
4
- import { fg, style } from '../ansi';
4
+ import { fg, style } from '../ansi.js';
5
5
  // Primary color: #f02a30 (Codeep red)
6
6
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
7
7
  const FORMATS = [
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Help screen component
3
3
  */
4
- import { fg, style } from '../ansi';
4
+ import { fg, style } from '../ansi.js';
5
5
  // Primary color: #f02a30 (Codeep red)
6
6
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
7
7
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Intro animation component - matches Ink version style
3
3
  */
4
- import { fg, style } from '../ansi';
4
+ import { fg, style } from '../ansi.js';
5
5
  // ASCII Logo (same as Ink version)
6
6
  const LOGO = [
7
7
  ' ██████╗ ██████╗ ██████╗ ███████╗███████╗██████╗ ',
@@ -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)
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Logout panel component
3
3
  */
4
- import { fg, style } from '../ansi';
4
+ import { fg, style } from '../ansi.js';
5
5
  // Primary color: #f02a30 (Codeep red)
6
6
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
7
7
  /**
@@ -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);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Search panel component
3
3
  */
4
- import { fg, style } from '../ansi';
4
+ import { fg, style } from '../ansi.js';
5
5
  // Primary color: #f02a30 (Codeep red)
6
6
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
7
7
  /**
@@ -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);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Status screen component
3
3
  */
4
- import { fg, style } from '../ansi';
4
+ import { fg, style } from '../ansi.js';
5
5
  // Primary color: #f02a30 (Codeep red)
6
6
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
7
7
  /**
@@ -3,7 +3,7 @@
3
3
  * Demo for full App with modals
4
4
  * Run with: npm run demo:app
5
5
  */
6
- import { App } from './App';
6
+ import { App } from './App.js';
7
7
  // Simulate API response
8
8
  function simulateResponse(app, text) {
9
9
  return new Promise((resolve) => {
@@ -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) => {
@@ -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';
@@ -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) {
@@ -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
  */
@@ -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) {
@@ -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
+ });
@@ -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
@@ -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
  /**
@@ -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(() => {
@@ -1,5 +1,5 @@
1
1
  import keytar from 'keytar';
2
- import { logger } from './logger';
2
+ import { logger } from './logger.js';
3
3
  const SERVICE_NAME = 'codeep';
4
4
  class KeychainStorage {
5
5
  getAccountName(providerId) {
@@ -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(() => {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Rate limiting utility to prevent API abuse
3
3
  */
4
- import { config } from '../config/index';
4
+ import { config } from '../config/index.js';
5
5
  class RateLimiter {
6
6
  requests = [];
7
7
  config;
@@ -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
@@ -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
  */
@@ -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
@@ -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
- expect(tools).toHaveLength(ALL_TOOL_NAMES.length);
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 ALL_TOOL_NAMES) {
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
- expect(tools).toHaveLength(ALL_TOOL_NAMES.length);
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 ALL_TOOL_NAMES) {
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 ALL_TOOL_NAMES) {
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', () => {
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { existsSync, readFileSync } from 'fs';
6
6
  import { join } from 'path';
7
- import { executeCommand } from './shell';
7
+ import { executeCommand } from './shell.js';
8
8
  const DEFAULT_OPTIONS = {
9
9
  runBuild: true,
10
10
  runTest: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "1.2.12",
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",