dsh-file-activity 0.5.7 → 0.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -161
- package/README.md +23 -67
- package/lib/api-route.js +72 -76
- package/lib/bash-ops.js +65 -54
- package/lib/bash-parse.js +154 -162
- package/lib/client.js +767 -362
- package/lib/client.src.js +28 -21
- package/lib/cwd.js +6 -6
- package/lib/index.js +76 -83
- package/lib/media-route.js +139 -90
- package/lib/observer.js +30 -24
- package/lib/parts/api.part.js +1 -5
- package/lib/parts/apply.part.js +91 -82
- package/lib/parts/auto-open.part.js +83 -60
- package/lib/parts/document-previews.part.js +62 -0
- package/lib/parts/format.part.js +1 -2
- package/lib/parts/i18n.part.js +15 -1
- package/lib/parts/interceptor.part.js +16 -27
- package/lib/parts/preview-data.part.js +111 -0
- package/lib/parts/preview-float.part.js +276 -0
- package/lib/parts/rows.part.js +1 -11
- package/lib/parts/settings.part.js +71 -0
- package/lib/parts/store.part.js +19 -0
- package/lib/parts/styles.part.js +49 -3
- package/lib/parts/tree.part.js +1 -2
- package/lib/parts/view.part.js +73 -19
- package/lib/persist.js +137 -0
- package/lib/quota-trim.js +121 -0
- package/lib/quota.js +164 -0
- package/lib/state.js +333 -136
- package/lib/store.js +262 -76
- package/package.json +11 -7
- package/lib/parts/preview.part.js +0 -308
package/CHANGELOG.md
CHANGED
|
@@ -2,164 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录 dsh-file-activity 的所有版本变更。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
-
## [0.5.
|
|
6
|
-
|
|
7
|
-
### 变更
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
- chore(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
- fix
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
### 变更
|
|
24
|
-
|
|
25
|
-
- fix(scripts): #72 插件依赖未随安装自动安装(dsh-shared 未发布 npm) (#96)
|
|
26
|
-
- fix(file-activity): 浮窗预览失焦自动关闭(issue #76) (#95)
|
|
27
|
-
- fix(file-activity): #72 补充 dsh-better-sidebar 级联安装——dependencies 声明 + 缺失提示
|
|
28
|
-
- style: prettier 全量格式修复(CI 格式门禁)
|
|
29
|
-
|
|
30
|
-
## [Unreleased]
|
|
31
|
-
|
|
32
|
-
### 变更
|
|
33
|
-
|
|
34
|
-
- fix(ui): 浮窗预览失焦自动关闭——点击外部任意处/切换页签即关,预览失败 2.5s 自动关闭或点击任意处关闭,头部「点击外部关闭」提示(issue #76)
|
|
35
|
-
|
|
36
|
-
## [0.5.4] - 2026-09-01
|
|
37
|
-
|
|
38
|
-
### 变更
|
|
39
|
-
|
|
40
|
-
- fix(file-activity): 浮窗预览优雅降级与工作区外文本读取(issue #68)
|
|
41
|
-
|
|
42
|
-
## [0.5.3] - 2026-09-01
|
|
43
|
-
|
|
44
|
-
### 变更
|
|
45
|
-
|
|
46
|
-
- fix(ui): 9 个插件未定义 token danger-primary 改用 error-primary(DSH 主题仅定义 business/error/success/warn)
|
|
47
|
-
|
|
48
|
-
## [0.5.2] - 2026-08-31
|
|
49
|
-
|
|
50
|
-
### 变更
|
|
51
|
-
|
|
52
|
-
- fix(ui): dsh-file-activity 移除页签选中态品牌蓝覆盖,回归宿主默认样式(issue #60)
|
|
53
|
-
- feat(ui): dsh-mermaid-render 卡片翻新——图标/前缀/状态(issue #54)
|
|
54
|
-
- feat(ui): dsh-my-guard 安全护栏面板翻新——图标/前缀/状态/交互(issue #54)
|
|
55
|
-
- feat(ui): 共享图标系统 + UI 规范文档(issue #54 阶段0)
|
|
56
|
-
- refactor(shared): 抽取 dsh-shared 共享工具包,10 个插件迁移消除重复实现(issue #45)
|
|
57
|
-
- chore(deps): 升级 react 19 兼容性——13 个插件 peer 声明 ^18.2.0 || ^19.2.0(issue #49)
|
|
58
|
-
- style(format): 全仓 prettier 格式化(issue #44)
|
|
59
|
-
|
|
60
|
-
## [0.5.1] - 2026-08-27
|
|
61
|
-
|
|
62
|
-
### 变更
|
|
63
|
-
|
|
64
|
-
- feat(file-activity): 侧边栏页签选中态改用品牌蓝,三态一眼可分(issue #25)
|
|
65
|
-
- feat(file-activity): 文件活动列表按文件类型显示专属彩色图标(issue #24)
|
|
66
|
-
|
|
67
|
-
## [0.5.0] - 2026-08-26
|
|
68
|
-
|
|
69
|
-
### 变更
|
|
70
|
-
|
|
71
|
-
- feat(file-activity): bash 命令文件操作识别(rm/touch/mv/cp/tee/重定向)+ 0 值计数徽标过滤
|
|
72
|
-
- docs+test: 全面审查修复——文档同步补全 + mermaid 测试增强
|
|
73
|
-
|
|
74
|
-
## [0.4.7] - 2026-08-25
|
|
75
|
-
|
|
76
|
-
### 变更
|
|
77
|
-
|
|
78
|
-
- **npm 页面元数据优化**:description 改为中英双语(中文在前);README 效果截图引用改为绝对 URL(unpkg),npm 包页面可直接显示图片。
|
|
79
|
-
|
|
80
|
-
## [0.4.6] - 2026-08-25
|
|
81
|
-
|
|
82
|
-
### 变更
|
|
83
|
-
|
|
84
|
-
- **Server 端按 P2 模块拆分**:`lib/index.js`(434 行)按职责拆分为 fence/state/store/observer/api-route/media-route 子模块(入口 50 行),覆盖率/变异统计范围同步扩展至全部 server 文件(Stryker 变异 90.91% ≥ 70%)。
|
|
85
|
-
- **Client 端方案 B 拆分**:`client.src.js` 模板 + 13 个片段(`lib/parts/`)经 `scripts/build.mjs` 拼接生成 `client.js`;eslint 尺寸规则覆盖 src/parts 源码、构建产物排除。
|
|
86
|
-
- **client-render 测试零依赖化**:不再硬编码本机 react 绝对路径,改为自写 createElement stub(CI 跨平台可跑)。
|
|
87
|
-
- 行为不变(纯重构)。
|
|
88
|
-
|
|
89
|
-
## [0.4.5] - 2026-08-24
|
|
90
|
-
|
|
91
|
-
### 修复
|
|
92
|
-
|
|
93
|
-
- **状态文件 `sessions` 为 `null` 时崩溃**:`loadState` 只检查 `typeof sessions !== 'object'`,而 `typeof null === 'object'`——手改/损坏的状态文件若把 `sessions` 写成 `null`,加载后 `Object.values(null)` 抛 TypeError(unhandled rejection)。现在显式把 `null` 一并重置为空对象,降级为全新状态不崩溃(复现测试 `host-mutation.mjs` 已永久保留)。
|
|
94
|
-
|
|
95
|
-
### 变更
|
|
96
|
-
|
|
97
|
-
- **质量门禁测试体系接入**:测试框架迁移至 Vitest(node:test → vitest,API 兼容);覆盖率门禁 `lines≥85 / branches≥75 / functions≥80`(当前实测 99.1/91.8/97.5);新增边界测试 `host-edge.mjs`(12 用例)与变异定向测试 `host-mutation.mjs`(49 用例);Stryker 变异测试达标(71.76% ≥ 70%);Gherkin 验收测试 11 场景/56 步骤(`test/features/`)随 `npm test` 运行。
|
|
98
|
-
|
|
99
|
-
## [0.4.4] - 2026-08-24
|
|
100
|
-
|
|
101
|
-
### 修复
|
|
102
|
-
|
|
103
|
-
- **工作区外文件的图片/PDF 预览不再破图**:浮动预览的媒体字节此前走侧边栏的 `/sidebar/file` 路由,而该路由只允许会话工作区(cwd)内的文件——agent 实际触碰过的 `/tmp` 临时文件等工作区外路径点击预览时一律 403,图片显示为破图、PDF 加载失败。现在媒体字节改由插件自身的 `/file-activity/file` 路由提供(**只放行该会话记录过的路径**:未记录 403、文件不存在 404、超 64MB 拒绝;fence 信任校验与侧边栏一致),图片在浮窗内正常渲染;PDF 因侧边栏内置查看器写死媒体路由,浮窗改用浏览器原生 PDF 帧 + 下载兜底。
|
|
104
|
-
- Markdown / 代码 / HTML 文本预览不受影响(侧边栏 `fs.read` 本身不限制工作区,`client-render.mjs` + 真实浏览器实测回归通过)。
|
|
105
|
-
|
|
106
|
-
## [0.4.3] - 2026-08-23
|
|
107
|
-
|
|
108
|
-
### 变更
|
|
109
|
-
|
|
110
|
-
- **README 补充现场效果图**:顶部新增真实现场效果图(侧边栏「文件活动」页签 最近访问/文件统计树 + 点击文件的浮窗预览),便于其他用户下载前了解功能。
|
|
111
|
-
- **发布包包含效果图**:`package.json` 的 `files` 加入 `assets/`,使 GitHub Release 的 tarball 一并包含效果图资源。
|
|
112
|
-
|
|
113
|
-
## [0.4.2] - 2026-08-23
|
|
114
|
-
|
|
115
|
-
### 修复
|
|
116
|
-
|
|
117
|
-
- **修复页签偶发"纯文字无样式"**:样式注入曾放在 `betterSidebar` 服务判空早退之后,HMR 重建/服务重载瞬间新实例可能跳过注入,导致已渲染的页签失去全部 CSS(纯白文本列表)。样式注入改为 `apply` 无条件最先执行(纯静态 CSS,不依赖任何服务),每个 fiber 持有自己的 `<style>` 元素,重建后至少保留一份。
|
|
118
|
-
|
|
119
|
-
## [0.4.1] - 2026-08-23
|
|
120
|
-
|
|
121
|
-
### 修复
|
|
122
|
-
|
|
123
|
-
- **会话切换不再残留上个会话的文件活动**:前端数据 store 改为按 `sessionId` 分桶(`bySession`),每个会话只渲染自己的数据桶——新建/切换会话瞬间立即显示该会话自己的记录(新会话直接为空),不再在 fetch 完成前显示上一个会话的残留数据;fetch 失败时也不再保留旧数据。切换会话时同时关闭上一个会话遗留的浮窗预览。
|
|
124
|
-
|
|
125
|
-
## [0.4.0] - 2026-08-22
|
|
126
|
-
|
|
127
|
-
### 变更
|
|
128
|
-
|
|
129
|
-
- **预览改为浮窗预览(复用侧边栏内置渲染)**:点击任意文件(最近访问 / 文件统计)弹出**悬浮预览窗**,窗口内容通过 `ctx.betterSidebar.matchFileViewer(path)` 复用侧边栏**内置的 file viewer** 渲染(代码语法高亮 / Markdown 富文本 / 图片 / PDF / HTML),并按 viewer 的 `fetchStrategy` 取内容(`fsRead` / `mediaUrl` / `custom`);不再打开侧边栏编辑器标签,也不自建预览组件。浮窗为轻量交互:**点击浮窗外任意处 / `Esc` 键关闭**,标题栏右上保留一个 `×` 关闭按钮;主体为可滚动区域,长文件或大图在窗内滚动查看。
|
|
130
|
-
- **UI 全面改版**(遵循 better-sidebar 设计语言):全部改用 DSH 语义 token(`--dsw-alias-*` 颜色、`--dsw-font-*` 字形、`--ds-*` 动效),flat 无阴影、hairline 细边、8px 圆角行 + hover 底色;样式表随 activation 注入、fiber teardown 卸载(HMR/禁用无残留)。
|
|
131
|
-
- **间距收紧 + 去冗余标题**:整体走 better-sidebar explorer 的紧凑规范(`2px 6px 8px` 容器、30px 行、box-sizing 缩进),移除内容区顶部的「文件活动」标题(tab 条已命名本页),刷新/清空改为右上角细颗粒图标按钮,内容紧贴 tab 条(更沉浸)。
|
|
132
|
-
- **文件夹 / 文件图标可区分**:目录行用**品牌强调色**的文件夹图标 + 折叠箭头 + **加粗**名字,文件行用**中性淡色**的文件图标 + 常规名字,彩色文件夹 vs 素色文档,一眼可辨。
|
|
133
|
-
- **最近访问只显示文件名**:不再展示完整绝对路径,只显示最后的文件名(悬停仍可见完整路径),减少视觉噪音。
|
|
134
|
-
- 操作类型用状态色徽标(新增 = success / 修改 = warn / 读取 = 品牌强调色),文件统计目录树与文件行改为彩色计数胶囊;文件/文件夹行加入图标与折叠箭头。
|
|
135
|
-
- 时间显示从 `HH:MM:SS` 改为**相对时间**(刚刚 / N 分钟前 / N 小时前 / N 天前 / MM/DD),悬停仍可查看完整时钟时间。
|
|
136
|
-
|
|
137
|
-
## [0.3.0] - 2026-08-22
|
|
138
|
-
|
|
139
|
-
### 变更
|
|
140
|
-
|
|
141
|
-
- 最近访问列表改为 **LRU**:同一文件只保留一条(重复访问移到列表最前、不重复显示),每会话最多保留 **5 条**(原 10 条);历史数据在加载时自动去重。
|
|
142
|
-
- 时间显示改为 **`HH:MM:SS` 时钟格式**(最近访问列表、文件统计行内时间、悬停提示的创建/最近访问时间),不再显示相对时间("刚刚 / x 分钟前")。
|
|
143
|
-
- **最近访问区块可折叠**:点击区块标题展开/收起列表。
|
|
144
|
-
- **文件统计目录树可收纳**:每个文件夹行可点击展开/收起(箭头指示状态),收起后隐藏其子目录与文件。
|
|
145
|
-
|
|
146
|
-
## [0.2.0] - 2026-08-22
|
|
147
|
-
|
|
148
|
-
### 变更
|
|
149
|
-
|
|
150
|
-
- 最近访问列表最多保留 **10 条**(原 300 条),避免列表过长。
|
|
151
|
-
- 文件统计新增时间信息:每个文件行内显示**最近访问时间**(相对时间),悬停可查看**创建时间**(首次接触时间)。
|
|
152
|
-
- 文件统计改为**树形目录**展示:按文件**绝对路径**组织成完整目录树(逐层嵌套,目录节点显示子树汇总计数),连续单子目录的路径链自动**压缩为点号标签**(`a/b/c` → `a.b.c`),不再使用扁平的「根目录」分组与点号拼接标签。
|
|
153
|
-
- 点击文件改为打开**浮窗预览**(不再在侧边栏 tab 栏打开):图片走媒体路由渲染、PDF 内嵌 iframe、文本/代码经 `fs.read` 滚动展示;浮窗内提供「在侧边栏打开」与「关闭」按钮。
|
|
154
|
-
- 浮窗样式改用 DSH 主题变量(`--dsw-alias-*`),深浅主题下均正确显示;文本加载失败时展示具体错误原因(如文件不存在),并支持相对路径按会话 cwd 解析。
|
|
155
|
-
- 浮窗**自动关闭**:鼠标移出浮窗约 0.5 秒后自动消失(移回则取消),无需手动点击关闭。
|
|
156
|
-
|
|
157
|
-
## [0.1.0] - 2026-08-22
|
|
158
|
-
|
|
159
|
-
### 新增
|
|
160
|
-
|
|
161
|
-
- 首个版本:基于 dsh-better-sidebar 的「文件活动」侧边栏页签。
|
|
162
|
-
- **最近访问**:按时间倒序记录文件读取 / 新增 / 修改事件,点击文件用侧边栏原生预览打开。
|
|
163
|
-
- **文件统计**:按文件统计读取 / 新增 / 修改次数,按文件夹平铺展示(多层文件夹以 `.` 拼接)。
|
|
164
|
-
- **默认启用 + 自动打开**:页签默认开启,会话首次打开自动展开(可在设置中关闭)。
|
|
165
|
-
- 状态按会话持久化到 `$DSH_HOME/file-activity.json`(防抖 + 原子写入)。
|
|
5
|
+
## [0.5.10] - 2026-09-16
|
|
6
|
+
|
|
7
|
+
### 变更
|
|
8
|
+
|
|
9
|
+
- test: 存量固定 sleep 收敛(whenReady / drained / 渲染态可观测)
|
|
10
|
+
- docs(清理): 文档瘦身 23765 → 8309 行并固化精简规范
|
|
11
|
+
- chore(artifacts): 同步 8 处共享件产物漂移 + 新增产物一致性门禁(fail-closed)
|
|
12
|
+
- chore(cleanup): 清理 unused-local-variable/useless-expression(含共享件假阳性判定)
|
|
13
|
+
- fix(security): 修复代码扫描 error/warning 告警(5 类规则)
|
|
14
|
+
- fix(test): cucumber-js --import glob 改用双引号,兼容 Windows
|
|
15
|
+
- fix(security): 批量修复 'Insecure temporary file' 安全漏洞
|
|
16
|
+
- fix: 移除未使用的导入,修复告警
|
|
17
|
+
- fix: 修复日志注入,修复告警
|
|
18
|
+
- fix: 移除未使用的stat导入,修复文件系统竞争条件
|
|
19
|
+
- fix: 修复 GitHub Code Scanning 安全漏洞 (43 个)
|
|
20
|
+
- fix(security): fix file system race conditions and missing regex anchor
|
|
21
|
+
- fix(security): use tmp library for secure temporary file creation
|
package/README.md
CHANGED
|
@@ -1,102 +1,58 @@
|
|
|
1
1
|
# dsh-file-activity
|
|
2
2
|
|
|
3
|
-
[](https://github.com/topics/dsh)
|
|
4
4
|
|
|
5
5
|
<div align="center">
|
|
6
6
|
<img alt="文件活动插件截图(最近访问 / 文件统计)" src="https://unpkg.com/dsh-file-activity/assets/screenshot.png" width="340" />
|
|
7
7
|
<br />
|
|
8
8
|
<img alt="浮窗预览:点击文件复用侧边栏内置 Markdown 渲染" src="https://unpkg.com/dsh-file-activity/assets/preview-float.png" width="340" />
|
|
9
9
|
<br />
|
|
10
|
-
<img alt="浮窗 HTML
|
|
10
|
+
<img alt="浮窗 HTML 预览:内容撑满整个浮窗主体" src="https://unpkg.com/dsh-file-activity/assets/preview-float-0.5.6.png" width="340" />
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
|
-
**
|
|
13
|
+
**dsh-file-activity**:DSH 侧边栏文件活动插件(宿主原生侧边栏扩展点,零第三方侧边栏依赖)——在右侧边栏新增「文件活动」页签,记录 agent 工具与插件自身路由的文件读取/新增/修改事件,提供**最近访问**(LRU)与**文件统计**(树形目录)两块视图,点击文件弹出浮窗预览。
|
|
14
14
|
|
|
15
15
|
## 功能
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
bsfeng.IdeaProjects.my-dsh-plugins ← 连续单子目录压缩为一行
|
|
25
|
-
README.md
|
|
26
|
-
lib/
|
|
27
|
-
index.js
|
|
28
|
-
client.js
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
3. **默认启用**:页签注册后默认开启(无需在设置页勾选),且每个会话首次打开时**自动打开**本页(可在侧边栏设置中关闭自动打开)。
|
|
32
|
-
|
|
33
|
-
## 工作原理
|
|
34
|
-
|
|
35
|
-
- **Server 端**(`lib/index.js`):监听 DSH `fs/observed` 事件捕获 agent 的 `read` / `write` / `edit` / `str_replace_editor` / `read_image` 等文件操作;提供 `/file-activity/api` 路由(`stats` / `record` / `clear`);状态按会话持久化到 `$DSH_HOME/file-activity.json`(防抖 + 原子写入)。
|
|
36
|
-
- `write` 通过每会话的已知文件表自动区分**新增**(首次接触)与**修改**(再次写入)。
|
|
37
|
-
- **Client 端**(`lib/client.js`):通过 `ctx.betterSidebar.registerTab` 注册页签;fetch 拦截捕获侧边栏自身操作(`/sidebar/api/fs.read`、`/sidebar/api/fs.write`、`/sidebar/file` 媒体预览)并上报 server;数据按会话(session)**分桶隔离**(`bySession`),每个会话只渲染自己的记录——新建/切换会话立即显示该会话的数据,**不会残留上一个会话的记录**;点击文件通过 `ctx.betterSidebar.matchFileViewer(path)` 匹配内置 viewer 并挂载其组件,打开浮窗预览。
|
|
17
|
+
- **最近访问**:按 LRU 记录文件读取/新增/修改事件(agent 工具读写 + 侧边栏打开/编辑保存),每会话最多 5 条、同一文件只出现一次(再次访问移到最前);区块标题可折叠,点击文件弹出浮窗预览。
|
|
18
|
+
- **文件统计**:按文件**绝对路径**组织成树形目录,每个文件显示「读取/新增/修改」次数与相对时间(悬停看完整时钟时间);连续单子目录压缩为点号标签(`a/b/c` → `a.b.c`),文件夹行可展开/收起。
|
|
19
|
+
- **默认启用**:页签注册后默认开启,且每个会话首次打开时自动打开本页(开关见「配置」)。
|
|
20
|
+
- **浮窗预览**:按后缀渲染——代码(宿主 CodeBlock 组件,取不到时退回纯文本 `<pre>`)/ Markdown(宿主官方渲染器)/ 图片 / PDF(浏览器原生查看器)/ 沙箱 HTML;点击窗外、按 `Esc` 或点 `×` 关闭,长内容窗内滚动。
|
|
21
|
+
- **工作区外文件也能预览**:字节(图片 / PDF / HTML)与文本(Markdown / 代码)都先走插件自身媒体路由,按 `(sessionId, path)` 授权(只放行本会话记录过的路径,未记录一律 403);插件拒绝该路径时才回退宿主 `fs.read`。
|
|
22
|
+
- **会话隔离**:按会话分桶存储与展示,新建/切换会话立即显示该会话数据,DSH 重启后历史自动恢复。
|
|
23
|
+
- **bash 命令文件触碰**:解析 bash 工具调用里可静态确定的写 / 删操作(rm / touch / tee / mv / cp / install / sed -i)并计入;解析不确定的(`-t` 重定位、未知命令)不记录,git 等未映射命令不跟踪。
|
|
38
24
|
|
|
39
25
|
## 安装
|
|
40
26
|
|
|
41
|
-
> 💡 **npm 安装(普通用户推荐)**:`dsh plugin --profile web add dsh-file-activity --trust-lockfile
|
|
27
|
+
> 💡 **npm 安装(普通用户推荐)**:`dsh plugin --profile web add dsh-file-activity --trust-lockfile`——无需克隆本仓库;依赖自动级联安装(`dsh-shared` 是唯一 dependencies,侧边栏能力全部来自宿主原生扩展点)。
|
|
42
28
|
|
|
43
29
|
```sh
|
|
44
30
|
# 方式一:dsh plugin(推荐)
|
|
45
|
-
# 1) 克隆本仓库(任意目录)
|
|
46
31
|
git clone https://github.com/baosfeng/my-dsh-plugins.git
|
|
47
|
-
# 2) 以本地 link 方式安装(将 <仓库路径> 替换为上面的克隆目录)
|
|
48
32
|
dsh plugin --profile web add link:<仓库路径>/plugins/dsh-file-activity
|
|
49
33
|
|
|
50
|
-
# 方式二:手动
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
# 2) 在 ~/.dsh/profiles/web 下执行 pnpm install
|
|
54
|
-
# 3) 在 ~/.dsh/profiles/web/cordis.patch.yml 增加:
|
|
55
|
-
# - insert:
|
|
56
|
-
# - id: file-activity
|
|
57
|
-
# name: 'dsh-file-activity'
|
|
34
|
+
# 方式二:手动 —— 在 profile package.json dependencies 加
|
|
35
|
+
# "dsh-file-activity": "link:<仓库路径>/plugins/dsh-file-activity"
|
|
36
|
+
# 再 pnpm install,并在 cordis.patch.yml 加 id: file-activity / name: 'dsh-file-activity'
|
|
58
37
|
```
|
|
59
38
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
> 编辑 profile 的 `cordis.patch.yml` 会在运行中通过 Cordis HMR 热挂载 server 端,无需重启 `dsh web`;client 端在页面刷新后生效。
|
|
39
|
+
装完**硬刷新浏览器**(Cmd/Ctrl+Shift+R)。编辑 profile 的 `cordis.patch.yml` 会经 Cordis HMR 热挂载 server 端,无需重启 `dsh web`。
|
|
63
40
|
|
|
64
41
|
## 使用
|
|
65
42
|
|
|
66
|
-
-
|
|
67
|
-
- 点击任意文件行 →
|
|
43
|
+
- 入口:侧边栏「新标签页」(guide)菜单 → 文件活动,或会话开始自动打开。
|
|
44
|
+
- 点击任意文件行 → 浮窗预览,不打开侧边栏编辑器标签。
|
|
68
45
|
- 顶部「刷新」手动重载,「清空」删除当前会话全部记录。
|
|
69
|
-
|
|
70
|
-
### 浮窗预览(复用内置渲染)
|
|
71
|
-
|
|
72
|
-
点击文件(最近访问 / 文件统计中的任意文件行)会打开一个**悬浮预览窗**,窗口内容由侧边栏**内置的文件查看器**(file viewer)渲染 —— 通过 `ctx.betterSidebar.matchFileViewer(path)` 拿到匹配的 viewer,取其 `component` 挂载,并按 viewer 的 `fetchStrategy` 取内容(`fsRead` 文本 / `mediaUrl` 媒体 / `custom` 自定义)。浮窗为轻量交互:**点击浮窗外任意处、按 `Esc` 或点标题栏右上 `×` 都能关闭**;浮窗主体是可滚动区域,长文件或大图在窗内滚动查看。
|
|
73
|
-
|
|
74
|
-
- **代码**(后台为侧边栏内置 CodeMirror 编辑器):语法高亮 + 行号;支持 预览/编辑 切换与保存。
|
|
75
|
-
- **Markdown**(`.md` / `.markdown`):渲染后的富文本预览,可切换 预览/编辑 模式。
|
|
76
|
-
- **图片**(png / jpg / jpeg / gif / webp / svg / bmp / ico / avif):通过媒体路由渲染。
|
|
77
|
-
- **PDF**:浏览器原生 PDF 查看器(浮窗内嵌,工具栏提供下载)。
|
|
78
|
-
- **HTML**:沙箱化渲染预览,内容**撑满整个浮窗主体**(iframe 随浮窗尺寸自适应、内部滚动,与代码/Markdown/图片预览体验一致,issue #111)。
|
|
79
|
-
- 其余文件按内置 viewer 的 `matchFileViewer` 回退匹配(如 `binary-download` 下载未知二进制)。
|
|
80
|
-
|
|
81
|
-
> 💡 **工作区外文件也能预览**:文件活动记录的是 agent 实际触碰过的文件,路径可能在会话工作区之外(如 `/tmp` 临时文件)。侧边栏自带的媒体路由只允许工作区内文件,因此图片/PDF 的字节由插件自身的 `/file-activity/file` 路由提供——它按会话记录授权(只放行本会话记录过的路径,未记录的路径一律 403),fence 与侧边栏一致。Markdown/代码/HTML 文本则仍走侧边栏 `fs.read`(不被工作区限制)。
|
|
46
|
+
- 资源上限:每会话路径 ≤ 300 / 全局 ≤ 20000 / 会话数 ≤ 64,超限按 LRU 淘汰最久未活动条目(非静默丢弃,写 warn 日志);审计与写盘按增量追加 + 原子 compact,磁盘用量有界。
|
|
82
47
|
|
|
83
48
|
## 配置
|
|
84
49
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
## 数据说明
|
|
88
|
-
|
|
89
|
-
- 按会话(session)隔离存储(服务端 `$DSH_HOME/file-activity.json` 按 sessionId 分桶 + 前端 `bySession` 分桶展示);**DSH 重启后历史数据自动恢复**(持久化加载);新建/切换会话不残留其他会话的记录。
|
|
90
|
-
- 最近访问列表每会话最多保留 5 条(LRU:同一文件仅一条,重复访问移到最前),文件统计不受条数限制。
|
|
91
|
-
- agent 的 `bash`/`git` 等命令内部的文件触碰不在此跟踪范围(只跟踪 DSH 原生文件工具与侧边栏操作)。
|
|
92
|
-
|
|
93
|
-
## 依赖
|
|
50
|
+
无 profile 配置项(不读 `cordis.patch.yml` 的 `config`);唯一开关是设置页 UI 偏好:
|
|
94
51
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
98
|
-
| `cordis` | 插件运行时 | 是(宿主提供) |
|
|
52
|
+
| 配置项 | 默认 | 作用 |
|
|
53
|
+
| ------------------------------------------------ | ---- | -------------------------------------------------------------------------- |
|
|
54
|
+
| 会话开始时自动打开(**设置 → 插件 → 文件活动**) | 开 | 每个会话首次打开时自动显示本页;关闭后仍可从侧边栏「新标签页」菜单手动打开 |
|
|
99
55
|
|
|
100
56
|
## 相关文档
|
|
101
57
|
|
|
102
|
-
→ [文件活动追踪模块文档](../../docs/文件活动追踪/概述.md) · [
|
|
58
|
+
→ [文件活动追踪模块文档](../../docs/文件活动追踪/概述.md) · [CHANGELOG](CHANGELOG.md)
|
package/lib/api-route.js
CHANGED
|
@@ -3,93 +3,89 @@
|
|
|
3
3
|
* Every request passes the trust fence first; responses are JSON with
|
|
4
4
|
* cache-control: no-cache.
|
|
5
5
|
*/
|
|
6
|
-
import { readJsonBody, writeError, writeJson } from 'dsh-shared'
|
|
7
|
-
import { sessionCwdOf } from './cwd.js'
|
|
8
|
-
import { mapOp } from './state.js'
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
6
|
+
import { readJsonBody, writeError, writeJson } from 'dsh-shared';
|
|
7
|
+
import { sessionCwdOf } from './cwd.js';
|
|
8
|
+
import { mapOp } from './state.js';
|
|
9
|
+
export function createApiHandler({ ctx, store, fence, }) {
|
|
10
|
+
return async (request, response) => {
|
|
11
|
+
if (!fence(request)) {
|
|
12
|
+
writeJson(response, 403, { ok: false, error: { code: 'forbidden', message: 'forbidden' } });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const url = new URL(request.url ?? '/', 'http://dsh.internal');
|
|
16
|
+
const method = apiMethodOf(url);
|
|
17
|
+
try {
|
|
18
|
+
if (method === 'record' && request.method === 'POST') {
|
|
19
|
+
await handleRecord(store, request, response);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (method === 'stats' && request.method === 'GET') {
|
|
23
|
+
handleStats(ctx, store, url, response);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (method === 'clear' && request.method === 'POST') {
|
|
27
|
+
await handleClear(store, request, response);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
writeJson(response, 404, {
|
|
31
|
+
ok: false,
|
|
32
|
+
error: { message: 'unknown file-activity API method' },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
writeError(response, error);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
39
|
}
|
|
40
|
-
|
|
41
40
|
/** Strip the /file-activity/api/ prefix; undefined for anything else. */
|
|
42
41
|
function apiMethodOf(url) {
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
const pathname = url.pathname;
|
|
43
|
+
return pathname.startsWith('/file-activity/api/') ? pathname.slice('/file-activity/api/'.length) : undefined;
|
|
45
44
|
}
|
|
46
|
-
|
|
47
45
|
/** POST /file-activity/api/record — client-reported sidebar operation. */
|
|
48
46
|
async function handleRecord(store, request, response) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
const payload = await readJsonBody(request);
|
|
48
|
+
const sessionId = typeof payload.sessionId === 'string' ? payload.sessionId : '';
|
|
49
|
+
const path = typeof payload.path === 'string' ? payload.path : '';
|
|
50
|
+
const op = typeof payload.op === 'string' ? payload.op : 'read';
|
|
51
|
+
store.record(sessionId, path, mapOp(op), Date.now());
|
|
52
|
+
writeJson(response, 200, { ok: true });
|
|
55
53
|
}
|
|
56
|
-
|
|
57
54
|
/** GET /file-activity/api/stats — recent + counts + session cwd. */
|
|
58
55
|
function handleStats(ctx, store, url, response) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
// Snapshot the leaf fields only — no live objects cross the wire.
|
|
68
|
-
const recent = session.recent.map((entry) => ({
|
|
69
|
-
path: entry.path,
|
|
70
|
-
op: entry.op,
|
|
71
|
-
time: entry.time,
|
|
72
|
-
}))
|
|
73
|
-
const counts = {}
|
|
74
|
-
for (const [path, counter] of Object.entries(session.counts)) {
|
|
75
|
-
counts[path] = {
|
|
76
|
-
read: counter.read,
|
|
77
|
-
create: counter.create,
|
|
78
|
-
modify: counter.modify,
|
|
79
|
-
firstSeen: counter.firstSeen,
|
|
80
|
-
lastSeen: counter.lastSeen,
|
|
56
|
+
const sessionId = url.searchParams.get('sessionId') ?? '';
|
|
57
|
+
// Session working directory, for client-side relative-path display.
|
|
58
|
+
const cwd = sessionCwdOf(ctx, sessionId);
|
|
59
|
+
const session = store.state.sessions[sessionId];
|
|
60
|
+
if (session === undefined) {
|
|
61
|
+
writeJson(response, 200, { ok: true, value: { recent: [], counts: {}, cwd } });
|
|
62
|
+
return;
|
|
81
63
|
}
|
|
82
|
-
|
|
83
|
-
|
|
64
|
+
// Snapshot the leaf fields only — no live objects cross the wire.
|
|
65
|
+
const recent = session.recent.map((entry) => ({
|
|
66
|
+
path: entry.path,
|
|
67
|
+
op: entry.op,
|
|
68
|
+
time: entry.time,
|
|
69
|
+
}));
|
|
70
|
+
const counts = {};
|
|
71
|
+
for (const [path, counter] of Object.entries(session.counts)) {
|
|
72
|
+
counts[path] = {
|
|
73
|
+
read: counter.read,
|
|
74
|
+
create: counter.create,
|
|
75
|
+
modify: counter.modify,
|
|
76
|
+
firstSeen: counter.firstSeen,
|
|
77
|
+
lastSeen: counter.lastSeen,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
writeJson(response, 200, { ok: true, value: { recent, counts, cwd } });
|
|
84
81
|
}
|
|
85
|
-
|
|
86
82
|
/** POST /file-activity/api/clear — wipe one session's records (persisted). */
|
|
87
83
|
async function handleClear(store, request, response) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
84
|
+
const payload = await readJsonBody(request);
|
|
85
|
+
const sessionId = typeof payload.sessionId === 'string' ? payload.sessionId : '';
|
|
86
|
+
if (sessionId !== '' && store.state.sessions[sessionId] !== undefined) {
|
|
87
|
+
delete store.state.sessions[sessionId];
|
|
88
|
+
store.schedulePersist();
|
|
89
|
+
}
|
|
90
|
+
writeJson(response, 200, { ok: true });
|
|
95
91
|
}
|