(_request: CursorMigrateToAcpRequest)
| 551 | } |
| 552 | |
| 553 | private buildMigratorForRequest(_request: CursorMigrateToAcpRequest): CursorLegacyMigrator { |
| 554 | const migratorOpts: CursorLegacyMigratorOptions = {} |
| 555 | return new CursorLegacyMigrator(migratorOpts, { |
| 556 | archiveSession: async (sessionId) => { |
| 557 | await this.archiveSession(sessionId) |
| 558 | }, |
| 559 | // NOTE: no awaitSessionInactive injection — handleSessionEnd() |
| 560 | // synchronously sets cache.active=false inside archiveSession, |
| 561 | // so any cache-based poll would return immediately and provide |
| 562 | // false reassurance. The migrator now relies on |
| 563 | // awaitLockRelease's minimum-dwell + SQLite busy-probe + |
| 564 | // size-stability combination instead. Codex review #34 P1 v3. |
| 565 | getCurrentSession: (sessionId, namespace) => { |
| 566 | const s = this.sessionCache.getSessionByNamespace(sessionId, namespace) |
| 567 | if (!s) return null |
| 568 | return { |
| 569 | active: s.active === true, |
| 570 | lifecycleState: typeof s.metadata?.lifecycleState === 'string' ? s.metadata.lifecycleState : undefined, |
| 571 | cursorSessionProtocol: typeof s.metadata?.cursorSessionProtocol === 'string' ? s.metadata.cursorSessionProtocol : undefined |
| 572 | } |
| 573 | }, |
| 574 | updateSessionAfterMigrate: (sessionId, namespace, lastUsedModel) => { |
| 575 | const result = this.flipCursorSessionProtocolToAcp(sessionId, namespace, lastUsedModel) |
| 576 | if (result.result === 'success') return { ok: true } |
| 577 | if (result.result === 'session-active') return { ok: false, reason: 'session_active' as const } |
| 578 | return { ok: false, reason: 'version_mismatch_or_missing' as const } |
| 579 | }, |
| 580 | // tiann/hapi#872: size sanity check needs to compare HAPI's known |
| 581 | // message history against the candidate legacy store's blob |
| 582 | // count. The store-handle stays on the engine; we only thread |
| 583 | // the count through so the migrator stays free of a direct |
| 584 | // hub.Store dependency. |
| 585 | getHapiMessageCount: (sessionId, _namespace) => { |
| 586 | try { |
| 587 | return this.store.messages.countMessages(sessionId) |
| 588 | } catch (err) { |
| 589 | // tiann/hapi#873 cold review: a silent 0 here trips |
| 590 | // the migrator's "skip sanity" branch and chronically |
| 591 | // disables the floor. Warn so a broken countMessages |
| 592 | // (lock contention pattern, schema drift) is visible |
| 593 | // in journalctl. |
| 594 | console.warn('[auto-migrate] countMessages threw; size sanity skipped', { |
| 595 | sessionId, |
| 596 | err: err instanceof Error ? err.message : String(err) |
| 597 | }) |
| 598 | return 0 |
| 599 | } |
| 600 | } |
| 601 | }) |
| 602 | } |
| 603 | |
| 604 | async switchSession(sessionId: string, to: 'remote' | 'local'): Promise<void> { |
| 605 | await this.rpcGateway.switchSession(sessionId, to) |
no test coverage detected