MCPcopy Create free account
hub / github.com/tiann/hapi / reopenSession

Method reopenSession

hub/src/sync/syncEngine.ts:1198–1275  ·  view source on GitHub ↗

* Revive an archived session so the web UI can reach it again. * * Behaviour: * - Active session: idempotent no-op (`resumed: false`). * - Non-archived inactive session: forwards to `resumeSession` without touching metadata. * - Archived session: validates that the agent has

(sessionId: string, namespace: string)

Source from the content-addressed store, hash-verified

1196 * needed to resume is missing.
1197 */
1198 async reopenSession(sessionId: string, namespace: string): Promise<ReopenSessionResult> {
1199 const access = this.sessionCache.resolveSessionAccess(sessionId, namespace)
1200 if (!access.ok) {
1201 return {
1202 type: 'error',
1203 message: access.reason === 'access-denied' ? 'Session access denied' : 'Session not found',
1204 code: access.reason === 'access-denied' ? 'access_denied' : 'session_not_found'
1205 }
1206 }
1207
1208 const session = access.session
1209 const metadata = session.metadata
1210
1211 if (session.active) {
1212 return { type: 'success', sessionId: access.sessionId, resumed: false }
1213 }
1214
1215 const isArchived = metadata?.lifecycleState === 'archived'
1216
1217 if (isArchived && metadata) {
1218 if (metadata.flavor === 'cursor' && !metadata.cursorSessionId) {
1219 const hasMessages = this.store.messages.getFirstMessages(access.sessionId, 1).length > 0
1220 if (hasMessages) {
1221 return {
1222 type: 'incomplete',
1223 message: 'Cursor session id is missing from metadata; reopen requires the original cursor chat id',
1224 missing: ['cursorSessionId']
1225 }
1226 }
1227 }
1228
1229 const archiveSnapshot = {
1230 lifecycleState: metadata.lifecycleState,
1231 archivedBy: metadata.archivedBy,
1232 archiveReason: metadata.archiveReason,
1233 lifecycleStateSince: metadata.lifecycleStateSince
1234 }
1235
1236 let applied: { cursorSessionProtocol?: 'acp' | 'stream-json' }
1237 try {
1238 applied = await this.sessionCache.clearSessionArchiveMetadata(access.sessionId)
1239 } catch (error) {
1240 const message = error instanceof Error ? error.message : 'Failed to clear archive metadata'
1241 return { type: 'error', message, code: 'metadata_conflict' }
1242 }
1243
1244 const resumeResult = await this.resumeSession(access.sessionId, namespace)
1245 if (resumeResult.type === 'error') {
1246 // Resume failed - put the archive flags back so the row stays archived in the UI
1247 // and the operator can retry. Best-effort: a concurrent metadata write that
1248 // succeeded between clear and restore (e.g. an unrelated rename) wins, in
1249 // which case we surface the original resume error rather than masking it.
1250 try {
1251 await this.sessionCache.restoreSessionArchiveMetadata(access.sessionId, archiveSnapshot)
1252 } catch {
1253 // Swallow restore failures - the resume error is the more important signal.
1254 }
1255 return resumeResult

Callers 2

createSessionsRoutesFunction · 0.45

Calls 5

resumeSessionMethod · 0.95
getFirstMessagesMethod · 0.80
resolveSessionAccessMethod · 0.45

Tested by

no test coverage detected