codeep 1.2.13 → 1.2.15

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.
@@ -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;
@@ -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) {
@@ -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)
@@ -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
  },
@@ -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
  */
@@ -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 = 16000) {
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.js';
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "1.2.13",
3
+ "version": "1.2.15",
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",