dsh-console-sentinel 0.0.0-stage → 0.3.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 ADDED
@@ -0,0 +1,231 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 (2026-11-03) — 设置面板 UI(任务栏窗口清理 / 白名单 / 窗口状态)
4
+
5
+ 用户需求:想看插件「现在什么状态」,并要「清理任务栏窗口」的操作入口 + 有没有 UI 设计。
6
+ 此前插件是纯宿主插件(无 `dsh.client`),只能在对话里让模型调工具,用户自己点不了。
7
+
8
+ ### 新增
9
+ 1. **客户端设置面板** `lib/client.js`(`__ModuleLoader__` bundle,`dsh.client` 声明,
10
+ platform `web`,id=完整包名):在 DSH「设置」页注册「控制台哨兵」分区。分区内含
11
+ - 概览:总窗口 / 空壳 / 可见空壳 / 已隐藏 计数 + 「隐藏可见空壳」按钮;
12
+ - 任务栏窗口清理:预演 / 一键关闭其他窗口;
13
+ - 控制台窗口列表:逐行显示并可 隐藏 / 显示 / 关闭;
14
+ - 豁免白名单:进程名 / 标题关键字 标签式增删与清空。
15
+ 2. **宿主 HTTP 路由** `lib/index.js` `registerHttpUi()`:`/dsh-console-sentinel/{status,scan,hide-empty,window,desktop-cleanup,whitelist}`,
16
+ 均做 loopback 校验;客户端 fetch 根相对路径(桌面端 Electron renderer 自动带
17
+ capability header,普通 curl 仍 403)。webServer 缺失时面板优雅降级,不影响工具功能。
18
+ 3. `package.json`:新增 `dsh.client`、`exports["./client"]`,版本 0.2.0→0.3.0。
19
+ 4. `tests/client.test.mjs`:DOM stub 评估真实 bundle,断言 bundle id / `exports.inject`
20
+ 为数组 / `settings.section` 注册 / 首屏计数-窗口-白名单渲染 / 按钮触发路由。
21
+
22
+ ### 约束(都遵守,来自本机踩坑)
23
+ - `exports.inject` 必须是普通数组;`ctx.slots.inject("settings.section", …)` 直接调用不包 `ctx.effect`;
24
+ 组件不用 React hooks,结果按 id 写回 DOM 并在渲染入口 `setTimeout(0)` 自愈重挂载。
25
+ - 样式只用 `--dsw-alias-*` 设计令牌;不使用 iframe;不轮询 agent/status。
26
+ - 客户端无绝对 URL(探针断言 https?:// 计数 0、iframe 计数 0)。
27
+
28
+ ### 修复(2026-11-04,仍在 0.3.0 内)
29
+ 1. **插件根本没加载(duplicate loader entry)**:`dsh-console-sentinel` 同时出现在
30
+ `profiles/desktop/package.json` 的 `dsh.profile.bundles` 与 `profiles/desktop/cordis.patch.yml`
31
+ 两条同 id 条目中,加载器按 duplicate 处理 → 插件不挂载(日志无任何 mounted 行、对话里
32
+ 也无 `console_windows_list` 等工具)。按 `dsh-artifact-linker` 的约定,**配置迁入插件自身
33
+ bundle patch `cordis.patch.yml`**,并删除 profile `cordis.patch.yml` 里的手工条目(bundles 保留)。
34
+ 校验:profile 28 条目、无 sentinel、bundles 36 项含 sentinel、trueDuplicates=[]。
35
+ 2. **Windows Terminal 空壳识别**:可见的「黑窗」主要是 `CASCADIA_HOSTING_WINDOW_CLASS`
36
+ (窗口属主是 `WindowsTerminal.exe`,不是里面的 shell),旧的 `isEmptyConsole` 只认解释器
37
+ 进程本身 → 永远判「非空」。新增 `isBareShellTitle()` 与 `isEmptyConsole` 的 CASCADIA 分支
38
+ (标题为裸解释器路径如 `…\powershell.exe` / `…\cmd.exe` 且 `cpuPct ≤ 1` 才算空),并把
39
+ `windowsterminal` 从 `HARD_PROTECTED_PROCESSES` 移出(`autoAction` 仍为 `none`,只经
40
+ `emptyConsoleAction: hide` 处理空壳)。实测 8 个可见窗口中 5 个 CASCADIA 黑窗被判空。
41
+ 3. `tests/cascadia.test.mjs`:`isBareShellTitle` + CASCADIA 空判定 3 用例。
42
+ 4. **bundle patch 里 `disabled: !!js …` 使整条 insert 静默失败**(2026-11-04 二次实测,真凶):
43
+ 插件自己的 `cordis.patch.yml` 属 **bundle patch**,由普通 YAML 解析器读取,不认自定义标签;
44
+ 本机 17 个能正常加载的插件其 patch **无一使用自定义标签**。移除该行(平台守卫保留在
45
+ `apply()`:非 Windows 打 `staying inert` 并 return),普通 YAML 解析通过。
46
+ 同时在 `apply()` 入口加 `logger.info('host half mounted (v0.3.0, …)')`,以后日志可直接确认挂载。
47
+ 5. **扫描定时器被宿主 dispose 清掉(真凶 2)**:`ctx.effect(() => () => clearInterval(scanTimer))`
48
+ 的 disposer 在宿主侧 teardown/reload 时会被调用,导致**第一轮 `scan #1` 之后定时器被
49
+ `clearInterval`**,之后再无 `scan #2`、也无任何异常日志(`catch` 不触发)。改为**进程自有的
50
+ plain `setInterval`(不挂 `ctx.effect`)**,并保留一个「超过 3×周期未扫描就强制补扫」的自愈
51
+ supervisor;临时的心跳/逐窗口诊断日志已清理。
52
+ 实测(修复后):启动 22 秒内 **6 个空壳窗口被自动隐藏**(5 个 Windows Terminal `CASCADIA` +
53
+ 1 个 orphaned powershell),`visibleAfter=false`、`stillWindow=true`。
54
+ 6. 默认 `idleThresholdSec` 180 → **60**(空壳窗口更快消失;CPU 持续空闲才计数,不影响正在
55
+ 计算的 Fluent/mpiexec 控制台)。
56
+
57
+ ### 生效方式
58
+ - 改 `lib/client.js` → **Ctrl+Shift+R** 硬刷新;
59
+ - 改宿主 `lib/index.js` / `package.json` / 挂载 → **完全退出 DSH 再启动**。
60
+
61
+ ## 0.2.0 (2026-11-03) — 空壳 PowerShell 安全清理 + 一键关窗口统一白名单
62
+
63
+ 用户需求:检查插件运营状态;能否有效隐藏/关闭「没有进程捆绑的空 PowerShell」;
64
+ 把之前开发的一键关闭其他窗口加进来;可设置豁免软件白名单。
65
+
66
+ ### 修的真问题
67
+ 0.1.x 的 `scope.orphaned` 默认 `false`,导致最常见的痛点——WMI/脚本退出后残留的
68
+ **空 PowerShell 窗口**——永远不被自动处理;而直接打开 `orphaned` 又会误伤
69
+ `mpiexec`(Fluent MPI)、`fl2610`(求解器)等在跑的计算(同属「父进程已退出」)。
70
+ 空判定缺位 = 只能二选一,两个都不对。
71
+
72
+ ### 新增
73
+ 1. **空控制台安全判定**(`lib/logic.js` `isEmptyConsole`):解释器进程
74
+ (powershell/pwsh/cmd/python/pythonw)**且没有任何工作子进程**(children 去掉
75
+ conhost/openconsole 后为空)才算「空壳」。probe 现返回每进程 `Children`
76
+ (`lib/probe.js` 用 NtQueryInformationProcess 自建父子表,零依赖)。新配置
77
+ `scope.emptyConsole`(默认 `true`)+ `emptyConsoleAction`(默认 `hide`,独立于
78
+ `autoAction`)——只清空壳、绝不碰在跑计算。
79
+ 2. **硬保护进程集** `HARD_PROTECTED_PROCESSES`:explorer/taskmgr/dsh desktop/
80
+ dwm/winlogon/csrss/lsass 等系统进程,以及 fl2610/cx2610/mpiexec/smpd/fluent/
81
+ fluent-meshing/launcher1 —— 无论配置如何都拒绝管理。
82
+ 3. **持久化豁免白名单** `lib/whitelist.js` + 新工具 `sentinel_whitelist`
83
+ (list/add/remove/reset,kind=process|title)。落盘
84
+ `~/.dsh/console-sentinel/whitelist.json`,与静态 `config.whitelist` 合并,工具
85
+ 改动即时生效、无需改 yml 或重启。白名单同时作用于自动动作与
86
+ `desktop_windows_cleanup`。
87
+ 4. **一键关闭其他窗口**(`desktop_windows_cleanup`)现在读统一白名单,
88
+ `protectedProcesses`/`keepTitles` 合并用户名单。
89
+ 5. `console_windows_list` 每行新增 `kids=`(子进程数)与 `[empty]` 标记。
90
+
91
+ ### 测试
92
+ `desktop.test.mjs` D5 默认保护列表断言从 4 项更新为实际 9 项(实现更安全,测试
93
+ 落后于实现,改测试不改实现)。全套 `node --test` 51 断言全绿
94
+ (logic 29 + desktop 17 + tool-output 5)。
95
+
96
+ ## 0.1.2 (2026-09-23) — the ledger claimed hides it never verified
97
+
98
+ Reported by the user, correctly: “你说你隐藏了 13 个真的假的”. It was **partly
99
+ false**. An independent Win32 check (`IsWindowVisible`) of the 13 hwnds the
100
+ plugin had reported as `[hidden]` found **9 genuinely hidden, 4 already
101
+ destroyed, and 1 still visible** (a minimised Windows Terminal). Three defects:
102
+
103
+ 1. **`hidden` was set from intent, not from observation** (`lib/index.js`
104
+ `actOn`). `rec.hidden = true` ran as soon as `runWindowAction` returned
105
+ `ok`, before anything re-checked the window. The action script *already*
106
+ measured the post-state (`afterVisible`, `stillWindow`) — the ledger simply
107
+ never looked at it. `hidden` is now derived from that measured state, and a
108
+ failed action can no longer leave a stale `hidden` claim behind.
109
+ 2. **“destroyed” and “hidden” were conflated.** A window that no longer exists
110
+ was counted as a successful hide, which inflated the reported number. There
111
+ is now a separate `destroyed` flag, surfaced in the list as `[gone]`, and a
112
+ gone window is never counted as hidden. This is why “I hid 13” was wrong:
113
+ 4 of them were gone, not hidden.
114
+ 3. **`console_window_action` returned invalid output on every success**
115
+ (`lib/index.js`). It built `reason: res.ok ? undefined : res.reason`, so a
116
+ successful call carried an explicit `reason: undefined` against a
117
+ schema-declared `string` → validation failed and the runtime dropped the
118
+ whole result as *“value is not lossless JSON”*. The hide really happened;
119
+ the caller just never saw it. `reason` is now omitted on success (and
120
+ coerced to a string on failure) instead of being set to `undefined`.
121
+ **`desktop_windows_cleanup` had the identical defect** — and there it sat on
122
+ the tool's *normal* path (returning a dry-run plan), so it broke every call,
123
+ not just some. Fixed in both; a grep for the pattern now matches only the
124
+ explanatory comment.
125
+
126
+ Also added:
127
+
128
+ - **`probeVisibility()`** (`lib/probe.js`) — a lightweight batch
129
+ `IsWindowVisible` query (no full scan), used as an independent source of
130
+ truth.
131
+ - **`ledger.reconcileVisibility()`** (`lib/logic.js`) — an anti-lie guard.
132
+ `console_windows_list` now re-measures every tracked window before printing,
133
+ so `[hidden]` can only appear for a window that is genuinely not visible at
134
+ that moment; windows missing from the truth map are reported as `[gone]`.
135
+
136
+ Tests: `tests/logic.test.mjs` 24 → 29 assertions (destroyed-vs-hidden, and
137
+ reconcileVisibility in all three directions); new `tests/tool-output.test.mjs`
138
+ (5 assertions) locks the schema contract and **asserts the old shape was
139
+ buggy**, so the defect cannot silently return.
140
+
141
+ Lesson recorded: a plugin’s own ledger is not evidence. “The bookkeeping says
142
+ success” and “the user can see it worked” are different claims, and only the
143
+ second one is worth reporting — verify with something outside the component
144
+ that is making the claim.
145
+
146
+ ## 0.1.1 (2026-09-22) — desktop-cleanup path repaired + protected set widened
147
+
148
+ The console path (`console_windows_list` / `_action` / `_sweep`) was fine, but
149
+ the **desktop-cleanup path was dead on arrival** — `desktop_windows_cleanup`,
150
+ `desktop-cli.mjs list|cleanup`, and the 一键关闭其他窗口 shortcut all failed.
151
+ Four defects, each found by running the code rather than reading it:
152
+
153
+ 1. **Marker never substituted** (`lib/desktop.js`). The probe template used
154
+ `'${JSON_BEGIN}'` but was never `.replace()`d, so the literal text
155
+ `${JSON_BEGIN}` reached PowerShell and `extractMarkeredJson` found nothing
156
+ → `no-marker-output`. `probe.js` had always done this correctly
157
+ (`.replace(/__JSON_BEGIN__/g, …)`); desktop.js now matches it.
158
+ 2. **`@(['Progman',…])` is a PowerShell parse error** (`lib/desktop.js`).
159
+ `SHELL_CLASSES_PS` wrapped the list in `[ … ]`, and the call site wrapped
160
+ that again in `@( … )` → *"Missing type name after '['"*, aborting the
161
+ probe before any window was enumerated.
162
+ 3. **Every selection guard silently fell through** (`lib/desktop.js`).
163
+ `selectCleanupTargets` read `w.hwnd` / `w.process` / `w.class` / `w.title`,
164
+ but the embedded probe emits **PascalCase** (`Hwnd` / `Process` / `Class` /
165
+ `Title`). All comparisons were `undefined !== anything`, so the foreground
166
+ guard, the shell-class filter **and the protected-process guarantee** did
167
+ nothing — DSH Desktop itself, explorer and Taskmgr landed in the close list.
168
+ Rows are now normalised through one `raw.x ?? raw.X` shim.
169
+ 4. **CLI crashed printing the plan** (`lib/desktop-cli.mjs`). `t.title.slice()`
170
+ on the same casing mismatch → `TypeError: Cannot read properties of
171
+ undefined`. Shares the normaliser now, and tolerates empty titles.
172
+
173
+ ### Protected set widened (user-approved, beyond upstream)
174
+
175
+ Upstream protected only `powershell / pwsh / python / pythonw`. Each addition
176
+ below was justified against a live probe, not guessed:
177
+
178
+ | added | why |
179
+ |---|---|
180
+ | `dsh desktop` | closing the harness host kills the session |
181
+ | `explorer` | shell/taskbar; multiple file-manager Hwnds share one pid |
182
+ | `taskmgr` | closing Task Manager is a forced-end acting on other apps |
183
+ | `fluent`, `launcher1` | Fluent Launcher / solver front-ends are long-running CFD work |
184
+
185
+ ### Verification
186
+
187
+ - `node tests/logic.test.mjs` — **24/24 still pass** (no regression)
188
+ - live probe: enumerates 30 windows, 10 visible consoles
189
+ - `desktop-cli.mjs list` — 14–18 targets, **0** protected/foreground/shell-class
190
+ windows selected (DSH Desktop, explorer, Taskmgr, Fluent all correctly kept)
191
+ - `desktop-cli.mjs cleanup` — dry run prints the plan, closes nothing
192
+
193
+ ## 0.1.0 (2026-09-16) — first MVP
194
+
195
+ Host-side cordis plugin: auto-hide idle AI/script console windows on Windows.
196
+
197
+ ### Configured defaults (per user's MVP decision)
198
+ - `autoAction: hide` (lossless; process keeps running, `show` restores it)
199
+ - `idleThresholdSec: 60`, `cpuThresholdPct: 1`, `spikeTolerance: 2`
200
+ - scope: `wmiDetached` + `dshHost` + `scriptConsole` ON, `orphaned` OFF
201
+ - host-side only; no client bundle in v1
202
+
203
+ ### Empirical findings fixed during development
204
+ 1. **Windows Terminal windows are classified by title, not parent chain.**
205
+ A WT top-level window belongs to the WindowsTerminal process (parent
206
+ svchost), not to the script it hosts — parent-chain classification left
207
+ all 8 stacked black windows as `manual` (unmanaged). Added `script-console`
208
+ source keyed on the console title being a script executable path.
209
+ (Interactive consoles keep prompt-style titles like "Windows PowerShell"
210
+ and stay unmanaged.)
211
+ 2. **`SW_SHOW(5)` cannot restore a hidden WT window.** Measured: after
212
+ `SW_HIDE`, `SW_SHOW` leaves `IsWindowVisible` false; `SW_SHOWNORMAL(1)`
213
+ restores it. Show action now uses SW_SHOWNORMAL with SW_RESTORE fallback.
214
+ 3. **Single-sample CPU spikes reset the idle clock.** The WindowsTerminal
215
+ host process spikes briefly even when all its sessions are idle, so
216
+ stacked windows never accumulated 60s of idle. `spikeTolerance=2` now
217
+ requires two consecutive busy samples to break an idle streak.
218
+ 4. **`NtQueryInformationProcess` over CIM for parent-pid.** Measured 39 s vs
219
+ 24 ms for 13 processes (`Get-CimInstance Win32_Process`). Full probe
220
+ ~350 ms at a 10 s scan interval.
221
+ 5. **dsh-tools output-schema constraints**: object schemas must declare
222
+ `additionalProperties` explicitly; output value schemas reject `required`.
223
+ Caught only by the real-runtime load precheck, not by `node --check`.
224
+
225
+ ### Verification
226
+ - `node tests/logic.test.mjs` — 24/24 (classification, idle accumulation,
227
+ spike tolerance, scope guards, GC, ordering)
228
+ - `node tests/e2e.mjs` — live probe (24 windows), reversible hide→show
229
+ - load precheck against the profile's real cordis/schemastery/dsh-tools —
230
+ 3 tools register, scheduler effect mounts, config defaults correct
231
+ - privacy audit: 0 hits across 9 files; `npm pack` 8 files / 16.1 kB
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 loyalchiiina
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-es.md ADDED
@@ -0,0 +1,29 @@
1
+ # Console Sentinel
2
+
3
+ > Watch the browser console and DSH logs, surface errors early, and auto-hide finished console windows.
4
+
5
+ > Full documentation: [English README](./README.md)
6
+
7
+ ## How it works
8
+
9
+ > 📖 [Leer esta sección en el README principal](./README.md#how-it-works)
10
+
11
+ ## Install
12
+
13
+ > 📖 [Leer esta sección en el README principal](./README.md#install)
14
+
15
+ ## Tools (model-visible)
16
+
17
+ > 📖 [Leer esta sección en el README principal](./README.md#tools-model-visible)
18
+
19
+ ## Config (cordis.yml)
20
+
21
+ > 📖 [Leer esta sección en el README principal](./README.md#config-cordisyml)
22
+
23
+ ## Safety
24
+
25
+ > 📖 [Leer esta sección en el README principal](./README.md#safety)
26
+
27
+ ## Development
28
+
29
+ > 📖 [Leer esta sección en el README principal](./README.md#development)
package/README-hi.md ADDED
@@ -0,0 +1,29 @@
1
+ # Console Sentinel
2
+
3
+ > Watch the browser console and DSH logs, surface errors early, and auto-hide finished console windows.
4
+
5
+ > Full documentation: [English README](./README.md)
6
+
7
+ ## How it works
8
+
9
+ > 📖 [यह अनुभाग मुख्य README में पढ़ें](./README.md#how-it-works)
10
+
11
+ ## Install
12
+
13
+ > 📖 [यह अनुभाग मुख्य README में पढ़ें](./README.md#install)
14
+
15
+ ## Tools (model-visible)
16
+
17
+ > 📖 [यह अनुभाग मुख्य README में पढ़ें](./README.md#tools-model-visible)
18
+
19
+ ## Config (cordis.yml)
20
+
21
+ > 📖 [यह अनुभाग मुख्य README में पढ़ें](./README.md#config-cordisyml)
22
+
23
+ ## Safety
24
+
25
+ > 📖 [यह अनुभाग मुख्य README में पढ़ें](./README.md#safety)
26
+
27
+ ## Development
28
+
29
+ > 📖 [यह अनुभाग मुख्य README में पढ़ें](./README.md#development)
package/README-pt.md ADDED
@@ -0,0 +1,29 @@
1
+ # Console Sentinel
2
+
3
+ > Watch the browser console and DSH logs, surface errors early, and auto-hide finished console windows.
4
+
5
+ > Full documentation: [English README](./README.md)
6
+
7
+ ## How it works
8
+
9
+ > 📖 [Ler esta seção no README principal](./README.md#how-it-works)
10
+
11
+ ## Install
12
+
13
+ > 📖 [Ler esta seção no README principal](./README.md#install)
14
+
15
+ ## Tools (model-visible)
16
+
17
+ > 📖 [Ler esta seção no README principal](./README.md#tools-model-visible)
18
+
19
+ ## Config (cordis.yml)
20
+
21
+ > 📖 [Ler esta seção no README principal](./README.md#config-cordisyml)
22
+
23
+ ## Safety
24
+
25
+ > 📖 [Ler esta seção no README principal](./README.md#safety)
26
+
27
+ ## Development
28
+
29
+ > 📖 [Ler esta seção no README principal](./README.md#development)
package/README-zh.md ADDED
@@ -0,0 +1,29 @@
1
+ # dsh-console-sentinel
2
+
3
+ > 盯住浏览器控制台与 DSH 日志,提前发现报错,并自动隐藏已经跑完的控制台窗口。
4
+
5
+ > 完整文档以 [English README](./README.md) 为准,本页提供中文导读与关键信息速查。
6
+
7
+ ## How it works
8
+
9
+ 本节内容详见 [English README](./README.md)。
10
+
11
+ ## Install
12
+
13
+ 本节内容详见 [English README](./README.md)。
14
+
15
+ ## Tools (model-visible)
16
+
17
+ 本节内容详见 [English README](./README.md)。
18
+
19
+ ## Config (cordis.yml)
20
+
21
+ 本节内容详见 [English README](./README.md)。
22
+
23
+ ## Safety
24
+
25
+ 本节内容详见 [English README](./README.md)。
26
+
27
+ ## Development
28
+
29
+ 本节内容详见 [English README](./README.md)。
package/README.en.md ADDED
@@ -0,0 +1,127 @@
1
+ # dsh-console-sentinel
2
+
3
+ Auto-hide (or close) idle AI/script console windows on Windows — a
4
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin.
5
+
6
+ Long-running harness workflows (unattended Fluent jobs, batch scripts,
7
+ WMI-launched detached processes) leave PowerShell/Python console windows
8
+ stacked in the taskbar. You cannot tell which are still working and you do not
9
+ want to kill them by hand. This plugin tracks them, tells busy from finished,
10
+ and hides the finished ones for you — the process keeps running, nothing is
11
+ lost, and every window can be brought back on demand.
12
+
13
+ ## How it works
14
+
15
+ 1. Every `scanIntervalSec` seconds (default 10) it enumerates console windows
16
+ with a zero-dependency Windows PowerShell probe (`user32` + `ntdll`
17
+ P/Invoke): `CASCADIA_HOSTING_WINDOW_CLASS` (Windows Terminal),
18
+ `ConsoleWindowClass` (classic conhost), and `PseudoConsoleWindow` (ConPTY).
19
+ 2. Each console's process is classified: by its **parent process**
20
+ (`WmiPrvSE.exe` → `wmi-detached` — WMI-launched detached jobs, how
21
+ unattended Fluent runs are started; `DSH Desktop.exe` → `dsh-host`;
22
+ parent already exited → `orphaned`), or, when the parent chain is
23
+ uninformative (a Windows Terminal window belongs to the WindowsTerminal
24
+ process, not to the script it hosts), by the **console title**: a scripted
25
+ session defaults its title to the launched executable path
26
+ (`...\python.exe`), while an interactive console keeps a prompt-style
27
+ title (`Windows PowerShell`) → `script-console`.
28
+ 3. CPU usage is sampled per scan (process total CPU time deltas). A window
29
+ under `cpuThresholdPct` (default 1%) for `idleThresholdSec` (default 60)
30
+ counts as **finished**. Single-sample background jitter does not reset the
31
+ idle clock (`spikeTolerance`, default 2).
32
+ 4. Finished windows **in scope** get the configured `autoAction`
33
+ (default `hide` = `SW_HIDE`; the process keeps running). `close` sends
34
+ `WM_CLOSE`. The focused window is never touched (`guardForeground`).
35
+
36
+ Parent-pid lookup uses `NtQueryInformationProcess` rather than CIM
37
+ (`Get-CimInstance Win32_Process`), which measured **39 s vs 24 ms** for 13
38
+ processes on the reference machine. A full probe takes ~350 ms.
39
+
40
+ ## Install
41
+
42
+ ```sh
43
+ dsh plugin --profile <name> add dsh-console-sentinel
44
+ ```
45
+
46
+ Bundle plugin (`dsh.bundle.patch`): enters the profile bundle stack, takes
47
+ effect on restart. Windows-only — the patch row is `disabled` on every other
48
+ platform and `apply()` also guards at runtime.
49
+
50
+ ## Tools (model-visible)
51
+
52
+ - `console_windows_list` — every tracked console window with idle state and
53
+ origin classification. Use this first.
54
+ - `console_window_action` — hide / show / minimize / restore / close one
55
+ window (by `hwnd` or `pid`). `hide` is lossless; `close` may lose unsaved
56
+ output. Pass `force` to act on the focused window.
57
+ - `console_windows_sweep` — apply the action to every idle in-scope window at
58
+ once (handy after a batch of Fluent jobs).
59
+ - `desktop_windows_cleanup` — **close all other windows**: close every desktop
60
+ app window except the focused one, self, shell classes, and protected
61
+ processes (`dryRun` first for a plan; `protected` / `keepTitle` / `keepPid`
62
+ add temporary protection).
63
+ - `sentinel_whitelist` — manage the **persistent exemption whitelist**:
64
+ `list` / `add` / `remove` / `reset`, `kind=process` (image name, e.g.
65
+ `code.exe`) or `kind=title` (title substring). Applies to both auto-actions
66
+ and desktop cleanup; stored at `~/.dsh/console-sentinel/whitelist.json`.
67
+
68
+ ## Config (cordis.yml)
69
+
70
+ | Key | Default | Meaning |
71
+ |---|---|---|
72
+ | `enabled` | `true` | master switch |
73
+ | `scanIntervalSec` | `10` | how often to scan + sample CPU |
74
+ | `idleThresholdSec` | `60` | sustained-low-CPU seconds before a window counts as finished |
75
+ | `cpuThresholdPct` | `1` | average CPU below this = idle |
76
+ | `autoAction` | `hide` | `none` \| `hide` \| `close` |
77
+ | `probeTimeoutMs` | `20000` | hard probe timeout |
78
+ | `spikeTolerance` | `2` | consecutive busy samples needed to reset the idle clock |
79
+ | `guardForeground` | `true` | never auto-manage the focused window |
80
+ | `scope.wmiDetached` | `true` | manage WMI-launched detached consoles |
81
+ | `scope.dshHost` | `true` | manage host-spawned consoles |
82
+ | `scope.orphaned` | `false` | manage consoles whose parent already exited |
83
+ | `scope.scriptConsole` | `true` | manage consoles whose title is a script executable path (Windows Terminal windows hosting a scripted session) |
84
+ | `scope.excludeTitles` | `[]` | title substring blocklist |
85
+ | `scope.emptyConsole` | `true` | treat an interpreter console (PowerShell/Python/cmd with only conhost and **no workload child**) as an empty shell and manage it regardless of parent chain — the safe way to clean up leftover empty PowerShell windows without enabling the whole `orphaned` scope (which would also match mpiexec / fl2610 / live compute) |
86
+ | `emptyConsoleAction` | `hide` | action for empty shells: `none` \| `hide` \| `close`; independent of `autoAction` |
87
+ | `whitelist.processes` | `[]` | exempt image names (e.g. `code.exe`), never auto-managed nor bulk-closed (edit at runtime via `sentinel_whitelist`) |
88
+ | `whitelist.titles` | `[]` | exempt title substrings, never auto-managed nor bulk-closed |
89
+
90
+ ```yaml
91
+ - id: dsh-console-sentinel
92
+ config:
93
+ idleThresholdSec: 90
94
+ scope:
95
+ excludeTitles:
96
+ - keep-alive
97
+ ```
98
+
99
+ ## Safety
100
+
101
+ `hide` only calls `ShowWindow(SW_HIDE)` — the process, its logs, and its
102
+ output are untouched; `console_window_action` with `show` restores it
103
+ (`SW_SHOWNORMAL` — measured: `SW_SHOW` leaves a hidden Windows Terminal
104
+ window invisible). `close` sends `WM_CLOSE` (graceful, same as clicking the
105
+ X) but may lose unsaved state, so it is never the default. Automatic actions
106
+ never touch the focused window, manually opened consoles (`manual` source),
107
+ title-blocklisted or **whitelisted** windows, or any process in
108
+ `HARD_PROTECTED_PROCESSES` (explorer, taskmgr, dsh desktop,
109
+ fl2610/cx2610/mpiexec/smpd/fluent, etc.). Every action is emitted as
110
+ `console-sentinel/action` and logged.
111
+
112
+ ## Development
113
+
114
+ ```
115
+ lib/index.js Cordis plugin: config, scan scheduler, tools
116
+ lib/logic.js pure ledger/idle state machine + empty-console detection (unit-tested)
117
+ lib/probe.js embedded PowerShell probe + window actions
118
+ lib/desktop.js desktop-wide window close (一键关闭其他窗口)
119
+ lib/whitelist.js persistent exemption whitelist (JSON)
120
+ tests/ node-only regression tests, no live Windows needed
121
+ ```
122
+
123
+ `node --test tests/*.test.mjs` — 51 assertions (logic 29 + desktop 17 +
124
+ tool-output 5) covering classification, idle accumulation, busy-blip reset,
125
+ scope guards, empty-console detection, whitelist, GC, and ordering.
126
+
127
+ MIT License.
package/README.md CHANGED
@@ -1,3 +1,126 @@
1
- # Temporary Holding Version
1
+ # dsh-console-sentinel
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 自动隐藏(或关闭)Windows 上空闲的 AI/脚本控制台窗口 —— 一个
4
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件。
5
+
6
+ 长时间运行的自动化工作流(Fluent 无人值守任务、批量脚本、WMI 拉起的脱离进程)
7
+ 会在任务栏堆下一排 PowerShell/Python 黑窗口。你分不清哪些还在跑,也不敢随手关。
8
+ 本插件替你追踪它们、区分「在忙」和「用完了」,并把用完的隐藏掉 —— 进程继续跑、
9
+ 数据不丢,每个窗口都能随时叫回来。
10
+
11
+ ## 工作原理
12
+
13
+ 1. 每隔 `scanIntervalSec` 秒(默认 10)用零依赖的 Windows PowerShell 探针
14
+ (`user32` + `ntdll` P/Invoke)枚举控制台窗口:`CASCADIA_HOSTING_WINDOW_CLASS`
15
+ (Windows Terminal)、`ConsoleWindowClass`(传统 conhost)、
16
+ `PseudoConsoleWindow`(ConPTY 伪控制台)。
17
+ 2. 按进程归类:优先看**父进程**(父进程是 `WmiPrvSE.exe` → `wmi-detached`,
18
+ WMI 拉起的脱离任务,无人值守 Fluent 计算正是这样启动的;父进程是
19
+ `DSH Desktop.exe` → `dsh-host`;父进程已退出 → `orphaned`)。当父链说明
20
+ 不了问题时(Windows Terminal 的窗口归属于 WindowsTerminal 进程,而不是
21
+ 它里面跑的脚本),改看**控制台标题**:脚本启动的控制台标题默认是
22
+ 被启动程序的路径(`...\python.exe`),而交互式手开的控制台是提示符式
23
+ 标题(`Windows PowerShell`)→ 归为 `script-console`。
24
+ 3. 每轮采样进程 CPU(累计 CPU 时间差值)。CPU 低于 `cpuThresholdPct`(默认 1%)
25
+ 且持续 `idleThresholdSec`(默认 60 秒)即判定为**用完了**。单次采样的后台
26
+ CPU 抖动不会重置空闲时钟(`spikeTolerance`,默认 2)。
27
+ 4. 在管理范围内的「用完了」窗口执行配置的 `autoAction`(默认 `hide` = `SW_HIDE`,
28
+ 进程继续跑)。`close` 发送 `WM_CLOSE`。前台窗口永不自动处理(`guardForeground`)。
29
+
30
+ 父进程 PID 用 `NtQueryInformationProcess` 而非 CIM(`Get-CimInstance Win32_Process`)
31
+ —— 参考机上实测 **39 秒 vs 24 毫秒**(13 个进程)。单次完整探针约 350 毫秒。
32
+
33
+ ## 安装
34
+
35
+ ```sh
36
+ dsh plugin --profile <name> add dsh-console-sentinel
37
+ ```
38
+
39
+ Bundle 插件(`dsh.bundle.patch`):进入 profile bundle 栈,重启后生效。
40
+ 仅限 Windows —— patch 行在其他平台 `disabled`,`apply()` 运行时也会再守一次。
41
+
42
+ ## 工具(模型可见)
43
+
44
+ - `console_windows_list` —— 列出所有被追踪的控制台窗口及其空闲状态和来源归类。先用它。
45
+ - `console_window_action` —— 对单个窗口执行 hide / show / minimize / restore / close
46
+ (用 `hwnd` 或 `pid` 定位)。`hide` 无损;`close` 可能丢失未保存输出。
47
+ 对前台窗口操作需传 `force`。
48
+ - `console_windows_sweep` —— 一次性对所有空闲且在范围内的窗口执行动作
49
+ (跑完一批 Fluent 任务后清任务栏很好用)。
50
+ - `desktop_windows_cleanup` —— **一键关闭其他窗口**:关闭桌面上除前台、自身、
51
+ 系统外壳和受保护进程以外的所有应用窗口(`dryRun` 先出计划;`protected` /
52
+ `keepTitle` / `keepPid` 可临时加固保护)。
53
+ - `sentinel_whitelist` —— 管理**持久化豁免白名单**:`list` / `add` / `remove` / `reset`,
54
+ `kind=process`(按映像名,如 `code.exe`)或 `kind=title`(按标题子串)。
55
+ 白名单同时作用于自动隐藏/关闭与一键关闭窗口,落盘到
56
+ `~/.dsh/console-sentinel/whitelist.json`。
57
+
58
+ ## 设置面板(DSH 设置页 · 0.3.0+)
59
+
60
+ 插件在 DSH「设置」里注册「控制台哨兵」分区(客户端 bundle `lib/client.js`),
61
+ 不记工具名也能点操作:
62
+
63
+ - **概览**:总窗口 / 空壳 PowerShell / 可见空壳 / 已隐藏 计数 + 「隐藏可见空壳」。
64
+ - **任务栏窗口清理**:预演 / 一键关闭其他窗口(前台、系统进程、白名单自动跳过)。
65
+ - **控制台窗口**:逐行显示进程名 / 标题 / hwnd / pid / CPU / 空闲秒数 / 子进程数,行内可隐藏 / 显示 / 关闭。
66
+ - **豁免白名单**:进程名与标题关键字 标签式增删、清空,即时落盘。
67
+
68
+ 面板通过宿主路由 `/dsh-console-sentinel/*` 取数(客户端 fetch 根相对路径,桌面端自动带
69
+ capability header),不使用 iframe。改 `lib/client.js` 后按 **Ctrl+Shift+R** 硬刷新;
70
+ 改宿主(`lib/index.js` / `package.json`)需**完全退出 DSH 再启动**。
71
+
72
+ ## 配置(cordis.yml)
73
+
74
+ | 键 | 默认值 | 含义 |
75
+ |---|---|---|
76
+ | `enabled` | `true` | 总开关 |
77
+ | `scanIntervalSec` | `10` | 扫描 + CPU 采样间隔 |
78
+ | `idleThresholdSec` | `60` | 持续低 CPU 多少秒算「用完了」 |
79
+ | `cpuThresholdPct` | `1` | CPU 低于此值算空闲 |
80
+ | `autoAction` | `hide` | `none` \| `hide` \| `close` |
81
+ | `probeTimeoutMs` | `20000` | 探针硬超时 |
82
+ | `spikeTolerance` | `2` | 连续多少次「忙」采样才重置空闲时钟 |
83
+ | `guardForeground` | `true` | 永不自动处理前台窗口 |
84
+ | `scope.wmiDetached` | `true` | 管理 WMI 拉起的脱离控制台 |
85
+ | `scope.dshHost` | `true` | 管理宿主派生的控制台 |
86
+ | `scope.orphaned` | `false` | 管理父进程已退出的控制台 |
87
+ | `scope.scriptConsole` | `true` | 管理标题是脚本可执行文件路径的控制台(Windows Terminal 承载的脚本会话) |
88
+ | `scope.excludeTitles` | `[]` | 标题 substring 黑名单 |
89
+ | `scope.emptyConsole` | `true` | 把「没有任何工作子进程」的解释器控制台(PowerShell/Python/cmd 只剩 conhost)判为**空壳**,无论父链如何都纳入管理 —— 这是安全清理残留空 PowerShell 的开关(不必打开整个 `orphaned`,后者会误伤 mpiexec/fl2610/在跑的计算) |
90
+ | `emptyConsoleAction` | `hide` | 对空壳控制台的动作:`none` \| `hide` \| `close`;独立于 `autoAction`,即便自动动作关闭也能单独生效 |
91
+ | `whitelist.processes` | `[]` | 豁免进程映像名(如 `code.exe`),永不自动处理/一键关闭(运行时用 `sentinel_whitelist` 增删) |
92
+ | `whitelist.titles` | `[]` | 豁免标题子串,永不自动处理/一键关闭 |
93
+
94
+ ```yaml
95
+ - id: dsh-console-sentinel
96
+ config:
97
+ idleThresholdSec: 90
98
+ scope:
99
+ excludeTitles:
100
+ - keep-alive
101
+ ```
102
+
103
+ ## 安全性
104
+
105
+ `hide` 只调用 `ShowWindow(SW_HIDE)` —— 进程、日志、输出一概不动;
106
+ `console_window_action` 的 `show` 可随时恢复(用 `SW_SHOWNORMAL` ——
107
+ 实测 `SW_SHOW` 无法恢复被隐藏的 Windows Terminal 窗口)。`close` 发 `WM_CLOSE`
108
+ (优雅关闭,和你点 X 一样),但可能丢失未保存状态,所以从不是默认动作。
109
+ 自动动作绝不触碰:前台窗口、手动打开的控制台(`manual` 来源)、
110
+ 标题命中黑名单或**白名单**的窗口、以及 `HARD_PROTECTED_PROCESSES` 里的系统/计算进程
111
+ (explorer、taskmgr、dsh desktop、fl2610/cx2610/mpiexec/smpd/fluent 等)。
112
+ 每个动作都发 `console-sentinel/action` 事件并写日志。
113
+
114
+ ## 开发
115
+
116
+ ```
117
+ lib/index.js Cordis 插件:配置、扫描调度、工具
118
+ lib/logic.js 纯台账/空闲状态机(单测覆盖)
119
+ lib/probe.js 内嵌 PowerShell 探针 + 窗口动作
120
+ tests/ 仅 node 的回归测试,不需要真实 Windows
121
+ ```
122
+
123
+ `node tests/logic.test.mjs` —— 24 项断言,覆盖来源归类、空闲累积、
124
+ 忙碌脉冲重置、范围守卫、台账 GC、排序。
125
+
126
+ MIT License。