From fcd766a5ff8169a4e365cd2204fbf6561909bd54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Fri, 29 May 2026 13:45:14 +0800 Subject: [PATCH] fix(desktop): reset picker state on selection + guard malformed preview payloads Co-Authored-By: Claude Sonnet 4.6 --- desktop/src/lib/previewEvents.test.ts | 16 ++++++++++++++++ desktop/src/lib/previewEvents.ts | 10 ++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/desktop/src/lib/previewEvents.test.ts b/desktop/src/lib/previewEvents.test.ts index 8814ce40..f7133e82 100644 --- a/desktop/src/lib/previewEvents.test.ts +++ b/desktop/src/lib/previewEvents.test.ts @@ -36,4 +36,20 @@ describe('subscribePreviewEvents', () => { attachments: [expect.objectContaining({ type: 'image', data: 'data:image/png;base64,AAAA' })], })) }) + + it('selection event resets pickerActive on the session', async () => { + useBrowserPanelStore.getState().open('s1', 'http://x/a') + useBrowserPanelStore.getState().setPicker('s1', true) + await subscribePreviewEvents('s1') + listeners['preview://event']!({ payload: JSON.stringify({ v: 1, type: 'selection', payload: { pageUrl: 'http://x/', element: { selector: '#t', tag: 'h1', classes: [] }, screenshot: { dataUrl: 'data:image/png;base64,AAAA', kind: 'element' } } }) }) + expect(useBrowserPanelStore.getState().bySession['s1']!.pickerActive).toBe(false) + }) + + it('ignores a malformed selection payload without throwing but still resets picker', async () => { + useBrowserPanelStore.getState().open('s1', 'http://x/a') + useBrowserPanelStore.getState().setPicker('s1', true) + await subscribePreviewEvents('s1') + expect(() => listeners['preview://event']!({ payload: JSON.stringify({ v: 1, type: 'selection', payload: { pageUrl: 'http://x/' } }) })).not.toThrow() + expect(useBrowserPanelStore.getState().bySession['s1']!.pickerActive).toBe(false) + }) }) diff --git a/desktop/src/lib/previewEvents.ts b/desktop/src/lib/previewEvents.ts index 568cf550..e569d566 100644 --- a/desktop/src/lib/previewEvents.ts +++ b/desktop/src/lib/previewEvents.ts @@ -22,8 +22,11 @@ export async function subscribePreviewEvents(sessionId: string): Promise<() => v attachments: [{ type: 'image', name: `screenshot-${kindLabel(msg.kind)}.png`, mimeType: 'image/png', data: msg.dataUrl }], }) } - else if (msg.type === 'selection' && msg.payload) { - const p = msg.payload as SelectionPayload & { screenshot?: { dataUrl?: string; kind?: string } } + else if (msg.type === 'selection') { + // 选区事件意味着页面侧已结束一次性拾取——同步关闭宿主侧 picker 态,避免按钮卡在按下态 + store.setPicker(sessionId, false) + const p = msg.payload as (SelectionPayload & { screenshot?: { dataUrl?: string; kind?: string } }) | undefined + if (!p || typeof p !== 'object' || !p.element) return useChatStore.getState().queueComposerPrefill(sessionId, { text: buildSelectionComposerText(p), attachments: p.screenshot?.dataUrl @@ -31,5 +34,8 @@ export async function subscribePreviewEvents(sessionId: string): Promise<() => v : [], }) } + else if (msg.type === 'error') { + console.warn('[preview-agent]', msg) + } }) }