@@ -749,7 +749,8 @@ export class AutohandAgent {
749749 maxQueueSize : 10 ,
750750 silentMode : disableTerminalRegions ,
751751 workspaceRoot : this . runtime . workspaceRoot ,
752- resolveShellSuggestion : ( input ) => this . resolveLlmShellSuggestion ( input )
752+ resolveShellSuggestion : ( input ) => this . resolveLlmShellSuggestion ( input ) ,
753+ suggestionProvider : ( ) => this . suggestionEngine ?. getSuggestion ( ) ?? undefined ,
753754 } ) ;
754755
755756 this . persistentInput . on ( 'queued' , ( text : string , count : number ) => {
@@ -1012,6 +1013,7 @@ export class AutohandAgent {
10121013 recentFiles,
10131014 } ) ;
10141015 } ) ( ) ;
1016+ this . persistentInput . setPendingSuggestion ( this . pendingSuggestion ) ;
10151017 }
10161018
10171019 // Show prompt immediately - don't wait for init
@@ -1444,6 +1446,7 @@ If lint or tests fail, report the issues but do NOT commit.`;
14441446 // so the LLM call runs concurrently with hooks/notifications below.
14451447 if ( this . suggestionEngine ) {
14461448 this . pendingSuggestion = this . suggestionEngine . generate ( this . conversation . history ( ) ) ;
1449+ this . persistentInput . setPendingSuggestion ( this . pendingSuggestion ) ;
14471450 }
14481451
14491452 // Fire stop hook after turn completes (non-blocking)
@@ -1596,16 +1599,23 @@ If lint or tests fail, report the issues but do NOT commit.`;
15961599 // otherwise the default placeholder is shown.
15971600 // Turns: wait up to 3s. The user is still reading output so a brief
15981601 // wait for contextual ghost text is acceptable.
1599- if ( this . pendingSuggestion ) {
1600- if ( ! this . isStartupSuggestion ) {
1601- const deadline = new Promise < void > ( ( r ) => setTimeout ( r , 3000 ) ) ;
1602- await Promise . race ( [ this . pendingSuggestion , deadline ] ) . catch ( ( ) => { } ) ;
1603- }
1604- this . isStartupSuggestion = false ;
1605- this . pendingSuggestion = null ;
1606- }
1607- const suggestionText = this . suggestionEngine ?. getSuggestion ( ) ?? undefined ;
1608- this . suggestionEngine ?. clear ( ) ;
1602+ // Suggestion uses a lazy provider: each render cycle in the prompt reads
1603+ // the latest value via getSuggestion(). This eliminates the race condition
1604+ // where the LLM takes >3s and the static snapshot was always undefined.
1605+ // The pendingSuggestion promise triggers a re-render when it resolves,
1606+ // so the ghost text appears as soon as the LLM responds — even if the
1607+ // prompt is already displayed.
1608+ const pendingSuggestion = this . pendingSuggestion ;
1609+ this . isStartupSuggestion = false ;
1610+ this . pendingSuggestion = null ;
1611+
1612+ const debugSuggestion = process . env . AUTOHAND_DEBUG === '1' ;
1613+ if ( debugSuggestion ) {
1614+ const state = pendingSuggestion ? 'pending' : 'none' ;
1615+ process . stderr . write ( `[SUGGESTION] Provider mode — pending=${ state } , engine=${ this . suggestionEngine ? 'exists' : 'null' } \n` ) ;
1616+ }
1617+
1618+ const engine = this . suggestionEngine ;
16091619 const input = await readInstruction (
16101620 ( ) => this . workspaceFileCollector . getCachedFiles ( ) ,
16111621 SLASH_COMMANDS ,
@@ -1614,8 +1624,9 @@ If lint or tests fail, report the issues but do NOT commit.`;
16141624 ( data , mimeType , filename ) => this . imageManager . add ( data , mimeType , filename ) ,
16151625 this . runtime . workspaceRoot ,
16161626 initialValue ,
1617- suggestionText ,
1618- ( line ) => this . resolveLlmShellSuggestion ( line )
1627+ ( ) => engine ?. getSuggestion ( ) ?? undefined ,
1628+ ( line ) => this . resolveLlmShellSuggestion ( line ) ,
1629+ pendingSuggestion ?? undefined
16191630 ) ;
16201631 // Only exit on explicit ABORT (double Ctrl+C). Palette cancel or dismiss should continue.
16211632 if ( input === 'ABORT' ) { // double Ctrl+C from prompt
@@ -4327,7 +4338,11 @@ If lint or tests fail, report the issues but do NOT commit.`;
43274338 if ( this . inkRenderer ) {
43284339 this . inkRenderer . setStatus ( status ) ;
43294340 } else if ( this . runtime . spinner ) {
4341+ // setSpinnerStatus already handles terminal regions internally
43304342 this . setSpinnerStatus ( status ) ;
4343+ } else if ( this . isUsingTerminalRegionsForActiveTurn ( ) ) {
4344+ // No spinner (suppressed when persistent input is used) — route directly
4345+ this . setPersistentInputActivityLine ( status ) ;
43314346 }
43324347 }
43334348
@@ -4821,6 +4836,8 @@ If lint or tests fail, report the issues but do NOT commit.`;
48214836 this . inkRenderer . setElapsed ( elapsed ) ;
48224837 } else if ( this . runtime . spinner ) {
48234838 this . setSpinnerStatus ( status ) ;
4839+ } else if ( this . isUsingTerminalRegionsForActiveTurn ( ) ) {
4840+ this . setPersistentInputActivityLine ( status ) ;
48244841 }
48254842 } ;
48264843 update ( ) ;
0 commit comments