🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

时间统计 CNyaotian-Lunar/dsh-usage-tab

DeepSeek Harness
CNyaotian-Lunar18 天前有提交已提供中文文档扫描:未发现敏感能力

查看会话时长、活跃空闲与每轮耗时分布

30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/14 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add CNyaotian-Lunar/dsh-usage-tab
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
dsh 原生插件 · vision
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 18 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🟢 扫描:未发现敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。

扫描于 2026/10/31(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

README

由 DeepSeek 最新模型翻译生成
dsh-usage-tab 给 DSH Web 会话页签加一个 「时间统计」 tab(排在 dsh-context 的「上下文」右边),把本会话的时间面貌画出来:挂钟跨度、活跃/空闲、每轮甘特、每小时分布、最慢的几轮。 English: Adds a "Time Stats" tab to DSH Web session tabs (placed to the right of "Context" from dsh-context) that charts the time profile of the current session: wall-clock span, active/idle split, a per-turn Gantt chart, an hourly distribution, and the slowest turns. P1 状态:tab 界面 + 本会话真实数据。数据全部来自本会话已有的三条现成通道,不碰宿主日志、不扫盘、不读 session_projcache。跨会话 / 全历史是 P2。
展开完整文档(README 剩余 1 节)
dsh-usage-tab

给 DSH Web 会话页签加一个 「时间统计」 tab(排在 dsh-context 的「上下文」右边),把本会话的时间面貌画出来:挂钟跨度、活跃/空闲、每轮甘特、每小时分布、最慢的几轮。

English: Adds a "Time Stats" tab to DSH Web session tabs (placed to the right of "Context" from dsh-context) that charts the time profile of the current session: wall-clock span, active/idle split, a per-turn Gantt chart, an hourly distribution, and the slowest turns.

P1 状态:tab 界面 + 本会话真实数据。数据全部来自本会话已有的三条现成通道,不碰宿主日志、不扫盘、不读 session_projcache。跨会话 / 全历史是 P2。

English: P1 status: the tab UI plus real data for the current session. All the data comes from three channels the session already exposes; it does not touch host logs, scan the disk, or read session_projcache. Cross-session / full-history support is P2.

P1.1:二级菜单(概览 / 日历 / 会话 / 设置)+ 三项偏好(空闲阈值 / 时区 / 数据范围)+ P1 口径止血。

English: P1.1: a sub-navigation menu (Overview / Calendar / Sessions / Settings), three preferences (idle threshold / time zone / data range), and stop-the-bleeding fixes for the P1 metric semantics.

P2 第一步:宿主半边注册自己的会话投影单元 usageDaily(把会话事件时间戳按宿主本地时区折成逐日活跃毫秒 + 总活跃/轮数/段数/最长段),「日历」页读真数据 —— 走*会话列表行自带的投影值(row.projectionValues.usageDaily,零额外 I/O),跨会话求和成 今日/本周/本月 + 近 12 周热力图 + 近 14 天柱状 + 段统计。拿不到就保持空态(值一律 —)。

English: P2, step 1: the host half registers its own session projection unit, usageDaily (folding session event timestamps into per-day active milliseconds in the host's local time zone, plus total active time / turn count / segment count / longest segment). The Calendar page now reads real data — taken from the projection values carried by each session list row (row.projectionValues.usageDaily, zero extra I/O) — summed across sessions into today / this week / this month totals, a 12-week heatmap, a 14-day bar chart, and segment statistics. If none of it is available, the view stays empty (every value shows —).

P2 第二步:冷会话回填 —— 启动 5 秒后(BACKFILL_START_DELAY_MS),对「从没打开过、也没被写过投影缓存」的老会话逐条冷读日志、交给宿主 sessionProjectionCache.coldSnapshot 补上 usageDaily。三条照跳的口径:live 会话(它自己会折)/ 缓存里已有该键的会话 / header.cwd 缺失的记录(含子代理会话)。旧日志被源侧拒读(SessionFormatUnsupported)时只记一条 debug + 一条计数 info,绝不改写源文件;单个失败不中断后续;卸载时 abort。
复验工具:node tools/projcache-probe.mjs(只读,回填前后各跑一次对比)。
实测复验(2026-10-13,重启后):usageDaily 覆盖率 85.0% → 94.2%,「只有本插件缺」归零;剩余那批全为「两键同缺」(源侧拒读,按红线接受并记录)。
中文: P2,第 2 步:冷会话回填 —— 启动 5 秒后(BACKFILL_START_DELAY_MS),把那些从未打开过、也从未缓存过投影的旧会话,从各自的冷日志里逐条读回来,交给宿主的 sessionProjectionCache.coldSnapshot,补进 usageDaily。有三种情况按设计跳过:活跃会话(它们自己会折叠数据)/ 缓存里已经有这个键的会话 / 缺少 header.cwd 的记录(包括子代理会话)。当源侧拒绝读取旧日志时(SessionFormatUnsupported),插件只记一行 debug 加一行计数 info,绝不改写源文件;单条失败不会中断其余部分,卸载会触发中止。复查工具:node tools/projcache-probe.mjs(只读;回填前后各跑一次并对比)。真机复查(2026-10-13,重启后): usageDaily 覆盖率从 85.0% 升到 94.2%,「仅本插件缺失」降到 零;其余全是「两个键都缺失」(源侧拒绝读取的那一批,按硬性规则接受并记录)。

P2 第三步:「会话」页(照一份「一页多视图 + 选项卡」的 UI 规范)—— A 默认:按工作区分组的纵向表 + 行内比例条(回答「时间花在哪个项目上」);B:逐日活动带(14 列 × 一行一会话,强度相对全局最大单日);C:主从双栏(左栏清单、右栏选中会话的逐日条形 + 总量)。
数据全部来自会话列表行自带的 row.projectionValues.usageDaily(零请求、零额外 I/O)。
🩸 口径:时长按所选时间窗(近 7 / 30 天 / 全部)逐日相加;段数 / 轮数 / 最长段是宿主的全日志总量,不随窗口变 —— 两类在卡面上分开写、绝不合成一个数。拿不到任何一行 ⇒ 保持空态骨架(值一律 —)。
三个工具条控件各管一摊:视图(A / B / C)· 时间窗(近 7 / 30 天 / 全部,只管时长怎么统计)·显示范围(7 / 14 / 30 天内 / 不限,只管哪些会话还列出来 —— 按「最后活动日」算,默认 14 天)。
清单上限 SESSION_LIST_LIMIT = 40(B / C 视图截断;A 视图列全部会话、靠容器滚动 ——按全局 40 条截断会把排在 40 名之后的整个工作区抹掉),被截掉的数量写脚注;条长 / 档位 / 柱高的基准取范围内全部会话的最大值(筛选只改可见性、不改深浅的含义)。
五种「空」分开表述:显示范围挡光 / 工作区筛不中 / 窗口内无活跃 / 全零活跃 / 真的没有投影。
⚠️ 宿主半边(lib/index.mjs)与客户端半边(lib/client.js)的改动都必须重启桌面版 App 才生效 ——本机是 Electron 套壳,没有「刷新页面」这个动作。

中文: P2,第 3 步:会话页面(沿用 UI 规范文档里「一页多视图 + 标签条」的模式)——视图 A(默认):按工作区分组的纵向表格,行内带占比条(回答「时间都花在哪个项目上」);视图 B:每日活动条带(14 列,每行一个会话,深浅相对全局最忙的单日);视图 C:主从两栏布局(左侧列表,右侧显示所选会话的每日条与合计)。所有数据都来自会话列表行携带的 row.projectionValues.usageDaily(零请求,零额外 I/O)。🩸 语义:时长按天累加,范围是所选时间窗口(最近 7 / 30 天 / 全部);分段数 / 轮次数 / 最长分段是宿主整份日志的合计,不随时间窗口变化——这两类数字在卡片上分开写,绝不混成一个数。没有可用行时,骨架保持为空(每个值都是 —)。 工具栏三个控件各管一件事:视图(A / B / C)· 时间窗口(最近 7 / 30 天 / 全部,只控制时长怎么算)· 显示范围(7 / 14 / 30 天内 / 不限,只控制哪些会话还列出来,按「最后活跃日」计算,默认 14 天;太久没动的会话默认隐藏)。列表上限是 SESSION_LIST_LIMIT = 40(视图 B / C 会截断;视图 A 列出全部会话,靠容器滚动——全局截断到 40 会整片抹掉排名 40 之后的工作区),被截掉的数量写在脚注里。条长 / 深浅档位 / 条高的基准取自范围内全部会话的最大值(筛选只改变可见性,不改变深浅的含义)。五种「空」分开描述:被显示范围截掉 / 被工作区筛掉 / 窗口内没有活动 / 活动全为零 / 确实没有投影。⚠️ 宿主半边(lib/index.mjs)或客户端半边(lib/client.js)的改动,都要重启桌面应用才生效——这台机器跑的是 Electron 外壳,没有「刷新页面」这个动作。

一眼看懂 / At a Glance

| 卡片 / Card | 画什么 / What it draws | 数据来自 / Data source |
| 本会话概览Current session overview | 轮次 / 步数 / 挂钟跨度 / 活跃时长 / 空闲率 / 首条消息时间Turns / steps / wall-clock span / active time / idle ratio / first-message time | sessionStats 投影 + turnTimingssessionStats projection + turnTimings |
| 使用时间线(甘特)Usage timeline (Gantt) | 每轮一根条;空白就是空闲One bar per turn; gaps are idle time | legacy.turnTimings |
| 每小时分布Hourly distribution | 24 根柱 = 每个步骤开始时间落在哪个小时24 bars = which hour each step's start time falls in | timeline.turns[].steps[].start.time |
| 每轮用时排行Slowest turns | 最久的 5 轮The 5 longest turns | legacy.turnTimings |
| 日历(P2 第一步起)Calendar (from P2 step 1) | 今日/本周/本月累计 · 近 12 周热力图 · 近 14 天柱状 · 连续使用段(最长/平均/段数)Today / this week / this month totals · 12-week heatmap · 14-day bars · continuous-use segments (longest / average / count) | 宿主 usageDaily 投影(随会话列表行下发,零额外 I/O)—— 跨会话求和Host usageDaily projection (shipped with the session list rows, zero extra I/O) — summed across sessions |
| 会话(P2 第三步起)Sessions (from P2 step 3) | A 按工作区分组的纵向表(行内比例条)· B 逐日活动带 · C 主从双栏(逐日条形 + 总量)A: vertical table grouped by workspace (in-row proportion bars) · B: daily-activity strip · C: master-detail two columns (per-day bars + totals) | 同上(row.projectionValues.usageDaily,逐会话保留身份)Same as above (row.projectionValues.usageDaily, keeping each session's identity) |

口径(硬约定):

English: Semantics (hard rules):

- 空闲阈值 5 分钟:相邻事件间隔 > 5 分钟视为断开,算新的一段;恰好 5 分钟仍算同一段。
- English: Idle threshold: 5 minutes — a gap of more than 5 minutes between adjacent events is treated as a break and starts a new segment; exactly 5 minutes still counts as the same segment.
- 挂钟跨度 = 最早一轮开始 → 最晚一轮结束;活跃时长 = 各活跃段之和;空闲率 = 空闲 ÷ 挂钟。
- English: Wall-clock span = from the start of the earliest turn to the end of the latest turn; active time = the sum of all active segments; idle ratio = idle time ÷ wall-clock span.
- 时区:界面按本机本地时间(Date#getHours),即北京时间。
- English: Time zone: the UI uses this machine's local time (Date#getHours), i.e. Beijing time.
- 缺失一律显示 —,绝不把 NaN / undefined 印到界面上(自测里有一条专门扫这个)。
- English: Anything missing is always shown as —; NaN / undefined are never printed to the UI (a dedicated self-test case scans for this).
- ⚠️ turnTimings / timeline 只覆盖浏览器已加载的会话窗口(长会话分页时会缺早期轮次); sessionStats 是全日志累计。两者口径不同,卡面里分开表述、不混加。
- English: ⚠️ turnTimings / timeline only cover the session window already loaded by the browser (early turns are missing when a long session is paginated); sessionStats is the whole-log cumulative total. The two have different semantics, so they are presented separately on the card and never added together.

文件 / Files

dsh-usage-tab/
├─ package.json # main=lib/index.mjs;dsh.client.platform="web";dsh.bundle.patch ├─ cordis.patch.yml # 官方挂载点(insert 一行) ├─ lib/ │ ├─ index.mjs # 宿主半边:注册 usageDaily 会话投影单元(零依赖;自带 .parse 的 schema 适配器) │ └─ client.js # 浏览器半边:手写 ModuleLoader 外壳 + 纯 JS + h() ├─ test/ │ └─ self-test.mjs # 不依赖宿主的自测(101 例;含回填用例 + T23a~T23p 会话页用例 + 宿主投影与统计用例;同时直接 import 宿主半边) ├─ tools/ │ ├─ struct-check-subnav.mjs # 结构级自检(静态锚点 + 渲染 + 坏值注入 + 宿主投影段) │ ├─ struct-check-p1fix.mjs # 结构级自检(P1 修复批次那一组锚点,同样是静态检查) │ ├─ counterfactual-subnav.mjs # 反事实对照(%TEMP% 副本上拆修复点,双驱动:真实 self-test + 真实 struct-check) │ ├─ counterfactual-p1fix.mjs # 反事实对照(P1 修复批次那一组变体) │ ├─ projcache-probe.mjs # 只读探针:usageDaily 覆盖率(含 ver 分布,上界口径)/ 仍缺哪些会话 / 解析失败清单(回填前后对比用) │ └─ install.mjs / uninstall.mjs

零构建:lib/client.js 是手写产物,不是 esbuild 输出 ——本机 profile 环境里 esbuild 解析探测 5 条路径全部 MODULE_NOT_FOUND。
所以这里没有 build.mjs、没有 devDependencies、没有 TS。

English: Zero build: lib/client.js is a hand-written artifact, not esbuild output — in this machine's profile environment, esbuild resolution probed five paths and every one came back MODULE_NOT_FOUND. So there is no build.mjs, no devDependencies, and no TypeScript here.

自测 / Self-test

cd <你克隆下来的目录>
node test/self-test.mjs

自测不依赖宿主,但测的是真产物:读 lib/client.js 原文 → 用 new Function('window','require',code) 在宿主 realm 里执行 → 假 window.__ModuleLoader__ 截获工厂 → require('react') 用桩顶上(其它模块名一律抛错,顺带验证「不许 import 不存在的模块」)→ 纯函数从 module.exports.__test 取出逐条断言。

English: The self-test does not depend on the host, but it tests the real artifact: it reads the raw text of lib/client.js, runs it inside the host realm with new Function('window','require',code), catches the factory with a fake window.__ModuleLoader__, stubs out require('react') (every other module name throws, which also verifies that "importing a module that does not exist is forbidden"), then pulls the pure functions out of module.exports.__test and asserts on them one by one.

覆盖:外壳契约 / 字典完整性 / 活跃段切分 / 空闲率 / 每小时分桶(步数与活跃毫秒两种量纲)/ 时长格式化与排序 / 甘特与柱高归一化 / 渲染层绝不上屏 NaN 或 undefined / apply 的注册参数与三条降级路径 / CSS 零硬编码配色 / 宿主 usageDaily 折叠(逐日活跃 / 5 分钟阈值 / fork 切点清账 / Object.is 门禁 / stateSchema 适配器 / 日桶封顶 / apply 降级)+ 客户端日历真数据通路(日键口径 / 跨会话聚合 / 日历模型 / 有数据与空态)+ 宿主冷会话回填(T22a~T22f:inject 四个服务与缺失不抛(且早退留一行 debug)/ 跳过口径与 coldSnapshot 参数(「只有别的投影键」不算已服务)/ 旧格式走 debug 不 warn 且不中断 / dispose 的定时器清理与中途 abort / apply() 必须真的把回填装起来 / 重复触发只跑一趟)+ 客户端「会话」页(T23a~T23p:数据层与身份兜底 / A 分组与排序 / B 14 列档位 / C 主从与默认选中 / 渲染冒烟 / 偏好进模型 / B·C 按 40 截断、A 视图列全部(不抹组) / 五种空态分开表述 / 基准取全部会话(含「组里没有全局最大会话」的夹具) / 口径脚注 / 全日志总量口径 / 数值上界 / 形状兜底(空载体 ids:[] / byId:{}) / 选中态挤回列表 / 「显示范围」默认 14 天与超范围不进清单 / 边界日与无活动日 / 工作区筛选生效时控件必须在屏上)。
回填用例多为异步(回填跑在 setTimeout + await 里),经 testAsync 登记、在同步用例跑完后统一 await。
覆盖范围:shell 契约 / 字典完整性 / 活跃段切分 / 空闲占比 / 按小时分桶(步数与活跃毫秒都算)/ 时长格式化与排序 / 甘特图与柱高归一化 / 渲染层绝不把 NaN 或 undefined 放到屏幕上 / apply 的注册参数及其三条降级路径 / CSS 里零硬编码颜色 / 宿主端 usageDaily 折叠(按天活跃 / 5 分钟阈值 / 在分叉点清空账户 / Object.is 闸门 / stateSchema 适配器 / 每日分桶上限 / apply 降级)+ 客户端真实数据日历路径(day-key 语义 / 跨会话聚合 / 日历模型 / 有数据 vs 空状态)+ 宿主端冷会话回填(T22a-T22f:注入四个服务,缺失时不抛错(并提前退出,打一行 debug)/ 跳过语义与 coldSnapshot 参数(「只有其他投影键存在」不算已服务)/ 旧格式走 debug 而不是 warn,且不中止 / dispose 时清理定时器与中途 abort / apply() 必须真正装上回填 / 重复触发只跑一次)+ 客户端 Sessions 页面(T23a-T23p:数据层与身份兜底 / 视图 A 的分组与排序 / 视图 B 的 14 列着色 / 视图 C 的主从与默认选中 / 渲染冒烟测试 / 偏好设置喂给模型 / B 和 C 在 40 处截断,而视图 A 列出全部(且不会清空某个分组) / 五种空状态分别描述 / 基线取自全部会话(包括一个 fixture:全局最大的会话不在分组内) / 语义脚注 / 整份日志的合计语义 / 数值上界 / 形状兜底(空载体 ids:[] / byId:{}) / 选中项被挤回列表 / 「显示范围」默认 14 天,范围外的会话不进列表 / 边界日与无活跃日 / 工作区筛选器启用时,其控件必须留在屏幕上)。多数回填用例是异步的(回填跑在 setTimeout + await 里);它们通过 testAsync 注册,等同步用例跑完后一起 await。

另有两条结构 / 反事实脚本(期望:OK=91 BAD=0 与 OK=121 BAD=0):

node tools/struct-check-subnav.mjs # 结构自检:静态锚点 + 渲染 + 坏值注入(含 G15~G17 清洗闸 / inject)
node tools/counterfactual-subnav.mjs # 反事实:拆掉修复点 ⇒ 真实 self-test / struct-check 必须变红(双驱动)

安装 / Install

本机现状:已装(路 B,多个 profile —— 名字随机器而定) —— tools/install.mjs --apply 做的事: ① 备份 profile 的 cordis.patch.yml;② 把源码(排除内部文档与产物目录)铺到 ~\.dsh\profiles\<profile>\vendor\dsh-usage-tab-src\;③ 在 profile\node_modules\ 建 Junction 指过去; ④ 往 profile 的 cordis.patch.yml 插一行 - insert: { id: usage-tab, name: 'dsh-usage-tab' }。
本机当前状态:已安装(路线 B,有多个配置档 —— 名字因机器而异) —— tools/install.mjs --apply 做的事:(1) 备份配置档的 cordis.patch.yml;(2) 把源码(排除内部文档和产物目录)复制到 ~\.dsh\profiles\<profile>\vendor\dsh-usage-tab-src\;(3) 在 profile\node_modules\ 里建一个 junction 指向那里;(4) 把 - insert: { id: usage-tab, name: 'dsh-usage-tab' } 这一行插进配置档的 cordis.patch.yml。

⚠️ 2026-10-13 修正:旧版把 profile 硬编码成 web(而日常实际跑的是 desktop)⇒ 改了 lib/ 跑 --apply 只同步 web、改完不生效且极难查。现在默认自动枚举 ~\.dsh\profiles\、只处理「已装过本插件」的那些(--profile <名字> 可显式指定单个)。

node tools/install.mjs # 干跑,看清要动什么
node tools/install.mjs --apply # 真装
node tools/uninstall.mjs --apply --backup "<安装时的备份目录>"

- 🩸 改了 lib/ 就要重新 --apply(vendor 是副本,不是软链)。
- 🩸 改了 lib/index.mjs 必须重启桌面版 App(bundle 列表在启动时读、模块已进 require cache); lib/client.js 同样随重启加载(本机是 Electron 套壳,没有"刷新页面"这个动作)。
- 🔍 回填复验:重启后跑 node tools/projcache-probe.mjs --keys usageDaily,contextActivity,看那批老会话的「缺」是否归零;宿主日志对应一行是 dsh-usage-tab: 冷会话回填完成 N 个会话的 usageDaily 投影。
- 🩸 绝不跑 pnpm install(会重新解包 file:vendor/.tgz、覆盖本机 vendor 的 0.1.7 补丁)。

官方路(需重启,未采用):dependencies 加 "dsh-usage-tab": "link:<路径>" + dsh.profile.bundles 加 "dsh-usage-tab"。cordis.patch.yml 里的 insert 行两种方式共用。
官方路线(需要重启,未采用):往 dependencies 里加 "dsh-usage-tab": "link:<path>",再往 dsh.profile.bundles 里加 "dsh-usage-tab"。cordis.patch.yml 里插入的那一行,两种做法共用。

权限与依赖 / Permissions & Dependencies

依赖:运行时零依赖 —— 不引入任何第三方包(宿主提供的 @deepseek-ai/ 走宿主自身)。
网络:运行时与仓库内工具均不发起网络请求(全源码内无 fetch / XMLHttpRequest / http / net 调用)。
文件:宿主半读插件偏好与会话投影缓存;冷会话回填时只读会话日志来补投影,从不改写会话文件。
⚠️ 仓库里的开发工具(tools/)会写文件:反事实脚本在系统临时目录建副本,并把日志写到 out/cf-logs/(out/ 已在 .gitignore 内)。
命令 / 凭据:运行时不执行外部命令、不读取任何凭据。
⚠️ 仓库里的开发工具会用 spawnSync 起子进程(跑自测 / 结构检查)—— 那只在开发时用,不在插件运行时路径上。
会话数据:只用本会话的事件时间戳做统计,全部本机计算、不外传。

⚠️ 以上是源码面的保守自述:「未发现」不等于「绝对不访问」 —— 请以实际源码为准。
本插件非官方插件,与 DeepSeek 官方无隶属关系。

许可 / License

MIT
预览分享卡片长什么样 ↓CNyaotian-Lunar/dsh-usage-tab
分享 · DeepSeek Harness Hub
CNyaotian-Lunar/dsh-usage-tab

查看会话时长、活跃空闲与每轮耗时分布

回到顶部