@@ -63,6 +63,71 @@ const DATA_GATHER_TOOLS = new Set([
6363] ) ;
6464const DATA_WRITE_TOOLS = new Set ( [ 'write_file' , 'edit_file' ] ) ;
6565
66+ /**
67+ * Detect if a user message describes a large/incremental task and auto-create todos.
68+ * This helps models stay on track during context rotations.
69+ */
70+ function autoCreateLargeTaskTodos ( message , mcpToolServer ) {
71+ if ( ! message || ! mcpToolServer ) return null ;
72+ const lower = message . toLowerCase ( ) ;
73+
74+ // Pattern: Large line count requests (500+ lines)
75+ const lineMatch = lower . match ( / ( \d { 3 , } ) \s * [ - ] ? \s * l i n e s ? / ) ;
76+ if ( lineMatch && parseInt ( lineMatch [ 1 ] , 10 ) >= 500 ) {
77+ const targetLines = parseInt ( lineMatch [ 1 ] , 10 ) ;
78+ const chunks = Math . ceil ( targetLines / 500 ) ;
79+ const items = [ ] ;
80+ items . push ( { text : `Create initial file structure` , status : 'pending' } ) ;
81+ for ( let i = 1 ; i <= Math . min ( chunks , 8 ) ; i ++ ) {
82+ const start = ( i - 1 ) * 500 + 1 ;
83+ const end = Math . min ( i * 500 , targetLines ) ;
84+ items . push ( { text : `Write lines ${ start } -${ end } ` , status : 'pending' } ) ;
85+ }
86+ if ( chunks > 8 ) {
87+ items . push ( { text : `Continue writing remaining ${ targetLines - 4000 } lines` , status : 'pending' } ) ;
88+ }
89+ items . push ( { text : `Review and finalize output` , status : 'pending' } ) ;
90+ return mcpToolServer . _writeTodos ( { items } ) ;
91+ }
92+
93+ // Pattern: Multiple functions/items (20+)
94+ const funcMatch = lower . match ( / ( \d { 2 , } ) \s * (?: u t i l i t y \s * ) ? f u n c t i o n s ? / ) ;
95+ if ( funcMatch && parseInt ( funcMatch [ 1 ] , 10 ) >= 20 ) {
96+ const targetFuncs = parseInt ( funcMatch [ 1 ] , 10 ) ;
97+ const chunks = Math . ceil ( targetFuncs / 10 ) ;
98+ const items = [ ] ;
99+ items . push ( { text : `Create file and initial structure` , status : 'pending' } ) ;
100+ for ( let i = 1 ; i <= Math . min ( chunks , 6 ) ; i ++ ) {
101+ const start = ( i - 1 ) * 10 + 1 ;
102+ const end = Math . min ( i * 10 , targetFuncs ) ;
103+ items . push ( { text : `Implement functions ${ start } -${ end } ` , status : 'pending' } ) ;
104+ }
105+ if ( chunks > 6 ) {
106+ items . push ( { text : `Continue implementing remaining ${ targetFuncs - 60 } functions` , status : 'pending' } ) ;
107+ }
108+ items . push ( { text : `Verify all functions are exported` , status : 'pending' } ) ;
109+ return mcpToolServer . _writeTodos ( { items } ) ;
110+ }
111+
112+ // Pattern: Multiple files (5+)
113+ const fileMatch = lower . match ( / ( \d + ) \s * (?: d i f f e r e n t \s * ) ? f i l e s ? / ) ;
114+ if ( fileMatch && parseInt ( fileMatch [ 1 ] , 10 ) >= 5 ) {
115+ const targetFiles = parseInt ( fileMatch [ 1 ] , 10 ) ;
116+ const items = [ ] ;
117+ items . push ( { text : `Plan file structure` , status : 'pending' } ) ;
118+ for ( let i = 1 ; i <= Math . min ( targetFiles , 10 ) ; i ++ ) {
119+ items . push ( { text : `Create file ${ i } of ${ targetFiles } ` , status : 'pending' } ) ;
120+ }
121+ if ( targetFiles > 10 ) {
122+ items . push ( { text : `Continue creating remaining ${ targetFiles - 10 } files` , status : 'pending' } ) ;
123+ }
124+ items . push ( { text : `Verify all files created` , status : 'pending' } ) ;
125+ return mcpToolServer . _writeTodos ( { items } ) ;
126+ }
127+
128+ return null ;
129+ }
130+
66131function register ( ctx ) {
67132 const {
68133 llmEngine, cloudLLM, mcpToolServer, playwrightBrowser, browserManager,
@@ -294,6 +359,19 @@ function register(ctx) {
294359 const summarizer = new ctx . ConversationSummarizer ( ) ;
295360 summarizer . setGoal ( message ) ;
296361
362+ // Auto-create todos for large/incremental tasks (helps model track progress across rotations)
363+ const autoTodoResult = autoCreateLargeTaskTodos ( message , mcpToolServer ) ;
364+ if ( autoTodoResult ?. success ) {
365+ console . log ( `[Cloud] Auto-created ${ autoTodoResult . created ?. length || 0 } todos for incremental task` ) ;
366+ if ( mainWindow && ! mainWindow . isDestroyed ( ) ) {
367+ mainWindow . webContents . send ( 'mcp-tool-results' , [ {
368+ tool : 'write_todos' ,
369+ params : { items : autoTodoResult . created } ,
370+ result : autoTodoResult ,
371+ } ] ) ;
372+ }
373+ }
374+
297375 while ( iteration < MAX_CLOUD_ITERATIONS ) {
298376 if ( isStale ( ) ) {
299377 if ( mainWindow ) mainWindow . webContents . send ( 'llm-token' , '\n*[Interrupted]*\n' ) ;
@@ -755,6 +833,19 @@ function register(ctx) {
755833 const summarizer = new ConversationSummarizer ( ) ;
756834 summarizer . setGoal ( message ) ;
757835
836+ // Auto-create todos for large/incremental tasks (helps model track progress across rotations)
837+ const autoTodoResult = autoCreateLargeTaskTodos ( message , mcpToolServer ) ;
838+ if ( autoTodoResult ?. success ) {
839+ console . log ( `[AI Chat] Auto-created ${ autoTodoResult . created ?. length || 0 } todos for incremental task` ) ;
840+ if ( mainWindow && ! mainWindow . isDestroyed ( ) ) {
841+ mainWindow . webContents . send ( 'mcp-tool-results' , [ {
842+ tool : 'write_todos' ,
843+ params : { items : autoTodoResult . created } ,
844+ result : autoTodoResult ,
845+ } ] ) ;
846+ }
847+ }
848+
758849 // Transactional rollback
759850 let rollbackRetries = 0 ;
760851 const maxRollbackRetries = modelTier . retryBudget ;
@@ -1039,9 +1130,23 @@ function register(ctx) {
10391130 }
10401131
10411132 const partial = fullResponseText . trim ( ) . length > 0 ? fullResponseText . substring ( Math . max ( 0 , fullResponseText . length - 1500 ) ) : '' ;
1133+
1134+ // Build strong continuation prompt with incremental progress tracking
1135+ const incrementalHint = summarizer . incrementalTask
1136+ ? `\n**INCREMENTAL TASK: ${ summarizer . incrementalTask . current } /${ summarizer . incrementalTask . target } ${ summarizer . incrementalTask . type } completed.**`
1137+ : '' ;
1138+ const fileProgressHint = Object . keys ( summarizer . fileProgress ) . length > 0
1139+ ? `\n**FILES IN PROGRESS:** ${ Object . entries ( summarizer . fileProgress ) . map ( ( [ f , p ] ) => `${ f } (${ p . writtenLines } lines)` ) . join ( ', ' ) } `
1140+ : '' ;
1141+
10421142 const hint = partial
1043- ? `\n\nYou were generating and context was rotated. Here is the end of what you wrote:\n---\n${ partial } \n---\nContinue from where you left off.`
1044- : `\nContext was rotated. The user request is: ${ message . substring ( 0 , 300 ) } ` ;
1143+ ? `\n\n## CONTINUE FROM HERE\n---\n${ partial } \n---` +
1144+ incrementalHint + fileProgressHint +
1145+ `\n\n**CRITICAL: DO NOT REFUSE. DO NOT SAY "I cannot continue."**` +
1146+ `\nUse append_to_file to add more content. Call a tool NOW to make progress.`
1147+ : `\nContext was rotated. The user request is: ${ message . substring ( 0 , 300 ) } ` +
1148+ incrementalHint + fileProgressHint +
1149+ `\n\n**Continue the task using tools. Do not refuse.**` ;
10451150
10461151 currentPrompt = {
10471152 systemContext : buildStaticPrompt ( ) ,
@@ -1337,15 +1442,14 @@ function register(ctx) {
13371442 // Increased from 50K to allow large file generation — context rotation will handle memory
13381443 const MAX_CONTINUATION_CHARS = 500000 ; // 500K chars (~125K lines of code)
13391444 let _contAbortReason = '' ;
1340- let _contShouldRotate = false ;
1445+ // ALWAYS attempt rotation on any abort reason if rotations available
1446+ let _contShouldRotate = contextRotations < MAX_CONTEXT_ROTATIONS ;
13411447 if ( fullResponseText . length > MAX_CONTINUATION_CHARS ) {
1342- // Instead of hard abort, trigger context rotation to continue with fresh buffer
1343- _contShouldRotate = true ;
13441448 _contAbortReason = `total output exceeds ${ MAX_CONTINUATION_CHARS } chars — rotating context` ;
13451449 } else if ( _contLowProgressCount >= 3 ) {
1346- _contAbortReason = 'no forward progress' ;
1450+ _contAbortReason = 'no forward progress — rotating context to recover ' ;
13471451 } else if ( _contRepeatCount >= 2 ) {
1348- _contAbortReason = 'repeated/near-identical content' ;
1452+ _contAbortReason = 'repeated/near-identical content — rotating context ' ;
13491453 }
13501454
13511455 // Forward-progress scoring: after 5 passes, if avg chars per pass varies <10% from first pass, abort
@@ -1364,21 +1468,41 @@ function register(ctx) {
13641468 _contRepeatCount = 0 ;
13651469 _contCharSizes = [ ] ;
13661470
1367- // If rotation was requested (char limit hit but task continues), trigger context rotation
1471+ // ALWAYS attempt rotation to recover and continue the task
13681472 if ( _contShouldRotate && contextRotations < MAX_CONTEXT_ROTATIONS ) {
13691473 console . log ( `[AI Chat] Large-output rotation triggered (${ contextRotations + 1 } /${ MAX_CONTEXT_ROTATIONS } )` ) ;
13701474 contextRotations ++ ;
13711475 try {
1372- // Store what we have so far and rotate
1476+ // Generate full summary with file progress tracking
1477+ summarizer . markRotation ( ) ;
1478+ const convSummary = summarizer . generateSummary ( {
1479+ maxTokens : Math . min ( Math . floor ( totalCtx * 0.25 ) , 3000 ) ,
1480+ activeTodos : mcpToolServer ?. _todos || [ ] ,
1481+ } ) ;
1482+
1483+ // Store partial output for context
13731484 const partialOutput = fullResponseText . slice ( - Math . min ( fullResponseText . length , 2000 ) ) ;
13741485 await llmEngine . resetSession ( true ) ;
13751486 await ensureLlmChat ( llmEngine , getNodeLlamaCppPath ) ;
13761487 if ( mainWindow && ! mainWindow . isDestroyed ( ) ) {
13771488 mainWindow . webContents . send ( 'llm-thinking-token' , '\n[Context rotated for large output]\n' ) ;
13781489 }
1490+
1491+ // Build strong continuation prompt with summary, progress, and directive
1492+ const incrementalHint = summarizer . incrementalTask
1493+ ? `\n**INCREMENTAL TASK: ${ summarizer . incrementalTask . current } /${ summarizer . incrementalTask . target } ${ summarizer . incrementalTask . type } completed.**`
1494+ : '' ;
1495+ const fileProgressHint = Object . keys ( summarizer . fileProgress ) . length > 0
1496+ ? `\n**FILES IN PROGRESS:** ${ Object . entries ( summarizer . fileProgress ) . map ( ( [ f , p ] ) => `${ f } (${ p . writtenLines } lines)` ) . join ( ', ' ) } `
1497+ : '' ;
1498+
13791499 currentPrompt = {
13801500 systemContext : buildStaticPrompt ( ) ,
1381- userMessage : buildDynamicContext ( ) + `\n\nYou were generating a large output and context was rotated. Continue from where you left off:\n---\n${ partialOutput } \n---\n` ,
1501+ userMessage : buildDynamicContext ( ) + '\n\n' + convSummary +
1502+ `\n\n## CONTINUE FROM HERE\n---\n${ partialOutput } \n---` +
1503+ incrementalHint + fileProgressHint +
1504+ `\n\n**CRITICAL: DO NOT REFUSE. DO NOT SAY "I cannot continue."**` +
1505+ `\nUse append_to_file to add more content. Call a tool NOW to make progress.` ,
13821506 } ;
13831507 sessionJustRotated = true ;
13841508 continue ;
@@ -1887,12 +2011,24 @@ function preGenerationContextCheck(opts) {
18872011 } catch ( _ ) { }
18882012 const summary = summarizer . generateQuickSummary ( mcpToolServer ?. _todos ) ;
18892013
2014+ // Build incremental progress hints
2015+ const incrementalHint = summarizer . incrementalTask
2016+ ? `\n**INCREMENTAL TASK: ${ summarizer . incrementalTask . current } /${ summarizer . incrementalTask . target } ${ summarizer . incrementalTask . type } completed.**`
2017+ : '' ;
2018+ const fileProgressHint = Object . keys ( summarizer . fileProgress || { } ) . length > 0
2019+ ? `\n**FILES IN PROGRESS:** ${ Object . entries ( summarizer . fileProgress ) . map ( ( [ f , p ] ) => `${ f } (${ p . writtenLines } lines)` ) . join ( ', ' ) } `
2020+ : '' ;
2021+ const noRefuseDirective = incrementalHint || fileProgressHint
2022+ ? `\n\n**CRITICAL: DO NOT REFUSE. Use append_to_file to continue. Call a tool NOW.**`
2023+ : '' ;
2024+
18902025 if ( isContinuationRotation ) {
18912026 return {
18922027 shouldContinue : true ,
18932028 prompt : {
18942029 systemContext : buildStaticPrompt ( ) ,
1895- userMessage : buildDynamicContext ( Math . floor ( maxPromptTokens * 0.10 ) ) + '\n' + message ,
2030+ userMessage : buildDynamicContext ( Math . floor ( maxPromptTokens * 0.10 ) ) + '\n' + message +
2031+ incrementalHint + fileProgressHint + noRefuseDirective ,
18962032 } ,
18972033 rotated : true ,
18982034 summary,
@@ -1904,7 +2040,9 @@ function preGenerationContextCheck(opts) {
19042040 shouldContinue : true ,
19052041 prompt : {
19062042 systemContext : buildStaticPrompt ( ) ,
1907- userMessage : buildDynamicContext ( ) + '\n' + summary + `\nContext rotated. Current request: ${ message . substring ( 0 , 300 ) } ` ,
2043+ userMessage : buildDynamicContext ( ) + '\n' + summary +
2044+ `\nContext rotated. Current request: ${ message . substring ( 0 , 300 ) } ` +
2045+ incrementalHint + fileProgressHint + noRefuseDirective ,
19082046 } ,
19092047 rotated : true ,
19102048 summary,
0 commit comments