dsh-mini-remote 0.1.4 → 0.1.6

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 CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  本文件记录每个版本改了什么。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [未发布]
6
+
7
+ ### 重画的
8
+
9
+ - **九张动画(18 帧)全部重画,两帧之间不再有"上半身和下半身割裂"。** 起因是老工艺——一张图里并排画两格,模型两格各画一遍同一个角色,裙子上的图案、袜口条纹、腿和发丝都会各差一点;后来靠"把第二帧手臂以下整段取自第一帧"去补,补丁落在腰上、胳膊上,于是又出现"书举起来和举起前割裂"。这版换了工艺:**先出第一帧,再由第一帧改出第二帧**(只喂第一帧、只描述该动的那一处),两帧出自同一张画;九张的形象统一照同一张母版。裙子以下那一段的帧间差异,除跑、冲刺(按设计动腿)外都在 5% 以下,多数 1% 上下。
10
+ - **"跑"那张返工过一次。** 第一版两帧都是"抬起同一条腿"的镜像姿势,外轮廓几乎重合,手机上翻起来像没动(用户报「腿部没有摆动起来」——量出来的下半身帧间差其实有 52.8%,问题在剪影,不在有没有动)。现在一膝抬到大腿近水平、脚提到膝高,另一条腿伸直踩地,第二帧换边。
11
+ - **新增两个出图工具。** `tools/whale-draw.py` 直连 OpenRouter 出图、可带参考图——插件自带的改图接口在 OpenRouter 上用不了(它固定打不存在的 `/images/edits`,实测 404),带参考图得走 `/images/generations` 的 `input_references`。`tools/whale-pair.py` 把两张单人格拼成两格并排的洋红底图再交给原来的切图脚本,两帧共用同一缩放,并自检底色是不是纯洋红。
12
+
13
+ ### 改了的
14
+
15
+ - **手机上终于能看到上下文用到多少了。** 顶栏那句「上下文 42%」一直是空的:原方案是去问 DSH 的 `contextPressure` 投影,实测这一组合里拿不到——接口通、不报错、投影里就是没有那三个数(第 27 轮就担心过这件事,见 `tasks/todo.md`)。现在改成从事件里量:分母取每次请求前 `request/context` 报的窗口大小,分子取每一步 `assistant/message` 带的用量,而且**把缓存读、缓存写一起加上**——三块计数互不重叠,少加一块量出来会偏小,越长的一轮偏得越多。两个数缺任一个就整段不显示,不编、不放占位符。
16
+
17
+ ### 新加的
18
+
19
+ - **图没显示出来时,手机会说一声。** 正文里写错图片地址(文件不在、或者类型不是图片),原来只画一个破图标,用户只看到"图没了"、不知道原因。现在手机弹一句「这张图手机看不到:那个位置没有这张图,或者它不是 PNG/JPG/WEBP/GIF」,图的位置也画一圈虚线标出来。
20
+
21
+ ### 修了的
22
+
23
+ - 构建指纹不再把 `lib/art/out-*.webp`(给手机看图的展示图,不进版本库)算进去。那批文件一增一删指纹就变,重启时"跑的是不是新代码"这条校验会因此白报一次红——2026-09-28 连着踩了两回。
24
+ - **手机上显示图片不再那么挑写法。** 原来正文里只有两种写法能用:完整网址,或者一个裸名字。写全路径(哪怕是 `lib/art/out-demo.webp` 这种本机路径)都只会画一个破图标——2026-09-28 用户为此第三次来问。现在第三种写法也认:电脑上任意一张图的位置(绝对路径,或相对项目根),PNG/JPG/WEBP/GIF 都行,不必先拷进 `lib/art`。这条通道只发图片类型(扩展名是唯一的闸门)、单张不超过 20MB、照旧要 token;地址里没有构建指纹,所以不让浏览器缓存。
25
+ - 两处会把「会话睡着了」说成「这个会话已经结束了,请重新选一个会话」的提示改准:撤回排队时说「队列里排着的也跟着没了——重新发一条吧」,停止时说「现在没在跑,不用停」。会话会醒,手机上还挂着它,说"结束了"会把人引到重选会话上去。
26
+
27
+ ## [0.1.5] - 2026-09-27
28
+
29
+ ### 新加的
30
+
31
+ - **回复里能带图了。** 原来 `![说明](地址)` 会被原样显示成一行字——渲染器不认图片,还把尖括号里的东西一律当字面文字(那是当初为安全定的规矩)。现在认两种写法:完整的 `http(s)` 链接原样用,手机直接去那个网站取图;写一个名字(如 `out-demo`)就走本机的图片通道 `/mini/art/<名字>.webp`,和鲸鱼娘立绘同一条路、同一样要密码。想让手机看到电脑上的图,把图放进 `lib/art/`、名字以 `out-` 开头就行(这个前缀写在 .gitignore 里,不进仓库)。只放开「图片」这一类,正文和配置文件仍然一个字都读不到。
32
+ - **两套外观:深色、浅色,还能跟随系统。** 设置里多了一行「外观」。浅色那套的色调从宣传图那张画上取(近白的冰蓝地、深宝蓝的字),深色那套照旧。选哪套记在手机上,下次打开还是它;页面主题色也跟着换,手机上那条状态栏不会跟页面打架。
33
+ - **在手机上排队时,界面会显示一把刻度尺**:一条细线上排着刻度点,第一个是正在跑的那条指令,后面每个点对应一条真排在队里的指令。队列空着的时候它整块不出现——2026-09-27 你报的:队列是空的,屏幕上却留着一句「排队中 0 条」和一个孤零零的金点。
34
+
35
+ ### 改了的
36
+
37
+ - **界面按你的设计稿重做了一版。** 所有卡片、气泡、输入框、按钮的圆角统一成 **2 像素**(原来是 10~20 像素那种"软卡片"),层次改由 **1 像素细线**建立,不再靠阴影和色块堆叠;配色换成深海墨蓝打底、鲸鱼娘的冰蓝做次级色,**暖金只留两处**——发送键,和"当前这一步"那个刻度。顶栏和底栏变成悬浮毛玻璃,内容从它们底下淡出。
38
+ - **删掉了四种"界面在表演"的动效**:顶栏那颗一直呼吸闪动的圆点、进度条上来回扫的流光、鲸鱼娘整块的上下浮动、会话抽屉的滑入。现在全页只剩两个动效:队列刻度由"待"变"过"时的 160 毫秒颜色过渡,和进度条逐格推进(见下面那条)。
39
+ - **排队那块改成一把刻度尺。** 一条细线上排着刻度点:第一个是**正在跑的那条指令**(暖金),后面每个点对应一条真排在队里的指令,超过 12 个就收成一个省略号。**这把尺子量的是队列,不是模型跑到哪一步**——插件按设计只看得到「你的指令」和「最终回复」,看不到它中间那几步,所以界面上不出现编出来的进度。
40
+ - 图标从字符(`☰`、`⚙`、`⟳`、`✕`)换成手写细线图标,笔法和回形针一致。
41
+ - 文案:页面标题和顶栏改用中文名「**极简遥控器**」(原来是英文 `DSH mini remote`)。
42
+ - **进度条改成逐格推进的。** 它原来是静止的:宽度固定在三成八,看着像卡住了(2026-09-27 你报的「底部的进度条好像不会动」)。现在宽度仍然写死不动,动的是位置——十六格一轮、每格 160 毫秒,一格一格往右走。它给得出「还在跑」,但**始终不说跑到多少**:插件只看得到你的指令和最终回复,看不到中间那几步,编个百分比就是撒谎。
43
+ - **浅色主题下主按钮改用宝蓝。** 深色下它仍是暖金——那是这块屏幕上唯一「该动手」的地方,醒目就是它的职责;浅色那套底色本来就从宣传图取的,冷底上再压一块饱和暖金不像自己人。所以发送键、设置里的主按钮、口令页那个按钮都换成宣传图的宝蓝配白字,暖金在浅色下只留刻度上「当前这一步」那一个点。
44
+
5
45
  ## [0.1.4] - 2026-09-27
6
46
 
7
47
  ### 新加的
@@ -107,3 +147,4 @@
107
147
  [0.1.2]: https://github.com/xingzhen199186/dsh-mini-remote/releases/tag/v0.1.2
108
148
  [0.1.3]: https://github.com/xingzhen199186/dsh-mini-remote/releases/tag/v0.1.3
109
149
  [0.1.4]: https://github.com/xingzhen199186/dsh-mini-remote/releases/tag/v0.1.4
150
+ [0.1.5]: https://github.com/xingzhen199186/dsh-mini-remote/releases/tag/v0.1.5
package/README.en.md CHANGED
@@ -1,29 +1,43 @@
1
- # dsh-mini-remote
2
-
3
- > A [DeepSeek Harness](https://github.com/deepseek-ai/DeepSeek-Harness) (DSH) plugin that puts a **minimal remote control** on your mobile device — only the instruction you send, the AI's final conclusion, and the one line it mutters between steps (shown in the whale girl's speech bubble) reach it. Tool calls, file reads and writes, sub-agent dispatch, and the body of the reasoning trace all stay on the computer.
1
+ # Minimal Remote · dsh-mini-remote
4
2
 
3
+ [![npm](https://img.shields.io/npm/v/dsh-mini-remote)](https://www.npmjs.com/package/dsh-mini-remote)
5
4
  ![DSH plugin](https://img.shields.io/badge/DSH_plugin-dsh--plugin-blue)
6
5
  ![License](https://img.shields.io/badge/license-MIT-green)
7
6
 
8
- [中文说明 →](README.md)
7
+ [中文说明](README.md)
8
+
9
+ ![Minimal Remote: only your instruction and the AI's conclusion reach the phone; everything in between stays on the computer](https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/promo.webp)
10
+
11
+ **You send one line; the phone gets one conclusion.**
12
+
13
+ Tool calls, file reads and writes, sub-agent dispatch, and the body of the reasoning trace never reach the phone. This plugin exists for one situation: you're out, and all you want to do is send an instruction and read the result — a phone, one input box, the latest reply.
14
+
15
+ [What it is](#what-it-is) ・ [When you'd use it](#when-youd-use-it) ・ [Install](#install) ・ [Connecting your phone](#connecting-your-phone-three-routes-pick-one) ・ [What the phone can do](#what-the-phone-can-do) ・ [FAQ](#faq) ・ [Security](#security) ・ [Why I built this](#why-i-built-this-plugin)
9
16
 
10
17
  ---
11
18
 
12
- ## Why I built this plugin
19
+ ## What it is
13
20
 
14
- The idea came from this: an agent task often takes a long time to finish, and if you step out, you need the phone to drive it remotely.
21
+ A plugin for [DeepSeek Harness](https://github.com/deepseek-ai/DeepSeek-Harness) (DSH, a plugin-extensible AI agent framework). Once installed:
15
22
 
16
- But the phone clients that exist show the PC's execution steps in faithful detail. You send one instruction; it may think for several minutes, read dozens of files, call tools a few times, and only then give you a conclusion. If you're out, or busy away from the computer, you simply don't have the time to watch the phone that closely.
23
+ - a web page opens on your phone with exactly one input box and the latest reply;
24
+ - instructions you send from the phone go straight to the session already running on your computer;
25
+ - everything in between — tool calls, files read and written, sub-agents, the reasoning trace itself — **is never pushed to the phone**;
26
+ - the only thing that "talks" is the whale girl: the line the model mutters between steps, spoken in her speech bubble.
17
27
 
18
- I don't think that approach is bad. It's complete and controllable, and if you're going to do serious work, it's the right one.
28
+ Here is what the interface looks like — dark and light, switchable in settings (rendered at a phone size of 390×844):
19
29
 
20
- But when I'm out, what I want is something else — **a lighter way to interact that asks less of my attention**. The phone screen is small, and so is the attention I have to spare when I'm out. Most of the time I only need one thing: **this round is done, time to send the next instruction.** And even at my desk, I rarely read the AI's running commentary while it works.
30
+ <img width="290" alt="Dark: one conversation, carrying only your instruction and the conclusion" src="https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/screen-dark.png" /> <img width="290" alt="Light: the same conversation, in the palette taken from the promo artwork" src="https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/screen-light.png" />
21
31
 
22
- So the interface here is deliberately crude: it drops the PC's execution steps entirely and puts only the AI's final conclusion in front of you.
32
+ It is **not** a second screen for the desktop interface. Approval dialogs, file diffs and tool-call chains exist on the computer and nowhere on the phone — deliberately. It does very little.
23
33
 
24
- **In a sense, it exists so that you look at it less.**
34
+ Other phone clients copy the computer's execution process over to the phone in full. This one does the opposite: **it sends only the two lines you asked for.** That is the single biggest difference between it and them.
25
35
 
26
- So you can spend your time more freely — instead of being stuck in that small screen while you're playing with your daughter or out on a trip.
36
+ ## When you'd use it
37
+
38
+ - **You're out and a task is still running.** The computer is at your desk mid-round; you just want to glance at the phone to see whether it finished, and hand it the next line while you're at it.
39
+ - **You're with your kid, eating, or out for a walk.** You don't want to touch the computer, but an idea shows up and you'd like it working on that already.
40
+ - **The computer is in the study and you're in the living room.** Not worth walking back, and not worth pulling that whole screen of process onto the phone.
27
41
 
28
42
  ---
29
43
 
@@ -33,6 +47,13 @@ So you can spend your time more freely — instead of being stuck in that small
33
47
  dsh plugin --profile web add dsh-mini-remote
34
48
  ```
35
49
 
50
+ **You must restart DSH once after installing** (close the `dsh web` window and start it again), or "Phone Remote" will not appear in the left column. The startup log then prints the phone URL and password.
51
+
52
+ The `web` in that command is a DSH **profile name** — the folder under `~/.dsh/profiles/`. Most people's default profile is called `web`; if yours isn't, use your own name instead.
53
+
54
+ <details>
55
+ <summary>Installing from GitHub source, from a local folder, and which DSH versions it fits</summary>
56
+
36
57
  Or straight from GitHub, if you would rather pin the source:
37
58
 
38
59
  ```powershell
@@ -45,45 +66,43 @@ If you already have the source on disk, you can point it at the folder:
45
66
  dsh plugin --profile web add <the folder you put the source in>
46
67
  ```
47
68
 
48
- **You must restart DSH once after installing**, or nothing new shows up in the settings page. After the restart a "Phone Remote" entry appears in the left column, and the startup log prints the phone URL and password.
49
-
50
69
  **Which DSH versions it fits.** This plugin was developed on DSH **0.1.5-rc.2** and re-checked item by item on **0.1.7-rc.2** (every interface it uses was unchanged); **versions below 0.1.5 have not been tried**. `rc` is the tag the project puts on preview builds, and DSH as a whole is still a developer preview — minor versions may break things, so versions newer than 0.1.7 are not guaranteed either. If it ever does hit an incompatibility, the usual symptom is one entry point going missing (the model line at the top, say) while everything else keeps working: the plugin is written so that a missing service switches off that one feature rather than the whole thing.
51
70
 
52
71
  Settings follow the same rule: the settings are declared the standard DSH way, and on the 0.1.5-era library that ability does not exist yet — the plugin **still installs and works exactly as before**, it just loses the "applies immediately" behaviour mentioned below. On the **0.1.7 generation** that ability is fully there.
53
72
 
73
+ </details>
74
+
54
75
  ---
55
76
 
56
- ## Configuration: three routes, pick what you need
77
+ ## Connecting your phone: three routes, pick one
57
78
 
58
79
  All three connection methods **exist at the same time**. Pick one, or leave several on.
59
80
 
81
+ | Route | When to use it | What you install | What it costs |
82
+ | --- | --- | --- | --- |
83
+ | **LAN** | Phone and computer on the same Wi-Fi — at home only | Nothing at all | It stops the moment you leave the house |
84
+ | **Tailscale** | You need it outside, and you want the address to stay fixed | Tailscale on the computer and on the phone | An app on both sides |
85
+ | **Public tunnel** | Nothing installed, reachable on any network | Nothing at all | The address changes on every restart; the first run downloads a ~50 MB component |
86
+
60
87
  Open DSH settings (bottom of the sidebar) → "Phone Remote" in the left column. Every route on that page has a QR code and a link, and the small text under each QR code says **when to use that route**. That's all you need to read.
61
88
 
62
89
  The plugin's own settings (default mode, external notifications, port, password and so on) also have a standard declaration, so writing them in DSH's standard configuration works too. If the same item is set in both places, **the standard configuration wins**. One difference worth knowing: **"default mode" and "external notifications" apply immediately, with no plugin restart**; port, bind address, tunnel, password and history length make the plugin restart and the phone reconnect once — those genuinely need to re-bind or rebuild, and the plugin says so instead of pretending they can be changed live.
63
90
 
64
91
  ### 1. LAN: at home only
65
92
 
66
- When the phone and the computer are on the same Wi-Fi, scan the "LAN" code and you're in. **Nothing extra to install** — the least fuss of the three.
67
-
68
- The cost is that it stops working the moment you leave: the phone switches to mobile data and this route is gone.
93
+ When the phone and the computer are on the same Wi-Fi, scan the "LAN" code and you're in. **Nothing extra to install** — the least fuss of the three. The cost is that it stops working the moment you leave: the phone switches to mobile data and this route is gone.
69
94
 
70
95
  ### 2. Tailscale: works outside, and the address stays fixed
71
96
 
72
- Install [Tailscale](https://tailscale.com) on both the computer and the phone, sign in to the same account, and this route appears on the pairing page by itself. The address is fixed — set it up once and forget it.
73
-
74
- The cost is an app on each side. If you don't mind installing it, **this is the steadiest route when you're out**.
97
+ Install [Tailscale](https://tailscale.com) on both the computer and the phone, sign in to the same account, and this route appears on the pairing page by itself. The address is fixed — set it up once and forget it. The cost is an app on each side; if you don't mind installing it, **this is the steadiest route when you're out**.
75
98
 
76
- You can take this route one step further and get an encrypted address as well (shaped like `https://your-machine.your-tailnet.ts.net/`). It is **the same computer over the same tunnel** as the entry above; the only difference is that the connection is encrypted. It is not a fourth route — the plain `http://` entry keeps working and keeps showing.
99
+ There's a switch on the pairing page that gets you an encrypted address as well (shaped like `https://your-machine.your-tailnet.ts.net/`). It is **the same computer over the same tunnel** as the entry above; the only difference is that the connection is encrypted. It is not a fourth route — the plain `http://` entry keeps working and keeps showing.
77
100
 
78
- What encryption buys is a private connection: the address bar shows the https padlock, and what travels the wire is encrypted. **That does not depend on which route you pick** — the public tunnel in route 3 below also gives you an `https://` address, equally encrypted.
101
+ The first time, you have to confirm it in Tailscale's admin console: it is a tailnet-wide switch, and the plugin cannot click it for you. So the pairing page puts the exact link Tailscale hands out right in front of you — one click and you're there. If this computer isn't signed in to Tailscale yet, it says so and gives you a sign-in link instead.
79
102
 
80
- The two encrypted routes have different prerequisites, so it's worth being precise. This one uses a name shaped like `your-machine.your-tailnet.ts.net`, and **that name only resolves inside your Tailscale network**, via MagicDNS on the phone's client. **But some clients don't have that capability** — MeshArc, the third-party client for HarmonyOS, states in its own README that MagicDNS is not enabled and you should use Tailscale IP addresses; on such a phone this encrypted route simply won't work, and it isn't a matter of a setting being switched off. The tunnel route uses a Cloudflare name that resolves for anyone and doesn't care about the phone; its cost is that the address changes on every restart.
103
+ ### 3. Public tunnel: nothing to install
81
104
 
82
- There's a switch on the pairing page — just turn it on. The first time, you have to confirm it in Tailscale's admin console: it is a tailnet-wide switch, and the plugin cannot click it for you. So the pairing page puts the exact link Tailscale hands out right in front of you — one click and you're there. If this computer isn't signed in to Tailscale yet, it says so and gives you a sign-in link instead.
83
-
84
- ### 3. Public access: nothing to install
85
-
86
- There's a switch at the bottom of the pairing page. Turn it on and Cloudflare hands you a public URL (shaped like `https://random-words.trycloudflare.com/`). The phone can reach it on any network, **with nothing installed**. It is an **encrypted connection**, just like the encrypted Tailscale route above — if your phone can't resolve your tailnet's name, this is how you still get encryption.
105
+ There's a switch at the bottom of the pairing page. Turn it on and Cloudflare hands you a public URL (shaped like `https://random-words.trycloudflare.com/`). The phone can reach it on any network, **with nothing installed**.
87
106
 
88
107
  Two costs, worth knowing before you decide:
89
108
 
@@ -92,12 +111,15 @@ Two costs, worth knowing before you decide:
92
111
 
93
112
  Once it's on, **wait half a minute before scanning.** Cloudflare needs a moment to publish the address. Scanning too early reports that it can't be opened — that's not breakage, it's just too soon.
94
113
 
95
- ---
114
+ ### About that "encrypted address"
96
115
 
97
- <img width="1314" height="2186" alt="screenshot_20260922_233535_com huawei hmos brows" src="https://github.com/user-attachments/assets/8befa186-3e20-4c0e-8efc-8ea523f77648" />
116
+ Two of the routes above can hand you an `https://` address, and what encryption covers is **the connection itself**: the address bar shows the https padlock, and what travels the wire is encrypted. That does not depend on which route you pick — both give the same thing.
98
117
 
118
+ The difference is who can resolve the name. The Tailscale one uses a name shaped like `your-machine.your-tailnet.ts.net`, and **that name only resolves inside your Tailscale network**, via MagicDNS on the phone's client. **But some clients don't have that capability** — MeshArc, the third-party client for HarmonyOS, states in its own README that you should use Tailscale IP addresses instead; on such a phone this encrypted route simply won't work, and it isn't a matter of a setting being switched off. The tunnel route uses a Cloudflare name that resolves for anyone and doesn't care about the phone; its cost is that the address changes on every restart.
99
119
 
100
- ## What the mobile side does
120
+ ---
121
+
122
+ ## What the phone can do
101
123
 
102
124
  Open the link and that's the whole interface: one input box, and the latest reply.
103
125
 
@@ -107,6 +129,16 @@ Open the link and that's the whole interface: one input box, and the latest repl
107
129
 
108
130
  **She speaks up while she works.** On the computer, the model often mutters a line between steps ("let me look at this file first"). That line is now spoken by the whale girl through her speech bubble — the talking pose is a newly drawn sprite, the rotation pauses for it, and afterwards picks up from where it stopped. It stays **in the bubble** and never bleeds into the answer area below. Only the line it deliberately writes *for a human reader* is spoken; its own working thoughts — often English, often a whole paragraph — never reach the phone at all. Should that one narration line itself be in English, it is shown as-is, untranslated (translating would mean putting words in its mouth).
109
131
 
132
+ **Two appearances, switchable anytime.** Settings has an "Appearance" row: **Dark** (the default), **Light**, and **Follow system**. Dark is deep-sea navy; the light palette is taken from the promo artwork — near-white ice blue for the ground, deep royal blue for the text, and the same royal blue on primary buttons. Your choice is stored on the phone, so it is still there next time you open the page.
133
+
134
+ <img width="290" alt="Settings (dark): display mode and appearance" src="https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/settings-dark.png" /> <img width="290" alt="Settings (light): the selected states and the primary button turn royal blue too" src="https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/settings-light.png" />
135
+
136
+ **The screen while a task is running.** She is mid-stride, the progress bar is moving, and the queue lists what goes next once this round ends. No tool calls and no file diffs on this screen — only the fact that something is running.
137
+
138
+ <img width="290" alt="Running (dark, photographed on a phone): the whale girl running, 15s elapsed" src="https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/running-dark-phone.jpg" /> <img width="290" alt="Running (light, photographed on a phone): the whale girl running, 7m43s elapsed, one instruction queued" src="https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/running-light-phone.jpg" />
139
+
140
+ Both are phone photographs (the browser's own chrome is in frame, so they sit slightly taller than a render would).
141
+
110
142
  **Two display modes.** Tap the ⚙ in the top right to switch. "Single frame" (the default) keeps only the newest reply on screen — good for "I just want to see how this one turned out". "Chat" is a back-and-forth bubble list — good for several rounds of follow-up questions. Use it for a while and you'll know which you prefer.
111
143
 
112
144
  **Open a session and you see what was said in it before.** Switch to a session that has existed for a while and Chat mode lists every earlier round (the instructions you sent, the conclusion of each round); Single frame shows only the last conclusion. **Very large sessions show only the most recent stretch**: reading one of those in full — tens of thousands of events — would drag the DSH process on your computer down, so the plugin reads just the tail and says so at the top ("this is not everything, only the most recent stretch"). That line is not boilerplate; it is the truth.
@@ -147,7 +179,25 @@ Change it in the password section of "Settings → Phone Remote". **At least 12
147
179
 
148
180
  **The pairing page carries the password — don't screenshot it and send it around.** The password is encoded straight into the QR code so you never have to type it. That's convenient, and the price is that whoever holds that image can get in.
149
181
 
150
- Beyond that: every endpoint requires the password; password comparison is constant-time; five wrong attempts from one source blocks that source for a minute; pairing information is **readable only from the local machine**, so other devices on the same Wi-Fi can't get it; the public tunnel is off by default and you have to turn it on yourself in the settings page; the phone sees no file contents — when you pick a workspace it lists folder names only, and never lists or reads files. **One thing worth stating plainly: the phone can switch the agent's permission preset.** On Full Access, the agent can then modify any file on this computer. That path is open.
182
+ Beyond that: every endpoint requires the password; password comparison is constant-time; five wrong attempts from one source blocks that source for a minute; pairing information is **readable only from the local machine**, so other devices on the same Wi-Fi can't get it; the public tunnel is off by default and you have to turn it on yourself in the settings page; the phone sees no file contents — when you pick a workspace it lists folder names only, and never lists or reads files. **The one exception is images**: when something the AI wants to show you includes a picture (an interface screenshot, say), that image is placed under the plugin's `lib/art/` directory with a name starting with `out-`, and the page fetches it over the same password-protected image route the sprites use. Only that one class of file — an image, with that prefix — can be fetched; message bodies, configuration files and everything else stay unreadable, and directories still cannot be listed. **One thing worth stating plainly: the phone can switch the agent's permission preset.** On Full Access, the agent can then modify any file on this computer. That path is open.
183
+
184
+ ---
185
+
186
+ ## Why I built this plugin
187
+
188
+ The idea came from this: an agent task often takes a long time to finish, and if you step out, you need the phone to drive it remotely.
189
+
190
+ But the phone clients that exist show the PC's execution steps in faithful detail. You send one instruction; it may think for several minutes, read dozens of files, call tools a few times, and only then give you a conclusion. If you're out, or busy away from the computer, you simply don't have the time to watch the phone that closely.
191
+
192
+ I don't think that approach is bad. It's complete and controllable, and if you're going to do serious work, it's the right one.
193
+
194
+ But when I'm out, what I want is something else — **a lighter way to interact that asks less of my attention**. The phone screen is small, and so is the attention I have to spare when I'm out. Most of the time I only need one thing: **this round is done, time to send the next instruction.** And even at my desk, I rarely read the AI's running commentary while it works.
195
+
196
+ So the interface here is deliberately crude: it drops the PC's execution steps entirely and puts only the AI's final conclusion in front of you.
197
+
198
+ **In a sense, it exists so that you look at it less.**
199
+
200
+ So you can spend your time more freely — instead of being stuck in that small screen while you're playing with your daughter or out on a trip.
151
201
 
152
202
  ---
153
203
 
package/README.md CHANGED
@@ -1,29 +1,43 @@
1
- # dsh-mini-remote
2
-
3
- > 一个 [DeepSeek Harness](https://github.com/deepseek-ai/DeepSeek-Harness)(DSH)插件,把一部手机变成**极简遥控器**——移动端只收到你发出去的指令、AI 最后的结论,以及执行中它自言自语的那一句(只进鲸鱼娘的气泡);工具调用、文件读写、子 Agent 调度、思考过程的正文全部留在电脑上。
1
+ # 极简遥控器 · dsh-mini-remote
4
2
 
3
+ [![npm](https://img.shields.io/npm/v/dsh-mini-remote)](https://www.npmjs.com/package/dsh-mini-remote)
5
4
  ![DSH plugin](https://img.shields.io/badge/DSH_plugin-dsh--plugin-blue)
6
5
  ![License](https://img.shields.io/badge/license-MIT-green)
7
6
 
8
- [English →](README.en.md)
7
+ [English](README.en.md)
8
+
9
+ ![极简遥控器:手机上只留你的指令和它的结论,中间过程留在电脑上](https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/promo.webp)
10
+
11
+ **你发一句话,手机上只收到它的结论。**
12
+
13
+ 工具调用、文件读写、子 Agent 调度、思考过程的正文,全都不上手机。这个插件是给「出门在外,只想发指令、等结论」这件事做的——一部手机、一个输入框、最新的一条回答,就这些。
14
+
15
+ [这是什么](#这是什么) ・ [什么时候会用到它](#什么时候会用到它) ・ [安装](#安装) ・ [连上手机](#连上手机三条路选一条) ・ [手机上能做什么](#手机上能做什么) ・ [常见问题](#常见问题) ・ [安全](#安全) ・ [为什么做这个插件](#为什么做这个插件)
9
16
 
10
17
  ---
11
18
 
12
- ## 为什么做这个插件
19
+ ## 这是什么
13
20
 
14
- 初衷是因为现在AGENT任务执行一轮往往需要很久,而如果出门,则需要手机端来远程操作。
21
+ 一个 [DeepSeek Harness](https://github.com/deepseek-ai/DeepSeek-Harness)(DSH,一个用插件扩展的 AI agent 框架)插件。装上之后:
15
22
 
16
- 但现有的手机端,会把PC的执行步骤非常还原地展现出来。你发一条指令,它可能要想上几分钟、读几十个文件、调好几次工具,最后才给你一个结论。如果出门或有事不在电脑前,实在没时间盯着手机看那么细致。
23
+ - 手机上打开一个网页,上面只有一个输入框和最新的一条回答;
24
+ - 你在手机上发的指令,直接交给电脑上正在跑的那个会话;
25
+ - AI 干活的中间过程(工具调用、读写文件、子 Agent、思考正文)**一律不推给手机**;
26
+ - 唯一会「说话」的是鲸鱼娘:模型在步骤之间自言自语的那一句,由她念出来,只进她的气泡。
17
27
 
18
- 我并不认为那种方式不好。信息完整、过程可控,如果你要认真处理任务,它是对的。
28
+ 界面长这样——深色和浅色两套,设置里随手换:
19
29
 
20
- 但出门在外的时候,就我个人而言,我需要的是另一种东西——**更轻便、更少占用注意力的交互方式**。手机屏幕就那么大,出门时的精力也就那么多。而大部分时候我真正需要的只有一件事:**这一轮跑完了,该发下一条指令了**。而且大部分时间,即使在电脑上,我也很少会去仔细阅读AI做执行过程中的“自言自语”。
30
+ <img width="290" alt="深色:一段对话,手机上只留你的指令和它的结论" src="https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/screen-dark.png" /> <img width="290" alt="浅色:同一段对话,色调跟宣传图那张画统一" src="https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/screen-light.png" />
21
31
 
22
- 所以这个插件的界面做得相当「原始」:它完全省略了电脑网页端的执行步骤,只把 AI 最后的结论送到你面前。
32
+ 它**不是**:电脑端界面的备用屏。电脑上有的审批弹窗、文件 diff、工具调用链,手机上都没有——它刻意做得很少。
23
33
 
24
- **某种意义上,它的存在,是为了让你更少地去看它。**
34
+ 别的手机端会把电脑上的执行过程原样搬过来,它反过来:**只把你要的那两句送过去**。这是它和它们最不一样的地方。
25
35
 
26
- 你可以更加自由地支配自己的时间,而不是当你和女儿玩耍时,抑或出游时,被困在那个小小的屏幕里。
36
+ ## 什么时候会用到它
37
+
38
+ - **出门在外,任务还在跑。** 你人在外面,电脑在桌上跑着一轮任务;你只想掏出手机看一眼「跑完没有」,顺便把下一句交代过去。
39
+ - **陪孩子、吃饭、散步的时候。** 手不想碰电脑,但脑子里冒出一个念头,想让它先干着。
40
+ - **电脑在书房,人在客厅。** 不值得专门走回去,也不想把电脑端那一整屏过程搬到手机上看。
27
41
 
28
42
  ---
29
43
 
@@ -33,6 +47,13 @@
33
47
  dsh plugin --profile web add dsh-mini-remote
34
48
  ```
35
49
 
50
+ 装完**必须重启一次 DSH**(把 `dsh web` 那个窗口关掉重开),左栏才会多出「手机遥控」;重启后的启动日志里会打印手机访问地址和密码。
51
+
52
+ 命令里的 `web` 是 DSH 的**配置档名**(profile)——就是 `~/.dsh/profiles/` 下面那个文件夹的名字,多数人的默认档就叫 `web`。不叫这个的话,把它换成你自己的。
53
+
54
+ <details>
55
+ <summary>从 GitHub 源码装、从本地目录装,以及它适配哪些 DSH 版本</summary>
56
+
36
57
  也可以直接从 GitHub 装,这样锁的是源码:
37
58
 
38
59
  ```powershell
@@ -45,45 +66,43 @@ dsh plugin --profile web add github:xingzhen199186/dsh-mini-remote
45
66
  dsh plugin --profile web add <你放源码的目录>
46
67
  ```
47
68
 
48
- 装完**必须重启一次 DSH**,设置页里才会出现新东西。重启后左栏会多出「手机遥控」,启动日志里也会打印手机访问地址和密码。
49
-
50
69
  **它适配哪些 DSH 版本。** 这个插件在 DSH **0.1.5-rc.2** 上开发,在 **0.1.7-rc.2** 上重新逐项核对过(插件用到的那些接口,一个都没变),**低于 0.1.5 的版本没试过**。`rc` 是官方给预览版编的号,DSH 目前整体还是开发者预览版,跨小版本可能有破坏性改动,所以比 0.1.7 更新的版本同样不保证。真遇上不兼容,症状一般是某一块入口不见了(比如顶栏那行模型),其余部分照常——插件是按「少了哪个服务就关掉哪块功能」写的,不会整个起不来。
51
70
 
52
71
  设置也是这样处理的:设置清单按 DSH 的标准方式声明,而在 0.1.5 那一代的库还没有相应能力,插件**照样装得上、功能照旧**,只是少了下面提到的「改完立即生效」。**0.1.7 这一代**上这条能力是齐的。
53
72
 
73
+ </details>
74
+
54
75
  ---
55
76
 
56
- ## 配置:三条路,按需要选
77
+ ## 连上手机:三条路,选一条
57
78
 
58
79
  三条连接方式**同时存在**,选一条就行,也可以都开着。
59
80
 
81
+ | 这条路 | 什么时候用 | 要装什么 | 代价 |
82
+ | --- | --- | --- | --- |
83
+ | **内网** | 手机和电脑在同一个 Wi-Fi,只在家里用 | 什么都不用装 | 一出家门就断 |
84
+ | **Tailscale** | 出门也要连,地址还想固定 | 电脑和手机各装一个 Tailscale | 两边都要装一个 App |
85
+ | **公网隧道** | 什么都不想装,任何网络都要能连 | 什么都不用装 | 地址每次重启都换;第一次要下一个约 50 MB 的组件 |
86
+
60
87
  打开 DSH 设置(侧边栏最下面)→ 左栏「手机遥控」。这一页上每条路都有一个二维码和一条链接,二维码底下那行小字写着**什么时候用这条**,看它就够了。
61
88
 
62
89
  插件的设置(默认模式、外部推送、端口、口令这些)也有一份标准声明,写在 DSH 的标准配置里同样认得。两处都写了同一项时,**以标准配置为准**。另有一条区别:**「默认模式」和「外部推送」改完立即生效、插件不重启**;端口、绑定地址、隧道、口令、历史条数这几样改完会让你重连一次——它们本来就得重新监听或重建,插件如实声明,不假装能热改。
63
90
 
64
91
  ### 一、内网:只在家里用
65
92
 
66
- 手机和电脑连同一个 Wi-Fi 时,扫「内网」那个码就能用。**不需要任何额外安装**,最省事的一条。
67
-
68
- 代价是出了门就连不上——手机一切到流量,这条就断了。
93
+ 手机和电脑连同一个 Wi-Fi 时,扫「内网」那个码就能用。**不需要任何额外安装**,最省事的一条。代价是出了门就连不上——手机一切到流量,这条就断了。
69
94
 
70
95
  ### 二、Tailscale:出门也能连,地址还固定
71
96
 
72
- 电脑和手机都装上 [Tailscale](https://tailscale.com)、登录同一个账号,配对页上就会自动多出这一条。地址固定,配一次以后不用再管。
97
+ 电脑和手机都装上 [Tailscale](https://tailscale.com)、登录同一个账号,配对页上就会自动多出这一条。地址固定,配一次以后不用再管。代价是两边都要装一个 App——如果你不介意装,**出门在外这条最稳**。
73
98
 
74
- 代价是两边都要装一个 App。如果你不介意装,**出门在外这条最稳**。
99
+ 配对页上有一个开关,打开它还能多拿一个加密地址(形如 `https://你的电脑名.你的网络名.ts.net/`)。它和上面那条是**同一台电脑、同一条通道**,区别只在连接加不加密;不是第四条路,那条明文的照旧能用、也照旧显示。
75
100
 
76
- 这条路上还能再开一步,多拿一个加密地址(形如 `https://你的电脑名.你的网络名.ts.net/`)。它和上面那条是**同一台电脑、同一条通道**,区别只在连接加不加密——不是第四条路,那条明文的照旧能用、也照旧显示。
101
+ 第一次开需要你在 Tailscale 的后台确认一次——那是整个网络级别的开关,插件代不了你点,所以配对页会把 Tailscale 给出的那条链接直接摆在你面前,点一下就过去。要是这台电脑还没登录 Tailscale,它会说清楚,并给你登录链接。
77
102
 
78
- 加密的好处是这条连接本身是密的:地址栏上会出现 https 那把锁,路上传的东西是加密的。**这一点不挑路**——下面第三条的公网隧道给的也是 `https://` 地址,一样加密。
103
+ ### 三、公网隧道:什么都不用装
79
104
 
80
- 两条加密路各有各的前提,说清楚免得你走错:这一条用的是形如 `你的电脑名.你的网络名.ts.net` 的域名,**它只在你的 Tailscale 网络内部解析得出**,靠的是手机客户端上的 MagicDNS。**但有些客户端没有这个能力**——HarmonyOS 上那个第三方客户端 MeshArc,它自己的说明里就写着「请用 Tailscale IP 地址」,那类手机上这条加密路走不通,不是设置没开的问题。隧道那条用的是 Cloudflare 的域名,谁都能解析,不挑手机,代价是地址每次重启都会换。
81
-
82
- 配对页上有一个开关,打开就行。第一次开需要你在 Tailscale 的后台确认一次——那是整个网络级别的开关,插件代不了你点,所以配对页会把 Tailscale 给出的那条链接直接摆在你面前,点一下就过去。要是这台电脑还没登录 Tailscale,它会说清楚,并给你登录链接。
83
-
84
- ### 三、公网访问:什么都不用装
85
-
86
- 配对页最底下有个开关,打开它,Cloudflare 会给你一个公网网址(形如 `https://随机词.trycloudflare.com/`)。手机在任何网络下都能连,**不用装任何东西**。它和上面那条加密路一样是**加密连接**——手机上的 Tailscale 解析不了你那个网络的域名的话,想要加密就走这条。
105
+ 配对页最底下有个开关,打开它,Cloudflare 会给你一个公网网址(形如 `https://随机词.trycloudflare.com/`)。手机在任何网络下都能连,**不用装任何东西**。
87
106
 
88
107
  两个代价,先知道再决定:
89
108
 
@@ -92,12 +111,15 @@ dsh plugin --profile web add <你放源码的目录>
92
111
 
93
112
  打开之后**等半分钟再扫**。Cloudflare 需要一点时间把这个地址公布出去,太早扫会提示打不开——那不是坏了,是还没到时候。
94
113
 
95
- ---
114
+ ### 关于「加密地址」,一次说清楚
115
+
116
+ 上面两条路都可能给你一个 `https://` 地址,加密的是**连接本身**:地址栏上会出现 https 那把锁,路上传的东西是密的。这一点不挑路,两条给的都一样。
96
117
 
97
- <img width="1314" height="2186" alt="screenshot_20260922_233535_com huawei hmos brows" src="https://github.com/user-attachments/assets/8befa186-3e20-4c0e-8efc-8ea523f77648" />
118
+ 区别在域名由谁解析:Tailscale 那条用的是形如 `你的电脑名.你的网络名.ts.net` 的域名,**它只在你的 Tailscale 网络内部解析得出**,靠手机客户端上的 MagicDNS——而**有些客户端没有这个能力**:HarmonyOS 上那个第三方客户端 MeshArc,它自己的说明里就写着「请用 Tailscale IP 地址」,那类手机上这条加密路走不通,不是设置没开。隧道那条用的是 Cloudflare 的域名,谁都能解析,不挑手机,代价是地址每次重启都会换。
98
119
 
120
+ ---
99
121
 
100
- ## 移动端能做什么
122
+ ## 手机上能做什么
101
123
 
102
124
  打开链接就是全部界面:一个输入框,和最新的一条回复。
103
125
 
@@ -105,7 +127,17 @@ dsh plugin --profile web add <你放源码的目录>
105
127
 
106
128
  **等结论。** 跑的时候上面显示「运行中…」。这期间你可以接着发,指令会排队。任务结束,结论直接出现在页面上,同时响一声、震一下。
107
129
 
108
- **她干活的时候会开口。** 电脑上的模型在步骤之间常会自言自语一句(「我先看一下这个文件」这类),这一句现在由鲸鱼娘念出来——说话的动作是新画的一张立绘,轮播会为它停一下,说完接着从停下的地方往下走。它**只在那只气泡里**,不会混进下面的回答区。气泡里只念**它特意写给人看的那一句**;它自己心里想的那些(常是英文、也常成段)一个字都不上手机。万一那句旁白本身就是英文,就照原样显示、不翻译——翻译等于替它编一句它没说过的话。
130
+ **鲸鱼娘动画。** 电脑上的模型在步骤之间常会自言自语一句(「我先看一下这个文件」这类),这一句现在由鲸鱼娘念出来——说话的动作是新画的一张立绘,轮播会为它停一下,说完接着从停下的地方往下走。
131
+
132
+ **两套外观,随手换。** 设置里有一行「外观」:**深色**(默认)、**浅色**、**跟随系统**。深色是深海墨蓝打底;浅色那套的色是从宣传图那张画上取的——近白的冰蓝地、深宝蓝的字,主按钮也换成同一个宝蓝。选哪套记在手机上,下次打开还是它。
133
+
134
+ <img width="290" alt="设置(深色):显示模式与外观" src="https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/settings-dark.png" /> <img width="290" alt="设置(浅色):选中态和主按钮都跟着换成宝蓝" src="https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/settings-light.png" />
135
+
136
+ **任务执行中的那一屏。** 她迈步跑着,进度条在走,队列里排着"跑完这轮就轮到它们"的几条。这一屏上没有工具调用、没有文件差异——只有"在跑"这件事本身。
137
+
138
+ <img width="290" alt="执行中(深色,手机实拍):鲸鱼娘正在跑、已计时 15 秒" src="https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/running-dark-phone.jpg" /> <img width="290" alt="执行中(浅色,手机实拍):鲸鱼娘正在跑、已计时 7 分 43 秒、队列排着一条" src="https://raw.githubusercontent.com/xingzhen199186/dsh-mini-remote/main/docs/running-light-phone.jpg" />
139
+
140
+ 两张都是手机实拍(浏览器自带的状态栏也在画面上,所以比渲染图略高一点)。
109
141
 
110
142
  **两种显示模式。** 点右上角的 ⚙ 切换。「单帧」(默认)整屏只留最新一条回复,适合「我就想看这次的结果」;「聊天」是一来一回的气泡列表,适合连着追问好几轮。用一阵子你会知道自己偏哪个。
111
143
 
@@ -147,7 +179,32 @@ dsh plugin --profile web add <你放源码的目录>
147
179
 
148
180
  **配对页上带着密码,别截图发出去。** 密码直接编在二维码里,扫完不用手敲——方便是方便,代价是谁拿到这张图谁就能进。
149
181
 
150
- 除此之外:所有接口都要密码,密码比对用定长比较;同一来源一分钟错 5 次就挡一分钟;配对信息**只允许本机读取**,同一个 Wi-Fi 下的其他设备拿不到;公网隧道默认关着,要你自己在设置页里打开;手机看不到任何文件内容——挑工作区的时候只列文件夹名字,文件一个都不列、也不读。**但有一件事要说清楚:手机上能切换 Agent 的权限档位。**切到「完全权限」,Agent 就能改这台电脑上的任何文件。这条路径是开着的。
182
+ 除此之外:所有接口都要密码,密码比对用定长比较;同一来源一分钟错 5 次就挡一分钟;配对信息**只允许本机读取**,同一个 Wi-Fi 下的其他设备拿不到;公网隧道默认关着,要你自己在设置页里打开;手机看不到任何文件内容——挑工作区的时候只列文件夹名字,文件一个都不列、也不读。**唯一的例外是图片**:要让手机看到一张图(比如我截给你的界面图),那张图会被放进插件目录 `lib/art/`、名字以 `out-` 开头,页面按「带密码的图片通道」把它取出来——能被取到的只有这一类(是图片、且是这个前缀),正文、配置文件和别的东西仍然一个字都读不到,也不能列目录。**但有一件事要说清楚:手机上能切换 Agent 的权限档位。** 切到「完全权限」,Agent 就能改这台电脑上的任何文件。这条路径是开着的。
183
+
184
+ ---
185
+
186
+ ## 为什么做这个插件
187
+
188
+ 初衷是因为现在 AGENT 任务执行一轮往往需要很久,而如果出门,则需要手机端来远程操作。
189
+
190
+ 但现有的手机端,会把 PC 的执行步骤非常还原地展现出来。你发一条指令,它可能要想上几分钟、读几十个文件、调好几次工具,最后才给你一个结论。如果出门或有事不在电脑前,实在没时间盯着手机看那么细致。
191
+
192
+ 我并不认为那种方式不好。信息完整、过程可控,如果你要认真处理任务,它是对的。
193
+
194
+ 但出门在外的时候,就我个人而言,我需要的是另一种东西——**更轻便、更少占用注意力的交互方式**。手机屏幕就那么大,出门时的精力也就那么多。而大部分时候我真正需要的只有一件事:**这一轮跑完了,该发下一条指令了**。而且大部分时间,即使在电脑上,我也很少会去仔细阅读 AI 做执行过程中的"自言自语"。
195
+
196
+ 所以这个插件的界面做得相当「原始」:它完全省略了电脑网页端的执行步骤,只把 AI 最后的结论送到你面前。
197
+
198
+ **某种意义上,它的存在,是为了让你更少地去看它。**
199
+
200
+ 你可以更加自由地支配自己的时间,而不是当你和女儿玩耍时,抑或出游时,被困在那个小小的屏幕里。
201
+
202
+ ## 相关文档
203
+
204
+ - [立绘与动画工艺](docs/character.md) —— 三个出图工具、共享提示词块、尺寸与验收口径。
205
+ - [立绘基准帧(2026-09-28 定稿)](docs/art-refs/2026-09-28/) —— 九张帧图、全部提示词原文,
206
+ 以及「以后改动画先对照它们」的规矩。
207
+ - [变更记录](CHANGELOG.md) —— 每版改了什么、为什么。
151
208
 
152
209
  ---
153
210
 
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/lib/build.js CHANGED
@@ -20,20 +20,29 @@ const HERE = dirname(fileURLToPath(import.meta.url))
20
20
  /**
21
21
  * 参与计算的文件:插件端的 lib/*,客户端那半边(改它同样要重启才生效),
22
22
  * 以及 lib/art 下的立绘——换了图,指纹就该跟着变。
23
+ *
24
+ * **哈希里放的是文件名,不是路径。** 插件平时是被 DSH 通过一条目录链接
25
+ * (`profiles/web/node_modules/dsh-mini-remote` → 工作副本)加载的,
26
+ * `import.meta.url` 拿到的于是是**链接那条**路径。早先这里把绝对路径也揉进哈希,
27
+ * 结果同一份代码经链接和经真实路径是两个数,「现在应答我的,是不是我改的那份代码」
28
+ * 这条检查永远报红。文件名加内容足够认人——路径是环境,不是代码。
23
29
  */
24
30
  function sources() {
25
31
  const files = []
26
32
  try {
27
33
  for (const f of readdirSync(HERE).sort()) {
28
- if (f.endsWith('.js') || f.endsWith('.html')) files.push(join(HERE, f))
34
+ if (f.endsWith('.js') || f.endsWith('.html')) files.push([f, join(HERE, f)])
29
35
  }
30
36
  } catch {
31
37
  // 目录读不到就退化成空指纹,不该让插件起不来。
32
38
  }
33
- files.push(join(HERE, '..', 'client', 'client.js'))
39
+ files.push(['client/client.js', join(HERE, '..', 'client', 'client.js')])
34
40
  try {
35
41
  for (const f of readdirSync(join(HERE, 'art')).sort()) {
36
- if (f.endsWith('.webp')) files.push(join(HERE, 'art', f))
42
+ // `out-` 开头的是给手机看图用的展示图(.gitignore 里专门让路的那批),随时会增删替换,
43
+ // 不是这份代码的一部分。把它们算进指纹的后果是:每贴一张看图指纹就变一次,重启校验
44
+ // 会因此白报一次红——2026-09-28 连着踩了两回。
45
+ if (f.endsWith('.webp') && !f.startsWith('out-')) files.push(['art/' + f, join(HERE, 'art', f)])
37
46
  }
38
47
  } catch {
39
48
  // 没有 art 目录(比如只拷了 .js 的旧安装)就当没有立绘,照常起。
@@ -47,8 +56,8 @@ function sources() {
47
56
  */
48
57
  export function buildId() {
49
58
  const hash = createHash('sha256')
50
- for (const file of sources()) {
51
- hash.update(file)
59
+ for (const [name, file] of sources()) {
60
+ hash.update(name)
52
61
  try {
53
62
  hash.update(readFileSync(file))
54
63
  } catch {