v0.6.0: beautifului 整合 + 会话/工作区/队列/权限/命令/附件全链路

- beautifului.dev 组件直连:Sidebar Nav、Tool Chips、Approval Card(抽屉化)、Streaming/Thinking、Code Block、Selection Actions、Prompt Bar、Search
This commit is contained in:
Rick Ye
2026-09-06 09:57:14 +08:00
parent 3c2e9e96c5
commit a5951849c8
96 changed files with 11828 additions and 4649 deletions
@@ -0,0 +1,81 @@
// 验证渲染层对 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`)
}