dsh-job-progress 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-job-progress contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,332 @@
1
+ <div align="center">
2
+
3
+ <img src="assets/logo.png" alt="dsh-job-progress" width="150" />
4
+
5
+ # dsh-job-progress
6
+
7
+ **How far along is it?** — a floating ball in the session that owns the job answers that.
8
+
9
+ A DeepSeek Harness plugin showing live progress for long-running background jobs:
10
+ downloads, model conversions, renders, batch jobs — anything that runs in the background.
11
+
12
+ [![License: MIT](https://img.shields.io/badge/License-MIT-2f7de1.svg)](./LICENSE)
13
+ [![Platform: DSH web](https://img.shields.io/badge/platform-DSH%20web-334eac.svg)](#compatibility)
14
+ [![Runtime: Node 22+](https://img.shields.io/badge/runtime-Node%2022%2B-3c873a.svg)](#compatibility)
15
+ [![Zero config](https://img.shields.io/badge/setup-zero%20config-1c7a54.svg)](#features)
16
+ [![PRs: welcome](https://img.shields.io/badge/PRs-welcome-7096d1.svg)](#contributing)
17
+ [![GitHub stars](https://img.shields.io/github/stars/Rice00/dsh-job-progress?style=flat&label=stars&color=7096d1)](https://github.com/Rice00/dsh-job-progress/stargazers)
18
+
19
+ ![The floating ball and its task panel](assets/screenshot.png)
20
+
21
+ <sub>The ball floats over the conversation, badges the running count, and expands into the task panel.</sub>
22
+
23
+ [Features](#features) · [Install](#install) · [Quick start](#quick-start) · [Protocol](#the-progress-protocol) · [Verify](#verify-the-install) · [Troubleshooting](#troubleshooting) · [**中文**](./README.zh.md)
24
+
25
+ </div>
26
+
27
+ ---
28
+
29
+ ## Features
30
+
31
+ | | |
32
+ |---|---|
33
+ | 🟢 **Floating ball** | Appears only in sessions that have something to watch. The badge counts running tasks (`9+` above nine). |
34
+ | 🖱️ **Draggable** | Put it wherever you like; the position survives page reloads. |
35
+ | 📊 **Live panel** | One row per job: state dot, label, progress bar, `%`, `done/total`, speed, ETA, elapsed time. |
36
+ | 🧹 **Clear finished** | Deletes this session's finished progress files. Running entries are never touched. |
37
+ | 🤝 **No cooperation needed** | Jobs come from the registry, so an unreported job still shows up — just without a bar. |
38
+ | 🔒 **Session-scoped** | A session only ever sees its own jobs. Other sessions' work never leaks in. |
39
+ | 🧩 **Any job kind** | Shell commands, ComfyUI renders, anything that registers a background job. |
40
+ | 🔑 **Zero config** | No credentials, no tokens, no network calls: it reads the job registry and its own files. |
41
+ | 📦 **No build step** | The client plugin is a hand-written module-loader module; no bundler output to keep in sync. |
42
+
43
+ ## Install
44
+
45
+ ### From a local folder (recommended)
46
+
47
+ ```bash
48
+ git clone https://github.com/Rice00/dsh-job-progress.git
49
+ dsh plugin --profile <profile> add link:/abs/path/to/dsh-job-progress # the checkout from above
50
+ ```
51
+
52
+ The bundle patch inserts one row (`job-progress`) into the profile. Then **restart that profile** —
53
+ host plugin modules are cached in-process, so a running harness will not pick the row up.
54
+
55
+ `link:` is a live link: **edits to the source folder take effect immediately** (after a restart for
56
+ host code, after a refresh for UI code), but the folder must not be moved afterwards. To copy the
57
+ files instead, use `file:/abs/path/to/dsh-job-progress` — then later edits need a re-install.
58
+
59
+ ### From GitHub or npm
60
+
61
+ ```bash
62
+ dsh plugin --profile <profile> add github:Rice00/dsh-job-progress
63
+ dsh plugin --profile <profile> add dsh-job-progress # once published to npm
64
+ ```
65
+
66
+ ### For an AI assistant (copy-paste)
67
+
68
+ ```
69
+ Please install the DSH plugin dsh-job-progress for me:
70
+
71
+ 1) Install it into the web profile, from GitHub:
72
+ dsh plugin --profile web add github:Rice00/dsh-job-progress
73
+ or from a local checkout (absolute path of the folder):
74
+ dsh plugin --profile web add link:<absolute-path>
75
+ 2) Restart that profile — host plugin modules are cached in-process, so the new row is
76
+ only picked up on boot. (UI-only changes just need a browser refresh.)
77
+ 3) Verify:
78
+ node <absolute-path>/test/preflight-client.mjs → must print "ALL PASS (12)"
79
+ the host log must contain: job-progress: mounted, progress root …
80
+ On Windows the host log is under %APPDATA%\DSH Desktop\logs\host\.
81
+ If DSH starts normally and no new errors appear in the renderer console, you are done.
82
+ ```
83
+
84
+ ## Quick start
85
+
86
+ Nothing to configure — run something in the background and the ball appears:
87
+
88
+ ```bash
89
+ node download.mjs https://example.com/model.safetensors # run_in_background
90
+ ```
91
+
92
+ To get a progress bar, have the producer report numbers (one file, any language):
93
+
94
+ ```js
95
+ import { track } from 'dsh-job-progress/progress';
96
+
97
+ const t = track({ label: 'model.safetensors', total: 66000000 });
98
+ t.update(bytesSoFar); // speed + ETA are measured for you
99
+ t.phase('verifying');
100
+ t.finish('done'); // or t.finish('failed', 'sha256 mismatch')
101
+ ```
102
+
103
+ Or straight from a shell, no import needed:
104
+
105
+ ```bash
106
+ node <plugin>/lib/dsh-progress.mjs set --label model.safetensors --done 12 --total 100
107
+ node <plugin>/lib/dsh-progress.mjs done --key model.safetensors
108
+ ```
109
+
110
+ ## How it works
111
+
112
+ ```
113
+ producer (your script) host plugin client plugin (browser)
114
+ track({ label, total }) ──▶ reads <DSH_HOME>/job-progress/ ──▶ polls jobProgress/snapshot
115
+ writes <key>.json <DSH_SESSION_ID>/*.json every 2 s and renders the
116
+ + registry snapshots ball, badge and panel
117
+ ```
118
+
119
+ The ball's position and the per-session "already cleared" list live in `localStorage`.
120
+
121
+ ## The progress protocol
122
+
123
+ A producer writes one JSON file per task into the session's progress directory:
124
+
125
+ ```
126
+ <DSH_HOME>/job-progress/<DSH_SESSION_ID>/<key>.json
127
+ ```
128
+
129
+ Both environment values already exist in every agent shell call, so a producer never has to be
130
+ told a path.
131
+
132
+ ```json
133
+ {
134
+ "label": "anima_preview_5B.safetensors",
135
+ "done": 4187599360,
136
+ "total": 9972879360,
137
+ "unit": "bytes",
138
+ "speed": 13107200,
139
+ "eta": 440,
140
+ "phase": "download",
141
+ "status": "running",
142
+ "note": "",
143
+ "jobId": "bash-3",
144
+ "updatedAt": 1758000000000
145
+ }
146
+ ```
147
+
148
+ | field | meaning |
149
+ |---|---|
150
+ | `label` | shown in the panel; also how an entry is matched to a registry job |
151
+ | `done` / `total` | units completed; `total: 0` renders as "running, unknown size" |
152
+ | `unit` | `bytes` (default, rendered KiB/MiB/GiB) or `count` (rendered raw) |
153
+ | `speed` / `eta` | optional; nothing is invented when they are absent |
154
+ | `phase` | free text; `merging` and `verifying` get built-in labels |
155
+ | `status` | `running` \| `done` \| `failed` |
156
+ | `jobId` | optional; pins the entry to a registry job instead of matching by label |
157
+ | `updatedAt` | ms-epoch heartbeat |
158
+
159
+ **Heartbeat.** While `status` is `running`, refresh `updatedAt` at least every ~15 s. A live entry
160
+ that stops refreshing is treated as gone, because a producer that stopped writing is
161
+ indistinguishable from one that crashed. `done` / `failed` entries stay visible for two minutes.
162
+
163
+ **Atomic writes.** Write `<file>.tmp`, then rename over the target; a reader must never observe a
164
+ half-written record.
165
+
166
+ ### CLI reference
167
+
168
+ | command | effect |
169
+ |---|---|
170
+ | `set --label <name> [--done N] [--total N] [--unit bytes\|count] [--phase P]` | create or continue a record |
171
+ | `done --key <key> [--note "..."]` | mark it finished |
172
+ | `failed --key <key> --note "..."` | mark it failed |
173
+ | `dir` | print the resolved progress directory |
174
+ | `clear [--key <key>]` | delete this session's progress files |
175
+
176
+ ## Clearing
177
+
178
+ **Clear finished** deletes every finished progress file of this session; running entries are never
179
+ touched.
180
+
181
+ Registry jobs cannot be deleted — they are read-only projections — so terminal job rows are
182
+ recorded in a per-session ignore list and stop appearing. That key includes the job's `startedAt`,
183
+ because job ids are `<kind>-N` counted per process and a restart would otherwise let a stale ignore
184
+ entry hide a brand-new job.
185
+
186
+ The host refuses a session id containing a path separator or `..`, and requires the session to
187
+ actually exist: the id doubles as a directory name, so this is a path-traversal fence, not a
188
+ formality. If the host does not answer, the UI reports the failure and changes nothing — it never
189
+ claims a clear that did not happen.
190
+
191
+ ## Compatibility
192
+
193
+ | | |
194
+ |---|---|
195
+ | **DSH** | tested on `0.1.5-rc.2`; works in the browser GUI and in the web host embedded in the Desktop app |
196
+ | **Profile** | any profile that carries the web UI (`web`, and `desktop` when the same row is added there) |
197
+ | **Runtime** | Node 22+ (the plugin adds no dependencies of its own) |
198
+ | **Job kinds** | anything registered in `ctx.jobs`, regardless of kind |
199
+ | **Requirements** | no credentials, no tokens, no network access |
200
+
201
+ ## Verify the install
202
+
203
+ ```bash
204
+ # 1) the client plugin: module contract, slot registration, one render pass
205
+ node test/preflight-client.mjs # → ALL PASS (12)
206
+
207
+ # 2) the host plugin mounted (Windows Desktop app)
208
+ Select-String -Path "$env:APPDATA\DSH Desktop\logs\host\dsh-*.log" -Pattern 'job-progress'
209
+ # → dsh-job-progress: mounted, progress root …\.dsh\job-progress
210
+
211
+ # 3) the row is in the profile
212
+ dsh --profile <profile> --dump-config | Select-String 'job-progress'
213
+ ```
214
+
215
+ Then start a background job in any session and hover the ball.
216
+
217
+ ## Troubleshooting
218
+
219
+ | symptom | cause | fix |
220
+ |---|---|---|
221
+ | The ball never appears | the session has no background jobs and no progress files | start a background job, or write a progress file |
222
+ | UI changes seem ignored | the client plugin is fetched on page load | refresh the page (F5) |
223
+ | Changes to `lib/index.js` seem ignored | host plugin modules are cached in-process | restart the profile |
224
+ | `Clear failed: host not ready` | the running host predates the `clear` endpoint | restart the profile |
225
+ | Something renders as a squircle | the app's global `corner-shape` | see "For plugin authors" below |
226
+ | "Did the row even load?" | check the host log | `dsh-job-progress: mounted, progress root …` |
227
+ | A task disappeared before it finished | the producer stopped writing for 15 s (crashed or killed) | make sure the producer keeps the heartbeat, or set `DSH_PROGRESS` writers accordingly |
228
+
229
+ <details>
230
+ <summary><b>Design notes — why there is a file protocol at all</b></summary>
231
+
232
+ Two properties of the harness shaped this design. Both were verified against the shipped packages,
233
+ not assumed:
234
+
235
+ 1. **The job registry carries no progress.** `ctx.jobs` records an id, kind, label, status
236
+ (`running → stopping → completed | killed | failed`) and timestamps — no field a producer could
237
+ fill with a percentage.
238
+ 2. **Job output cannot be sampled.** `ShellProcess.readOutput` is *incremental* (consecutive reads
239
+ never repeat output) and `ctx.jobs.read()` marks a terminal job as reported. A panel that polled
240
+ job output to parse percentages would steal output the model is about to read and swallow its
241
+ completion notice. This plugin never calls `read()`; it reads only **registry snapshots** (pure
242
+ projections) plus its own files.
243
+
244
+ So progress has to be reported by whatever does the work, while job discovery stays automatic.
245
+
246
+ </details>
247
+
248
+ <details>
249
+ <summary><b>For plugin authors — circles in this GUI</b></summary>
250
+
251
+ The app sets **`corner-shape: superellipse(1.5)` globally**. Under that setting *every*
252
+ `border-radius` — including `50%` — paints as a **squircle**, not as a circular arc. A computed
253
+ `border-radius: 50%` is therefore not evidence that something renders as a circle. The app's own
254
+ stylesheets opt specific elements back with `corner-shape: round` (spinners, dots, switch thumbs).
255
+ Add that declaration when you need a true circle:
256
+
257
+ ```css
258
+ border-radius: 50%;
259
+ corner-shape: round; /* omit it and you get a squircle — which may well be what you want */
260
+ ```
261
+
262
+ This plugin deliberately keeps the squircle: it matches the GUI's own design language.
263
+
264
+ </details>
265
+
266
+ <details>
267
+ <summary><b>Development</b></summary>
268
+
269
+ ```bash
270
+ node test/preflight-client.mjs # no install, no browser needed
271
+ ```
272
+
273
+ The client plugin runs in the browser, where its errors land in a renderer console that is not
274
+ written to disk — so a broken client plugin looks exactly like a plugin that never loaded. The
275
+ preflight loads `lib/client.js` through a stubbed module loader and exercises the factory, `apply`,
276
+ slot registration and one render pass against a stub React. It caught a missing `module`/`exports`
277
+ declaration and a render-gate regression while this plugin was being written.
278
+
279
+ ```
280
+ cordis.patch.yml bundle patch: inserts the `job-progress` row
281
+ package.json manifest: bundle patch + web client plugin
282
+ lib/client.js client plugin: ball, drag, panel, clear
283
+ lib/index.js host plugin: registry snapshots + progress dir + jobProgress/clear
284
+ lib/dsh-progress.mjs producer side: protocol, helper, CLI
285
+ test/preflight-client.mjs 12 checks for the client plugin
286
+ assets/ logo and screenshot used by this README
287
+ ```
288
+
289
+ The host plugin registers a Typert Remote service (`jobProgress`) reached through the standard
290
+ `/api` gateway as `jobProgress/snapshot` and `jobProgress/clear`. Set `debug: true` on the
291
+ `job-progress` row to have the snapshot response carry which registries were found and how many
292
+ entries and jobs were matched.
293
+
294
+ </details>
295
+
296
+ ## Uninstall
297
+
298
+ ```bash
299
+ dsh plugin --profile <profile> remove dsh-job-progress
300
+ ```
301
+
302
+ Progress files written by producers stay under `<DSH_HOME>/job-progress/` — delete them yourself if
303
+ you no longer need them. The plugin never deletes anything outside that directory.
304
+
305
+ ## Roadmap
306
+
307
+ - [ ] Jobs owned by subagents (today a session sees its own owner's jobs only)
308
+ - [ ] Move the icon and any large assets to a host route instead of an inlined data URI
309
+ - [ ] Richer phases, so a producer can announce arbitrary stages with labels
310
+ - [ ] More translations of this README
311
+
312
+ ## Contributing
313
+
314
+ [Issues](https://github.com/Rice00/dsh-job-progress/issues) and pull requests are welcome. Before
315
+ opening a PR:
316
+
317
+ ```bash
318
+ node test/preflight-client.mjs # must print ALL PASS
319
+ ```
320
+
321
+ Keep the client plugin dependency-free (React only) and run the preflight after any edit — it exists
322
+ because client-side mistakes are otherwise invisible.
323
+
324
+ ## License
325
+
326
+ [MIT](./LICENSE)
327
+
328
+ <div align="center">
329
+
330
+ MIT License © dsh-job-progress contributors
331
+
332
+ </div>
package/README.zh.md ADDED
@@ -0,0 +1,297 @@
1
+ <div align="center">
2
+
3
+ <img src="assets/logo.png" alt="dsh-job-progress" width="150" />
4
+
5
+ # dsh-job-progress
6
+
7
+ **跑到哪了?**——让我们快速查询当前进度(为每个急哭的人设计)。
8
+
9
+ 一个 DeepSeek Harness 插件:为长时间运行的后台任务显示实时进度。下载、模型转换、渲染、批量任务,都算。
10
+
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-2f7de1.svg)](./LICENSE)
12
+ [![Platform: DSH web](https://img.shields.io/badge/platform-DSH%20web-334eac.svg)](#兼容性)
13
+ [![Runtime: Node 22+](https://img.shields.io/badge/runtime-Node%2022%2B-3c873a.svg)](#兼容性)
14
+ [![Zero config](https://img.shields.io/badge/setup-zero%20config-1c7a54.svg)](#功能)
15
+ [![PRs: welcome](https://img.shields.io/badge/PRs-welcome-7096d1.svg)](#参与贡献)
16
+ [![GitHub stars](https://img.shields.io/github/stars/Rice00/dsh-job-progress?style=flat&label=stars&color=7096d1)](https://github.com/Rice00/dsh-job-progress/stargazers)
17
+
18
+ ![悬浮小球与任务面板](assets/screenshot.png)
19
+
20
+ <sub>小球浮在对话上方,角标显示正在跑的数量,点开就是任务面板。</sub>
21
+
22
+ [功能](#功能) · [安装](#安装) · [快速上手](#快速上手) · [协议](#进度协议) · [验证](#验证安装) · [常见问题](#常见问题) · [**English**](./README.md)
23
+
24
+ </div>
25
+
26
+ ---
27
+
28
+ ## 功能
29
+
30
+ | | |
31
+ |---|---|
32
+ | 🟢 **悬浮小球** | 只在有任务可看的会话里出现。角标是正在跑的任务数(超过 9 显示 `9+`)。 |
33
+ | 🖱️ **可以拖** | 摆哪儿都行,位置刷新页面后还在。 |
34
+ | 📊 **实时面板** | 一行一个任务:状态点、名称、进度条、百分比、`已完成/总量`、速度、剩余时间、已用时间。 |
35
+ | 🧹 **清除已完成** | 删掉本会话已完成的进度文件,正在跑的绝不动。 |
36
+ | 🤝 **不靠自觉** | 任务列表来自作业登记表,所以没上报进度的任务照样会出现,只是没有进度条。 |
37
+ | 🔒 **会话隔离** | 一个会话只能看到自己的任务,别的会话的东西不会漏进来。 |
38
+ | 🧩 **不限任务类型** | shell 命令、ComfyUI 渲染,任何登记了后台任务的都行。 |
39
+ | 🔑 **零配置** | 不要凭据、不要令牌、不联网:它只读作业登记表和自己写的文件。 |
40
+ | 📦 **无需构建** | 前端插件是手写的模块加载器模块,没有打包产物要同步。 |
41
+
42
+ ## 安装
43
+
44
+ ### 本地目录(推荐)
45
+
46
+ ```bash
47
+ git clone https://github.com/Rice00/dsh-job-progress.git
48
+ dsh plugin --profile <profile> add link:/abs/path/to/dsh-job-progress # 指向上面检出的目录
49
+ ```
50
+
51
+ bundle 补丁会往 profile 里插一行(`job-progress`)。**然后重启这个 profile**——宿主插件模块在进程内缓存,运行中的 harness 不会自动读到新行。
52
+
53
+ `link:` 是活链接:**改源码立刻生效**(宿主代码重启后生效,界面代码刷新后生效),但装完之后不能挪动这个目录。想连文件一起复制走,用 `file:/abs/path/to/dsh-job-progress`,代价是以后每次改都得重新装一遍。
54
+
55
+ ### 从 GitHub 或 npm 安装
56
+
57
+ ```bash
58
+ dsh plugin --profile <profile> add github:Rice00/dsh-job-progress
59
+ dsh plugin --profile <profile> add dsh-job-progress # 发布到 npm 之后
60
+ ```
61
+
62
+ ### 交给 AI 助手(直接复制)
63
+
64
+ ```
65
+ 请帮我安装 DSH 插件 dsh-job-progress:
66
+
67
+ 1) 装进 web profile,两种来源任选:
68
+ 从 GitHub:
69
+ dsh plugin --profile web add github:Rice00/dsh-job-progress
70
+ 或从本地检出(填这个文件夹的绝对路径):
71
+ dsh plugin --profile web add link:<绝对路径>
72
+ 2) 重启该 profile——宿主插件模块在进程内缓存,新行只在启动时读。
73
+ (只改界面的话,刷新浏览器就够了。)
74
+ 3) 验证:
75
+ node <绝对路径>/test/preflight-client.mjs → 应输出 "ALL PASS (12)"
76
+ 宿主日志里应出现: job-progress: mounted, progress root …
77
+ Windows 下宿主日志在 %APPDATA%\DSH Desktop\logs\host\。
78
+ 只要 DSH 能正常启动、渲染进程控制台没有新报错,就算装好了。
79
+ ```
80
+
81
+ ## 快速上手
82
+
83
+ 没有任何要配置的东西——后台跑点活,小球就出来了:
84
+
85
+ ```bash
86
+ node download.mjs https://example.com/model.safetensors # 后台运行
87
+ ```
88
+
89
+ 想让进度条动起来,让干活的那边报几个数就行(一个文件,什么语言都行):
90
+
91
+ ```js
92
+ import { track } from 'dsh-job-progress/progress';
93
+
94
+ const t = track({ label: 'model.safetensors', total: 66000000 });
95
+ t.update(bytesSoFar); // 速度和剩余时间会自动算
96
+ t.phase('verifying');
97
+ t.finish('done'); // 或 t.finish('failed', 'sha256 校验失败')
98
+ ```
99
+
100
+ 不想改代码,直接在 shell 里报也行:
101
+
102
+ ```bash
103
+ node <plugin>/lib/dsh-progress.mjs set --label model.safetensors --done 12 --total 100
104
+ node <plugin>/lib/dsh-progress.mjs done --key model.safetensors
105
+ ```
106
+
107
+ ## 工作原理
108
+
109
+ ```
110
+ 产出方(你的脚本) 宿主插件 前端插件(浏览器)
111
+ track({ label, total }) ──▶ 读 <DSH_HOME>/job-progress/ ──▶ 每 2 秒取一次 jobProgress/snapshot
112
+ 写 <key>.json <DSH_SESSION_ID>/*.json 渲染小球、角标和面板
113
+ + 作业登记表快照
114
+ ```
115
+
116
+ 小球的位置和"哪些已经清掉了"记在 `localStorage` 里。
117
+
118
+ ## 进度协议
119
+
120
+ 一个任务一个 JSON 文件,写在会话自己的进度目录下:
121
+
122
+ ```
123
+ <DSH_HOME>/job-progress/<DSH_SESSION_ID>/<key>.json
124
+ ```
125
+
126
+ 这两个环境变量在每次 agent shell 调用里都已经存在,所以产出方不需要任何人告诉它路径。
127
+
128
+ ```json
129
+ {
130
+ "label": "anima_preview_5B.safetensors",
131
+ "done": 4187599360,
132
+ "total": 9972879360,
133
+ "unit": "bytes",
134
+ "speed": 13107200,
135
+ "eta": 440,
136
+ "phase": "download",
137
+ "status": "running",
138
+ "note": "",
139
+ "jobId": "bash-3",
140
+ "updatedAt": 1758000000000
141
+ }
142
+ ```
143
+
144
+ | 字段 | 含义 |
145
+ |---|---|
146
+ | `label` | 面板上显示的名字;也是按名字匹配作业登记表的依据 |
147
+ | `done` / `total` | 已完成量;`total: 0` 显示成"正在跑,总量未知" |
148
+ | `unit` | `bytes`(默认,按 KiB/MiB/GiB 显示)或 `count`(原样显示) |
149
+ | `speed` / `eta` | 可选;没有就不编 |
150
+ | `phase` | 自由文本;`merging`、`verifying` 有内置译文 |
151
+ | `status` | `running` \| `done` \| `failed` |
152
+ | `jobId` | 可选;直接钉住某个作业,而不是按名字匹配 |
153
+ | `updatedAt` | 心跳,毫秒时间戳 |
154
+
155
+ **心跳。** `status` 是 `running` 时,至少要每 15 秒刷新一次 `updatedAt`。停止刷新的活条目会被当作已经没了——一个不再写文件的产出方,和一个崩掉的产出方,从外面看不出区别。`done` / `failed` 的条目会多留两分钟。
156
+
157
+ **原子写入。** 先写 `<file>.tmp` 再改名覆盖目标文件;读的人永远不该看到半截记录。
158
+
159
+ ### 命令行
160
+
161
+ | 命令 | 作用 |
162
+ |---|---|
163
+ | `set --label <名字> [--done N] [--total N] [--unit bytes\|count] [--phase P]` | 新建或续写一条记录 |
164
+ | `done --key <key> [--note "..."]` | 标记完成 |
165
+ | `failed --key <key> --note "..."` | 标记失败 |
166
+ | `dir` | 打印实际用的进度目录 |
167
+ | `clear [--key <key>]` | 删除本会话的进度文件 |
168
+
169
+ ## 清除
170
+
171
+ **清除已完成**会删掉本会话所有已完成的进度文件,正在跑的一律不动。
172
+
173
+ 作业登记表里的任务删不掉——它只是只读投影——所以已经结束的任务行会记进本会话的忽略名单,从此不再出现。名单的键里带了任务的 `startedAt`:作业 id 是按进程数出来的 `<kind>-N`,否则重启之后一条旧的忽略记录会把一个全新的任务也藏掉。
174
+
175
+ 会话 id 里带路径分隔符或 `..` 的,宿主直接拒掉,而且要求会话真的存在。这个 id 同时是目录名,所以这是防路径穿越的闸门,不是走形式。宿主不答应的时候,界面会报失败、什么都不改——不会出现"看起来清掉了、文件还在"。
176
+
177
+ ## 兼容性
178
+
179
+ | | |
180
+ |---|---|
181
+ | **DSH** | 在 `0.1.5-rc.2` 上实测;浏览器 GUI 和桌面应用内嵌的 web 宿主都能用 |
182
+ | **Profile** | 任何带 web 界面的 profile(`web`;同一行加到 `desktop` 也可以) |
183
+ | **运行时** | Node 22+(插件本身不带任何依赖) |
184
+ | **作业类型** | 登记进 `ctx.jobs` 的任何作业,不限类型 |
185
+ | **额外要求** | 不要凭据、不要令牌、不需要联网 |
186
+
187
+ ## 验证安装
188
+
189
+ ```bash
190
+ # 1) 前端插件:模块契约、插槽注册、跑一遍渲染
191
+ node test/preflight-client.mjs # → ALL PASS (12)
192
+
193
+ # 2) 宿主插件挂上了没(Windows 桌面应用)
194
+ Select-String -Path "$env:APPDATA\DSH Desktop\logs\host\dsh-*.log" -Pattern 'job-progress'
195
+ # → dsh-job-progress: mounted, progress root …\.dsh\job-progress
196
+
197
+ # 3) profile 里有没有这一行
198
+ dsh --profile <profile> --dump-config | Select-String 'job-progress'
199
+ ```
200
+
201
+ 然后在任意会话里起一个后台任务,把鼠标移到小球上。
202
+
203
+ ## 常见问题
204
+
205
+ | 现象 | 原因 | 怎么办 |
206
+ |---|---|---|
207
+ | 小球根本不出现 | 这个会话没有后台任务,也没有进度文件 | 起一个后台任务,或者写一个进度文件 |
208
+ | 改了界面像是没生效 | 前端插件在页面加载时才取 | 刷新页面(F5) |
209
+ | 改了 `lib/index.js` 像是没生效 | 宿主插件模块在进程内缓存 | 重启 profile |
210
+ | `清除失败:宿主未就绪` | 正在跑的宿主比 `clear` 接口还老 | 重启 profile |
211
+ | 有东西渲染成了方圆形 | 应用全局的 `corner-shape` | 见下面"给插件作者" |
212
+ | "这一行到底加载上没有?" | 查宿主日志 | `dsh-job-progress: mounted, progress root …` |
213
+ | 任务还没完就不见了 | 产出方超过 15 秒没写心跳(崩了或被杀了) | 让产出方保持心跳 |
214
+
215
+ <details>
216
+ <summary><b>设计说明——为什么要搞一套文件协议</b></summary>
217
+
218
+ 这个设计是被 harness 的两个事实定下来的。两条都对着已发布的包验证过,不是猜的:
219
+
220
+ 1. **作业登记表里没有进度。** `ctx.jobs` 只有 id、类型、名称、状态(`running → stopping → completed | killed | failed`)和时间戳,没有任何字段能让产出方填一个百分比进去。
221
+ 2. **作业的产出读不得。** `ShellProcess.readOutput` 是**增量**的(连着读两次不会重复给你同样的内容),而 `ctx.jobs.read()` 会把已结束的作业标记为"已上报"。一个靠轮询作业输出来抠百分比的插件,会偷走模型马上要读的输出,还会吞掉它的完成通知。本插件从不调用 `read()`,只读**登记表快照**(纯投影)和自己的文件。
222
+
223
+ 所以进度只能由干活的那边报,而任务的发现过程保持自动。
224
+
225
+ </details>
226
+
227
+ <details>
228
+ <summary><b>给插件作者——这个 GUI 里的圆</b></summary>
229
+
230
+ 应用全局设了 **`corner-shape: superellipse(1.5)`**。在这个设置下,*任何* `border-radius`——包括 `50%`——都画成**方圆形**,而不是圆弧。所以 `border-radius: 50%` 的计算值不能证明它渲染出来是个圆。应用自己的样式表里,需要真圆的地方(转圈、圆点、开关滑块)都用 `corner-shape: round` 单独改了回去。要真圆就补上这行:
231
+
232
+ ```css
233
+ border-radius: 50%;
234
+ corner-shape: round; /* 不写就是方圆形——也可能正是你想要的 */
235
+ ```
236
+
237
+ 本插件是故意留着方圆形的:它和应用本身的设计语言一致。
238
+
239
+ </details>
240
+
241
+ <details>
242
+ <summary><b>开发</b></summary>
243
+
244
+ ```bash
245
+ node test/preflight-client.mjs # 不用装东西,不用开浏览器
246
+ ```
247
+
248
+ 前端插件跑在浏览器里,出错只会落到渲染进程的控制台,而那个控制台不写盘——所以"前端插件写坏了"和"插件压根没加载"看起来一模一样。preflight 用一个假的模块加载器把 `lib/client.js` 装进来,对着桩 React 跑一遍工厂函数、`apply`、插槽注册和一次渲染。它在这个插件开发过程中抓到过一次漏写 `module`/`exports` 声明,和一次渲染门槛的回归。
249
+
250
+ ```
251
+ cordis.patch.yml bundle 补丁:插入 `job-progress` 这一行
252
+ package.json 清单:bundle 补丁 + web 前端插件
253
+ lib/client.js 前端插件:小球、拖拽、面板、清除
254
+ lib/index.js 宿主插件:作业登记表快照 + 进度目录 + jobProgress/clear
255
+ lib/dsh-progress.mjs 产出方:协议、辅助函数、命令行
256
+ test/preflight-client.mjs 前端插件的 12 项检查
257
+ assets/ 本 README 用的 logo 和截图
258
+ ```
259
+
260
+ 宿主插件注册了一个 Typert Remote 服务(`jobProgress`),走标准的 `/api` 网关,接口是 `jobProgress/snapshot` 和 `jobProgress/clear`。给 `job-progress` 这一行加上 `debug: true`,快照返回里会带上找到了哪些登记表、匹配到多少条记录和作业。
261
+
262
+ </details>
263
+
264
+ ## 卸载
265
+
266
+ ```bash
267
+ dsh plugin --profile <profile> remove dsh-job-progress
268
+ ```
269
+
270
+ 产出方写在 `<DSH_HOME>/job-progress/` 下的进度文件不会跟着删——不需要了就自己清。插件从不删这个目录以外的任何东西。
271
+
272
+ ## 后续计划
273
+
274
+ - [ ] 子代理名下的作业(现在一个会话只看得到自己 owner 的作业)
275
+ - [ ] 图标和大资源改走宿主路由,不再内联成 data URI
276
+ - [ ] 更丰富的阶段,让产出方能自己命名任意阶段
277
+ - [ ] 这份 README 的更多语言版本
278
+
279
+ ## 参与贡献
280
+
281
+ 欢迎提 [issue](https://github.com/Rice00/dsh-job-progress/issues) 和 PR。开 PR 之前:
282
+
283
+ ```bash
284
+ node test/preflight-client.mjs # 必须 ALL PASS
285
+ ```
286
+
287
+ 前端插件请保持零依赖(只用 React),改完记得跑一遍 preflight——它存在的理由就是前端的问题平时看不见。
288
+
289
+ ## 许可证
290
+
291
+ 本项目采用 [MIT](./LICENSE) 许可。
292
+
293
+ <div align="center">
294
+
295
+ MIT License © dsh-job-progress contributors
296
+
297
+ </div>
Binary file
Binary file
@@ -0,0 +1,8 @@
1
+ # dsh-job-progress bundle patch — inserts the `job-progress` row into the profile.
2
+ #
3
+ # The row carries no required configuration: the progress directory defaults to
4
+ # `<DSH home>/job-progress` and the panel polls the host on its own. A user layer
5
+ # may override this row by id (`job-progress`) — the same-id row wins.
6
+ - insert:
7
+ - id: job-progress
8
+ name: 'dsh-job-progress'