@@ -3,27 +3,27 @@ import * as fs from "node:fs"
33import * as path from "node:path"
44import { t } from "i18next"
55import { AgentRegistry } from "./AgentRegistry"
6+ import { renameMapKey } from "./mapUtils"
67import {
78 parseParallelModeBranch ,
89 parseParallelModeWorktreePath ,
910 isParallelModeCompletionMessage ,
1011 parseParallelModeCompletionBranch ,
1112} from "./parallelModeParser"
12- import { findKilocodeCli } from "./CliPathResolver"
1313import { canInstallCli , getCliInstallCommand , getLocalCliInstallCommand , getLocalCliBinDir } from "./CliInstaller"
1414import { CliProcessHandler , type CliProcessHandlerCallbacks } from "./CliProcessHandler"
1515import type { StreamEvent , KilocodeStreamEvent , KilocodePayload , WelcomeStreamEvent } from "./CliOutputParser"
1616import { extractRawText , tryParsePayloadJson } from "./askErrorParser"
1717import { RemoteSessionService } from "./RemoteSessionService"
1818import { KilocodeEventProcessor } from "./KilocodeEventProcessor"
19+ import { CliSessionLauncher } from "./CliSessionLauncher"
1920import type { RemoteSession } from "./types"
2021import { getUri } from "../../webview/getUri"
2122import { getNonce } from "../../webview/getNonce"
2223import { getViteDevServerConfig } from "../../webview/getViteDevServerConfig"
2324import { getRemoteUrl } from "../../../services/code-index/managed/git-utils"
2425import { normalizeGitUrl } from "./normalizeGitUrl"
25- import type { ClineMessage } from "@roo-code/types"
26- import type { ProviderSettings } from "@roo-code/types"
26+ import type { ClineMessage , ProviderSettings } from "@roo-code/types"
2727import {
2828 captureAgentManagerOpened ,
2929 captureAgentManagerSessionStarted ,
@@ -53,6 +53,7 @@ export class AgentManagerProvider implements vscode.Disposable {
5353 private remoteSessionService : RemoteSessionService
5454 private processHandler : CliProcessHandler
5555 private eventProcessor : KilocodeEventProcessor
56+ private sessionLauncher : CliSessionLauncher
5657 private sessionMessages : Map < string , ClineMessage [ ] > = new Map ( )
5758 // Track first api_req_started per session to filter user-input echoes
5859 private firstApiReqStarted : Map < string , boolean > = new Map ( )
@@ -72,6 +73,12 @@ export class AgentManagerProvider implements vscode.Disposable {
7273 this . registry = new AgentRegistry ( )
7374 this . remoteSessionService = new RemoteSessionService ( { outputChannel } )
7475
76+ // Initialize session launcher with pre-warming
77+ // Pre-warming starts slow lookups (CLI: 500-2000ms, git: 50-100ms) immediately
78+ // so they complete before the user clicks "Start" to reduce time-to-first-token
79+ this . sessionLauncher = new CliSessionLauncher ( outputChannel , ( ) => this . getApiConfigurationForCli ( ) )
80+ this . sessionLauncher . startPrewarm ( )
81+
7582 // Initialize currentGitUrl from workspace
7683 void this . initializeCurrentGitUrl ( )
7784
@@ -142,6 +149,7 @@ export class AgentManagerProvider implements vscode.Disposable {
142149 } )
143150 } ,
144151 onPaymentRequiredPrompt : ( payload ) => this . showPaymentRequiredPrompt ( payload ) ,
152+ onSessionRenamed : ( oldId , newId ) => this . handleSessionRenamed ( oldId , newId ) ,
145153 }
146154
147155 this . processHandler = new CliProcessHandler ( this . registry , callbacks )
@@ -196,6 +204,8 @@ export class AgentManagerProvider implements vscode.Disposable {
196204 ( ) => {
197205 this . panel = undefined
198206 this . stopAllAgents ( )
207+ // Clear pre-warm state when panel closes
208+ this . sessionLauncher . clearPrewarm ( )
199209 } ,
200210 null ,
201211 this . disposables ,
@@ -207,6 +217,21 @@ export class AgentManagerProvider implements vscode.Disposable {
207217 captureAgentManagerOpened ( )
208218 }
209219
220+ /** Rename session key in all session-keyed maps. */
221+ private handleSessionRenamed ( oldId : string , newId : string ) : void {
222+ this . outputChannel . appendLine ( `[AgentManager] Renaming session: ${ oldId } -> ${ newId } ` )
223+
224+ renameMapKey ( this . sessionMessages , oldId , newId )
225+ renameMapKey ( this . firstApiReqStarted , oldId , newId )
226+ renameMapKey ( this . processStartTimes , oldId , newId )
227+ renameMapKey ( this . sendingMessageMap , oldId , newId )
228+
229+ const messages = this . sessionMessages . get ( newId )
230+ if ( messages ) {
231+ this . postMessage ( { type : "agentManager.chatMessages" , sessionId : newId , messages } )
232+ }
233+ }
234+
210235 private handleMessage ( message : { type : string ; [ key : string ] : unknown } ) : void {
211236 this . outputChannel . appendLine ( `Agent Manager received message: ${ JSON . stringify ( message ) } ` )
212237
@@ -419,36 +444,23 @@ export class AgentManagerProvider implements vscode.Disposable {
419444 return
420445 }
421446
422- // Get workspace folder early to fetch git URL before spawning
423447 // Note: we intentionally allow starting parallel mode from within an existing git worktree.
424448 // Git worktrees share a common .git dir, so `git worktree add/remove` still works from a worktree root.
425449 const workspaceFolder = vscode . workspace . workspaceFolders ?. [ 0 ] ?. uri . fsPath
426450
427- // Get git URL for the workspace (used for filtering sessions)
428- let gitUrl : string | undefined
429- if ( workspaceFolder ) {
430- try {
431- gitUrl = normalizeGitUrl ( await getRemoteUrl ( workspaceFolder ) )
432- } catch ( error ) {
433- this . outputChannel . appendLine (
434- `[AgentManager] Could not get git URL: ${ error instanceof Error ? error . message : String ( error ) } ` ,
435- )
436- }
437- }
438-
439451 const onSetupFailed = ( ) => {
440452 if ( ! workspaceFolder ) {
441453 void vscode . window . showErrorMessage ( "Please open a folder before starting an agent." )
442454 }
443455 this . postMessage ( { type : "agentManager.startSessionFailed" } )
444456 }
445457
458+ // Git URL lookup is now handled by spawnCliWithCommonSetup using pre-warmed promise
446459 await this . spawnCliWithCommonSetup (
447460 prompt ,
448461 {
449462 parallelMode : options ?. parallelMode ,
450463 label : options ?. labelOverride ,
451- gitUrl,
452464 existingBranch : options ?. existingBranch ,
453465 } ,
454466 onSetupFailed ,
@@ -462,7 +474,7 @@ export class AgentManagerProvider implements vscode.Disposable {
462474
463475 /**
464476 * Common helper to spawn a CLI process with standard setup.
465- * Handles CLI path lookup, workspace folder validation, API config, and event callback wiring .
477+ * Delegates to CliSessionLauncher for pre-warming and spawning .
466478 * @returns true if process was spawned, false if setup failed
467479 */
468480 private async spawnCliWithCommonSetup (
@@ -476,47 +488,23 @@ export class AgentManagerProvider implements vscode.Disposable {
476488 } ,
477489 onSetupFailed ?: ( ) => void ,
478490 ) : Promise < boolean > {
479- const workspaceFolder = vscode . workspace . workspaceFolders ?. [ 0 ] ?. uri . fsPath
480- if ( ! workspaceFolder ) {
481- this . outputChannel . appendLine ( "ERROR: No workspace folder open" )
482- onSetupFailed ?.( )
483- return false
484- }
485-
486- const cliPath = await findKilocodeCli ( ( msg ) => this . outputChannel . appendLine ( `[AgentManager] ${ msg } ` ) )
487- if ( ! cliPath ) {
488- this . outputChannel . appendLine ( "ERROR: kilocode CLI not found" )
489- this . showCliNotFoundError ( )
490- onSetupFailed ?.( )
491- return false
492- }
493-
494- const processStartTime = Date . now ( )
495- let apiConfiguration : ProviderSettings | undefined
496- try {
497- apiConfiguration = await this . getApiConfigurationForCli ( )
498- } catch ( error ) {
499- this . outputChannel . appendLine (
500- `[AgentManager] Failed to read provider settings for CLI: ${
501- error instanceof Error ? error . message : String ( error )
502- } `,
503- )
504- }
505-
506- this . processHandler . spawnProcess (
507- cliPath ,
508- workspaceFolder ,
491+ const result = await this . sessionLauncher . spawn (
509492 prompt ,
510- { ...options , apiConfiguration } ,
493+ options ,
494+ this . processHandler ,
511495 ( sid , event ) => {
512- if ( ! this . processStartTimes . has ( sid ) ) {
513- this . processStartTimes . set ( sid , processStartTime )
496+ if ( result . processStartTime && ! this . processStartTimes . has ( sid ) ) {
497+ this . processStartTimes . set ( sid , result . processStartTime )
514498 }
515499 this . handleCliEvent ( sid , event )
516500 } ,
501+ ( ) => {
502+ this . showCliNotFoundError ( )
503+ onSetupFailed ?.( )
504+ } ,
517505 )
518506
519- return true
507+ return result . success
520508 }
521509
522510 /**
@@ -1150,7 +1138,7 @@ export class AgentManagerProvider implements vscode.Disposable {
11501138 this . processHandler . dispose ( )
11511139 this . sessionMessages . clear ( )
11521140 this . firstApiReqStarted . clear ( )
1153-
1141+ this . sessionLauncher . clearPrewarm ( )
11541142 this . panel ?. dispose ( )
11551143 this . disposables . forEach ( ( d ) => d . dispose ( ) )
11561144 }
0 commit comments