@@ -211,6 +211,29 @@ export function resumeTranscriptLoadErrorBlock(err: unknown): {
211211 return { type : "error" , message : `Could not load prior session transcript: ${ message } ` } ;
212212}
213213
214+ export type ResumeSeed = {
215+ turnsUsed : number ;
216+ mcpServers : ConnectedMcpServer [ ] ;
217+ } ;
218+
219+ const FRESH_RESUME_SEED : ResumeSeed = { turnsUsed : 0 , mcpServers : [ ] } ;
220+
221+ /**
222+ * Fold a resumed session's run.json into a concrete seed once, at the
223+ * resume boundary, so every downstream reader (the run sink, the
224+ * connected-servers list, the immediate post-resume saveState) trusts a
225+ * fully-populated value instead of each repeating its own `?? 0` / `?? []`
226+ * default. A fresh (non-resumed) run gets the same shape via
227+ * FRESH_RESUME_SEED, so callers never branch on "was this a resume."
228+ */
229+ export function resolveResumeSeed ( pickedState : RunState | null ) : ResumeSeed {
230+ if ( pickedState === null ) return FRESH_RESUME_SEED ;
231+ return {
232+ turnsUsed : pickedState . turnsUsed ,
233+ mcpServers : pickedState . mcpServers ?? [ ] ,
234+ } ;
235+ }
236+
214237const GRANT_SCOPE_LABEL : Record < GrantScope , string > = {
215238 session : "This session" ,
216239 project : "This project" ,
@@ -406,13 +429,17 @@ export async function runTUI(initialConfig: Config): Promise<number> {
406429 let resumeSkipInitialTask = config . skipInitialTask === true ;
407430 let startedAt = Date . now ( ) ;
408431 let runTaskTitle = config . task ;
432+ // Resolved once at the resume boundary so turnsUsed/mcpServers reads
433+ // downstream never repeat their own omission-handling default.
434+ let resumeSeed : ResumeSeed = FRESH_RESUME_SEED ;
409435
410436 if ( config . resumePicker ) {
411437 const picked = await pickSession ( config . cwd , { includeCompleted : config . force } ) ;
412438 if ( picked === null ) return 0 ;
413439 sessionId = picked . sessionId ;
414440 resumeSkipInitialTask = true ;
415441 const pickedState = await loadState ( config . cwd , sessionId ) ;
442+ resumeSeed = resolveResumeSeed ( pickedState ) ;
416443 if ( pickedState !== null ) {
417444 startedAt = pickedState . startedAt ;
418445 runTaskTitle = pickedState . task ;
@@ -435,11 +462,11 @@ export async function runTUI(initialConfig: Config): Promise<number> {
435462 // run.json at all.
436463 await saveState ( config . cwd , sessionId , {
437464 status : "running" ,
438- turnsUsed : 0 ,
465+ turnsUsed : resumeSeed . turnsUsed ,
439466 task : runTaskTitle . trim ( ) . length > 0 ? runTaskTitle . trim ( ) : "(conversation)" ,
440467 startedAt,
441468 model : `${ config . providerName } :${ config . model } ` ,
442- mcpServers : [ ] ,
469+ mcpServers : resumeSeed . mcpServers ,
443470 } ) ;
444471
445472 // Crash guard: if anything from setup onward throws all the way out of
@@ -1313,6 +1340,7 @@ export async function runTUI(initialConfig: Config): Promise<number> {
13131340 const runSink = createRunSink ( {
13141341 emitter,
13151342 hookManager,
1343+ initialTurnCount : resumeSeed . turnsUsed ,
13161344 onTurnComplete : ( ctx ) => {
13171345 // provider_id is the canonical provider kind, never ctx.source.sourceId:
13181346 // sourceId is the user-typed label from onboarding/settings, and free
@@ -1332,7 +1360,7 @@ export async function runTUI(initialConfig: Config): Promise<number> {
13321360
13331361 // MCP servers connected so far, keyed by name so a reconnect after a failure
13341362 // replaces rather than duplicates the entry.
1335- let connectedMcpServers : ConnectedMcpServer [ ] = [ ] ;
1363+ let connectedMcpServers : ConnectedMcpServer [ ] = resumeSeed . mcpServers ;
13361364 // Every configured server's latest state, for the /mcp surface. Unlike
13371365 // `connectedMcpServers` (persisted run metadata) this keeps the ones that
13381366 // failed or are still waiting on authorization.
0 commit comments