@@ -256,11 +256,8 @@ func (m model) sessionPickerCmd() (model, tea.Cmd) {
256256 now : m .now ,
257257 }
258258 return m , func () tea.Msg {
259- picker , localErr := snapshot .buildSessionPicker ()
260- warning := ""
261- if localErr != nil {
262- warning = "Sessions\n Warning: could not read local Zero sessions; showing external sessions only: " + agentsessions .DisplayField (localErr .Error ())
263- }
259+ picker , foreignProblems , localErr := snapshot .buildSessionPicker ()
260+ warning := sessionPickerWarning (localErr , foreignProblems )
264261 msg := sessionPickerLoadedMsg {originSession : originSession , generation : generation }
265262 if picker != nil {
266263 msg .picker , msg .text = picker , warning
@@ -275,6 +272,28 @@ func (m model) sessionPickerCmd() (model, tea.Cmd) {
275272 }
276273}
277274
275+ // sessionPickerWarning keeps partial discovery failures visible without
276+ // turning them into a failed picker. Errors may contain paths or transcript
277+ // identifiers from foreign stores, so this is also the single display
278+ // boundary that sanitizes every local and foreign problem before it reaches
279+ // the terminal transcript.
280+ func sessionPickerWarning (localErr error , foreignProblems []error ) string {
281+ warnings := []string {}
282+ if localErr != nil {
283+ warnings = append (warnings , "Warning: could not read local Zero sessions; showing external sessions only: " + agentsessions .DisplayField (localErr .Error ()))
284+ }
285+ for _ , problem := range foreignProblems {
286+ if problem == nil {
287+ continue
288+ }
289+ warnings = append (warnings , "Warning: could not read some external sessions: " + agentsessions .DisplayField (problem .Error ()))
290+ }
291+ if len (warnings ) == 0 {
292+ return ""
293+ }
294+ return "Sessions\n " + strings .Join (warnings , "\n " )
295+ }
296+
278297// sessionPickerResultIsCurrent reports whether a discovery result still
279298// describes a request whose preconditions hold: no run has started, the same
280299// session is active, and no newer /resume has replaced it.
@@ -495,16 +514,17 @@ func sessionWhenTime(parsed time.Time, now time.Time) string {
495514// one row per resumable session — title (Label) + id and relative age (Meta). Returns
496515// nil when there are no resumable sessions so the caller falls back to the text path.
497516func (m model ) newSessionPicker () * commandPicker {
498- picker , _ := m .buildSessionPicker ()
517+ picker , _ , _ := m .buildSessionPicker ()
499518 return picker
500519}
501520
502521// buildSessionPicker keeps the local and external sources independent. A local
503- // store failure removes only local rows; callers receive the error separately
504- // so they can warn without hiding discoverable external work.
505- func (m model ) buildSessionPicker () (* commandPicker , error ) {
522+ // store failure removes only local rows; callers receive local and foreign
523+ // problems separately so they can warn without hiding discoverable work from
524+ // the sources that did succeed.
525+ func (m model ) buildSessionPicker () (* commandPicker , []error , error ) {
506526 if m .sessionStore == nil {
507- return nil , nil
527+ return nil , nil , nil
508528 }
509529 // A FAILED READ IS THE ONLY REASON TO GIVE UP HERE. An EMPTY local history is
510530 // not: foreign sessions are discovered independently of the store, and the
@@ -555,7 +575,8 @@ func (m model) buildSessionPicker() (*commandPicker, error) {
555575 Tab : agent ,
556576 })
557577 }
558- return pickerFromParts (items , m .foreignSessionItems (metas , now )), err
578+ foreign , problems := m .foreignSessionItems (metas , now )
579+ return pickerFromParts (items , foreign ), problems , err
559580}
560581
561582// pickerFromParts assembles the picker from the two independent sources, and
@@ -674,8 +695,9 @@ func importedSessionNote(result agentsessions.ImportResult, workspace string) st
674695//
675696// Reading these is a bounded index of each transcript's head, never the whole
676697// file (see internal/agentsessions). A store that is missing or has changed
677- // shape contributes nothing rather than failing the picker — /resume must still
678- // open on a machine where one vendor shipped a new format this morning.
698+ // shape contributes a caller-visible problem rather than failing the picker —
699+ // /resume must still open on a machine where one vendor shipped a new format
700+ // this morning, without pretending the partial index is complete.
679701// importedSourceRefs is the set of foreign sessions that have actually been
680702// imported, so the picker can skip offering them a second time — listing a
681703// session twice, once as itself and once as its copy, is worse than not offering
@@ -700,17 +722,17 @@ func importedSourceRefs(existing []sessions.Metadata) map[string]bool {
700722 if meta .EventCount == 0 {
701723 continue
702724 }
703- if agent , sourceID , ok := agentsessions .ParseImportTag (meta .Tag ); ok {
704- imported [agent + ":" + sourceID ] = true
725+ if ref , ok := agentsessions .ForeignSourceRefFromTag (meta .Tag ); ok {
726+ imported [ref ] = true
705727 }
706728 }
707729 return imported
708730}
709731
710- func (m model ) foreignSessionItems (existing []sessions.Metadata , now time.Time ) []pickerItem {
732+ func (m model ) foreignSessionItems (existing []sessions.Metadata , now time.Time ) ( []pickerItem , [] error ) {
711733 imported := importedSourceRefs (existing )
712734
713- found , _ := agentsessions .DiscoverAllCached (m .agentSessionsEnv , m .cwd )
735+ found , problems := agentsessions .DiscoverAllCached (m .agentSessionsEnv , m .cwd )
714736 items := make ([]pickerItem , 0 , len (found ))
715737 for _ , session := range found {
716738 ref := session .Agent + ":" + session .ID
@@ -736,7 +758,7 @@ func (m model) foreignSessionItems(existing []sessions.Metadata, now time.Time)
736758 ForeignSource : & source ,
737759 })
738760 }
739- return items
761+ return items , problems
740762}
741763
742764// sessionAgentName is the agent a session came from, for the picker's tab strip.
0 commit comments