dsh-ui-tweaks 0.3.1 → 0.4.0
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/README.en.md +102 -96
- package/README.md +15 -4
- package/lib/archive.js +242 -0
- package/lib/archive.js.map +1 -0
- package/lib/client.js +781 -29
- package/lib/client.js.map +1 -1
- package/lib/config.js +11 -3
- package/lib/config.js.map +1 -1
- package/lib/git-web.js +3 -1
- package/lib/git-web.js.map +1 -1
- package/lib/git.js +250 -34
- package/lib/git.js.map +1 -1
- package/lib/index.js +12 -2
- package/lib/index.js.map +1 -1
- package/lib/mcp.js +502 -0
- package/lib/mcp.js.map +1 -0
- package/lib/types/archive.d.ts +72 -0
- package/lib/types/archive.d.ts.map +1 -0
- package/lib/types/client/archive.d.ts +42 -0
- package/lib/types/client/archive.d.ts.map +1 -0
- package/lib/types/client/gitbar.d.ts +2 -2
- package/lib/types/client/gitbar.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +78 -5
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/mcp.d.ts +29 -0
- package/lib/types/client/mcp.d.ts.map +1 -0
- package/lib/types/config.d.ts +23 -3
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/git-web.d.ts.map +1 -1
- package/lib/types/git.d.ts +29 -1
- package/lib/types/git.d.ts.map +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/mcp.d.ts +104 -0
- package/lib/types/mcp.d.ts.map +1 -0
- package/lib/types/web.d.ts +17 -1
- package/lib/types/web.d.ts.map +1 -1
- package/lib/web.js +6 -6
- package/lib/web.js.map +1 -1
- package/package.json +117 -110
package/README.en.md
CHANGED
|
@@ -1,96 +1,102 @@
|
|
|
1
|
-
# dsh-ui-tweaks
|
|
2
|
-
|
|
3
|
-
A [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) (DSH) web plugin that live-tunes the conversation UI from the Settings panel.
|
|
4
|
-
|
|
5
|
-
## Preview
|
|
6
|
-
|
|
7
|
-
| | |
|
|
8
|
-
|---|---|
|
|
9
|
-
|  |  |
|
|
10
|
-
| **Conversation timeline**: right-side rail — hover to preview messages, click to jump, scroll-highlighting, auto-dodges a right sidebar | **Table style**: the Claude Desktop look (light-gray rounded cards) |
|
|
11
|
-
|  |  |
|
|
12
|
-
| **Dialog width**: message column, composer and stats line widen together | **Settings panel**: font size / table style / dialog width / timeline |
|
|
13
|
-
|
|
14
|
-
## Features
|
|
15
|
-
|
|
16
|
-
- **Message font size (px)** — type any value (10–32); applies to message text, headings, tables and code.
|
|
17
|
-
- **Table style** — choose `Default` or the **Claude Desktop** look (light-gray rounded cell cards with small gaps, no borders; cells share the inline-code background; header not bold).
|
|
18
|
-
- **Dialog width (px)** — type any value (600–1600); the message column, the composer input and the stats line below it widen together.
|
|
19
|
-
- **Conversation timeline (toggleable, off by default)** — a thin navigation rail at the right of the message area: one indicator line per user message, hover to expand a preview panel, scroll-highlighting of the current position, click to smooth-scroll to that message (loading older history on demand). Works in **light and dark mode**, and **always hugs the message area's right edge** — even with a right sidebar installed and expanded (e.g. dsh-better-sidebar), the rail dodges it instead of overlapping. Auto-hidden while a session has fewer than two user messages.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
pnpm
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
- **
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
`
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
`[data-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
1
|
+
# dsh-ui-tweaks
|
|
2
|
+
|
|
3
|
+
A [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) (DSH) web plugin that live-tunes the conversation UI from the Settings panel.
|
|
4
|
+
|
|
5
|
+
## Preview
|
|
6
|
+
|
|
7
|
+
| | |
|
|
8
|
+
|---|---|
|
|
9
|
+
|  |  |
|
|
10
|
+
| **Conversation timeline**: right-side rail — hover to preview messages, click to jump, scroll-highlighting, auto-dodges a right sidebar | **Table style**: the Claude Desktop look (light-gray rounded cards) |
|
|
11
|
+
|  |  |
|
|
12
|
+
| **Dialog width**: message column, composer and stats line widen together | **Settings panel**: font size / table style / dialog width / timeline |
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- **Message font size (px)** — type any value (10–32); applies to message text, headings, tables and code.
|
|
17
|
+
- **Table style** — choose `Default` or the **Claude Desktop** look (light-gray rounded cell cards with small gaps, no borders; cells share the inline-code background; header not bold).
|
|
18
|
+
- **Dialog width (px)** — type any value (600–1600); the message column, the composer input and the stats line below it widen together.
|
|
19
|
+
- **Conversation timeline (toggleable, off by default)** — a thin navigation rail at the right of the message area: one indicator line per user message, hover to expand a preview panel, scroll-highlighting of the current position, click to smooth-scroll to that message (loading older history on demand). Works in **light and dark mode**, and **always hugs the message area's right edge** — even with a right sidebar installed and expanded (e.g. dsh-better-sidebar), the rail dodges it instead of overlapping. Auto-hidden while a session has fewer than two user messages.
|
|
20
|
+
- **Archive manager (toggleable, off by default)** — an **Archive** page in the Settings dialog listing archived sessions (title / workspace / relative time) with per-row **Restore** and **Delete** actions plus batch **Restore all** / **Delete all** buttons.
|
|
21
|
+
- **Restore** removes a session from the archive set (its log and workspace slot are kept, so the conversation returns to the normal sidebar list).
|
|
22
|
+
- **Delete** PERMANENTLY deletes the session — the server removes its JSONL log from disk, detaches it from workspace accounting and the archive set, and clears its projection cache (irreversible). Only genuinely **running** sessions are refused; opened-but-idle sessions are also removed from the in-memory store, so the row disappears live.
|
|
23
|
+
- The list refreshes live via the `host/archived-sessions-changed` event and a session-list re-pull, with no page reload.
|
|
24
|
+
- **MCP manager (toggleable, off by default)** — an **MCP** page in the Settings dialog listing every configured MCP server (`@deepseek-ai/dsh-mcp-client` loader entries) with its live status, command/url, env vars and registered tools, plus full management: **Add / Edit** (a structured form — instance id, name, stdio or HTTP type, timeout ms, command, args, env — OR raw YAML, both validated), **Enable / Disable / Delete**, and **Restart** (runtime-only). Changes persist to the profile's `cordis.patch.yml` and DSH's built-in patch watcher hot-reloads just that server.
|
|
25
|
+
|
|
26
|
+
All changes apply **live** — no reload needed. The same values can be hand-edited in the settings document:
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
ui-tweaks:
|
|
30
|
+
fontSize: 16
|
|
31
|
+
tableStyle: claude
|
|
32
|
+
dialogWidth: 880
|
|
33
|
+
timelineEnabled: true # defaults to false (off); set true to enable
|
|
34
|
+
archiveManagerEnabled: true # defaults to false (off); set true to show the Archive page
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Settings entry: **Settings → UI Tweaks**.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# from npm (recommended, prebuilt)
|
|
43
|
+
npx -y @deepseek-ai/dsh plugin --profile web add dsh-ui-tweaks
|
|
44
|
+
|
|
45
|
+
# from GitHub (source; runs the self-contained prepare build)
|
|
46
|
+
npx -y @deepseek-ai/dsh plugin --profile web add github:wlj521/dsh-ui-tweaks
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
For GitHub installs, pnpm may ask you to approve the package's build script —
|
|
50
|
+
add the exact key it prints to the profile's `pnpm-workspace.yaml`:
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
allowBuilds:
|
|
54
|
+
dsh-ui-tweaks: true
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
…then run `add` again. Restart DSH web once after installing (bundle plugins
|
|
58
|
+
are scanned at process start).
|
|
59
|
+
|
|
60
|
+
> If pnpm reports symlink/hoist errors, set `nodeLinker: hoisted` in the
|
|
61
|
+
> profile's `pnpm-workspace.yaml`.
|
|
62
|
+
|
|
63
|
+
## Development
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pnpm install
|
|
67
|
+
pnpm build # tsc (server) + tsc (client) + bundle lib/client.js
|
|
68
|
+
pnpm typecheck
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Load against a running DSH with an overlay, or install as a bundle:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx -y @deepseek-ai/dsh web --patch ./cordis.patch.yml # dev overlay
|
|
75
|
+
npx -y @deepseek-ai/dsh plugin --profile web add . # bundle install from this checkout
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## How it works
|
|
79
|
+
|
|
80
|
+
- **Server** (`src/index.ts`) registers the `ui-tweaks` settings namespace and
|
|
81
|
+
mounts a same-origin route (`/_dsh/ui-tweaks/settings`) — the Web settings
|
|
82
|
+
RPC only exposes a fixed allowlist of namespaces since rc.6, so a custom route
|
|
83
|
+
is how a plugin owns a configuration page. `src/timeline.ts` also registers
|
|
84
|
+
the `dshChatTimeline` session projection that durably enumerates user
|
|
85
|
+
messages.
|
|
86
|
+
- **Browser** (`src/client/index.tsx`) reads/writes that route, renders the
|
|
87
|
+
Settings section, and applies the values live via a runtime `<style>` element
|
|
88
|
+
that overrides stable DSH anchors (`[data-chat-flow]`,
|
|
89
|
+
`[data-composer-card]`, `body` markdown font tokens, markdown tables inside
|
|
90
|
+
`[data-slot="conversation.chat.node"]`).
|
|
91
|
+
- **Timeline** (`src/client/timeline.tsx`) mounts in the
|
|
92
|
+
`conversation.input.dock` slot and portals to `body`. Data sources, fastest
|
|
93
|
+
first: session projection → loaded chat nodes → background `loadOlder`. Its
|
|
94
|
+
position is anchored by measuring the right edge and vertical center of
|
|
95
|
+
`[data-conversation-scroll]` (the message area), so it follows both the DSH
|
|
96
|
+
column grid and any right-sidebar layout push (`#root` margin-right);
|
|
97
|
+
colors ride DSH theme tokens (`--dsw-alias-*`) for correct light/dark
|
|
98
|
+
rendering.
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh-ui-tweaks
|
|
2
2
|
|
|
3
|
-
[DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/)(DSH)Web UI
|
|
3
|
+
[DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/)(DSH)Web UI 插件:在设置面板中实时调整对话界面——字体大小、表格样式、对话框宽度、可开关的对话时间线、输入框上方的 **GitBar**(git 状态胶囊:分支 / 差异 / 提交),以及可开关的**归档管理**(设置中的「归档」页面:查看、恢复或彻底删除已归档会话)。
|
|
4
4
|
|
|
5
5
|
## 预览
|
|
6
6
|
|
|
@@ -19,11 +19,19 @@
|
|
|
19
19
|
- **表格样式**:可选 `默认` 或 **Claude Desktop** 风格(浅灰圆角单元格卡片、单元格间有间隙、无边框、单元格与行内代码同底色、表头不加粗)。
|
|
20
20
|
- **对话框宽度(px)**:直接输入数字(600–1600);消息列、输入框、输入框下方的统计栏(轮数/步数/耗时/tok/s)**三者同步变宽**。
|
|
21
21
|
- **对话时间线(可开关,默认关闭)**:在消息区右侧显示细竖导航轨——每条用户消息一根指示线,悬停展开面板预览消息、随滚动高亮当前位置、点击平滑跳转到对应消息(自动加载更早历史)。**浅色/深色模式都正常显示**,且**始终贴在消息区右侧**:即使安装了右侧边栏(如 dsh-better-sidebar)并展开,时间线也会自动避让、不会与侧边栏重叠。会话中用户消息少于 2 条时自动隐藏。
|
|
22
|
-
- **GitBar
|
|
22
|
+
- **GitBar(默认关闭,可在设置中开启)**:当会话的工作目录是一个 git 仓库时,在输入框上方显示三颗 DSH 原生风格的紧凑胶囊(与输入框同宽、随“对话框宽度”设置联动):
|
|
23
23
|
- **分支胶囊(左)**:当前分支名(有未提交改动时带橙色圆点);点击**向上**弹出分支面板——本地 / 远程分支列表(点击即 `git switch`),底部可**新建分支**(`git switch -c` 并自动切换)。
|
|
24
24
|
- **差异胶囊(右)**:`+N −M · K 个文件`;点击从右侧滑出**差异面板**:文件列表 + 逐文件 diff(默认**只显示有差异的 hunk**,右上可切“完整文件”视图)。面板**支持拖动拉伸宽度**,展开时**自动把对话区往左挤**(不影响右侧时间线),底部可直接**提交 / 提交并推送**。面板内三段(文件列表 / diff 内容 / 提交区)之间的分隔线**可上下拖动调整高度**(双击复位;提交说明框随提交区高度拉伸,可写多行,Shift+Enter 换行)。
|
|
25
25
|
- **commit message 胶囊(右)**:点击弹出提交对话框——输入说明(**留空点提交会自动生成**,LLM 优先、失败回退启发式规则)、待提交文件清单(只显示文件与 ±行数,**点击文件即打开差异面板定位到该文件**),按钮:取消 / ✨ 生成 / 提交 / 提交并推送(新分支自动 `-u` 设上游)。
|
|
26
26
|
- 非 git 仓库或无会话 cwd 时整行自动隐藏;所有操作走服务端 `execFile('git', …)`(无 shell、带超时)。
|
|
27
|
+
- **归档管理(可开关,默认关闭)**:设置面板中的「归档」页面,列出所有已归档会话(标题 / 所在工作区 / 相对时间),支持**恢复**与**彻底删除**:
|
|
28
|
+
- **恢复**:把会话移出归档——日志与工作区槽位原样保留,会话回到侧边栏列表。
|
|
29
|
+
- **删除**:**彻底删除**该会话——服务端将其 JSONL 日志从磁盘移除、从工作区记账与归档集合中清除、并清理投影缓存,会话永久消失(不可恢复)。只有**正在运行**(有任务在跑)的会话会被拒绝;已打开但空闲的会话也会从内存中移除,删除后实时从列表消失。
|
|
30
|
+
- 顶部另有**全部恢复 / 全部删除**批量操作(删除类操作需二次确认)。列表随 `host/archived-sessions-changed` 事件与客户端会话列表刷新实时更新,无需刷新页面。
|
|
31
|
+
- **MCP 管理(可开关,默认关闭)**:设置面板中的「MCP 管理」页面,展示 profile 里配置的所有 MCP 服务器(`@deepseek-ai/dsh-mcp-client` 实例)及其状态(运行中 / 错误 / 加载中 / 未运行 / 已停用)、命令、环境变量、已注册工具,并支持完整管理:
|
|
32
|
+
- **添加 / 编辑**:编辑器支持**表单**(实例 ID / 名称 / 类型 stdio 或 HTTP / 超时时间 ms / 命令 / 参数 / 环境变量 / URL / 请求头)或直接**粘贴 YAML** 两种方式,保存前会做格式校验(名称与 ID 字符集、超时正整数、URL 协议、命令非空、未知 YAML 字段等)。
|
|
33
|
+
- **启用 / 停用 / 删除 / 重启**:改动直接写入 profile 的 `cordis.patch.yml`(保持注释与结构),DSH 内置补丁监视器热重载加载器——被改动的那个服务器**实时**启动/停止/重启,不影响其它服务器;重启单独运行时生效、不改配置。环境变量值仅在本机浏览器可见,用于编辑。
|
|
34
|
+
- 新增后请确认服务器能成功连接(状态为「运行中」并注册了工具);启动失败的实例会显示「错误」并可重启重试。
|
|
27
35
|
|
|
28
36
|
所有修改**即时生效**,无需刷新。同一份配置也可以直接在设置文档里手改:
|
|
29
37
|
|
|
@@ -33,7 +41,8 @@ ui-tweaks:
|
|
|
33
41
|
tableStyle: claude
|
|
34
42
|
dialogWidth: 880
|
|
35
43
|
timelineEnabled: true # 默认 false(关闭),设为 true 开启
|
|
36
|
-
gitBarEnabled:
|
|
44
|
+
gitBarEnabled: true # 默认 false(关闭),设为 true 开启 GitBar
|
|
45
|
+
archiveManagerEnabled: true # 默认 false(关闭),设为 true 开启「归档」页面
|
|
37
46
|
# suggestModel: 'provider:model' # 可选:指定生成提交说明的模型
|
|
38
47
|
```
|
|
39
48
|
|
|
@@ -77,11 +86,13 @@ npx -y @deepseek-ai/dsh plugin --profile web add . # 从本目录作为 b
|
|
|
77
86
|
|
|
78
87
|
## 工作原理
|
|
79
88
|
|
|
80
|
-
- **服务端**(`src/index.ts`):注册 `ui-tweaks` 设置命名空间,并挂载同源路由 `/_dsh/ui-tweaks/settings
|
|
89
|
+
- **服务端**(`src/index.ts`):注册 `ui-tweaks` 设置命名空间,并挂载同源路由 `/_dsh/ui-tweaks/settings`——自 rc.6 起,Web 设置 RPC 只暴露固定白名单命名空间,因此自定义路由是插件拥有配置页的方式。另在 `src/timeline.ts` 注册 `dshChatTimeline` 会话投影单元,持久化枚举用户消息。
|
|
81
90
|
- **Git 后端**(`src/git.ts` + `src/git-web.ts`):通过 `ctx.get('sessions')`(可选服务)解析会话 header 的 `cwd` 作为“当前项目”,用 `child_process.execFile('git', …)`(无 shell、cwd 固定、超时 + 中止传播)执行只读/写操作;同源路由 `/_dsh/ui-tweaks/git/*` 提供 status / branches / diff(hunk 或完整文件,含绝对行号)/ suggest / commit / push / checkout / create。提交说明生成优先走 `ctx.get('llm')`(可选服务,收集 `text-delta` 流),不可用时回退到启发式规则(按文件类型推断 conventional commit 类型与 scope)。
|
|
82
91
|
- **浏览器端**(`src/client/index.tsx`):读写该路由、渲染设置页,并通过运行时 `<style>` 元素实时应用样式,覆盖稳定的 DSH 锚点(`[data-chat-flow]`、`[data-composer-card]`、`body` 上的 markdown 字体 token、`[data-slot="conversation.chat.node"]` 内的 markdown 表格)。
|
|
83
92
|
- **时间线**(`src/client/timeline.tsx`):挂在 `conversation.input.dock` 插槽、portal 到 `body`。数据按速度优先:会话投影 → 已加载聊天节点 → 后台 `loadOlder`。位置通过测量 `[data-conversation-scroll]`(消息区)右缘与垂直中线动态锚定,因此 DSH 原生列布局与右侧边栏(`#root` 的 margin-right 布局推挤)变化时都会自动跟随;颜色全部使用 DSH 主题变量(`--dsw-alias-*`),浅色/深色模式均正常。
|
|
84
93
|
- **GitBar**(`src/client/gitbar.tsx`):挂在同一个 `conversation.input.dock` 插槽。胶囊行以 `width:100%` + `max-width` 与 composer 卡片同宽(buildRuntimeCss 在“对话框宽度”变化时同步覆盖该宽度,实现联动),胶囊行自身背景透明、只有胶囊可见;差异面板展开时通过 `#root { margin-right }` 把对话区往左挤,时间线 rail(锚定消息区右缘)因此不被遮挡。面板内的高度分配采用「只给被拖的那一段显式高度、diff 段 `flex:1` 吃掉余量」的方式,配合 45% 上限,拖动永远不会撑破面板。
|
|
94
|
+
- **归档管理**(服务端 `src/archive.ts` + 浏览器端 `src/client/archive.tsx`):作为 `settings.section` 插槽(设置面板中的「归档」页面)。列表数据直接来自框架标准 hook `useSessions` + `useWorkspaces`(`archivedSessionIds`),无需额外查询;操作走同源路由 `/_dsh/ui-tweaks/archive`。**恢复**把会话 id 从工作区存储域的 `archivedSessionIds` 全局单例中移除(DSH 只暴露单向 `archiveSession`,无公开的取消归档 API,故直接写活体存储域句柄并同步工作区注册表的内存缓存)。**彻底删除**依次:拒绝正在运行的会话(agent `status === 'running'` 才拒绝,空闲会话先 `cancel` + `whenIdle`)→ 用持久化后端自身的 `findLog` 定位并 `rm` 会话日志目录 → 调用公开的 `WorkspaceEntity.detachSession` 摘除工作区记账 → 从归档集合移除并同步注册表内存缓存与 header 索引 → 清理 `session_projcache` → 从内存 SessionStore 摘除该会话(触发 `host/session-removed` 实时消失)。
|
|
95
|
+
- **MCP 管理**(服务端 `src/mcp.ts` + 浏览器端 `src/client/mcp.tsx`):同源路由 `/_dsh/ui-tweaks/mcp`。**列表**枚举 `ctx.loader.entries()` 中 `@deepseek-ai/dsh-mcp-client` 实例(id / config / fiber 状态:active=2、failed=3 等)并按 `mcp__<serverName>__` 前缀从工具注册表统计工具。**重启**调用 `entry.fiber.restart()`(仅运行时)。**添加 / 编辑 / 删除 / 启用停用**通过 `yaml`(eemeli)的 Document API 直接编辑 profile 的 `cordis.patch.yml`(保留注释与未知补丁结构,原子写 tmp+rename),随后由 DSH 内置的 `watchUserPatches` 热重载监视器重新应用补丁——`cordis-plugin-include` 对根组做**增量** `root.update`,因此只有被改动的 MCP 实例会重启,其它不受影响;环境变量值返回给同源浏览器(本机配置编辑需要),YAML 模式在服务端用 `yaml.parse` + 白名单校验。
|
|
85
96
|
|
|
86
97
|
## 协议
|
|
87
98
|
|
package/lib/archive.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-ui-tweaks — archive manager (host half).
|
|
3
|
+
*
|
|
4
|
+
* The browser Archive manager (the "归档" settings section, shown when the
|
|
5
|
+
* archiveManagerEnabled setting is on) lists archived sessions and offers
|
|
6
|
+
* two per-session actions plus batch actions through this same-origin route:
|
|
7
|
+
*
|
|
8
|
+
* - **restore** / **restore-all**: move a session out of the archive set. The
|
|
9
|
+
* session log and its workspace accounting slot are kept untouched, so the
|
|
10
|
+
* conversation reappears in the normal sidebar list.
|
|
11
|
+
* - **delete** / **delete-all**: PERMANENTLY delete a session — its durable
|
|
12
|
+
* JSONL log is removed from disk, it is detached from every workspace's
|
|
13
|
+
* accounting, removed from the archive set, dropped from the workspace
|
|
14
|
+
* registry's in-memory header index, and best-effort-cleaned from the
|
|
15
|
+
* session projection cache. Only a genuinely RUNNING agent is refused;
|
|
16
|
+
* opened-but-idle sessions are also removed from the in-memory SessionStore
|
|
17
|
+
* so they vanish from the live list immediately (the host relays
|
|
18
|
+
* `session/disposed` as `host/session-removed`).
|
|
19
|
+
*
|
|
20
|
+
* DSH has no public API for either operation. The archive set lives in the
|
|
21
|
+
* `workspace` storage domain's global singleton (the same record
|
|
22
|
+
* `WorkspaceRegistry` keeps in memory), so this backend writes the filtered
|
|
23
|
+
* set through the live domain handle (`ctx.storageDomain.get('workspace')`)
|
|
24
|
+
* and keeps the registry's in-memory cache coherent in the same write, so the
|
|
25
|
+
* registry's next mutation cannot resurrect a restored id. Session-log
|
|
26
|
+
* deletion uses the persistence backend's own locator (`findLog`) so only the
|
|
27
|
+
* exact encoded session directory is removed; workspace accounting removal
|
|
28
|
+
* uses the registry's public `WorkspaceEntity.detachSession`.
|
|
29
|
+
* @module dsh-ui-tweaks/archive
|
|
30
|
+
*/
|
|
31
|
+
import { rm } from 'node:fs/promises';
|
|
32
|
+
import { dirname } from 'node:path';
|
|
33
|
+
import { workspaceDomainState } from '@deepseek-ai/dsh-workspace';
|
|
34
|
+
import { isRecord, json, messageOf, readJson, requestError, sameOriginPost } from "./web.js";
|
|
35
|
+
/** Exact route used by the browser Archive panel. */
|
|
36
|
+
export const ARCHIVE_ROUTE = '/_dsh/ui-tweaks/archive';
|
|
37
|
+
/** A live session was targeted by a permanent delete. */
|
|
38
|
+
export class SessionLiveError extends Error {
|
|
39
|
+
code = 'session-live';
|
|
40
|
+
constructor() {
|
|
41
|
+
super('cannot permanently delete a live session: close it first');
|
|
42
|
+
this.name = 'SessionLiveError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function parseRequest(value) {
|
|
46
|
+
if (!isRecord(value))
|
|
47
|
+
throw new TypeError('request body must be an object');
|
|
48
|
+
if (value.action === 'restore' || value.action === 'delete') {
|
|
49
|
+
const sessionId = value.sessionId;
|
|
50
|
+
if (typeof sessionId !== 'string' || sessionId.length === 0)
|
|
51
|
+
throw new TypeError('sessionId must be a non-empty string');
|
|
52
|
+
return { action: value.action, sessionId };
|
|
53
|
+
}
|
|
54
|
+
if (value.action === 'restore-all' || value.action === 'delete-all')
|
|
55
|
+
return { action: value.action };
|
|
56
|
+
throw new TypeError('action must be "restore", "delete", "restore-all" or "delete-all"');
|
|
57
|
+
}
|
|
58
|
+
/** Same-origin Archive read/write handler. */
|
|
59
|
+
export class ArchiveBackend {
|
|
60
|
+
ctx;
|
|
61
|
+
constructor(ctx) {
|
|
62
|
+
this.ctx = ctx;
|
|
63
|
+
}
|
|
64
|
+
registry() {
|
|
65
|
+
return this.ctx.get('workspaceRegistry');
|
|
66
|
+
}
|
|
67
|
+
storageDomain() {
|
|
68
|
+
return this.ctx.get('storageDomain');
|
|
69
|
+
}
|
|
70
|
+
snapshot() {
|
|
71
|
+
const archivedSessionIds = [...(this.registry()?.archivedSessionIds ?? [])];
|
|
72
|
+
return { archivedSessionIds };
|
|
73
|
+
}
|
|
74
|
+
/** Remove one (or all) ids from the durable archive set, syncing the
|
|
75
|
+
* registry's in-memory cache in the same write. */
|
|
76
|
+
async writeArchiveSet(registry, keep) {
|
|
77
|
+
const global = this.storageDomain()?.get('workspace')?.global;
|
|
78
|
+
if (global === undefined)
|
|
79
|
+
throw new Error('workspace storage domain is unavailable');
|
|
80
|
+
const state = workspaceDomainState.parse(global.get());
|
|
81
|
+
const next = {
|
|
82
|
+
...state,
|
|
83
|
+
archivedSessionIds: state.archivedSessionIds.filter(keep),
|
|
84
|
+
};
|
|
85
|
+
if (next.archivedSessionIds.length === state.archivedSessionIds.length)
|
|
86
|
+
return;
|
|
87
|
+
await global.set(next);
|
|
88
|
+
// Keep the registry's in-memory cache coherent so its next write (e.g.
|
|
89
|
+
// another archive, a workspace create) does not resurrect the ids.
|
|
90
|
+
registry.state = next;
|
|
91
|
+
}
|
|
92
|
+
/** Restore one session: remove it from the durable archive set. */
|
|
93
|
+
async restore(sessionId) {
|
|
94
|
+
const registry = this.registry();
|
|
95
|
+
if (registry === undefined)
|
|
96
|
+
throw new Error('workspace registry is unavailable');
|
|
97
|
+
if (!registry.archivedSessionIds.includes(sessionId))
|
|
98
|
+
return;
|
|
99
|
+
await this.writeArchiveSet(registry, id => id !== sessionId);
|
|
100
|
+
}
|
|
101
|
+
/** Restore every archived session at once. */
|
|
102
|
+
async restoreAll() {
|
|
103
|
+
const registry = this.registry();
|
|
104
|
+
if (registry === undefined)
|
|
105
|
+
throw new Error('workspace registry is unavailable');
|
|
106
|
+
if (registry.archivedSessionIds.length === 0)
|
|
107
|
+
return;
|
|
108
|
+
await this.writeArchiveSet(registry, () => false);
|
|
109
|
+
}
|
|
110
|
+
/** Permanently delete one session: log, accounting, archive set, and caches. */
|
|
111
|
+
async deleteSession(sessionId) {
|
|
112
|
+
// Refuse only a genuinely RUNNING agent. Sessions that were merely opened
|
|
113
|
+
// (and archived) stay attached to the in-memory SessionStore while idle —
|
|
114
|
+
// refusing every attached session would make most archived sessions
|
|
115
|
+
// undeletable.
|
|
116
|
+
const agents = this.ctx.get('agents');
|
|
117
|
+
const agent = agents?.get(sessionId);
|
|
118
|
+
if (agent !== undefined && agent.status === 'running')
|
|
119
|
+
throw new SessionLiveError();
|
|
120
|
+
if (agent !== undefined) {
|
|
121
|
+
// Clear any queued work and let the (idle) agent settle before the log
|
|
122
|
+
// is removed.
|
|
123
|
+
agent.cancel({ kind: 'user' });
|
|
124
|
+
await agent.whenIdle();
|
|
125
|
+
}
|
|
126
|
+
// 1. Remove the durable session log (its parent directory) from disk.
|
|
127
|
+
const persistence = this.ctx.get('sessionPersistence');
|
|
128
|
+
if (persistence?.findLog !== undefined) {
|
|
129
|
+
const logPath = await persistence.findLog(sessionId);
|
|
130
|
+
if (logPath !== undefined)
|
|
131
|
+
await rm(dirname(logPath), { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
// 2. Detach from every workspace's accounting (public entity API), then
|
|
134
|
+
// remove from the archive set, then forget it in the registry's
|
|
135
|
+
// in-memory header index so membership checks drop it without restart.
|
|
136
|
+
const registry = this.registry();
|
|
137
|
+
if (registry !== undefined) {
|
|
138
|
+
for (const workspace of registry.list()) {
|
|
139
|
+
if (workspace.sessionIds.includes(sessionId))
|
|
140
|
+
await workspace.detachSession(sessionId);
|
|
141
|
+
}
|
|
142
|
+
await this.writeArchiveSet(registry, id => id !== sessionId);
|
|
143
|
+
registry.headers?.delete(sessionId);
|
|
144
|
+
registry.sessionPaths?.delete(sessionId);
|
|
145
|
+
}
|
|
146
|
+
// 3. Best-effort: drop the session's projection-cache checkpoint.
|
|
147
|
+
const table = this.storageDomain()?.get('session_projcache')?.table?.('sessions');
|
|
148
|
+
if (table?.delete !== undefined)
|
|
149
|
+
await table.delete(sessionId);
|
|
150
|
+
// 4. Drop the (idle) session from the in-memory SessionStore so it
|
|
151
|
+
// vanishes from the live list immediately: the store's entry detach
|
|
152
|
+
// fires `session/disposed`, which the host relays as
|
|
153
|
+
// `host/session-removed` and the browser forgets the session. The
|
|
154
|
+
// agent's own lifecycle stays registered until process end — it is
|
|
155
|
+
// idle, unreachable, and cleared by the next restart.
|
|
156
|
+
const sessions = this.ctx.get('sessions');
|
|
157
|
+
sessions?.store?.get(sessionId)?.detach?.();
|
|
158
|
+
}
|
|
159
|
+
/** Permanently delete every archived session (live ones are skipped). */
|
|
160
|
+
async deleteAll() {
|
|
161
|
+
const registry = this.registry();
|
|
162
|
+
if (registry === undefined)
|
|
163
|
+
throw new Error('workspace registry is unavailable');
|
|
164
|
+
const archived = [...registry.archivedSessionIds];
|
|
165
|
+
let deleted = 0;
|
|
166
|
+
let skipped = 0;
|
|
167
|
+
for (const id of archived) {
|
|
168
|
+
try {
|
|
169
|
+
await this.deleteSession(id);
|
|
170
|
+
deleted += 1;
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
skipped += 1;
|
|
174
|
+
this.ctx.logger.warn('dsh-ui-tweaks archive delete skipped %s: %s', id, messageOf(error));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return { deleted, skipped };
|
|
178
|
+
}
|
|
179
|
+
/** Handle the exact Archive route. */
|
|
180
|
+
async handle(req, res) {
|
|
181
|
+
if (req.method === 'GET') {
|
|
182
|
+
try {
|
|
183
|
+
json(res, 200, { ok: true, value: this.snapshot() });
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
this.ctx.logger.warn('dsh-ui-tweaks Archive snapshot failed: %s', messageOf(error));
|
|
187
|
+
requestError(res, 503, 'archive-unavailable', 'UI Tweaks archive is unavailable');
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (req.method !== 'POST') {
|
|
192
|
+
res.setHeader('Allow', 'GET, POST');
|
|
193
|
+
requestError(res, 405, 'method-not-allowed', 'Use GET or POST');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (!sameOriginPost(req)) {
|
|
197
|
+
requestError(res, 403, 'origin-rejected', 'The request must originate from this DSH Web application');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
let request;
|
|
201
|
+
try {
|
|
202
|
+
request = parseRequest(await readJson(req));
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
requestError(res, 400, 'invalid-request', messageOf(error));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
if (request.action === 'restore')
|
|
210
|
+
await this.restore(request.sessionId);
|
|
211
|
+
else if (request.action === 'restore-all')
|
|
212
|
+
await this.restoreAll();
|
|
213
|
+
else if (request.action === 'delete')
|
|
214
|
+
await this.deleteSession(request.sessionId);
|
|
215
|
+
else
|
|
216
|
+
await this.deleteAll();
|
|
217
|
+
json(res, 200, { ok: true, value: this.snapshot() });
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
const live = error instanceof SessionLiveError;
|
|
221
|
+
this.ctx.logger.warn('dsh-ui-tweaks Archive mutation failed: %s', messageOf(error));
|
|
222
|
+
requestError(res, live ? 409 : 400, live ? 'session-live' : 'archive-rejected', messageOf(error));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Attach the Archive route whenever a webServer service is present.
|
|
228
|
+
* @param ctx - plugin context owning route effects.
|
|
229
|
+
* @param backend - Archive handler.
|
|
230
|
+
*/
|
|
231
|
+
export function installArchiveWeb(ctx, backend) {
|
|
232
|
+
ctx.inject(['webServer'], (webCtx) => {
|
|
233
|
+
webCtx.effect(() => {
|
|
234
|
+
return webCtx.webServer.register({
|
|
235
|
+
kind: 'exact',
|
|
236
|
+
path: ARCHIVE_ROUTE,
|
|
237
|
+
handler: (req, res) => backend.handle(req, res),
|
|
238
|
+
});
|
|
239
|
+
}, 'dsh-ui-tweaks: Archive routes');
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"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"}
|