dsh-transfer-hud 1.0.0 → 1.1.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.md +241 -50
- package/client.js +42 -10
- package/index.js +545 -46
- package/locale/en.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +7 -2
- package/progress-tool.js +586 -0
package/README.md
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
English | [中文](#中文)
|
|
4
4
|
|
|
5
|
-
A thin strip below the message composer that shows what DeepSeek Harness is
|
|
6
|
-
right now
|
|
7
|
-
(`
|
|
5
|
+
A thin strip below the message composer that shows what DeepSeek Harness is doing
|
|
6
|
+
**right now** — downloads, installs, builds, model loads and any long command. Zero
|
|
7
|
+
runtime dependencies; a Host half (`index.js`) plus a Web client half (`client.js`). The
|
|
8
|
+
strip is also where background jobs and `run_with_progress` commands are stopped.
|
|
8
9
|
|
|
9
|
-
> **Version:** this npm release is **1.
|
|
10
|
-
> **v1.
|
|
11
|
-
> internal revision to 1.0.0
|
|
10
|
+
> **Version:** this npm release is **1.1.0** and packages the workspace task-progress HUD
|
|
11
|
+
> revision **v1.3.0** (loads and long tasks, the `run_with_progress` tool, cross-platform
|
|
12
|
+
> process handling). The first public release renumbered the internal revision to 1.0.0;
|
|
13
|
+
> 1.1.0 is the matching npm version for workspace v1.3.0.
|
|
12
14
|
|
|
13
15
|
## English
|
|
14
16
|
|
|
@@ -16,12 +18,13 @@ right now. Zero runtime dependencies; a Host half (`index.js`) plus a Web Client
|
|
|
16
18
|
|
|
17
19
|
| Source | Row | Progress |
|
|
18
20
|
|---|---|---|
|
|
19
|
-
| **Background jobs** (`ctx.jobs`: `pwsh`/`bash` jobs started with `run_in_background`, or promoted after a timeout) | real job row: label, status, **Stop** button | percent / bytes / rate / ETA
|
|
20
|
-
| **Foreground shell commands** (`pwsh`, `bash`, `ops_bash`, `terminal_send`, …) | indeterminate row (`foreground: true`, no Stop button) | none — the Host
|
|
21
|
+
| **Background jobs** (`ctx.jobs`: `pwsh`/`bash` jobs started with `run_in_background`, or promoted after a timeout) | real job row: label, status, a `download` / `load` badge (`下载` / `加载` in the Chinese UI), **Stop** button | percent / bytes / rate / ETA whenever the output ring carries a meter (curl, wget, aria2, tqdm / `hf download`, huggingface, modelscope, `git` counters, npm/pip/apt, ffmpeg, docker, `step 120/500`, `[=====> ]`, …), parsed from **stdout and stderr** — most tools print their meter to stderr |
|
|
22
|
+
| **Foreground shell commands** (`pwsh`, `bash`, `ops_bash`, `terminal_send`, …) | indeterminate row (`foreground: true`, no Stop button) when the call looks like a download/load, or once it has been running for more than 12 s | none — the Host sees the call, not its live output; the row proves work is running and disappears about 8 s after the tool result |
|
|
23
|
+
| **`run_with_progress` model tool** | real row (`kind: 'tool'`, **Stop** button while running) | **live** percent / bytes / rate / ETA parsed from the command's merged stdout+stderr, because the plugin owns the child process; the row lingers 30 s after the command ends |
|
|
21
24
|
|
|
22
|
-
When a foreground call starts a background job (`run_in_background: true`, or a
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
When a foreground call starts a background job (`run_in_background: true`, or a foreground
|
|
26
|
+
command promoted on timeout), the job row replaces the foreground row: the Host reads
|
|
27
|
+
`job/registered`'s `toolCall.callId` and dedupes.
|
|
25
28
|
|
|
26
29
|
### What counts as a download
|
|
27
30
|
|
|
@@ -47,70 +50,177 @@ The classifier is deliberately narrow so ordinary API traffic never shows a row:
|
|
|
47
50
|
are ignored. An opaque call such as `python fetch.py` is recognised only when the call's
|
|
48
51
|
description says "download/fetch/…" or its file/URL matches a known artifact pattern.
|
|
49
52
|
|
|
53
|
+
### What counts as a load
|
|
54
|
+
|
|
55
|
+
A load is everything else that is clearly working rather than answering. Three signals
|
|
56
|
+
admit a row:
|
|
57
|
+
|
|
58
|
+
- **Any progress meter in a job's output** — a percent, a `done/total` byte pair, a
|
|
59
|
+
rendered bar (`[====> ]`), a tqdm iteration rate (`it/s`), a step / epoch / phase
|
|
60
|
+
counter. A meter alone makes it a load row, even without a single download word.
|
|
61
|
+
- **Loader commands** in a job label or in command position: `npm`/`pnpm`/`yarn`
|
|
62
|
+
`install|ci|add`, `apt(-get) install`, `dnf`/`yum`/`zypper install`, `pacman -S`,
|
|
63
|
+
`snap`/`flatpak install`, `dpkg -i`, `rpm -i`, `brew install`, `conda install`,
|
|
64
|
+
`pip install`, `docker pull|build`, `ffmpeg`, `cargo`/`go build`, `make`/`cmake`,
|
|
65
|
+
`dotnet build|restore`, `ollama pull`, macOS `installer`/`pkgutil`, and load verbs such
|
|
66
|
+
as 加载 / 构建 / 编译 / 安装 / 渲染 / 转码 / 导出 / 训练 / 解压.
|
|
67
|
+
- **Long foreground calls without any hint** are promoted to an indeterminate load row
|
|
68
|
+
after 12 s.
|
|
69
|
+
|
|
70
|
+
Reads and searches are excluded: a job or command whose command word is
|
|
71
|
+
`cat`/`Get-Content`/`grep`/`rg`/`find`/`ls`/`dir` does not become a load just because its
|
|
72
|
+
output happens to contain `100%` or `eta`. A description alone is not enough either, so
|
|
73
|
+
`Check build status` running `git status` stays silent. Background-job **labels** are
|
|
74
|
+
scanned with the broader historical hint (the whole multi-line script): a background job
|
|
75
|
+
is rarer and a missed load costs more than an extra strip row.
|
|
76
|
+
|
|
77
|
+
A download keeps its `下载` badge only when a download-specific signal — the label, the
|
|
78
|
+
tail text, or the foreground classifier — says so; a tqdm training run is a load, not a
|
|
79
|
+
download.
|
|
80
|
+
|
|
81
|
+
### The `run_with_progress` tool
|
|
82
|
+
|
|
83
|
+
The Host half registers a model tool, `run_with_progress`, and injects a short global
|
|
84
|
+
system-prompt hint that tells the model to use it for long operations. Because the plugin
|
|
85
|
+
owns the child process, a command run through this tool streams a real percent / rate /
|
|
86
|
+
ETA into the strip even though ordinary foreground shell calls expose no progress to the
|
|
87
|
+
Host.
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"command": "curl -# -L -o model.safetensors https://example.com/model.safetensors",
|
|
92
|
+
"label": "Downloading model weights"
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- **When to use it.** Downloads, installs, builds, model loads, transcoding, dataset
|
|
97
|
+
processing and any command expected to run longer than about 10 seconds. Both the tool
|
|
98
|
+
description and the global prompt prefer this tool over a plain `ops_bash` / `pwsh`
|
|
99
|
+
call for those tasks.
|
|
100
|
+
- **Arguments.** `command` (required string — one shell command line) and `label`
|
|
101
|
+
(optional short label for the strip).
|
|
102
|
+
- **Where it runs.** In the calling session's working directory, so relative paths
|
|
103
|
+
resolve against the session's workspace; `stdout` and `stderr` are merged and parsed as
|
|
104
|
+
they arrive, and the row is served on `/dsh-download-hud/state` like any other row.
|
|
105
|
+
- **Concurrency.** Up to 4 commands run at once (other sessions included); a fifth call
|
|
106
|
+
returns a `Busy` text result and adds no row.
|
|
107
|
+
- **Cancel.** The user can press **Stop** on the row: the Host kills the whole process
|
|
108
|
+
tree, the row becomes `killed`, and the tool returns `status: killed`. Cancelling the
|
|
109
|
+
tool call (turn abort) takes the same path.
|
|
110
|
+
- **Result.** `status`, `exit code`, `duration` and the command output. Output is returned
|
|
111
|
+
whole up to 12 KB; beyond that the first 2 KB and last 10 KB are kept, separated by a
|
|
112
|
+
`[N characters truncated]` marker.
|
|
113
|
+
- **Keep the meter.** The prompt asks for `curl -#` / `--progress-bar` and never `-s`,
|
|
114
|
+
default progress output for `wget`/`pip`/`npm`/`hf`/`aria2`, `ffmpeg -progress pipe:1
|
|
115
|
+
-nostats`, and no `$ProgressPreference = 'SilentlyContinue'` in PowerShell.
|
|
116
|
+
- **Degrades gracefully.** Hosts without a `tools` / `systemPrompt` service still load the
|
|
117
|
+
plugin and keep the strip; only the tool registration or the prompt hint is skipped.
|
|
118
|
+
|
|
50
119
|
### Install
|
|
51
120
|
|
|
52
121
|
Requires DeepSeek Harness `>=0.2.1-rc.2 <0.3.0-0` and a Web or Desktop profile
|
|
53
122
|
(the plugin has a Host half and a browser Client half).
|
|
54
123
|
|
|
55
|
-
1. Open **Settings → Plugins**, install bundle `dsh-transfer-hud@1.
|
|
124
|
+
1. Open **Settings → Plugins**, install bundle `dsh-transfer-hud@1.1.0`.
|
|
56
125
|
2. Or ask the agent: `plugin_manager` → `action: "install_bundle"`,
|
|
57
|
-
`target: "dsh-transfer-hud@1.
|
|
126
|
+
`target: "dsh-transfer-hud@1.1.0"`.
|
|
58
127
|
|
|
59
128
|
**Restart the app after installing or upgrading.** HMR applies a new bundle, but the
|
|
60
129
|
profile's Host watch roots are empty (`root: []`) and a replaced package needs a fresh
|
|
61
130
|
JavaScript module generation, so the new Host code is loaded on restart; the Client half
|
|
62
131
|
then follows with the usual client HMR / page refresh. Verification: after the restart,
|
|
63
|
-
run a foreground download (e.g. `curl -o file.bin <url>`)
|
|
64
|
-
(`run_in_background: true`)
|
|
65
|
-
uninstall.
|
|
132
|
+
run a foreground download (e.g. `curl -o file.bin <url>`), a background job
|
|
133
|
+
(`run_in_background: true`), or a long command through `run_with_progress` and the strip
|
|
134
|
+
appears below the composer. Remove the bundle to uninstall.
|
|
66
135
|
|
|
67
136
|
### Configuration
|
|
68
137
|
|
|
69
138
|
None. The row takes no config; the strip is automatic and only reads activity that
|
|
70
139
|
already belongs to this Harness.
|
|
71
140
|
|
|
141
|
+
### Cross-platform
|
|
142
|
+
|
|
143
|
+
The HUD and the `run_with_progress` runner are platform-neutral and ship no native code —
|
|
144
|
+
they use Node's `child_process` plus the platform's own process-control command.
|
|
145
|
+
|
|
146
|
+
- **Windows, macOS, Linux.** Commands run through the platform's default shell
|
|
147
|
+
(`ComSpec` on Windows, `/bin/sh` on POSIX), so ordinary portable command lines work
|
|
148
|
+
unchanged.
|
|
149
|
+
- **Cancellation kills the whole tree.** On Windows the runner spawns
|
|
150
|
+
`taskkill /PID <pid> /T /F`; on POSIX it starts the shell in its own process group and
|
|
151
|
+
sends `SIGTERM` to the group, then `SIGKILL` after 1.5 s. Neither path leaves an
|
|
152
|
+
orphaned child process behind.
|
|
153
|
+
- **Recognition covers both platforms.** The download/load classifier knows POSIX
|
|
154
|
+
transfer tools (`curl`, `wget`, `aria2c`, `rsync`, `scp`, `hf`, `pip`, …) and Windows
|
|
155
|
+
ones (`Invoke-WebRequest` / `iwr`, `Start-BitsTransfer`, `bitsadmin`, .NET
|
|
156
|
+
`DownloadFile`), plus both package-manager families: `apt`, `dnf`, `yum`, `zypper`,
|
|
157
|
+
`pacman`, `snap`, `flatpak`, `dpkg`, `rpm`, `brew` — and `apk`, `conda`, `pip`, npm and
|
|
158
|
+
the language package managers.
|
|
159
|
+
- **Verification.** Windows is exercised live on the development host; POSIX behaviour is
|
|
160
|
+
covered by unit tests that inject the platform and assert process-group
|
|
161
|
+
`SIGTERM`→`SIGKILL` versus `taskkill /T /F`, so no macOS/Linux host is required for
|
|
162
|
+
that coverage.
|
|
163
|
+
|
|
72
164
|
### Limits
|
|
73
165
|
|
|
74
|
-
- **Foreground
|
|
75
|
-
output to the Host, so there is no percent/rate/ETA — only "
|
|
166
|
+
- **Foreground rows are indeterminate, and appear only after 12 s.** A foreground tool
|
|
167
|
+
call exposes no live output to the Host, so there is no percent/rate/ETA — only "work is
|
|
168
|
+
running". The promoted row appears only after 12 s, by which time the call may already
|
|
169
|
+
be near its end.
|
|
76
170
|
- **Session-scoped.** Rows belong to the session the strip is mounted in. A download
|
|
77
171
|
started by a subagent/teammate session is not shown in the parent conversation.
|
|
78
|
-
- **Opaque scripts need a hint.** See "What counts as a
|
|
172
|
+
- **Opaque scripts need a hint.** See "What counts as a load" above.
|
|
79
173
|
- **No progress output, no meter.** `Invoke-WebRequest` with
|
|
80
174
|
`$ProgressPreference = 'SilentlyContinue'` prints nothing to parse; the row still
|
|
81
175
|
appears from the label hint but stays indeterminate.
|
|
82
|
-
- **
|
|
83
|
-
|
|
176
|
+
- **False positives cost one strip row.** Any job whose tail prints something meter-shaped
|
|
177
|
+
(`100%`, `[===]`, `eta`) is admitted; reads/searches are suppressed, but a plain script
|
|
178
|
+
that prints such text can still show up.
|
|
179
|
+
- **Stop only stops background jobs and `run_with_progress` rows.** A plain foreground
|
|
180
|
+
command cannot be killed from the HUD (the Host has no handle on it); use the tool when
|
|
181
|
+
a task must be cancellable.
|
|
182
|
+
- **The tool has no default timeout.** A hung command stays `running` until the user stops
|
|
183
|
+
it or the turn is cancelled (the tool call's own timeout still applies on top).
|
|
184
|
+
- **The model can bypass the tool.** The global prompt is guidance, not enforcement: a
|
|
185
|
+
shell call made directly falls back to the job/foreground classifier, and a plain
|
|
186
|
+
foreground command still shows no percent.
|
|
187
|
+
- **Rows linger.** A finished foreground row stays about 8 s and a `run_with_progress` row
|
|
188
|
+
30 s, so a row can still be visible after its command has ended.
|
|
189
|
+
- **Host code reload needs a plugin reload.** The profile's HMR watch roots are empty
|
|
190
|
+
(`root: []`), so editing the Host half of a running app needs a bundle toggle or an app
|
|
191
|
+
restart; the Client half reloads with the usual client HMR.
|
|
84
192
|
- UI rendering (strip/panel layout) can only be verified in a browser.
|
|
85
193
|
|
|
86
194
|
### Development
|
|
87
195
|
|
|
88
|
-
Host and Client test harnesses
|
|
89
|
-
from the published tarball
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
```
|
|
196
|
+
The source repository ships Host, tool and Client test harnesses under `test/`; the
|
|
197
|
+
directory is excluded from the published tarball. From the source checkout, `npm test`
|
|
198
|
+
runs the Host harness under a mock context (classification, parsing, 12 s promotion,
|
|
199
|
+
session scoping, row cap), the real `run_with_progress` runner with short-lived Node child
|
|
200
|
+
processes (live row, kill, cancellation, 12 KB truncation), and the Client factory under
|
|
201
|
+
mock browser globals. No extra dependencies are needed.
|
|
95
202
|
|
|
96
203
|
### License
|
|
97
204
|
|
|
98
|
-
MIT.
|
|
205
|
+
MIT. Copyright (c) 2026 xiuerhuahuazi.
|
|
99
206
|
|
|
100
207
|
## 中文
|
|
101
208
|
|
|
102
|
-
|
|
103
|
-
|
|
209
|
+
消息输入框下方的一条细长进度条,显示本 Harness 当前正在做的事——下载、安装、构建、模型
|
|
210
|
+
加载以及任何长命令,并可从进度条上停止后台任务与 `run_with_progress` 命令。零运行时
|
|
211
|
+
依赖,由 Host 半(`index.js`)与 Web Client 半(`client.js`)组成。
|
|
104
212
|
|
|
105
|
-
> **版本**:本次 npm 发布为 **1.
|
|
106
|
-
>
|
|
213
|
+
> **版本**:本次 npm 发布为 **1.1.0**,打包的是工作区任务进度 HUD **v1.3.0**(加载与
|
|
214
|
+
> 长任务、`run_with_progress` 工具、跨平台进程管理);首次公开发布曾将内部修订号重编为
|
|
215
|
+
> 1.0.0,1.1.0 对应工作区 v1.3.0。
|
|
107
216
|
|
|
108
217
|
### 显示内容
|
|
109
218
|
|
|
110
219
|
| 来源 | 行 | 进度 |
|
|
111
220
|
|---|---|---|
|
|
112
|
-
| **后台任务**(`ctx.jobs`:用 `run_in_background` 启动、或超时转后台的 `pwsh`/`bash` 任务) |
|
|
113
|
-
| **前台 shell 命令**(`pwsh`、`bash`、`ops_bash`、`terminal_send`……) |
|
|
221
|
+
| **后台任务**(`ctx.jobs`:用 `run_in_background` 启动、或超时转后台的 `pwsh`/`bash` 任务) | 真实任务行:标签、状态、`下载` / `加载` 徽标、**停止**按钮 | 输出环携带进度时显示百分比/字节/速度/剩余时间(curl、wget、aria2、tqdm / `hf download`、huggingface、modelscope、`git` 计数、npm/pip/apt、ffmpeg、docker、`step 120/500`、`[=====> ]`……),同时解析 **stdout 与 stderr**——多数工具把进度打印到 stderr |
|
|
222
|
+
| **前台 shell 命令**(`pwsh`、`bash`、`ops_bash`、`terminal_send`……) | 当调用看起来像下载/加载、或已运行超过 12 秒时,显示不确定行(`foreground: true`,无停止按钮) | 无——Host 只能看到调用,看不到其运行中的输出;该行表明任务正在运行,并在工具结果返回约 8 秒后消失 |
|
|
223
|
+
| **`run_with_progress` 模型工具** | 真实行(`kind: 'tool'`,运行中带**停止**按钮) | **实时**百分比/字节/速度/剩余时间,来自命令合并后的 stdout+stderr(子进程由插件持有);命令结束后该行再保留 30 秒 |
|
|
114
224
|
|
|
115
225
|
当前台调用启动后台任务(`run_in_background: true`,或超时转后台)时,任务行会替换前台行:
|
|
116
226
|
Host 读取 `job/registered` 的 `toolCall.callId` 去重。
|
|
@@ -137,46 +247,127 @@ Host 读取 `job/registered` 的 `toolCall.callId` 去重。
|
|
|
137
247
|
`python fetch.py` 这类不透明调用只有描述包含 download/fetch 等词、或文件/URL 命中已知
|
|
138
248
|
产物模式时才会被识别。
|
|
139
249
|
|
|
250
|
+
### 什么算作加载
|
|
251
|
+
|
|
252
|
+
加载指除此之外一切明显在「干活」而不是在「回答」的任务。三类信号可让行出现:
|
|
253
|
+
|
|
254
|
+
- **任务输出里出现任何进度信号**——百分比、`done/total` 字节对、渲染条
|
|
255
|
+
(`[====> ]`)、tqdm 的迭代速率(`it/s`)、step / epoch / phase 计数。只要出现进度
|
|
256
|
+
信号就算加载行,哪怕没有任何下载字样。
|
|
257
|
+
- **加载器命令**出现在任务标签或命令位置:`npm`/`pnpm`/`yarn` `install|ci|add`、
|
|
258
|
+
`apt(-get) install`、`dnf`/`yum`/`zypper install`、`pacman -S`、`snap`/`flatpak
|
|
259
|
+
install`、`dpkg -i`、`rpm -i`、`brew install`、`conda install`、`pip install`、
|
|
260
|
+
`docker pull|build`、`ffmpeg`、`cargo`/`go build`、`make`/`cmake`、
|
|
261
|
+
`dotnet build|restore`、`ollama pull`、macOS 的 `installer`/`pkgutil`,以及
|
|
262
|
+
加载 / 构建 / 编译 / 安装 / 渲染 / 转码 / 导出 / 训练 / 解压 等动词。
|
|
263
|
+
- **没有任何线索的长前台调用**在运行满 12 秒后晋升为不确定的加载行。
|
|
264
|
+
|
|
265
|
+
读取与搜索命令被排除:命令词是 `cat`/`Get-Content`/`grep`/`rg`/`find`/`ls`/`dir` 的
|
|
266
|
+
任务或命令,不会因为输出里恰好出现 `100%` 或 `eta` 就变成加载。仅靠描述也不够,因此
|
|
267
|
+
「Check build status」运行 `git status` 仍保持静默。后台任务的**标签**用更宽的历史线索
|
|
268
|
+
扫描(整段多行脚本):后台任务更少见,漏掉一次加载的代价高于多出一条横条。
|
|
269
|
+
|
|
270
|
+
只有下载专属证据——标签、tail 文本或前台分类器——才保留 `下载` 徽标;tqdm 训练任务属于
|
|
271
|
+
加载,不是下载。
|
|
272
|
+
|
|
273
|
+
### `run_with_progress` 工具
|
|
274
|
+
|
|
275
|
+
Host 半注册模型工具 `run_with_progress`,并注入一段短的全局系统提示,让模型在长操作时
|
|
276
|
+
使用它。由于子进程由插件持有,经由该工具执行的命令能把实时百分比/速度/剩余时间流进横条,
|
|
277
|
+
而普通前台 shell 调用对 Host 不暴露任何进度。
|
|
278
|
+
|
|
279
|
+
```json
|
|
280
|
+
{
|
|
281
|
+
"command": "curl -# -L -o model.safetensors https://example.com/model.safetensors",
|
|
282
|
+
"label": "Downloading model weights"
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
- **使用场景**:下载、安装、构建、模型加载、转码、数据集处理,以及任何预计超过约 10 秒
|
|
287
|
+
的命令。工具描述与全局提示都要求这类任务优先走本工具,而不是普通的 `ops_bash` /
|
|
288
|
+
`pwsh` 调用。
|
|
289
|
+
- **参数**:`command`(必填字符串,一条 shell 命令行)与 `label`(可选,横条上的短标签)。
|
|
290
|
+
- **执行位置**:在调用会话的工作目录中执行,相对路径因此按会话工作区解析;`stdout` 与
|
|
291
|
+
`stderr` 合并后边到边解析,行与其他来源一样通过 `/dsh-download-hud/state` 提供。
|
|
292
|
+
- **并发**:最多同时运行 4 条(跨会话合计);第 5 次调用返回 `Busy` 文本结果且不建行。
|
|
293
|
+
- **取消**:用户可在行上点**停止**,Host 杀掉整棵进程树,行变为 `killed`,工具返回
|
|
294
|
+
`status: killed`;取消工具调用(回合中止)走同一条路径。
|
|
295
|
+
- **返回**:`status`、`exit code`、`duration` 与命令输出。输出 12 KB 以内完整返回;超出时
|
|
296
|
+
保留开头 2 KB 与结尾 10 KB,并以 `[N characters truncated]` 标明截断。
|
|
297
|
+
- **保留进度输出**:提示要求 `curl -#` / `--progress-bar` 且绝不用 `-s`;`wget`/`pip`/
|
|
298
|
+
`npm`/`hf`/`aria2` 用默认进度输出;`ffmpeg` 加 `-progress pipe:1 -nostats`;PowerShell
|
|
299
|
+
不要设 `$ProgressPreference = 'SilentlyContinue'`。
|
|
300
|
+
- **优雅降级**:缺少 `tools` / `systemPrompt` 服务的 Host 仍能加载插件、保留横条,只跳过
|
|
301
|
+
工具注册或提示注入。
|
|
302
|
+
|
|
140
303
|
### 安装
|
|
141
304
|
|
|
142
305
|
需要 DeepSeek Harness `>=0.2.1-rc.2 <0.3.0-0`,以及 Web 或 Desktop profile
|
|
143
306
|
(插件同时包含 Host 半与浏览器 Client 半)。
|
|
144
307
|
|
|
145
|
-
1. 打开 **设置 → 插件**,安装 bundle `dsh-transfer-hud@1.
|
|
308
|
+
1. 打开 **设置 → 插件**,安装 bundle `dsh-transfer-hud@1.1.0`。
|
|
146
309
|
2. 或让 agent 调用:`plugin_manager` → `action: "install_bundle"`,
|
|
147
|
-
`target: "dsh-transfer-hud@1.
|
|
310
|
+
`target: "dsh-transfer-hud@1.1.0"`。
|
|
148
311
|
|
|
149
312
|
**安装或升级后请重启应用。** HMR 会应用新的 bundle,但该 profile 的 Host watch root 为空
|
|
150
313
|
(`root: []`),且替换已安装的包需要新的 JavaScript 模块代号,因此新的 Host 代码要重启后
|
|
151
314
|
才加载;Client 半随后走常规的 client HMR / 页面刷新。验证方式:重启后运行一个前台下载
|
|
152
|
-
(如 `curl -o file.bin <url
|
|
153
|
-
|
|
315
|
+
(如 `curl -o file.bin <url>`)、后台任务(`run_in_background: true`),或经
|
|
316
|
+
`run_with_progress` 执行一条长命令,输入框下方会出现进度条。移除该 bundle 即卸载。
|
|
154
317
|
|
|
155
318
|
### 配置
|
|
156
319
|
|
|
157
320
|
无。该行不需要配置,进度条自动出现,且只读取本 Harness 自身已有的活动。
|
|
158
321
|
|
|
322
|
+
### 跨平台
|
|
323
|
+
|
|
324
|
+
进度条与 `run_with_progress` 执行器本身与平台无关,不含原生代码——只使用 Node 的
|
|
325
|
+
`child_process` 与平台自带的进程控制命令。
|
|
326
|
+
|
|
327
|
+
- **Windows / macOS / Linux**:命令通过各平台默认 shell 执行(Windows 为 `ComSpec`,
|
|
328
|
+
POSIX 为 `/bin/sh`),普通可移植命令行无需改动。
|
|
329
|
+
- **取消会杀掉整棵进程树**:Windows 上调用 `taskkill /PID <pid> /T /F`;POSIX 上让 shell
|
|
330
|
+
成为独立进程组组长,先向整组发 `SIGTERM`,1.5 秒后再发 `SIGKILL`。两条路径都不会留下
|
|
331
|
+
孤儿进程。
|
|
332
|
+
- **命令识别覆盖两平台**:下载/加载分类器同时认识 POSIX 传输工具(`curl`、`wget`、
|
|
333
|
+
`aria2c`、`rsync`、`scp`、`hf`、`pip`……)与 Windows 工具(`Invoke-WebRequest` /
|
|
334
|
+
`iwr`、`Start-BitsTransfer`、`bitsadmin`、.NET `DownloadFile`),以及两套包管理器:
|
|
335
|
+
`apt`、`dnf`、`yum`、`zypper`、`pacman`、`snap`、`flatpak`、`dpkg`、`rpm`、`brew`,
|
|
336
|
+
另有 `apk`、`conda`、`pip`、npm 及各语言包管理器。
|
|
337
|
+
- **验证方式**:Windows 在开发机上做 live 验证;POSIX 行为由注入平台的单测覆盖,断言
|
|
338
|
+
进程组 `SIGTERM`→`SIGKILL` 与 `taskkill /T /F` 两条分支——因此不需要在 CI 中准备
|
|
339
|
+
macOS/Linux 真机。
|
|
340
|
+
|
|
159
341
|
### 限制
|
|
160
342
|
|
|
161
|
-
-
|
|
162
|
-
|
|
343
|
+
- **前台行是不确定的,且要 12 秒后才出现**:前台工具调用不向 Host 暴露运行中输出,所以
|
|
344
|
+
没有百分比/速度/剩余时间,只表示「正在干活」;晋升行要等 12 秒,此时调用可能已接近结束。
|
|
163
345
|
- **按会话隔离**:行属于进度条挂载的会话;子代理/队友会话发起的下载不会显示在父会话中。
|
|
164
|
-
-
|
|
346
|
+
- **不透明脚本需要提示**:见上文「什么算作加载」。
|
|
165
347
|
- **没有进度输出就没有进度**:`Invoke-WebRequest` 在
|
|
166
348
|
`$ProgressPreference = 'SilentlyContinue'` 下不打印可解析内容;行仍会按标签提示出现,
|
|
167
349
|
但保持不确定状态。
|
|
168
|
-
-
|
|
350
|
+
- **误报的代价只有一条横条**:任务 tail 里出现进度形状(`100%`、`[===]`、`eta`)的任务都
|
|
351
|
+
会被接纳;读取/搜索已抑制,但普通脚本打印这类文本时仍可能出行。
|
|
352
|
+
- **停止只作用于后台任务与 `run_with_progress` 行**:普通前台命令无法从进度条取消
|
|
353
|
+
(Host 没有它的句柄);需要可取消时请使用该工具。
|
|
354
|
+
- **该工具没有默认超时**:挂死的命令会一直保持 `running`,直到用户停止或回合被取消
|
|
355
|
+
(工具调用自身的超时仍叠加生效)。
|
|
356
|
+
- **模型可能绕开工具**:全局提示只是引导而非强制;直接发起的 shell 调用回落到任务/前台
|
|
357
|
+
分类器,普通前台命令依旧没有百分比。
|
|
358
|
+
- **行会短暂保留**:结束后的前台行约保留 8 秒、`run_with_progress` 行保留 30 秒,因此
|
|
359
|
+
命令结束后行可能还短暂可见。
|
|
360
|
+
- **Host 代码重载需要重载插件**:该 profile 的 HMR watch root 为空(`root: []`),修改
|
|
361
|
+
运行中应用的 Host 半需要切换 bundle 或重启应用;Client 半走常规 client HMR。
|
|
169
362
|
- 界面渲染(进度条/浮层布局)只能在浏览器中验证。
|
|
170
363
|
|
|
171
364
|
### 开发
|
|
172
365
|
|
|
173
|
-
Host
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
node plugins/dsh-transfer-hud/test/client.test.mjs
|
|
178
|
-
```
|
|
366
|
+
源码仓库的 `test/` 下包含 Host、工具与 Client 三套测试,该目录不包含在发布的 tarball 中。
|
|
367
|
+
在源码检出中运行 `npm test`:Host 套件在 mock 上下文下覆盖分类、解析、12 秒晋升、会话隔离
|
|
368
|
+
与行数上限,真实 `run_with_progress` 执行器用短命的 Node 子进程覆盖实时行、停止、取消与
|
|
369
|
+
12 KB 截断,Client 工厂在 mock 浏览器全局对象下运行。无需额外依赖。
|
|
179
370
|
|
|
180
371
|
### 许可证
|
|
181
372
|
|
|
182
|
-
MIT。
|
|
373
|
+
MIT。Copyright (c) 2026 xiuerhuahuazi。
|
package/client.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Download HUD — Client half.
|
|
3
3
|
*
|
|
4
|
-
* A thin, space-free strip below the message composer that appears
|
|
5
|
-
* background job
|
|
6
|
-
*
|
|
4
|
+
* A thin, space-free strip below the message composer that appears while any
|
|
5
|
+
* background job reports progress: downloads with a meter (percent, throughput,
|
|
6
|
+
* remaining time) and long-running loads/jobs without one (running + elapsed).
|
|
7
|
+
* Clicking it opens a details popover with per-job progress and a stop action.
|
|
8
|
+
*
|
|
9
|
+
* Rows carry `category: 'download' | 'load'`. Rows from an older Host that only
|
|
10
|
+
* carry `download === true` are still rendered, as downloads. Rows with neither
|
|
11
|
+
* are ignored, so unrelated background jobs never leak into the HUD.
|
|
7
12
|
*
|
|
8
13
|
* Data comes from the Host half's same-origin route; this module renders only.
|
|
9
14
|
*/
|
|
@@ -23,8 +28,8 @@ window.__ModuleLoader__.load({
|
|
|
23
28
|
|
|
24
29
|
const TEXT = {
|
|
25
30
|
en: {
|
|
26
|
-
live: '
|
|
27
|
-
jobs: '
|
|
31
|
+
live: 'Running',
|
|
32
|
+
jobs: 'tasks',
|
|
28
33
|
stopped: 'Stopped',
|
|
29
34
|
done: 'Done',
|
|
30
35
|
failed: 'Failed',
|
|
@@ -32,13 +37,16 @@ window.__ModuleLoader__.load({
|
|
|
32
37
|
stop: 'Stop',
|
|
33
38
|
stopping: 'Stopping…',
|
|
34
39
|
noProgress: 'running',
|
|
35
|
-
details: '
|
|
40
|
+
details: 'Task progress',
|
|
36
41
|
elapsed: 'elapsed',
|
|
37
42
|
command: 'command',
|
|
43
|
+
aria: 'Running tasks',
|
|
44
|
+
kindDownload: 'download',
|
|
45
|
+
kindLoad: 'load',
|
|
38
46
|
},
|
|
39
47
|
zh: {
|
|
40
|
-
live: '
|
|
41
|
-
jobs: '
|
|
48
|
+
live: '进行中',
|
|
49
|
+
jobs: '个任务',
|
|
42
50
|
stopped: '已停止',
|
|
43
51
|
done: '已完成',
|
|
44
52
|
failed: '失败',
|
|
@@ -46,9 +54,12 @@ window.__ModuleLoader__.load({
|
|
|
46
54
|
stop: '取消',
|
|
47
55
|
stopping: '正在停止…',
|
|
48
56
|
noProgress: '运行中',
|
|
49
|
-
details: '
|
|
57
|
+
details: '任务进度',
|
|
50
58
|
elapsed: '已用时',
|
|
51
59
|
command: '命令',
|
|
60
|
+
aria: '进行中的任务',
|
|
61
|
+
kindDownload: '下载',
|
|
62
|
+
kindLoad: '加载',
|
|
52
63
|
},
|
|
53
64
|
}
|
|
54
65
|
|
|
@@ -84,6 +95,9 @@ window.__ModuleLoader__.load({
|
|
|
84
95
|
.ddh-stop:disabled{opacity:.5;cursor:default}
|
|
85
96
|
.ddh-row-sub{display:flex;gap:10px;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;color:var(--dsw-alias-label-tertiary);font-size:11px;font-variant-numeric:tabular-nums}
|
|
86
97
|
.ddh-row-bar{height:2px;border-radius:1px;background:var(--dsw-alias-border-l1);overflow:hidden;margin-top:2px}
|
|
98
|
+
.ddh-kind{flex:none;font-size:10px;line-height:14px;padding:0 4px;border-radius:4px;border:0.5px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);white-space:nowrap}
|
|
99
|
+
.ddh-kind-download{color:var(--dsw-alias-label-secondary)}
|
|
100
|
+
.ddh-kind-load{background:var(--dsw-alias-bg-layer-2);border-color:transparent;color:var(--dsw-alias-brand-primary)}
|
|
87
101
|
.ddh-note{padding:8px 12px;color:var(--dsw-alias-state-error-primary);font-size:11px}
|
|
88
102
|
`
|
|
89
103
|
|
|
@@ -130,6 +144,16 @@ window.__ModuleLoader__.load({
|
|
|
130
144
|
return job.status === 'running' || job.status === 'stopping'
|
|
131
145
|
}
|
|
132
146
|
|
|
147
|
+
// The Host labels each row `category: 'download' | 'load'`. Older Host
|
|
148
|
+
// halves only sent `download: true`; treat those as downloads for
|
|
149
|
+
// compatibility. Anything else is not a progress task and stays hidden.
|
|
150
|
+
function categoryOf(job) {
|
|
151
|
+
if (!job || typeof job !== 'object') return ''
|
|
152
|
+
if (job.category === 'download' || job.category === 'load') return job.category
|
|
153
|
+
if (job.download === true) return 'download'
|
|
154
|
+
return ''
|
|
155
|
+
}
|
|
156
|
+
|
|
133
157
|
function statusText(job, text) {
|
|
134
158
|
if (job.status === 'completed') return text.done
|
|
135
159
|
if (job.status === 'failed') return text.failed
|
|
@@ -141,7 +165,7 @@ window.__ModuleLoader__.load({
|
|
|
141
165
|
function visibleJobs(state, now) {
|
|
142
166
|
const jobs = state && Array.isArray(state.jobs) ? state.jobs : []
|
|
143
167
|
return jobs.filter((job) => {
|
|
144
|
-
if (!job
|
|
168
|
+
if (!categoryOf(job)) return false
|
|
145
169
|
if (isLive(job)) return true
|
|
146
170
|
const finishedAt = typeof job.finishedAt === 'number' ? job.finishedAt : 0
|
|
147
171
|
return finishedAt > 0 && now - finishedAt < LINGER_MS
|
|
@@ -319,6 +343,7 @@ window.__ModuleLoader__.load({
|
|
|
319
343
|
h('span', { key: 'count' }, `${jobs.length}`),
|
|
320
344
|
]),
|
|
321
345
|
h('div', { key: 'rows', className: 'ddh-rows' }, jobs.map((job) => {
|
|
346
|
+
const category = categoryOf(job)
|
|
322
347
|
const dotSuffix = job.status === 'completed' ? ' ddh-dot-done'
|
|
323
348
|
: job.status === 'failed' ? ' ddh-dot-failed'
|
|
324
349
|
: isLive(job) ? '' : ' ddh-dot-idle'
|
|
@@ -341,6 +366,10 @@ window.__ModuleLoader__.load({
|
|
|
341
366
|
h('div', { key: 'top', className: 'ddh-row-top' }, [
|
|
342
367
|
h('span', { key: 'dot', className: `ddh-dot${dotSuffix}` }),
|
|
343
368
|
h('span', { key: 'name', className: 'ddh-row-name', title: job.fullLabel || job.label }, job.label),
|
|
369
|
+
h('span', {
|
|
370
|
+
key: 'kind',
|
|
371
|
+
className: `ddh-kind ddh-kind-${category}`,
|
|
372
|
+
}, category === 'load' ? text.kindLoad : text.kindDownload),
|
|
344
373
|
rowPercent ? h('span', { key: 'pct', className: 'ddh-row-pct' }, rowPercent) : null,
|
|
345
374
|
isLive(job) && job.killable !== false
|
|
346
375
|
? h('button', {
|
|
@@ -379,6 +408,7 @@ window.__ModuleLoader__.load({
|
|
|
379
408
|
type: 'button',
|
|
380
409
|
className: 'ddh-strip',
|
|
381
410
|
'aria-expanded': open ? 'true' : 'false',
|
|
411
|
+
'aria-label': text.aria,
|
|
382
412
|
onClick: () => setOpen((previous) => !previous),
|
|
383
413
|
}, [
|
|
384
414
|
h('span', { key: 'line', className: 'ddh-line' }, [
|
|
@@ -421,6 +451,8 @@ window.__ModuleLoader__.load({
|
|
|
421
451
|
|
|
422
452
|
return {
|
|
423
453
|
inject: ['slots', 'locale'],
|
|
454
|
+
// White-box seam for test/client.test.mjs only; the loader ignores it.
|
|
455
|
+
__test: { TEXT, categoryOf, visibleJobs },
|
|
424
456
|
apply(ctx) {
|
|
425
457
|
const text = TEXT[pickLocale(ctx)]
|
|
426
458
|
const Component = createDownloadHud(ctx, text)
|