(
result: ResumeLoadResult,
opts: {
forkSession: boolean
sessionIdOverride?: string
transcriptPath?: string
includeAttribution?: boolean
},
context: {
modeApi: CoordinatorModeApi | null
mainThreadAgentDefinition: AgentDefinition | undefined
agentDefinitions: AgentDefinitionsResult
currentCwd: string
cliAgents: AgentDefinition[]
initialState: AppState
},
)
| 408 | * and --resume paths in main.tsx. |
| 409 | */ |
| 410 | export async function processResumedConversation( |
| 411 | result: ResumeLoadResult, |
| 412 | opts: { |
| 413 | forkSession: boolean |
| 414 | sessionIdOverride?: string |
| 415 | transcriptPath?: string |
| 416 | includeAttribution?: boolean |
| 417 | }, |
| 418 | context: { |
| 419 | modeApi: CoordinatorModeApi | null |
| 420 | mainThreadAgentDefinition: AgentDefinition | undefined |
| 421 | agentDefinitions: AgentDefinitionsResult |
| 422 | currentCwd: string |
| 423 | cliAgents: AgentDefinition[] |
| 424 | initialState: AppState |
| 425 | }, |
| 426 | ): Promise<ProcessedResume> { |
| 427 | // Match coordinator/normal mode to the resumed session |
| 428 | let modeWarning: string | undefined |
| 429 | if (feature('COORDINATOR_MODE')) { |
| 430 | modeWarning = context.modeApi?.matchSessionMode(result.mode) |
| 431 | if (modeWarning) { |
| 432 | result.messages.push(createSystemMessage(modeWarning, 'warning')) |
| 433 | } |
| 434 | } |
| 435 | |
| 436 | // Reuse the resumed session's ID unless --fork-session is specified |
| 437 | if (!opts.forkSession) { |
| 438 | const sid = opts.sessionIdOverride ?? result.sessionId |
| 439 | if (sid) { |
| 440 | // When resuming from a different project directory (git worktrees, |
| 441 | // cross-project), transcriptPath points to the actual file; its dirname |
| 442 | // is the project dir. Otherwise the session lives in the current project. |
| 443 | switchSession( |
| 444 | asSessionId(sid), |
| 445 | opts.transcriptPath ? dirname(opts.transcriptPath) : null, |
| 446 | ) |
| 447 | // Rename asciicast recording to match the resumed session ID so |
| 448 | // getSessionRecordingPaths() can discover it during /share |
| 449 | await renameRecordingForSession() |
| 450 | await resetSessionFilePointer() |
| 451 | restoreCostStateForSession(sid) |
| 452 | } |
| 453 | } else if (result.contentReplacements?.length) { |
| 454 | // --fork-session keeps the fresh startup session ID. useLogMessages will |
| 455 | // copy source messages into the new JSONL via recordTranscript, but |
| 456 | // content-replacement entries are a separate entry type only written by |
| 457 | // recordContentReplacement (which query.ts calls for newlyReplaced, never |
| 458 | // the pre-loaded records). Without this seed, `claude -r {newSessionId}` |
| 459 | // finds source tool_use_ids in messages but no matching replacement records |
| 460 | // → they're classified as FROZEN → full content sent (cache miss, permanent |
| 461 | // overage). insertContentReplacement stamps sessionId = getSessionId() = |
| 462 | // the fresh ID, so loadTranscriptFile's keyed lookup will match. |
| 463 | await recordContentReplacement(result.contentReplacements) |
| 464 | } |
| 465 | |
| 466 | // Restore session metadata so /status shows the saved name and metadata |
| 467 | // is re-appended on session exit. Fork doesn't take ownership of the |
no test coverage detected