fix: verify Cursor chat store before reopen (#1037)

* test: reproduce issue #841

* test: cover Cursor chat store discovery

* fix: verify Cursor chat store before resume (closes #841)

* test: preserve non-Cursor resume behavior

* test: cover conservative Cursor resume gating

* fix: gate Cursor reopen until store verification

* test: cover legacy Cursor drawer fallback

* fix: scan unique legacy Cursor store drawer

* test: preserve raw Cursor workspace path hashing

* fix: hash raw Cursor workspace path

* test: pin Cursor probe owner and machine

* fix: probe Cursor store on recorded owner

* test: normalize Cursor probe owner home

* fix: normalize Cursor probe owner home
This commit is contained in:
SSU-WEI HUANG
2026-07-16 12:34:41 +08:00
committed by GitHub
parent adb6f41858
commit 520c3f511a
27 changed files with 955 additions and 34 deletions
+3
View File
@@ -159,6 +159,9 @@ export default {
'session.action.export': 'Export conversation',
'session.action.archive': 'Archive',
'session.action.reopen': 'Reopen',
'session.action.reopenCursorChecking': 'Checking whether Cursor chat data is still available on the recorded machine.',
'session.action.reopenCursorMissing': 'Cursor chat data is no longer available on the recorded machine.',
'session.action.reopenCursorCheckFailed': 'Could not verify Cursor chat data on the recorded machine.',
'session.action.delete': 'Delete',
'session.action.copy': 'Copy',
+3
View File
@@ -159,6 +159,9 @@ export default {
'session.action.export': '导出对话',
'session.action.archive': '归档',
'session.action.reopen': '重新打开',
'session.action.reopenCursorChecking': '正在检查记录设备上的 Cursor 聊天数据是否仍然可用。',
'session.action.reopenCursorMissing': '记录设备上的 Cursor 聊天数据已不可用。',
'session.action.reopenCursorCheckFailed': '无法验证记录设备上的 Cursor 聊天数据。',
'session.action.delete': '删除',
'session.action.copy': '复制',
+1
View File
@@ -17,6 +17,7 @@ export const queryKeys = {
slashCommands: (sessionId: string) => ['slash-commands', sessionId] as const,
sessionCodexModels: (sessionId: string) => ['session-codex-models', sessionId] as const,
sessionCursorModels: (sessionId: string) => ['session-cursor-models', sessionId] as const,
sessionCursorChatStore: (sessionId: string) => ['session-cursor-chat-store', sessionId] as const,
sessionPiModels: (sessionId: string) => ['session-pi-models', sessionId] as const,
machineCursorModels: (machineId: string) => ['machine-cursor-models', machineId] as const,
sessionOpencodeModels: (sessionId: string) => ['session-opencode-models', sessionId] as const,
+34 -1
View File
@@ -64,7 +64,40 @@ describe('sessionResume', () => {
flavor: 'cursor',
cursorSessionId: 'cursor-thread-1',
},
}), 5)).toBe(true)
}), 5, true)).toBe(true)
})
it('conservatively rejects cursor resume until the chat store is verified', () => {
expect(inactiveSessionCanResume(makeSession({
metadata: {
path: '/tmp/project',
host: 'localhost',
flavor: 'cursor',
cursorSessionId: 'cursor-thread-1',
},
}), 5)).toBe(false)
})
it('rejects cursor resume when the recorded chat store is missing on its machine', () => {
expect(inactiveSessionCanResume(makeSession({
metadata: {
path: '/tmp/project',
host: 'localhost',
flavor: 'cursor',
cursorSessionId: 'cursor-thread-1',
},
}), 5, false)).toBe(false)
})
it('does not apply Cursor chat store status to other agent flavors', () => {
expect(inactiveSessionCanResume(makeSession({
metadata: {
path: '/tmp/project',
host: 'localhost',
flavor: 'codex',
codexSessionId: 'codex-thread-1',
},
}), 5, false)).toBe(true)
})
it('resolveAgentSessionIdFromMetadata still returns cursorSessionId regardless of protocol', () => {
+5
View File
@@ -34,6 +34,7 @@ export function resolveAgentSessionIdFromMetadata(
export function inactiveSessionCanResume(
session: Session,
userMessageCount: number,
cursorChatOnDisk?: boolean,
): boolean {
if (session.active) {
return true
@@ -42,6 +43,10 @@ export function inactiveSessionCanResume(
return false
}
if (resolveAgentSessionIdFromMetadata(session.metadata)) {
const flavor = isKnownFlavor(session.metadata.flavor) ? session.metadata.flavor : 'claude'
if (flavor === 'cursor') {
return cursorChatOnDisk === true
}
return true
}
const flavor = isKnownFlavor(session.metadata.flavor) ? session.metadata.flavor : 'claude'