@@ -25,7 +25,15 @@ export interface SavedProject {
2525 id : string ;
2626 name : string ;
2727 path : string ;
28- tabs ?: ( { id ?: string ; sid ?: string ; title : string ; backend ?: string ; archived ?: boolean } & SavedTabChrome ) [ ] ;
28+ tabs ?: ( {
29+ id ?: string ;
30+ sid ?: string ;
31+ title : string ;
32+ backend ?: string ;
33+ /** backend 为 'acp' 时:驱动该会话的 registry agent(重启动/恢复时必需)。 */
34+ acpAgent ?: { id : string ; name : string } ;
35+ archived ?: boolean ;
36+ } & SavedTabChrome ) [ ] ;
2937 /** 并行任务 worktree 项目的元数据(isWorktree/mainRepoPath/branch/baseBranch/slug)。 */
3038 worktree ?: WorktreeMeta ;
3139 /** 本项目最近一次新建会话所用的引擎后端(缺省 = jucode)。 */
@@ -287,9 +295,10 @@ export class SessionStore {
287295 backend : BackendId = 'jucode' ,
288296 archived = false ,
289297 chrome ?: SavedTabChrome ,
290- reuseId ?: string
298+ reuseId ?: string ,
299+ acpAgent ?: { id : string ; name : string }
291300 ) {
292- const s = this . #newSession( backend , undefined , reuseId ) ;
301+ const s = this . #newSession( backend , backend === 'acp' ? acpAgent : undefined , reuseId ) ;
293302 if ( title ) s . chat . title = title ;
294303 s . archived = archived ;
295304 if ( chrome ?. color ) s . color = chrome . color ;
@@ -321,9 +330,10 @@ export class SessionStore {
321330 title : string ,
322331 backend : BackendId = 'jucode' ,
323332 archived = false ,
324- chrome ?: SavedTabChrome
333+ chrome ?: SavedTabChrome ,
334+ acpAgent ?: { id : string ; name : string }
325335 ) {
326- const s = this . #newSession( backend , undefined , reuseId ) ;
336+ const s = this . #newSession( backend , backend === 'acp' ? acpAgent : undefined , reuseId ) ;
327337 if ( title ) s . chat . title = title ;
328338 s . archived = archived ;
329339 if ( chrome ?. color ) s . color = chrome . color ;
@@ -561,8 +571,11 @@ export class SessionStore {
561571 /** Snapshot of the layout + open tabs for persistence. Every session is
562572 * written (empty windows survive a workspace switch under their desktop
563573 * id); `sid` only when the engine actually persisted the conversation —
564- * never `/resume` one it didn't. The backend id is only written when it
565- * isn't the default, so pre-existing layouts stay byte-identical. */
574+ * never `/resume` one it didn't. A restored session keeps its `sid` even
575+ * while its replayed transcript is still empty (replay is async and may
576+ * fail; the engine-side conversation exists regardless). The backend id
577+ * is only written when it isn't the default, so pre-existing layouts stay
578+ * byte-identical; 'acp' tabs also carry their agent so restore can respawn. */
566579 serialize ( ) : SavedProject [ ] {
567580 return this . projects . map ( ( p ) => ( {
568581 id : p . id ,
@@ -574,9 +587,10 @@ export class SessionStore {
574587 tabs : p . sessions
575588 . map ( ( s ) => ( {
576589 id : s . id ,
577- ...( s . chat . resumable ? { sid : s . chat . sessionId } : { } ) ,
590+ ...( s . chat . sessionId && ( s . chat . resumable || s . restored ) ? { sid : s . chat . sessionId } : { } ) ,
578591 title : s . chat . title ,
579592 ...( s . backendId !== 'jucode' ? { backend : s . backendId } : { } ) ,
593+ ...( s . backendId === 'acp' && s . acpAgent ? { acpAgent : s . acpAgent } : { } ) ,
580594 ...( s . archived ? { archived : true } : { } ) ,
581595 ...( s . color ? { color : s . color } : { } ) ,
582596 ...( s . icon ? { icon : s . icon } : { } ) ,
@@ -619,7 +633,19 @@ export class SessionStore {
619633 // Tabs saved before multi-backend support carry no backend field →
620634 // jucode (normalizeBackendId maps unknown/missing to the default).
621635 // Chrome fields are re-validated here (the file is user-editable).
622- const backend = normalizeBackendId ( t . backend ) ;
636+ let backend = normalizeBackendId ( t . backend ) ;
637+ // An 'acp' tab needs its agent back to respawn; older files carry
638+ // none on the tab → fall back to the project's last agent. Without
639+ // any, never spawn a bare 'acp' (create_session rejects it).
640+ const savedAgent =
641+ t . acpAgent && typeof t . acpAgent . id === 'string' && typeof t . acpAgent . name === 'string'
642+ ? { id : t . acpAgent . id , name : t . acpAgent . name }
643+ : undefined ;
644+ let acpAgent = backend === 'acp' ? ( savedAgent ?? proj . lastAcpAgent ) : undefined ;
645+ if ( backend === 'acp' && ! acpAgent ) {
646+ backend = 'jucode' ;
647+ acpAgent = undefined ;
648+ }
623649 const chrome = {
624650 color : normalizeColor ( t . color ) ,
625651 icon : parseTabIcon ( t . icon ) ,
@@ -628,8 +654,8 @@ export class SessionStore {
628654 // With a conversation to resume, resume it; an empty window spawns
629655 // fresh. Both keep the saved desktop id (pre-id files mint anew).
630656 const id = t . sid
631- ? this . restoreSession ( proj , t . sid , t . title , backend , ! ! t . archived , chrome , t . id )
632- : this . #spawnSaved( proj , t . id ! , t . title , backend , ! ! t . archived , chrome ) ;
657+ ? this . restoreSession ( proj , t . sid , t . title , backend , ! ! t . archived , chrome , t . id , acpAgent )
658+ : this . #spawnSaved( proj , t . id ! , t . title , backend , ! ! t . archived , chrome , acpAgent ) ;
633659 if ( ! first && ! t . archived ) first = id ;
634660 }
635661 }
0 commit comments