// deploy/m2_probe4.js —— Vue store 内部状态探针(PM 使用,在远端运行) // 通过 __vueParentComponent 链拿到 pinia,直读 chat store 的消息状态, // 区分「store 未更新」(handler 问题)与「store 已更新但 DOM 未渲染」(响应式问题) const { chromium } = require('playwright'); async function dumpStore(page) { return page.evaluate(() => { const findRoot = (el) => { let node = el; while (node) { if (node.__vue_app__) return node.__vue_app__; node = node.__vueParentComponent?.parent || null; } return null; }; const rootEl = document.querySelector('#app') || document.querySelector('.app-shell'); const app = rootEl ? findRoot(rootEl) : null; if (!app) return { err: '未找到 vue app' }; const pinia = app.config.globalProperties.$pinia; if (!pinia) return { err: '未找到 pinia' }; const chat = pinia._s.get('chat'); if (!chat) return { err: '未找到 chat store' }; const msgs = chat.messages.map((m) => ({ role: m.role, status: m.status, id: m.id, blocks: (m.blocks || []).map((b) => b.type === 'pipeline' ? { type: 'pipeline', status: b.status, steps: b.steps.map((s) => `${s.phase}:${s.state}`) } : { type: b.type, len: b.type === 'text' ? (b.html || '').length : b.type === 'stream' ? (b.text || '').length : undefined }, ), })); return { busy: chat.busy, completedOnce: chat.completedOnce, currentEvents: chat.currentEvents.map((e) => e.stage), sessionId: chat.sessionId, msgs, }; }); } (async () => { const browser = await chromium.launch(); const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } }); const page = await ctx.newPage(); await page.goto('http://127.0.0.1:5180', { waitUntil: 'domcontentloaded' }); await page.waitForSelector('.app-shell', { timeout: 15000 }); await page.click('.new-task-btn'); await page.waitForSelector('.composer textarea', { timeout: 15000 }); await page.fill('.composer textarea', 'SPL gene family in rice'); await page.click('.send-btn'); const t0 = Date.now(); let last = ''; while (Date.now() - t0 < 150000) { const snap = await dumpStore(page); const msgs = snap.msgs || []; const lastAssist = msgs.filter((m) => m.role === 'assistant').pop(); const pipe = lastAssist?.blocks?.find((b) => b.type === 'pipeline'); const key = JSON.stringify({ busy: snap.busy, done: snap.completedOnce, steps: pipe?.steps, nEv: snap.currentEvents?.length }); if (key !== last) { last = key; const el = Math.round((Date.now() - t0) / 1000); console.log(`T+${el}s busy=${snap.busy} completedOnce=${snap.completedOnce} events=${snap.currentEvents?.length} 步骤=${JSON.stringify(pipe?.steps)}`); console.log(` 事件序列: ${(snap.currentEvents || []).slice(-5).join(', ')}`); } if (!snap.busy && snap.completedOnce && Date.now() - t0 > 30000) break; await new Promise((r) => setTimeout(r, 2000)); } console.log('\n=== 最终 store 状态 ==='); console.log(JSON.stringify(await dumpStore(page), null, 2)); console.log('\n=== 最终 DOM 流水线 ==='); const dom = await page.evaluate(() => { const card = document.querySelector('.pipeline-card'); if (!card) return '无卡片'; return card.textContent.slice(0, 300); }); console.log(dom); await browser.close(); })();