dsh-shutdown-guard 1.5.0 → 1.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.6.0 — 2026-11-06
4
+
5
+ - **悬浮窗口**:卡片上多了一个入口,点开是真正的第二个窗口,可以拖到副屏一直盯着倒计时,切到别的会话也看得见。窗口里就是侧边栏卡片本身——同一套状态机、同一套配色、同一枚主按钮,左上角指示点随 `blocked / off / fired / countdown / hold / armed` 变色并呼吸,最近事件留在卡片下方。外观(A / B / C)、主题、语言三项由宿主持有(`POST /ui` 写进 `data/ui.json`,`/state` 的 `ui` 字段读出),在设置里一改,窗口下一轮轮询就重画,不用关重开。之所以不继续只靠 `localStorage`:桌面端的侧边栏与 `window.open` 出来的窗口可能落在不同的存储分区,早先的表现就是「语言能跟随、颜色与外观不跟随」。
6
+ - **悬浮窗简洁模式(默认开启)**:只留卡片本身,隐藏顶部品牌行、状态徽章、统计行、最近事件和底部提示;页面背景透明、卡片居中,看起来就是一张悬在桌面上的卡片。开关放在设置页的「外观」组里,窗口内刻意不放切换控件——一个只剩一张卡片的窗口,不该再叠一个开关上去。
7
+ - **状态变化发系统通知**:四类分别可开关——进入倒计时、即将关机、首次阻断、阻断恢复。正文只写状态与关键数字(剩余秒数、被哪道闸挡住、重试还有几秒),不含会话标题或路径。手动点「武装 / 解除 / 取消」引起的任何变化一律不提醒(2 秒静默窗口);同一状态按「类型 + 状态 + 阻断原因」做指纹去重,不会连弹。点通知会聚焦已有的悬浮窗,没有则新开一个。
8
+ - **会话数改成真数,含子 Agent**:以前只数顶层会话,多智能体协作里后台跑着的子 Agent 完全不可见——父会话闲着的时候卡片甚至显示 0 个会话,看着像什么都没在跑。现在卡片上的会话数是「主会话 + 子 Agent」总数,HUD 与终端里的 agent 数是「正在运行的 agent 数」,两者都取宿主的真数;「N 个会话」那句话同样按总数说。设置页的「状态」组里没有这一行(分组时已移除),会话数只在卡片与终端视图显示。
9
+ - **雷达点数改对了,卡片与悬浮窗现在都算到子 agent**:以前只数主会话,团队里派生的子 agent 不画点,看着像只有一两个在跑。现在两处同一口径,都按「运行中的 agent 总数(含子 agent)」算:卡片的外观 B 按这个数画点;悬浮窗的 B 形态圆点、C 形态的 agent 数,以及 A 形态的「N 个会话」也一并换成同源,旁边的统计小字跟着变。两处都最多画 12 个,超出的在右上角显示 `+N`,并补了三语读屏描述「N 个 agent,其中 M 个在运行」——此前它只是一张没有说明的图。**这是一处用户可见的变化**:团队场景(1 个主会话 + 2 个子 agent)下,悬浮窗 B 的圆点会从 1 个变成 3 个。
10
+ - **配置升级到 v2(`data/config.json`)**:通知的四类开关与 AFK 自动回答从全局搬进了当前配置——它们描述的是「这台机器上怎么守夜」,跟着配置走才合理,切配置时会一起变。宿主新增 `createProfile` / `renameProfile` / `deleteProfile` / `setDefault` / `updateProfile` 五条命令,写入只认白名单字段,未知键忽略(新客户端不会把旧宿主写坏)。老文件不用手动改:读取时兼容 1.5.1 的全局 `afkAutoAnswer` 和 `ui.json` 里的旧 `notify`,下次保存自动写成 `version: 2`。
11
+ - ⚠️ **默认配置改为强锁定**:以前改默认配置的倒计时(60 秒)、边距(5 秒)、两个判定开关和 AFK,会被静默夹紧回原值——用户以为改成功了。现在直接返回 400,并说明是哪个字段不能改。默认配置是安全基线,要自定义请新建一个配置。通知的四类开关不在此列,默认配置下仍可逐项改。
12
+ - **设置页改成四个标签页**:外观 / 守夜(自定义配置开关 + 配置 + 守夜行为)/ 通知 / 状态,原来那根长列表按主题拆开,找一项不用再从头滚到底。标签是真 `tablist`:左右方向键与 Home / End 可切;四个面板始终保持挂载(非活动的只是 `hidden`),所以填了一半的配置名、滚到一半的位置,切个标签回来都还在。原先各组的内容一条没少(判定与关机那一组并进了守夜行为),行式骨架也保留:标题与说明在左、控件在右,组间 0.5px 分隔线,组标题各带一枚小图标;说明文字能压一行就压一行,只有 AFK 那条保留完整说明——它得讲清我们会替你回什么、不会替你选什么。
13
+ - **卡片样式改成带缩略图的三张卡片**:以前是三个字母药丸(A / B / C),光看字母猜不出长什么样。现在是三张卡片,各配一张 400×256 的高清卡面缩略图——图里就带着「待命 / Armed / Взведено」这些字与卡面底色,下面标「极简 / 雷达 / 终端」。缩略图共 18 张:A / B / C × 中 / 英 / 俄 × 浅 / 深,因为图里有文字,不能靠反色复用。选哪张不看主题名,而是量设置面板实际背景色的亮度(`L > 0.5` 用浅色图;透明白底会往上找第一个不透明祖先),所以换了宿主皮肤、改了 Claude Style 的配色也能选对;切换 200ms 节流,只换 `img.src`,不重建 DOM、不丢选中态。
14
+ - **那三张卡片缩略图换成了高清版**:以前放大看发虚,圆点和文字边缘都糊;现在按两倍尺寸出图,设置页里看得清楚,浅色深色下都一样。
15
+ - **配置管理界面**:设置页里就能新建(从当前配置复制一份)、重命名、删除、设为默认,不用再改 JSON。默认配置那行只显示倒计时秒数,没有改名与删除按钮——它是安全基线,宿主侧同样会拒绝。删除要按两次:第一次按钮变成「确认删除」(2 秒内再点才生效,超时自动复原);删掉的如果正是当前配置,会自动回落到默认配置。名称留空或超过 24 个字符会在行内提示,失败原因直接显示宿主返回的那句话(比如「自定义配置已关闭」)。宿主与界面不再各自判断「这份配置算不算用过」:列表一律按宿主返回的内容渲染,刚新建的配置哪怕一个开关都没动,也会立刻出现在列表里,不会出现「建了却看不见」。
16
+ - **「配置」搬进了「守夜」标签页,「自定义配置」开关提到页面最上面**:开关单独占一行、放在页首,配置块紧跟在它下方,只有开关打开时才渲染(关着时整块不进页面,不是灰掉占位)。「判定与关机」那一组标题已取消,原来那四条判定闸并进了「守夜行为」,排在 AFK 自动回答之前——先定规矩,再看守夜时怎么表现,顺序跟思考顺序一致。顺带把「外观」页里与标签名重复的那条组标题去掉了,页签已经写着「外观」,里面不必再重复一遍。
17
+ - **新装不再预生成八个空配置**:以前一进设置页就摆着八个空槽,看着像必须一个个填满;现在只有「默认配置」一行,需要第二份配置时点「+新建」才创建。空槽不再是初始化的一部分,而是按需生成。
18
+ - **关掉「自定义配置」再打开,会回到上次用的那份配置**:不用重新点一次;如果那份配置已经被删,就停在「默认配置」,不会指向一个不存在的名字。升级时也会清理旧数据里从没动过的空槽——只有「名字没改过、里面任何一个开关也没动过」的才会消失;**改过名或动过任一开关(倒计时、判定条件、通知、AFK 都算)的配置都会保留**,不会丢数据。
19
+ - **旧的「默认 | 1 | 2 | 3」槽位快捷条已移除**:配置切换统一收进上面那个列表,「设为默认」一步到位——点它就直接切到该配置,不再先选槽位再确认。少一处入口,也少一种「刚才切的是哪个」的困惑。
20
+ - **开关点下去立刻生效**:点击时本地先置位并重绘,请求在背后发;失败时在那一行显示内联红字「保存失败,已还原」并回滚到宿主值,不静默吞掉。以前要等下一轮 `/state` 轮询回来才变,体感像卡住。
21
+ - **控件手感**:分段选择(卡片样式 / 主题 / 语言 / 倒计时)改成滑动切换——高亮块从旧位置滑到新位置,而不是瞬间跳过去;开关拨动带 350ms 回弹动画。纯视觉改动,不改变任何取值逻辑。
22
+ - **设置页头部加了 GitHub 与 npm 入口**:标题右侧两枚**单色**图标,分别打开仓库页与 npm 包页,都在新标签打开,悬停才变亮,读屏与悬停各有文字说明;npm 那枚按它自己的长条比例单独放大,不跟方形图标挤成一样大。**有意不显示版本号**——写死一个版本号迟早会和 `package.json` 对不上,要看版本有徽章与 CHANGELOG。
23
+ - **`Claude Style` 主题跟随 Claude Style 的实时配色**:当 Claude Style 用自己那套配色时(它的 `data-dsh-claude-palette` 不是 `host`),卡片实时跟随它的表面、文字、次要文字、分隔线、强调色、危险色与轨道;在 Claude Style 里改配色或切明暗,卡片不用刷新就跟着变,也不用重装插件。
24
+ - **没装 Claude Style 插件也能选它**:主题选项从现在起叫 `Claude Style`(三语同名,俄文也保留英文原名)。检测不到 dsh-claude-style 时不会把选项藏起来,而是用一套内置的近似配色(陶土橙),并在主题行下面用一行灰字说明;装上插件后这一行消失,自动改跟插件的实时配色。三档别混为一谈:**没装插件** → 内置陶土橙;**装了插件但 palette 是「跟随宿主」** → 走我们自己的琥珀;**装了插件且 palette 是 `claude`** → 才是真正的 Claude 配色。
25
+ - **Claude Style 用「跟随宿主」配色时,本插件回到自己的默认配色**:palette 设成 `host`(或根本读不到这个值、或它的变量解析不出来)时,卡片不会去复制一套近黑 / 近白的宿主中性色、把自己变成黑白卡片,而是整档换回我们自己的配色——浅底白卡配深琥珀 `#e08f13`、深底深灰配亮琥珀 `#f0a83a`,胶带平时也取我们自己的琥珀色(真正进入警戒后回到红黄斜纹,见下一条),只沿用宿主的明暗。**想看到真正的 Claude 配色,需要去 Claude Style 的设置里把 palette 切成 `claude`。** 另有一道保险:跟随模式下强调色若恰好没有彩度,会退回同样的琥珀,正常配色下不会触发。
26
+ - **警示带会跟着状态变(卡片与悬浮窗都一样)**:用插件自带配色时,未武装与被阻断这两档,警示带是主题色的深浅两档——安静、不抢眼,卡片停在一边时不晃眼;从武装开始,到真正执行关机为止,都是经典的红黄斜纹,那才是这款插件的识别符。切换是渐变的,不会闪。换成 Claude Style 或 DeepSeek 蓝配色时这条不生效:那两档整套都在跟主题走,红黄斜纹在那里正好是撞色,所以全程用主题色的深浅两档。
27
+ - **图标**:插件列表里不再显示默认齿轮——`package.json` 的 `icon` 指向一枚盾牌(琥珀底、实心关机符号,36px 档单独画过)。设置页左侧导航里我们那一行也换成同一枚盾牌的单色版,用 CSS `mask` 加 `currentColor` 绘制,深浅主题自动跟随。宿主目前没有官方图标字段,这是 DOM 补丁;宿主将来提供字段时应改用它。设置页里这一项的名称改成产品名 `Shutdown Guard`(三语同一字面量,不翻译),侧边栏卡片标题仍是「任务完成后关机」。
28
+ - **文案**:中文界面刻意保留一批英文技术词(`OFF` / `ARMED` / `T-minus` / `shutdown --when idle` 等状态与命令名),解释性文字(说明、计数、按钮、设置页)全为中文;英文与俄文界面各自完整翻译,不受这条混排约定影响。三语键集齐平(134 / 134 / 134)。
29
+ - **俄语计数表达修正**:俄语数词要跟着数量变格,中文和英文里「2 个任务 / 2 jobs」的写法照搬到俄语就是病句(旧界面会显示 `2 задач · 3 агентов`)。现在改成冒号式「`фоновых задач: 2 · агентов: 3`」,任何数字都成立,不用再为 1 / 2–4 / 5+ 分别造句子。
30
+ - 修复:热重载后侧边栏偶发出现两个卡片(第二个是改动前的旧卡片,文案还是英文)。旧实例留在 DOM 里的节点和它的轮询定时器没人管;现在用实例令牌 `window.__dshShutdownGuardOwner` 定主,新实例挂载即接管,旧实例下一拍发现主人不是自己就清掉自己的节点并停手,`<style>` 标签同样去重。
31
+ - 修复:事件视图的语言问题(两处同一根源的漏传)。一处是中文界面里「最近事件」全是英文、增量游标一并失效——`recentEvents()` 的两个调用点漏传 `lang`,参数错位后语言落到英文兜底;另一处是切换语言后事件里的阻断原因仍停在旧语言,俄文界面会出现「…:等待恢复:服务端故障…」这种中俄混排。现在事件文本与阻断原因都在出口按当前语言现渲染,切换后同一批历史事件跟着变;老日志里已有的记录照原样显示,不丢历史。
32
+ - 修复:设置菜单里的插件标题不跟随语言。`ctx.locale.register` 注册命名空间,并在切换语言后重新注册 `settings.section` 槽位,两条路径都覆盖,不需要重启 DSH。
33
+ - 修复:冷启动后语言丢失。语言偏好改由宿主 `data/ui.json` 持有,`localStorage` 只作首帧兜底,不再依赖某个存储分区。
34
+ - 测试:宿主断言 28 → 43 项,新增 `events-i18n-ok`、`events-since-ok`、`events-nested-i18n-ok`、`window-page-ok`、`window-radar-parity-ok`(悬浮窗与卡片的 agent 点数同源)、`window-tape-ok`(悬浮窗警示带与卡片同一套状态规则)、`ui-state-ok`、`ui-compact-ok`、`profile-v2-ok`、`profile-legacy-ok`(后两项覆盖 v1 → v2 读取与旧全局字段迁移)、`agent-total-ok`、`agent-total-fallback-ok`(后两项覆盖子 Agent 计数与宿主不提供成员列表时的回落);发版前又补了 4 条配置用例——空槽按需创建、从没动过的空槽在升级时清理、记住上次用的配置、那份配置被删后回落到默认。客户端从 1 条 bundle smoke 扩到 14 项:雷达 5 条(点数取运行中 agent 总数、不退回主会话数、上限 12 个与 `+N`、读屏描述、宿主不提供该字段时回落)、强调色 4 条(`oklch` 与 `color-mix` 两种推导都不会推出没彩度的灰、灰度强调色能退回琥珀、不用 canvas 取色)、内置配色 3 条(读不到 Claude Style 的配色属性才用内置陶土橙、palette 为 `host` 时不走内置配色、属性为空表示插件已安装)、主题名 1 条(选项显示为 `Claude Style`)。
35
+ - 文档:新增面向下载者的 `RELEASE-1.6.0.md`;三份 README 的安装、配置与截图按本版更新,并加上 awesome-dsh-plugin 的收录徽章(贴在 dshbase 那枚旁边)。dsh-plugin.org 与 dpharness 的页面还没上线或还没更新,暂不贴。
36
+ - ⚠️ 升级注意:`data/config.json` 无需手动迁移,读取时自动兼容、保存时写成 `version: 2`;默认配置现在拒绝改动,想自定义就新建一个配置;旧版全局的 AFK 与通知开关会并入升级时生效的那个配置。**包变大了,这是预期内的**:设置页那 18 张缩略图以内联 base64 的形式进了 `lib/client.js`(约 300KB),该文件从约 143KB 涨到约 470KB;本版 npm 包约 360 kB(未压缩解包约 750 kB),仍是 11 个文件,不是装错了。
37
+
38
+ ## v1.5.1 — 2026-11-04
39
+
40
+ - 三语界面:中文 / English / Русский。宿主侧文案目录 78 键、侧边栏卡片 74 键,三语键集齐平、零缺失;日志与提示全部改走 `t(lang, key)`,`lib/index.mjs` 内已无硬编码中文串。
41
+ - 新增语言选择器:设置 → 插件 → 任务完成后关机 → 「语言」,切换即时生效——经 `POST /api/dsh-shutdown-guard/config` 的 `language` 字段落盘,卡片强制重绘,不需要重启 DSH。
42
+ - 新增 `language` 配置项:取值 `auto` | `zh` | `en` | `ru`,默认 `auto`(跟随系统语言)。它由 `loadStore`(`lib/index.mjs:283`)读取、经 `POST /config` 在运行时写入;**不是** `normalizeProfile` 字段,三份 README 的配置表已单独为它补一行。
43
+ - 语言偏好同时写入宿主配置(`data/config.json`)与浏览器 `localStorage`:前者是权威值,后者让卡片在首帧就按所选语言渲染,重启后不再闪一下中文。
44
+ - 文档:新增 `README.en.md`、`README.ru.md`,三份 README 的配置表统一补 `language` 行;演示页改成三语(`scripts/demo/i18n.js`,157 键)。
45
+ - 修复:切换语言后,事件日志里切换前的旧行会永久停留在旧语言;现在切换时整环重取(`lib/client.js` `setSnapshot`)。
46
+ - 修复:`saveStore()` 在 `apply()` 外引用越界变量,写 `data/config.json` 失败时会抛 `ReferenceError`、`log.configWriteFailed` 提示永远打不出来(`lib/index.mjs:292-295`、`:1039`)。
47
+ - 可访问性:设置里 5 个开关补 `aria-label`,屏幕阅读器可读出开关名称(未新增文案键)。
48
+ - 测试:新增 i18n 目录断言——`LANGS` 固定为三语、`catalogCoverage().missing` 为空、每语键数与基准一致。
49
+ - ⚠️ 升级注意:**已有配置无需任何改动或迁移**。`language` 缺省即 `auto`,与 v1.5.0 的行为一致;升级后若想固定语言,在设置里选一次即可。
50
+
3
51
  ## v1.5.0 — 2026-11-02
4
52
 
5
53
  - 改名:`dsh-shutdown-panel` → **`dsh-shutdown-guard`**(Shutdown Guard)。原名只描述了界面,没表达它真正的特长——关机前逐条排除。`shutdown` 仍在包名第二位,市场按关键词搜索时排序不受影响。
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 ltayshark0637
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
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ltayshark0637
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
21
  SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,314 @@
1
+ # Shutdown Guard · Shut down when tasks finish
2
+ [中文](README.md) · **English** · [Русский](README.ru.md)
3
+
4
+ <p align="center"><img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/banner.png" width="880" alt="Shutdown Guard — automatic shutdown when tasks finish, a night-watch card in the sidebar"></p>
5
+
6
+ [![version](https://img.shields.io/badge/version-1.6.0-2ea44f)](CHANGELOG.md)
7
+ [![demo](https://img.shields.io/badge/%E2%96%B6-LIVE%20DEMO-d97757)](https://ltayshark0637.github.io/dsh-Shutdown-Guard/demo.html)
8
+ [![DSH](https://img.shields.io/badge/DSH-%E2%89%A50.1.5--rc.1-4a90d9)](https://github.com/deepseek-ai)
9
+ [![platform](https://img.shields.io/badge/platform-Windows-0078d4)](#constraints)
10
+ [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
11
+ [![dshbase install-tested](https://dshbase.com/badges/dsh-shutdown-guard.svg)](https://dshbase.com/plugins/dsh-shutdown-guard/) [![awesome-dsh-plugin listed](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com/p/ltayshark0637/dsh-Shutdown-Guard/)
12
+
13
+ Adds a "shut down automatically when all tasks finish" card to the DeepSeek Harness sidebar, with a more varied and polished visual treatment, decision logic, and customization. It sits below Skill Center and above the session list, and updates in real time with task state.
14
+
15
+ **▶ [Interactive demo](https://ltayshark0637.github.io/dsh-Shutdown-Guard/demo.html)** — no installation needed; see what it looks like and how it decides. Two panes: **Learn** (rules, appearance, states) and **Try** (a random scenario where you arm the guard, cancel it, and release it yourself, all the way to a shutdown or a block). All data is generated locally at random; nothing is sent over the network.
16
+
17
+ ## Why you need it
18
+
19
+ People who run long tasks know this scene: you hand the agent a batch of work in the evening, want the machine to shut down when it is done, and you are not at the desk.
20
+
21
+ But "task finished" does not mean "safe to shut down" — background jobs may still be running, a session may have errored, the agent may be waiting for your answer, or you may have stopped it halfway. Shutting down in any of those cases is an incident. The plugin blocks each of these situations and shuts down only when it is genuinely safe.
22
+
23
+ ## Rules: when it actually shuts down
24
+
25
+ In a blocked state (see below) nothing is sent at all; otherwise all five gates must pass before `shutdown /s` is issued:
26
+
27
+ 1. **Armed** — you turned it on manually
28
+ 2. **No countdown already running** — avoids duplicate issuance
29
+ 3. **All root sessions idle** — no agent is still working
30
+ 4. **No running or stopping background jobs** — `blockOnJobs`, on by default
31
+ 5. **No session error in this batch** — `onlyOnSuccess`, on by default
32
+
33
+ If any one is missing, it does not shut down and writes the reason on the card and in the settings page.
34
+
35
+ ## Three looks
36
+
37
+ The same look in light and dark; the trilingual captures live in `docs/screenshots/` as `<nn>-armed-<look>-<lang>-<theme>.png`.
38
+
39
+ | Minimal (light) | Radar (light) | Terminal (light) |
40
+ |:------------------------------------------------------------------------------:|:-----------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------:|
41
+ | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/03-armed-a-en-light.png" width="250"> | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/09-armed-b-en-light.png" width="250"> | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/15-armed-c-en-light.png" width="250"> |
42
+
43
+ | Minimal (dark) | Radar (dark) | Terminal (dark) |
44
+ |:------------------------------------------------------------------------------:|:-----------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------:|
45
+ | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/04-armed-a-en-dark.png" width="250"> | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/10-armed-b-en-dark.png" width="250"> | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/16-armed-c-en-dark.png" width="250"> |
46
+ | Large status text + one-line explanation, scrolling hazard tape at the bottom (red-and-yellow while standing by) | Radar + a status line, sessions and background jobs shown as dots | Three terminal lines (command, counts, waiting) over six gate indicator lights |
47
+
48
+ Dots in the radar are added and removed, never reordered: when the count changes, the dots you are looking at do not jump.
49
+
50
+ The hazard tape along the bottom of the card and the floating window follows the state: in the built-in palettes it sits in two quiet shades of the theme colour while the card is off or blocked, and from the moment you arm it until the shutdown actually runs it goes back to the familiar red-and-yellow stripes. The change is a 280 ms fade, not a flash. With Claude Style or DeepSeek Blue the tape never switches to red-and-yellow; it keeps following that theme throughout — the stripes would clash there.
51
+
52
+ <p align="center"><img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/anim-radar.gif" width="330" alt="Radar sweep"></p>
53
+
54
+ Pick one of the three thumbnail cards in the Appearance tab; it takes effect immediately, no restart. The picture switches with the UI language and the lightness of the surface behind it.
55
+
56
+ ## Six runtime states
57
+
58
+ | Off | Standby | Waiting on background jobs |
59
+ |:---------------------------------------------------:|:-----------------------------------------------------:|:----------------------------------------------------------------------:|
60
+ | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/01-off.png" width="250"> | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/02-armed.png" width="250"> | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/03-hold.png" width="250"> |
61
+
62
+ | Countdown | Issued | Blocked |
63
+ |:----------------------------------------------------:|:-----------------------------------------------------------------------:|:------------------------------------------------------------------------:|
64
+ | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/04-fire.png" width="250"> | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/05-fired.png" width="250"> | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/08-blocked-halted.png" width="250"> |
65
+
66
+ `Off` → `Standby` → `Waiting on background jobs` → `Countdown` → `Issued`; a blocking error at any stage moves it to `Blocked`. You can cancel at any point during the countdown; **cancelling leaves it armed** — the next batch will still shut down.
67
+
68
+ Want to flip through all six states? In section 04 of the demo's Learn pane, the three capsules `A` `B` `C` at the top right switch which look the preview uses.
69
+
70
+ ## Blocked states
71
+
72
+ | halted · needs manual action | waiting · retries automatically |
73
+ |:--------------------------------------------------------------:|:---------------------------------------------------------------------------------:|
74
+ | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/08-blocked-halted.png" width="250"> | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/09-blocked-waiting.png" width="250"> |
75
+
76
+ For errors that cannot be handled as ordinary failures, the plugin **stays armed**, changes the card's colour, and writes out the reason:
77
+
78
+ - **`halted`** (slate blue) — insufficient quota, invalid credentials, context overrun, unsupported capability, disk full. Only a human can resolve it; click "disarm and retry".
79
+ - **`waiting`** (slate green, a breathing dot) — rate limiting, 5xx, network interruption, empty response, timeout, image offload. Retried automatically according to policy; at the right moment a "continue the original plan" instruction is injected.
80
+
81
+ <p align="center"><img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/anim-blocked.gif" width="380" alt="Waiting for recovery, the status dot breathing"></p>
82
+
83
+ Retry cadence is configured per failure type; see [Retry policies](#retry-policies).
84
+
85
+ ## AFK auto-pilot
86
+
87
+ When nobody is at the desk, the plugin covers three things for you:
88
+
89
+ - **Auto-answer questions** (on by default): when the agent asks something, it replies "The user is AFK. Finish the tasks already assigned, write up a summary report to disk, and do not touch branches that were not assigned." It returns `selected: []` plus custom text, so **it never selects an option for you**; turn this off and questions come to you as usual.
90
+ - **Error recovery**: when a session errors, the agent is first asked to investigate and continue the original plan; a second failure in the same session switches to "stop retrying and write up a report".
91
+ - **Interruption protection**: if you stop a task yourself midway, that does not trigger a shutdown.
92
+
93
+ ## Where it lives
94
+
95
+ | Armed: below Skill Center, above the session list | Countdown: same spot, the whole strip turns red |
96
+ |:-------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------:|
97
+ | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/10-sidebar-hold.png" width="250"> | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/11-sidebar-fire.png" width="250"> |
98
+
99
+ The card is pinned to this sidebar slot by default; the button on the card header opens it as a standalone floating window that stays in view when you switch sessions (see "Other details" below).
100
+
101
+ ## Other details
102
+
103
+ - **Floating window**: the button in the card's top-right corner opens the status panel in a window of its own, so it stays in view when you switch to another session. The "compact floating window" switch in settings is on by default and keeps only the status and countdown; turn it off for the full panel.
104
+
105
+ - **Vertical collapse**: collapsed it keeps only the title and status; click the title to expand.
106
+
107
+ <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/16-folded.png" width="250" align="right" alt="Collapsed state">
108
+
109
+ - **Theme adaptation**: the card carries its own `--dsp-*` palette and picks light or dark automatically from the host surface's brightness; you can also force dark / light / Claude Style. The settings page always follows the host theme.
110
+
111
+ - **Language**: "follow system" by default — `LC_ALL` / `LC_MESSAGES` / `LANG`, then the `Intl` locale, finally `en`; you can also pin Chinese / English / Russian. The "follow" state for language and theme is held by the host, so it is not duplicated per device.
112
+
113
+ - **Event log**: the settings page shows recent events and "why it did not shut down last time"; the full stream is in `data/events.log`.
114
+
115
+ <br clear="all">
116
+
117
+ ## Settings page
118
+
119
+ | Dark | Light |
120
+ |:---------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:|
121
+ | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/12-settings-dark.png" width="380"> | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/13-settings-light.png" width="380"> |
122
+
123
+ Four tabs: **Appearance** / **Vigil** / **Notifications** / **Status**. It opens on Appearance, and every switch takes effect immediately.
124
+
125
+ - **Appearance**: five rows, no group header — the three thumbnail cards (Minimal / Radar / Terminal, the picture following the UI language and the lightness of the surface), theme (follow / dark / light / Claude Style), collapse by default, compact floating window, UI language.
126
+ - **Vigil**: the "custom profiles" switch sits alone at the top, outside any group; turning it on reveals the **Profiles** group, which lists the default profile and every custom one with switch, make-default, rename and delete, plus **+ New** at the end. Below it, the **Watch behaviour** group puts the four gate rows ahead of AFK: "All sessions idle" is the floor condition, always on and not switchable; "Background jobs finished" blocks background jobs; "skip this round if any session errored" blocks erroring batches; the countdown length is only editable in a custom profile. "AFK auto-answer" comes last and states "replies never select an option for you; turn this off and questions come to you as usual."
127
+ - **Notifications**: one group — a master switch plus four per-kind switches (countdown started / shutting down / first block / block recovered). Arming, disarming and cancelling by hand stay silent.
128
+ - **Status**: one group — current block, why it did not shut down last time, recent events.
129
+
130
+ ## Light and third-party themes
131
+
132
+ | Light card | Claude Style |
133
+ |:-----------------------------------------------------:|:------------------------------------------------------:|
134
+ | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/14-light.png" width="250"> | <img src="https://cdn.jsdelivr.net/gh/ltayshark0637/dsh-Shutdown-Guard@main/docs/screenshots/15-claude.png" width="250"> |
135
+
136
+ The card does not inherit the host palette; it brings its own, while the settings page follows the host theme. Claude Style in that row does not need the plugin first: without dsh-claude-style the card uses a built-in look-alike palette (terracotta) and a grey note appears under the theme row; install the plugin and it follows the live palette instead, the note gone. If the plugin is installed but set to `palette: host`, you get our own amber palette — not the terracotta.
137
+
138
+ ## Installation
139
+
140
+ Published on npm; recommended:
141
+
142
+ ```bash
143
+ dsh plugin --profile web add dsh-shutdown-guard
144
+ ```
145
+
146
+ Or install straight from GitHub:
147
+
148
+ ```bash
149
+ dsh plugin --profile web add github:ltayshark0637/dsh-Shutdown-Guard
150
+ ```
151
+
152
+ Then **restart DSH**.
153
+
154
+ > ⚠️ **The client bundle is only wired into the module graph at DSH startup**: after editing `lib/client.js` you must restart; refreshing the page does nothing. The same applies to the server side.
155
+
156
+ **On a restricted network**: the GitHub command is essentially `pnpm add github:...` and needs a clone. If cloning fails, prefer the npm command (it goes through the registry); if neither works, install from a local path — DSH Settings → Plugins → Add, enter the **absolute path** of this directory → Install → restart.
157
+
158
+ **Manual fallback**: create a symlink `node_modules\dsh-shutdown-guard` under `profiles\web` pointing at this directory, then:
159
+
160
+ - add `link:<absolute path of this directory>` to `dependencies` and `pnpm.overrides` in `profiles\web\package.json`
161
+ - append `dsh-shutdown-guard` to `dsh.profile.bundles`
162
+
163
+ ## Permissions and impact
164
+
165
+ Once installed, the plugin does the following:
166
+
167
+ | Area | Concrete behaviour |
168
+ | --- | --- |
169
+ | **Command execution** | Calls Windows `shutdown.exe` through `subprocess`: `/s /t <seconds>` schedules the shutdown, `/a` cancels it. **Only invoked when armed and all five gates pass** |
170
+ | **File access** | Only `data/` inside the plugin directory: `config.json` (configuration) and `events.log` (event log) |
171
+ | **Events** | `agent/status`, `agent/error`, `session/event`, `user-questions/request` |
172
+ | **HTTP routes** | Under the local DSH Web server, `/api/dsh-shutdown-guard/`: `GET /state`, `GET /events`, `POST /arm`, `POST /disarm`, `POST /cancel`, `POST /config`; POST validates that Origin is `127.0.0.1` or `localhost` |
173
+ | **Network** | **No external requests at all**, no data collection |
174
+ | **Platform** | **Windows only**. On anything other than `win32` the plugin silently does not load |
175
+
176
+ While AFK auto-pilot is on, the plugin answers the agent's questions and injects recovery instructions into erroring sessions during armed periods; you can switch this off at any time in settings.
177
+
178
+ ## Configuration
179
+
180
+ Configuration has two layers: one **default profile** and any number of **custom profiles**. Each profile carries its own countdown length, both gate switches, AFK and the four notification switches.
181
+
182
+ Three sources, lowest priority first:
183
+
184
+ 1. `cordis.patch.yml` (the shipped patch inside the plugin package)
185
+ 2. `data/config.json` (persisted at runtime; whatever the settings page changes lands here)
186
+ 3. `POST /config` (runtime change, also persisted)
187
+
188
+ ### Why the default profile is locked
189
+
190
+ The default profile is **everyone's safe floor**: 60 seconds, wait for background jobs, skip erroring batches — all hard-wired, not editable. The reasoning is practical:
191
+
192
+ - 60 seconds is the shortest window in which you can still see the card and hit cancel; at 10 seconds the cost of an accidental shutdown dwarfs the minute you saved.
193
+ - Because the default cannot drift, you can always switch back to it without wondering whether your last tweak is still in effect.
194
+ - Want something else? Fine — but you have to create a custom profile explicitly, and the edit lands there, never on the floor.
195
+
196
+ ### Creating a custom profile
197
+
198
+ Settings → **Appearance** → **Profiles** → `+ New`: it copies the current profile, you name it (≤24 characters) and save; it becomes the active profile.
199
+
200
+ - A fresh install has exactly one profile, the default one. The second one has to be created with `+ New` — no empty slots are pre-seeded.
201
+ - Turn the "custom profiles" switch off and back on, and you land on the profile you were using; if that one was deleted in the meantime, you stay on the default profile.
202
+ - On upgrade, slots you never touched are cleaned up. A profile you renamed, or one whose switches you changed at least once, is kept.
203
+ - Every custom profile can be made the default, renamed, or deleted.
204
+ - Deleting the profile in use falls back to the default profile, with a confirmation first.
205
+ - When the "custom profiles" master switch is off, no custom profile applies and the default is used.
206
+
207
+ ### Keys
208
+
209
+ These live per profile:
210
+
211
+ | Key | Range | Default | Description |
212
+ | --------------- | ------ | ------ | ----------------------------------------- |
213
+ | `countdownSec` | 10–600 | 60 | Seconds of countdown before shutdown (fixed at 60 in the default profile, not editable) |
214
+ | `onlyOnSuccess` | bool | `true` | Skip the shutdown when any session in this batch errored |
215
+ | `blockOnJobs` | bool | `true` | Skip the shutdown while background jobs are running |
216
+ | `afkAutoAnswer` | bool | `true` | Answer questions and perform error recovery for the user while armed |
217
+ | `notify` | object | all `true` | The four notification switches: `countdown` / `fired` / `blocked` / `recovered` |
218
+
219
+ Global (not tied to a profile):
220
+
221
+ | Key | Range | Default | Description |
222
+ | ---------- | ------ | ------ | ---- |
223
+ | `language` | enum | `auto` | UI language; `auto` follows the system language (`LC_ALL` / `LC_MESSAGES` / `LANG` → `Intl` → `en`), or pin it to `zh` / `en` / `ru` |
224
+ | `customEnabled` | bool | `false` | Whether custom profiles may be used |
225
+ | `activeProfileId` | string | `default` | The profile currently in effect |
226
+
227
+ `marginSec` (0–60, default 5) adds extra headroom to `shutdown /t`.
228
+
229
+ ### Retry policies
230
+
231
+ Each error code is assigned a policy name; the policies themselves are configurable:
232
+
233
+ | Policy | Default cadence | Assigned to |
234
+ | ------------ | -------------------------------------- | ---------------------------------- |
235
+ | `rate-limit` | backoff 30 → 60 → 120 → 240 → 300, capped | `RATE_LIMIT` |
236
+ | `transient` | fixed 20 seconds | `SERVER` / `TRANSPORT` / `TIMEOUT` |
237
+ | `empty` | fixed 30 seconds | `EMPTY_RESPONSE` |
238
+ | `offload` | fixed 5 seconds | `IMAGE_OFFLOAD_REQUIRED` |
239
+
240
+ Rate limiting uses backoff rather than a fixed interval because hammering a rate-limited service **extends** the limit rather than just wasting requests. 5xx and network failures recover on a predictable timescale, so a fixed interval is simply faster. Image offload is a local disk operation and is almost immediately retryable, hence 5 seconds.
241
+
242
+ Override a single error code:
243
+
244
+ ```yaml
245
+ retryAssign:
246
+ RATE_LIMIT: slow-limit
247
+ retryPolicies:
248
+ slow-limit: { mode: backoff, base: 60, max: 900 }
249
+ ```
250
+
251
+ An unknown `code` falls back to `transient`; malformed policies are dropped and the defaults are kept. The currently effective assignments are available from `retryAssign` and `retryPolicies` in `/state`.
252
+
253
+ **Where preferences live**: card look, theme and collapse are pure UI preferences and live in the browser's `localStorage` (`dsh-shutdown-guard.variant` / `.theme` / `.folded`); the compact floating window and the four notification switches are owned by the host (`data/ui.json`); language and the profile keys land in `data/config.json`.
254
+
255
+ ## HTTP API
256
+
257
+ Prefix `/api/dsh-shutdown-guard`, used by the card's own same-origin polling:
258
+
259
+ | Method | Path | Description |
260
+ | ---- | -------------------------- | -------------------------------------------------------------------------------- |
261
+ | GET | `/state?since=<ms>` | Full state plus incremental events |
262
+ | POST | `/arm` `/disarm` `/cancel` | Arm / disarm / cancel this countdown |
263
+ | POST | `/config` | Change configuration: `customEnabled`, `activeProfileId`, `profileId`+`patch`, `afkAutoAnswer`, `language` |
264
+ | GET | `/events?since=&limit=` | Event stream |
265
+
266
+ POSTs from a non-local Origin return 403; non-POST returns 405.
267
+
268
+ ## Logs
269
+
270
+ - `<plugin directory>\data\events.log` — human-readable event stream (categories `config` / `afk` / `skip`); the settings page's "Event log" reads it
271
+ - `<DSH_HOME>\dsh-shutdown-guard.log` — process-level append log
272
+
273
+ ## Development
274
+
275
+ ```bash
276
+ node test/host.test.mjs # server: 26 assertions, mock ctx, never actually shuts down
277
+ node test/client.test.cjs # client bundle smoke test
278
+ node scripts/gen-gallery.cjs # generate the screenshot page from the real CSS in lib/client.js
279
+ powershell -NoProfile -ExecutionPolicy Bypass -File scripts/screenshot.ps1 # headless Edge renders PNGs (transparent background + auto crop)
280
+ node scripts/gen-gif.cjs # generate the three animated GIFs used in the README
281
+ node scripts/gen-banner.cjs # generate the 1280x640 social preview image
282
+ node scripts/gen-demo.cjs # generate docs/demo.html (the interactive demo)
283
+ ```
284
+
285
+ All screenshots are generated from the real CSS in `lib/client.js`, so they cannot drift from the product. The animations work the same way: CSS animations are frozen at several points in time via `animation-delay`, captured frame by frame, and assembled with a built-in GIF89a encoder — no ffmpeg or ImageMagick needed on the machine.
286
+
287
+ The Learn section of the demo page is rendered live too: the six states are generated on the spot with the same `card()` markup as the product, not captured as screenshots.
288
+
289
+ ## FAQ
290
+
291
+ **Q: Why does changing a setting do nothing?**
292
+ A: Both the server and the client need DSH restarted to load new code. Local HMR does not apply to locally linked plugins.
293
+
294
+ **Q: Why didn't it shut down after arming?**
295
+ A: Check "why it did not shut down last time" in settings; it names the specific reason (background jobs, errors, not armed, etc.).
296
+
297
+ **Q: Can I change my mind during the countdown?**
298
+ A: Yes. Use "Cancel shutdown" on the card or just run `shutdown /a`; cancelling keeps it armed, so the next batch still applies.
299
+
300
+ **Q: Can it shut down by mistake?**
301
+ A: With the default configuration, the plugin acts only when all four hold — all sessions idle, no background jobs, no errors, and you armed it manually. Breaking any one of them cancels or skips the shutdown. Manual task termination is also detected and excluded.
302
+
303
+ **Q: Does it work outside Windows?**
304
+ A: Not yet; that is planned for later.
305
+
306
+ ## Constraints
307
+
308
+ - Windows only: exits early on anything other than `win32` and mounts nothing.
309
+ - Zero runtime dependencies: the server uses only `node:fs` / `node:path` / `node:crypto`, the browser only `require('react')`.
310
+ - Does not modify `app.asar` or DSH source.
311
+
312
+ ## License
313
+
314
+ [MIT](LICENSE)