feat: 新增第 8 个内置 tab「文件追踪」——移植 dsh-file-trace 插件

- 数据层:直接折叠会话事件日志(tool/call + tool/result 对)提取文件操作,
  与仓库「读事件日志而非动注册表」的取向一致;含错误文本捕获与
  running 态实时更新(ctx.on('session/event'),不可见时暂停、恢复时追平)
- 视图:操作列表 + 底部可拖拽 diff 面板;行级 diff(删红/增绿/改蓝)+
  行内字符级高亮;读取视图带真实行号;失败操作统一展示真实错误文本
- 语法着色:轻量分词器(关键字/字符串/数字/类型/函数/注释/预处理),
  色相经 color-mix 落在 diff 三色之间的空档;跨行块注释状态按行序推导
- 单实例 tab,+ 菜单 order 55,角标显示操作数;设置页可整体关闭
  (与独立维护的 dsh-file-trace 插件共存)
- i18n:10 个新 key 同步全部 21 个词典;builtins 测试 7→8;
  README/指南内置清单同步
This commit is contained in:
lhh010
2026-08-30 20:59:50 +08:00
parent 3aab7ca3a5
commit 02d3936b92
31 changed files with 1658 additions and 15 deletions
+18 -3
View File
@@ -1,6 +1,7 @@
/**
* The 7 built-in tab descriptors: the plugin registers its own pages
* (editor / git / subagent / sidechat / terminal / browser / diff) through
* The 8 built-in tab descriptors: the plugin registers its own pages
* (editor / git / subagent / sidechat / terminal / browser / diff /
* file-trace) through
* the same {@link BetterSidebarService} external plugins use — eating its
* own dogfood. The terminal descriptor owns its quota (`TERMINAL_LIMIT`)
* and mints `terminal:<uuid>` ids through `createTab`; the browser mints
@@ -21,7 +22,9 @@ import { SubagentView } from '../SubagentView.tsx'
import { consumeSidechatSeed, SideChatView, sidechatThreadIdOf } from '../SideChatView.tsx'
import { api } from '../api.ts'
import { BrowserView } from '../BrowserView.tsx'
import { IconTerminalOutline16, IconDiffOutline16, IconGlobeOutline16 } from '../icons.tsx'
import { IconTerminalOutline16, IconDiffOutline16, IconGlobeOutline16, IconHistoryOutline16 } from '../icons.tsx'
import { FileTraceTab } from '../filetrace/FileTraceTab.tsx'
import { extractFileOps } from '../filetrace/ops.ts'
import { TERMINAL_FONT_SIZE_MAX, TERMINAL_FONT_SIZE_MIN } from '../../prefs-shared.ts'
import type { ComponentType } from 'react'
import type { SessionScope } from '../api.ts'
@@ -335,6 +338,18 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
}),
component: (props) => <BrowserView {...props} />,
},
{
id: 'file-trace',
title: () => t('fileTrace'),
icon: (size: number) => <IconHistoryOutline16 size={size} />,
order: 55,
single: true,
badge: (ctx, scope) => {
const events = ctx.sessions.get(scope.sessionId)?.events
return events === undefined ? null : extractFileOps(events).length
},
component: (props) => <FileTraceTab {...props} />,
},
{
id: 'diff',
title: () => t('git'),