main:会话控制、可操作链接与展示语义
buffin · origin/main...main · 2026-07-22 · 自包含,读完即弃
写法说明:每段代码均取自当前分支并由主线程复核;青色斜体是解读,灰色是源码注释。每条旅程最后给出排查路标。
1TL;DR
这 7 个本地领先提交将桌面会话从“发送文本、显示流”扩展为三条路径:slash 命令被解析为结构化运行时控制;助手输出中的路径可在当前会话工作目录中预览、打开或定位;流式文本和系统主题把权威事实与表面展示策略分开保存。provider 差异分别收敛在 daemon 回执、renderer 命令目录和 Electron main 文件授权三个边界。
2变更地图
| 设计重心 | 外围变更 |
|---|---|
| 命令目录/状态机、控制回执、流式 reveal cursor、文件链接、main/preload 桥接、系统主题。 | locale、锁文件解析产物、Tailwind token/hover 变体。测试占 1,899 变更行。 |
3架构一图流
以前 · 独立的表面行为
现在 · 结构化控制与受限文件通道
4数据与状态先行
store 增加 session 命令目录、消息开始时间和持续时长;文件请求包含 sessionId、路径和可选源位置。展示层不保存真相,只从 store 派生策略。
export interface Message {
id: string; role: 'user' | 'assistant'; parts: MessagePart[]
status: 'complete' | 'running'; startedAt?: number; durationMs?: number
}
export interface SessionState {
messages: Message[]; isRunning: boolean; usage: UsageInfo | null
commands: AgentControlCommandDefDto[] | null
}export type SessionFileRequest = {
sessionId: string; path: string; line?: number; column?: number; endLine?: number
}
5旅程一:输入命令与运行时配置
use-composer-draft→命令目录
session-command→controller→tRPC→daemon receipt
创建前使用 provider 的命令目录;已有会话优先使用 daemon 推送的 commands.updated。同一解析器同时给补全菜单和 Enter 提交使用,未知 slash 文本仍是普通消息,缺少必填参数则只补全。
const commands = (sessionCommands ?? providerCommands ?? []).filter(isReachableCommand)
function commandSubmission(commands, input) {
const parsed = parseSlashCommand(input.trim())
if (!parsed) return { kind: 'message', text: input.trim() }
const command = commands.find((candidate) => candidate.name === parsed.name)
if (!command) return { kind: 'message', text: input.trim() }
return commandInvocation(command, parsed.raw)
}controller 监听权威 isRunning,而不是由组件猜测 run 生命周期;模型和 reasoning/effort 合并在一个 Popover,已有会话只显示 provider 标记为 runtime 的字段。
const result = await handle.runner.runControl(command)
await handle.sink.flushTerminalDeltas({ runId: run.id })
await handle.sink.ensureControlCompletion({ runId: run.id, command: command.name, result })
await this.finishControlAndBroadcast(run.id, result.status === 'error' ? 'failed' : 'completed')排查路标 · 命令
| 症状 | 入口 |
|---|---|
| 候选缺失、slash 被当普通文本 | session-command.ts、use-composer-draft.ts |
| 命令后状态或错误不正确 | use-session-composer-controller.ts、service.ts |
6旅程二:助手链接到会话文件
纯函数先区分外链、文件和未知 scheme,并处理 :12:3、:12-14、#L12-L14。main 才按 session ID 查询 cwd,再对 cwd 和目标执行 realpath;因此符号链接和上级目录越界也受约束。
const canonicalCwd = await realpath(cwd)
const candidate = await realpath(resolve(canonicalCwd, expandedPath))
const relativePath = relative(canonicalCwd, candidate)
if (relativePath === '..' || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) {
throw new Error('File link points outside the session working directory.')
}
if (!(await stat(candidate)).isFile()) throw new Error('File link target is not a regular file.')预览最多返回目标附近 200 行;到目标行前扫描超过 4 MiB 时返回 unavailable。打开另有限制,已知可执行扩展名不会交给系统 shell。
排查路标 · 文件链接
| 症状 | 入口 |
|---|---|
| 路径未成为控件 | detect-paths.ts、classify.ts、SessionLink.tsx |
| 控件可见但预览不可用 | use-file-link.ts、session-file-bridge.ts |
| 工作区文件无法打开 | resolveSessionFilePath 与文件类型限制 |
7旅程三:流式展示与系统主题
“文本是否还会增长”“run 是否结束”“这个挂载是否已显示全文”是不同状态。ThreadRevealPolicy 为 surface 派生 skip / pace / hold;usePlaybackBuffer 持有本挂载的游标,所以 pace 后完成时仍会逐步追平,而非突然跳到末尾。
function deriveRevealPolicy(input, execution): ThreadRevealPolicy {
if (!input.visible || input.isHydrating || input.reduceMotion) return 'skip'
if (execution === 'waiting-approval' || execution === 'waiting-input') return 'hold'
return execution === 'running' ? 'pace' : 'skip'
}主题也是同一拆分:ThemePreference 保存用户意图,ThemeName 是 system 当前解析出的 dark/light 色板;renderer 更新 CSS token,main 收到原始 system 值并同步 nativeTheme/title-bar overlay。
export function applyThemePreference(preference, prefersDark?) {
activeThemePreference = preference
const theme = resolveThemePreference(preference, prefersDark)
applyTheme(theme)
window.buffin.setTheme(preference)
return theme
}排查路标 · 展示与主题
| 症状 | 入口 |
|---|---|
| 完成消息跳字或停在半截 | thread-presentation.ts、use-playback-buffer.ts |
| 运行时长不对 | session-runtime-store.ts、SessionThread.tsx |
| system 主题原生标题栏不一致 | theme-preference.ts、theme-bridge.ts |
8测试与风险地图
| 有兜底 | 薄冰(事实) |
|---|---|
| 命令目录、参数、control RPC;reveal 策略;URI/裸路径/行列范围;cwd、符号链接越界、预览上限;system 主题和 tab action。 | 🟡 未见真实 Electron 进程注册 bridge 的端到端测试。 🟡 未见 runner 抛异常后 error receipt 内容的专门断言。 ⚪ session cwd cache 是否可跨 cwd 变更复用,当前材料无法证明,uncertain。 |
9覆盖声明
本报告覆盖 origin/main...main 的 99 个文件和 7 个提交。session renderer、desktop 跨进程/主题、daemon 与依赖/文档分别全量精读;主线程二次读取并裁剪了报告中的命令、链接、文件桥接、展示、主题和 daemon 代码。locale、lockfile 和 token 配置已按外围变更归类,未略读。