* Migrate all messages in chat.jsonl to use a new workspace ID * This is used during workspace rename to update the workspaceId field in all historical messages * IMPORTANT: Should be called AFTER the session directory has been renamed
(oldWorkspaceId: string, newWorkspaceId: string)
| 1998 | * IMPORTANT: Should be called AFTER the session directory has been renamed |
| 1999 | */ |
| 2000 | async migrateWorkspaceId(oldWorkspaceId: string, newWorkspaceId: string): Promise<Result<void>> { |
| 2001 | return this.fileLocks.withLock(newWorkspaceId, async () => { |
| 2002 | try { |
| 2003 | // Migrate the sealed archive first so a crash mid-migration never leaves |
| 2004 | // the active file pointing at a stale-ID archive. |
| 2005 | const archiveMessages = await this.readArchivedHistory(newWorkspaceId); |
| 2006 | if (archiveMessages.length > 0) { |
| 2007 | await writeFileAtomic( |
| 2008 | this.getChatArchivePath(newWorkspaceId), |
| 2009 | this.serializeHistoryEntries(archiveMessages, newWorkspaceId) |
| 2010 | ); |
| 2011 | } |
| 2012 | |
| 2013 | // Read messages from the NEW workspace location (directory was already renamed). |
| 2014 | // Structural rewrite requires full file content. |
| 2015 | const messages = await this.readChatHistory(newWorkspaceId); |
| 2016 | if (messages.length === 0) { |
| 2017 | // No active messages to migrate, just transfer the sequence counter. |
| 2018 | // Floor it with the archive max: an archive-only session (active file |
| 2019 | // deleted/truncated) renamed in a fresh process has no cached counter, |
| 2020 | // and seeding 0 would reuse archived historySequence values. |
| 2021 | const oldCounter = this.sequenceCounters.get(oldWorkspaceId) ?? 0; |
| 2022 | const archiveFloor = (await this.getArchiveTailMaxSequence(newWorkspaceId)) + 1; |
| 2023 | this.sequenceCounters.set(newWorkspaceId, Math.max(oldCounter, archiveFloor)); |
| 2024 | this.sequenceCounters.delete(oldWorkspaceId); |
| 2025 | return Ok(undefined); |
| 2026 | } |
| 2027 | |
| 2028 | // Rewrite all messages with new workspace ID |
| 2029 | const newHistoryPath = this.getChatHistoryPath(newWorkspaceId); |
| 2030 | const historyEntries = this.serializeHistoryEntries(messages, newWorkspaceId); |
| 2031 | |
| 2032 | // Atomic write prevents corruption if app crashes mid-write |
| 2033 | await writeFileAtomic(newHistoryPath, historyEntries); |
| 2034 | |
| 2035 | // Transfer sequence counter to new workspace ID |
| 2036 | const oldCounter = this.sequenceCounters.get(oldWorkspaceId) ?? 0; |
| 2037 | this.sequenceCounters.set(newWorkspaceId, oldCounter); |
| 2038 | this.sequenceCounters.delete(oldWorkspaceId); |
| 2039 | |
| 2040 | log.debug( |
| 2041 | `Migrated ${messages.length} messages from ${oldWorkspaceId} to ${newWorkspaceId}` |
| 2042 | ); |
| 2043 | |
| 2044 | return Ok(undefined); |
| 2045 | } catch (error) { |
| 2046 | const message = getErrorMessage(error); |
| 2047 | return Err(`Failed to migrate workspace ID: ${message}`); |
| 2048 | } |
| 2049 | }); |
| 2050 | } |
| 2051 | } |
no test coverage detected