44 "context"
55 "encoding/json"
66 "fmt"
7+ "io"
78 "log/slog"
89 "os"
910 "path/filepath"
@@ -193,7 +194,17 @@ func (c *PTYConversation) Start(ctx context.Context) {
193194 }
194195
195196 if c .initialPromptReady && ! c .loadStateSuccessful && c .cfg .StatePersistenceConfig .LoadState {
196- _ = c .loadState ()
197+ if err := c .loadStateLocked (); err != nil {
198+ // Add error message to conversation so user is aware
199+ errorMsg := fmt .Sprintf ("AgentAPI state restoration failed, the conversation history may be missing: %v" , err )
200+ c .messages = append (c .messages , ConversationMessage {
201+ Id : len (c .messages ),
202+ Message : errorMsg ,
203+ Role : ConversationRoleAgent ,
204+ Time : c .cfg .Clock .Now (),
205+ })
206+ c .cfg .Logger .Error ("Failed to load state" , "error" , err )
207+ }
197208 c .loadStateSuccessful = true
198209 }
199210
@@ -534,16 +545,14 @@ func (c *PTYConversation) Text() string {
534545}
535546
536547func (c * PTYConversation ) SaveState () error {
537- conversation := c .Messages ()
538-
539548 c .lock .Lock ()
540549 defer c .lock .Unlock ()
541550
542551 stateFile := c .cfg .StatePersistenceConfig .StateFile
543552 saveState := c .cfg .StatePersistenceConfig .SaveState
544553
545554 if ! saveState {
546- c .cfg .Logger .Info ("" )
555+ c .cfg .Logger .Info ("State persistence is disabled " )
547556 return nil
548557 }
549558
@@ -553,6 +562,8 @@ func (c *PTYConversation) SaveState() error {
553562 return nil
554563 }
555564
565+ conversation := c .messagesLocked ()
566+
556567 // Serialize initial prompt from message parts
557568 var initialPromptStr string
558569 if len (c .cfg .InitialPrompt ) > 0 {
@@ -598,8 +609,8 @@ func (c *PTYConversation) SaveState() error {
598609 return nil
599610}
600611
601- // LoadState loads the state, this method assumes that caller holds the Lock
602- func (c * PTYConversation ) loadState () error {
612+ // loadStateLocked loads the state, this method assumes that caller holds the Lock
613+ func (c * PTYConversation ) loadStateLocked () error {
603614 stateFile := c .cfg .StatePersistenceConfig .StateFile
604615 loadState := c .cfg .StatePersistenceConfig .LoadState
605616
@@ -613,20 +624,25 @@ func (c *PTYConversation) loadState() error {
613624 return nil
614625 }
615626
616- // Read state file
617- data , err := os .ReadFile (stateFile )
627+ // Open state file
628+ f , err := os .Open (stateFile )
618629 if err != nil {
619- c .cfg .Logger .Warn ("Failed to load state file" , "path" , stateFile , "err" , err )
620- return xerrors .Errorf ("failed to read state file: %w" , err )
621- }
622-
623- if len (data ) == 0 {
624- c .cfg .Logger .Info ("No previous state to load (file is empty)" , "path" , stateFile )
625- return nil
630+ c .cfg .Logger .Warn ("Failed to open state file" , "path" , stateFile , "err" , err )
631+ return xerrors .Errorf ("failed to open state file: %w" , err )
626632 }
633+ defer func () {
634+ if closeErr := f .Close (); closeErr != nil {
635+ c .cfg .Logger .Warn ("Failed to close state file" , "path" , stateFile , "err" , closeErr )
636+ }
637+ }()
627638
628639 var agentState AgentState
629- if err := json .Unmarshal (data , & agentState ); err != nil {
640+ decoder := json .NewDecoder (f )
641+ if err := decoder .Decode (& agentState ); err != nil {
642+ if err == io .EOF {
643+ c .cfg .Logger .Info ("No previous state to load (file is empty)" , "path" , stateFile )
644+ return nil
645+ }
630646 c .cfg .Logger .Warn ("Failed to load state file (corrupted or invalid JSON)" , "path" , stateFile , "err" , err )
631647 return xerrors .Errorf ("failed to unmarshal state (corrupted or invalid JSON): %w" , err )
632648 }
@@ -663,7 +679,7 @@ func (c *PTYConversation) adjustScreenAfterStateLoad(screen string) string {
663679 // Before the first user message after loading state, return the last message from the loaded state.
664680 // This prevents computing incorrect diffs from the restored screen, as the agent's message should
665681 // remain stable until the user continues the conversation.
666- if c .userSentMessageAfterLoadState == false {
682+ if ! c .userSentMessageAfterLoadState {
667683 newScreen = "\n " + c .messages [len (c .messages )- 1 ].Message
668684 }
669685
0 commit comments