// 验证渲染层对 session_switched 历史消息的渲染逻辑: // 用与主进程一致的 session.messages 源,跑 MessageItem 的 readBlocks + AssistantBody 判断。 import os from 'node:os' import { createAgentSessionServices, getAgentDir } from '@earendil-works/pi-coding-agent' const cwd = os.homedir() const services = await createAgentSessionServices({ cwd, agentDir: getAgentDir() }) // SessionManager.list + 加载最近会话的等价做法:直接用 services 里加载当前会话 const session = services.sessionManager ? null : null // 走 SessionManager 拿最近会话并加载 const { SessionManager } = await import('@earendil-works/pi-coding-agent') const sm = SessionManager.continueRecent(cwd) const smAny = sm // SessionManager.continueRecent 返回的 manager 需要挂到 session 上才有 messages; // 更直接:读它指向的会话文件并解析 message 条目 const entries = [] if (typeof smAny.loadEntries === 'function') { entries.push(...(await smAny.loadEntries())) } console.log('loadEntries available:', typeof smAny.loadEntries === 'function', 'entries:', entries.length) // 退路:直接解析磁盘上的最近 jsonl(与主进程 toCloneable 后的消息同构) import { readdirSync, readFileSync, statSync } from 'node:fs' import { join } from 'node:path' const dir = join(cwd, '.pi', 'agent', 'sessions', '--C--Users-21976--') const files = readdirSync(dir).filter((f) => f.endsWith('.jsonl')) files.sort((a, b) => statSync(join(dir, b)).mtimeMs - statSync(join(dir, a)).mtimeMs) const latest = files[0] console.log('latest session file:', latest) const lines = readFileSync(join(dir, latest), 'utf8').trim().split('\n') const messages = [] for (const line of lines) { let entry try { entry = JSON.parse(line) } catch { continue } if (entry.type === 'message' && entry.message) messages.push(entry.message) } // ==== 渲染层逻辑复刻(与 MessageItem.tsx 一致)==== function readBlocks(raw) { if (!raw || typeof raw !== 'object') return [] const content = raw.content if (typeof content === 'string') return [{ type: 'text', text: content }] if (Array.isArray(content)) { return content.filter((c) => typeof c === 'object' && c !== null) } return [] } for (const [i, m] of messages.entries()) { console.log(`\n=== history msg #${i} role=${m.role} ===`) const blocks = readBlocks(m) let rendered = 0 for (const block of blocks) { if (block.type === 'thinking' && typeof block.text === 'string') { console.log(' -> ThinkingBlock (text field)') rendered++ continue } if (block.type === 'text' && block.text) { console.log(` -> Markdown("${String(block.text).slice(0, 60)}…")`) rendered++ continue } if (block.type === 'toolCall' && block.id) { console.log(' -> ToolCard', block.name) rendered++ continue } console.log(' -> **被丢弃** block type =', block.type, 'keys =', Object.keys(block).join(',')) } console.log(` rendered ${rendered}/${blocks.length} blocks`) }