@@ -181,7 +181,6 @@ func (o *Options) InitDefaults() {
181181
182182 // Session management options
183183 o .ResumeSession = ""
184- o .NewSession = false
185184 o .ListSessions = false
186185 o .DeleteSession = ""
187186 o .SessionBackend = "memory"
@@ -334,19 +333,19 @@ func (opt *Options) bindCLIFlags(f *pflag.FlagSet) error {
334333 f .StringVar (& opt .SandboxImage , "sandbox-image" , opt .SandboxImage , "container image to use for the sandbox" )
335334
336335 f .StringVar (& opt .ResumeSession , "resume-session" , opt .ResumeSession , "ID of session to resume (use 'latest' for the most recent session)" )
337- f .BoolVar (& opt .NewSession , "new-session" , opt .NewSession , "create a new session" )
338336 f .BoolVar (& opt .ListSessions , "list-sessions" , opt .ListSessions , "list all available sessions" )
339337 f .StringVar (& opt .DeleteSession , "delete-session" , opt .DeleteSession , "delete a session by ID" )
338+ f .BoolVar (& opt .NewSession , "new-session" , opt .NewSession , "start a new persistent session" )
340339 f .StringVar (& opt .SessionBackend , "session-backend" , opt .SessionBackend ,
341340 "session backend to use (memory or filesystem)" )
342341
343342 return nil
344343}
345344
346345func RunRootCommand (ctx context.Context , opt Options , args []string ) error {
347- var err error // Declare err once for the whole function
346+ var err error
348347
349- // Automatically upgrade backend to filesystem if session persistence flags are requested explicitly.
348+ // Automatically upgrade backend to filesystem if session persistence flags are requested explicitly
350349 if (opt .NewSession || opt .ResumeSession != "" || opt .ListSessions || opt .DeleteSession != "" ) && opt .SessionBackend == "memory" {
351350 klog .Infof ("Upgrading session-backend to 'filesystem' based on provided flags" )
352351 opt .SessionBackend = "filesystem"
@@ -397,144 +396,145 @@ func RunRootCommand(ctx context.Context, opt Options, args []string) error {
397396
398397 klog .Info ("Application started" , "pid" , os .Getpid ())
399398
400- var llmClient gollm.Client
401- if opt .SkipVerifySSL {
402- llmClient , err = gollm .NewClient (ctx , opt .ProviderID , gollm .WithSkipVerifySSL ())
399+ var recorder journal.Recorder
400+ if opt .TracePath != "" {
401+ var fileRecorder journal.Recorder
402+ fileRecorder , err = journal .NewFileRecorder (opt .TracePath )
403+ if err != nil {
404+ return fmt .Errorf ("creating trace recorder: %w" , err )
405+ }
406+ defer fileRecorder .Close ()
407+ recorder = fileRecorder
403408 } else {
404- llmClient , err = gollm .NewClient (ctx , opt .ProviderID )
405- }
406- if err != nil {
407- return fmt .Errorf ("creating llm client: %w" , err )
409+ // Ensure we always have a recorder, to avoid nil checks
410+ recorder = & journal.LogRecorder {}
411+ defer recorder .Close ()
408412 }
409- defer llmClient .Close ()
410413
411414 // Initialize session management
412415 var session * api.Session
413416 var sessionManager * sessions.SessionManager
414417
415- if opt .NewSession || opt .ResumeSession != "" {
416- sessionManager , err = sessions .NewSessionManager (opt .SessionBackend )
418+ sessionManager , err = sessions .NewSessionManager (opt .SessionBackend )
419+ if err != nil {
420+ return fmt .Errorf ("failed to create session manager: %w" , err )
421+ }
422+
423+ // Build agentFactory for new agents
424+ agentFactory := func (ctx context.Context ) (* agent.Agent , error ) {
425+ var client gollm.Client
426+ var err error
427+ if opt .SkipVerifySSL {
428+ client , err = gollm .NewClient (ctx , opt .ProviderID , gollm .WithSkipVerifySSL ())
429+ } else {
430+ client , err = gollm .NewClient (ctx , opt .ProviderID )
431+ }
417432 if err != nil {
418- return fmt .Errorf ("failed to create session manager : %w" , err )
433+ return nil , fmt .Errorf ("creating llm client : %w" , err )
419434 }
420435
421- if opt .NewSession {
422- meta := sessions.Metadata {
423- ProviderID : opt .ProviderID ,
424- ModelID : opt .ModelID ,
425- }
426- session , err = sessionManager .NewSession (meta )
436+ return & agent.Agent {
437+ Model : opt .ModelID ,
438+ Provider : opt .ProviderID ,
439+ Kubeconfig : opt .KubeConfigPath ,
440+ LLM : client ,
441+ MaxIterations : opt .MaxIterations ,
442+ PromptTemplateFile : opt .PromptTemplateFilePath ,
443+ ExtraPromptPaths : opt .ExtraPromptPaths ,
444+ Tools : tools .Default (),
445+ Recorder : recorder ,
446+ RemoveWorkDir : opt .RemoveWorkDir ,
447+ SkipPermissions : opt .SkipPermissions ,
448+ EnableToolUseShim : opt .EnableToolUseShim ,
449+ MCPClientEnabled : opt .MCPClient ,
450+ Sandbox : opt .Sandbox ,
451+ SandboxImage : opt .SandboxImage ,
452+ SessionBackend : opt .SessionBackend ,
453+ RunOnce : opt .Quiet ,
454+ InitialQuery : queryFromCmd ,
455+ }, nil
456+ }
457+
458+ agentManager := agent .NewAgentManager (agentFactory , sessionManager )
459+
460+ // Register cleanup for all sessions and agents
461+ defer agentManager .Close ()
462+
463+ if opt .ResumeSession != "" {
464+ if opt .ResumeSession == "latest" {
465+ session , err = sessionManager .GetLatestSession ()
427466 if err != nil {
428- return fmt .Errorf ("failed to create a new session: %w" , err )
467+ return fmt .Errorf ("failed to get latest session: %w" , err )
429468 }
430- if opt .SessionBackend == "filesystem" {
431- klog .Infof ("Created new session: %s\n " , session .ID )
469+ if session == nil {
470+ // No latest session found, create a new one
471+ klog .Info ("No previous session found to resume. Creating new session." )
432472 }
433473 } else {
434- if opt .ResumeSession == "" || opt .ResumeSession == "latest" {
435- session , err = sessionManager .GetLatestSession ()
436- if err != nil {
437- return fmt .Errorf ("failed to get latest session: %w" , err )
438- }
439- if session == nil {
440- meta := sessions.Metadata {
441- ProviderID : opt .ProviderID ,
442- ModelID : opt .ModelID ,
443- }
444- session , err = sessionManager .NewSession (meta )
445- if err != nil {
446- return fmt .Errorf ("failed to create new session: %w" , err )
447- }
448- if opt .SessionBackend == "filesystem" {
449- klog .Infof ("No previous session found. Created new session: %s\n " , session .ID )
450- }
451- }
452- } else {
453- sessionID := opt .ResumeSession
454- session , err = sessionManager .FindSessionByID (sessionID )
455- if err != nil {
456- return fmt .Errorf ("session %s not found: %w" , sessionID , err )
457- }
458- }
459-
460- if session != nil {
461- if err := sessionManager .UpdateLastAccessed (session ); err != nil {
462- klog .Warningf ("Failed to update session last accessed time: %v" , err )
463- }
474+ session , err = sessionManager .FindSessionByID (opt .ResumeSession )
475+ if err != nil {
476+ return fmt .Errorf ("session %s not found: %w" , opt .ResumeSession , err )
464477 }
465478 }
466479 }
467480
468- var chatStore api.ChatMessageStore
469- if session != nil {
470- chatStore = session .ChatMessageStore
471- }
481+ var defaultAgent * agent.Agent
472482
473- var recorder journal.Recorder
474- if opt .TracePath != "" {
475- var fileRecorder journal.Recorder
476- fileRecorder , err = journal .NewFileRecorder (opt .TracePath )
483+ // If no session loaded (or resume failed/not requested), create a new one
484+ if session == nil {
485+ meta := sessions.Metadata {
486+ ModelID : opt .ModelID ,
487+ ProviderID : opt .ProviderID ,
488+ }
489+ session , err = sessionManager .NewSession (meta )
477490 if err != nil {
478- return fmt .Errorf ("creating trace recorder : %w" , err )
491+ return fmt .Errorf ("failed to create a new session : %w" , err )
479492 }
480- defer fileRecorder .Close ()
481- recorder = fileRecorder
493+
494+ defaultAgent , err = agentManager .GetAgent (ctx , session .ID )
495+ if err != nil {
496+ return fmt .Errorf ("failed to get agent for new session: %w" , err )
497+ }
498+ klog .Infof ("Created new session: %s\n " , session .ID )
482499 } else {
483- // Ensure we always have a recorder, to avoid nil checks
484- recorder = & journal.LogRecorder {}
485- defer recorder .Close ()
486- }
500+ // Update last accessed for resumed session
501+ if err := sessionManager .UpdateLastAccessed (session ); err != nil {
502+ klog .Warningf ("Failed to update session last accessed time: %v" , err )
503+ }
504+ klog .Infof ("Resuming session: %s\n " , session .ID )
487505
488- k8sAgent := & agent.Agent {
489- Model : opt .ModelID ,
490- Provider : opt .ProviderID ,
491- Kubeconfig : opt .KubeConfigPath ,
492- LLM : llmClient ,
493- MaxIterations : opt .MaxIterations ,
494- PromptTemplateFile : opt .PromptTemplateFilePath ,
495- ExtraPromptPaths : opt .ExtraPromptPaths ,
496- Tools : tools .Default (),
497- Recorder : recorder ,
498- RemoveWorkDir : opt .RemoveWorkDir ,
499- SkipPermissions : opt .SkipPermissions ,
500- EnableToolUseShim : opt .EnableToolUseShim ,
501- MCPClientEnabled : opt .MCPClient ,
502- RunOnce : opt .Quiet ,
503- InitialQuery : queryFromCmd ,
504- ChatMessageStore : chatStore ,
505- Sandbox : opt .Sandbox ,
506- SandboxImage : opt .SandboxImage ,
507- Session : session ,
508- SessionBackend : opt .SessionBackend ,
509- }
510-
511- err = k8sAgent .Init (ctx )
512- if err != nil {
513- return fmt .Errorf ("starting k8s agent: %w" , err )
506+ defaultAgent , err = agentManager .GetAgent (ctx , session .ID )
507+ if err != nil {
508+ return fmt .Errorf ("failed to get agent for session: %w" , err )
509+ }
514510 }
515- defer k8sAgent .Close ()
516511
517512 var userInterface ui.UI
518513 switch opt .UIType {
519514 case ui .UITypeTerminal :
520515 // since stdin is already consumed, we use TTY for taking input from user
521516 useTTYForInput := hasInputData
522- userInterface , err = ui .NewTerminalUI (k8sAgent , useTTYForInput , opt .ShowToolOutput , recorder )
517+ userInterface , err = ui .NewTerminalUI (defaultAgent , useTTYForInput , opt .ShowToolOutput , recorder )
523518 if err != nil {
524519 return fmt .Errorf ("creating terminal UI: %w" , err )
525520 }
526521 case ui .UITypeWeb :
527- userInterface , err = html .NewHTMLUserInterface (k8sAgent , opt .UIListenAddress , recorder )
522+ userInterface , err = html .NewHTMLUserInterface (agentManager , sessionManager , opt . ModelID , opt . ProviderID , opt .UIListenAddress , recorder )
528523 if err != nil {
529524 return fmt .Errorf ("creating web UI: %w" , err )
530525 }
531526 case ui .UITypeTUI :
532- userInterface = ui .NewTUI (k8sAgent )
527+ userInterface = ui .NewTUI (defaultAgent )
533528 default :
534529 return fmt .Errorf ("ui-type mode %q is not known" , opt .UIType )
535530 }
536531
537- return repl (ctx , queryFromCmd , userInterface , k8sAgent )
532+ err = userInterface .Run (ctx )
533+ if err != nil && ! errors .Is (err , context .Canceled ) {
534+ return fmt .Errorf ("running UI: %w" , err )
535+ }
536+
537+ return nil
538538}
539539
540540func handleCustomTools (toolConfigPaths []string ) error {
@@ -576,24 +576,6 @@ func handleCustomTools(toolConfigPaths []string) error {
576576 return nil
577577}
578578
579- // repl is a read-eval-print loop for the chat session.
580- func repl (ctx context.Context , initialQuery string , ui ui.UI , agent * agent.Agent ) error {
581- query := initialQuery
582- // Note: Initial greeting and MCP status are now handled by the agent itself
583- // through the message-based system
584- err := agent .Run (ctx , query )
585- if err != nil {
586- return fmt .Errorf ("running agent: %w" , err )
587- }
588-
589- err = ui .Run (ctx )
590- if err != nil && ! errors .Is (err , context .Canceled ) {
591- return fmt .Errorf ("running UI: %w" , err )
592- }
593-
594- return nil
595- }
596-
597579// Redirect standard log output to our custom klog writer
598580// This is primarily to suppress warning messages from
599581// genai library https://github.com/googleapis/go-genai/blob/6ac4afc0168762dc3b7a4d940fc463cc1854f366/types.go#L1633
@@ -608,7 +590,6 @@ func redirectStdLogToKlog() {
608590// Define a custom writer that forwards messages to klog.Warning
609591type klogWriter struct {}
610592
611- // Implement the io.Writer interface
612593func (writer klogWriter ) Write (data []byte ) (n int , err error ) {
613594 // We trim the trailing newline because klog adds its own.
614595 message := string (bytes .TrimSuffix (data , []byte ("\n " )))
@@ -689,7 +670,11 @@ func resolveKubeConfigPath(opt *Options) error {
689670 if err != nil {
690671 return fmt .Errorf ("failed to get user home directory: %w" , err )
691672 }
692- opt .KubeConfigPath = filepath .Join (home , ".kube" , "config" )
673+ defaultPath := filepath .Join (home , ".kube" , "config" )
674+ // Only use the default path if it exists
675+ if _ , err := os .Stat (defaultPath ); err == nil {
676+ opt .KubeConfigPath = defaultPath
677+ }
693678 }
694679
695680 // We resolve the kubeconfig path to an absolute path, so we can run kubectl from any working directory.
@@ -756,7 +741,6 @@ func handleDeleteSession(opt Options) error {
756741 return fmt .Errorf ("failed to create session manager: %w" , err )
757742 }
758743
759- // Check if session exists
760744 session , err := manager .FindSessionByID (opt .DeleteSession )
761745 if err != nil {
762746 return fmt .Errorf ("session %s not found: %w" , opt .DeleteSession , err )
0 commit comments