deepseek-coordination 0.1.9 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +349 -1083
- package/README.zh-CN.md +349 -697
- package/cordis.patch.yml +5 -74
- package/lib/client.js +33772 -0
- package/lib/index.d.mts +252 -0
- package/lib/index.mjs +27374 -0
- package/package.json +98 -114
- package/client.js +0 -338
- package/src/host/bin/dsh-os.bat +0 -19
- package/src/host/bin/dsh-os.ps1 +0 -5039
- package/src/host/bin/dsh-os.sh +0 -3385
- package/src/host/bin/dsh-os7.ps1 +0 -5518
- package/src/host/command.txt +0 -136
- package/src/host/docs/command-original.txt +0 -26
- package/src/host/docs/os-spec.md +0 -528
- package/src/host/docs/permission-model.md +0 -206
- package/src/host/spec/charsets.tsv +0 -28
- package/src/host/spec/verbs.tsv +0 -120
- package/src/permission/index.js +0 -546
- package/src/plugin/index.js +0 -226
- package/src/plugin/invoke.js +0 -306
- package/src/plugin/settings.js +0 -349
- package/src/plugin/takeover.js +0 -436
- package/src/plugin/workflow.js +0 -1494
- package/tools/measure-cache.mjs +0 -223
package/src/host/docs/os-spec.md
DELETED
|
@@ -1,528 +0,0 @@
|
|
|
1
|
-
# dsh-os 原子操作层 · 落地规范
|
|
2
|
-
|
|
3
|
-
> 状态:**已确认规范,待批准实现**(本文件不含任何脚本代码)。
|
|
4
|
-
> 上游契约:`command.txt`(24 行,已按评审结论修订)+ `notice.txt`(作业约定)。
|
|
5
|
-
> 设计依据:Playwright Mouse/Keyboard API、xdotool(1)、W3C WebDriver Actions、Windows `SendInput`/UIA、DSH 内部 seam 约定(`docs` 之外的事实来自对 `app.asar` 的只读勘察)。
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 0. 环境事实(本机实测,决定实现细节)
|
|
10
|
-
|
|
11
|
-
| 事实 | 值 | 影响 |
|
|
12
|
-
|---|---|---|
|
|
13
|
-
| PowerShell | **5.1.19041.3031**(.NET Framework) | 禁用 PS7 专有语法:`??`、`? :`、`-Encoding utf8NoBOM`、`Get-Encoding` |
|
|
14
|
-
| `System.Text.Encoding.CodePages` 程序集 | **不存在** | 不得使用 `CodePagesEncodingProvider`(那是 .NET Core API) |
|
|
15
|
-
| `GetEncoding()` 内建编码 | 65001 / 936 / **54936** / 950 / 932 / 1200 / 1201 / 28591 / 1252 全部可用 | charset 映射表可直接用 .NET;GB18030 有原生支持 |
|
|
16
|
-
| `UTF8Encoding($false).GetPreamble()` | 长度 0 | 写「无 BOM UTF-8」的唯一正确写法 |
|
|
17
|
-
| 系统工具 | `git` 不在 PATH | 验收不依赖 git;测试驱动只用 PowerShell + bash |
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## 1. 目录布局(计划创建的文件)
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
deepseek-coordination/
|
|
25
|
-
├─ command.txt # 上游契约(人工维护,不改)
|
|
26
|
-
├─ docs/
|
|
27
|
-
│ └─ os-spec.md # 本文件
|
|
28
|
-
├─ spec/
|
|
29
|
-
│ ├─ verbs.tsv # 机器可读动词表:verb/arity/参数/权限/退出码(单一真源)
|
|
30
|
-
│ └─ charsets.tsv # IANA 名 → PS/.NET 编码名 → iconv 名 → 探测特征
|
|
31
|
-
├─ bin/
|
|
32
|
-
│ ├─ dsh-os.bat # Windows 入口:ASCII-only 薄壳,chcp 65001 → 转发 %* 到 .ps1
|
|
33
|
-
│ ├─ dsh-os.ps1 # Windows 实现(UTF-8 **with BOM** 保存)
|
|
34
|
-
│ └─ dsh-os.sh # Linux/Unix/macOS 实现(LF,chmod +x)
|
|
35
|
-
└─ tests/
|
|
36
|
-
├─ golden.ps1 # Windows 驱动的黄金用例(调 dsh-os.bat)
|
|
37
|
-
├─ golden.sh # Unix 驱动的同一批用例(调 dsh-os.sh)
|
|
38
|
-
└─ conformance.ps1 # 校验 spec/verbs.tsv 与 command.txt 一致(防止规范漂移)
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
**为什么只有一个动词表文件**:`verbs.tsv` 是"参数签名 + 权限 + 退出码"的单一真源。`.ps1` 与 `.sh` 各自在启动时解析它做自查(`dsh-os selfcheck`),避免三套实现各自漂移——这正是本项目的立项动机。
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
## 2. 调用契约
|
|
46
|
-
|
|
47
|
-
### 2.1 调用面
|
|
48
|
-
|
|
49
|
-
```bat
|
|
50
|
-
dsh-os.bat <verb> [subverb] <positional...> [--flag [value]...]
|
|
51
|
-
```
|
|
52
|
-
```sh
|
|
53
|
-
./dsh-os.sh <verb> [subverb] <positional...> [--flag [value]...]
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
* 动词两段式:`file` / `mouse` / `keyboard` / `execute` / `window` / `wait` / `text` / `structure` / `clipboard` / `selfcheck`。
|
|
57
|
-
* **参数值永远作为独立 argv 元素传入,调用方不得做 shell 展开**。harness 侧必须用 argv 数组(`execFile` 语义 / DSH 的 `ctx.subprocess`),**绝不拼接命令字符串**——这是"Windows 空格问题"的根因所在。
|
|
58
|
-
* 值内含空格时:命令行用户用引号(`"C:\Program Files\x.txt"`);`cmd.exe` 用户同样用引号,`.bat` 的 `%*` 会原样转发引号。
|
|
59
|
-
|
|
60
|
-
### 2.2 输出信封(stdout,UTF-8 无 BOM,默认单行 JSON)
|
|
61
|
-
|
|
62
|
-
```json
|
|
63
|
-
{ "ok": true, "verb": "file.charset", "result": { }, "meta": { "durationMs": 3 } }
|
|
64
|
-
{ "ok": false, "verb": "file.charset", "error": { "code": "E_NOT_A_FILE", "message": "…", "hint": "…" },
|
|
65
|
-
"meta": { "durationMs": 2 } }
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
* `--pretty` 输出缩进 JSON,供人类阅读。**stderr 只放人类可读的补充诊断,绝不参与解析。**
|
|
69
|
-
* `--quiet` 只输出 `result` 字段(便于 shell 管道)。
|
|
70
|
-
* 所有 JSON 由脚本显式生成**单行**(`ConvertTo-Json -Compress` / `jq -c` 或手工转义),不得依赖控制台默认换行。
|
|
71
|
-
|
|
72
|
-
### 2.3 退出码
|
|
73
|
-
|
|
74
|
-
| 码 | 含义 |
|
|
75
|
-
|---|---|
|
|
76
|
-
| 0 | 成功 |
|
|
77
|
-
| 1 | 用法错误(未知动词、缺参、参数互斥) |
|
|
78
|
-
| 2 | 类型/取值错误(偏移越界、非字符边界、坐标非法) |
|
|
79
|
-
| 3 | 权限未授予(需要 `--allow`,或系统权限不足) |
|
|
80
|
-
| 4 | 平台不支持(Wayland 无 XTest、缺 UIA、缺辅助功能授权) |
|
|
81
|
-
| 5 | 目标/编码/IO 错误(不存在、非文件、编码不符、写失败) |
|
|
82
|
-
| 6 | 超时(`wait` 系列) |
|
|
83
|
-
| 7 | 内部错误 |
|
|
84
|
-
|
|
85
|
-
**退出码是粗分类,`error.code` 才是权威**;调用方按 code 分支,**绝不解析 message 文本**(沿用 DSH `FsError` 的既有约定)。
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## 3. 完整命令表
|
|
90
|
-
|
|
91
|
-
权限列:`R` = 只读/查询,不要对话框;`W` = 变更,需要用户已接受的授权。
|
|
92
|
-
|
|
93
|
-
### 3.1 file
|
|
94
|
-
|
|
95
|
-
| 命令 | 权限 | 参数与语义 |
|
|
96
|
-
|---|---|---|
|
|
97
|
-
| `file create path content charset` | W | 目标已存在则 `E_EXISTS`(不覆盖)。父目录自动创建。缺省 `charset=utf-8`(无 BOM) |
|
|
98
|
-
| `file charset path` | R | 返回 `{charset, bom, eol, confidence}`。非普通文件 → `E_NOT_A_FILE` |
|
|
99
|
-
| `file encode path charset [--backup] [--bom keep\|strip\|add]` | W | 原地换编码,内容语义不变,行尾不动;缺省保留原 BOM 状态;`--backup` 先写 `path.bak` |
|
|
100
|
-
| `file length path [--no-recursive]` | R | 文件→字节数;目录→递归总字节(`--no-recursive` 改为自身条目大小) |
|
|
101
|
-
| `file information path` | R | `{type,size,charset,bom,eol,lines,mode,atime,mtime,ctime,realpath}` |
|
|
102
|
-
| `file list path` | R | 列**一层**目录:`[{name,type,size,mtime}]`(不递归、不支持 glob) |
|
|
103
|
-
| `file delete path [--permanent]` | W | 缺省移入回收站;`--permanent` 才真删。**调用前必须核对解析后的绝对路径**(`notice.txt` 要求) |
|
|
104
|
-
| `file move source destination` | W | 同卷 rename;跨卷则复制+删除。目标已存在 → `E_EXISTS` |
|
|
105
|
-
| `file copy source destination` | W | 目标已存在 → `E_EXISTS` |
|
|
106
|
-
| `file mkdir path [--parents]` | W | `--parents` 等价 `mkdir -p`;缺省父目录不存在则 `E_NO_PARENT` |
|
|
107
|
-
| `file append path content charset` | W | 追加到文件尾(保留原 BOM 与行尾) |
|
|
108
|
-
| `file insert path content charset offset` | W | 在**字节偏移** `offset` 处插入;`offset` 必须落在**字符边界**,否则 `E_BOUNDARY`(多字节字符不可切碎) |
|
|
109
|
-
| `file view path [charset] [start] [end]` | R | `start`/`end` 为**字节偏移、半开区间 `[start,end)`**,缺省全文。`charset` 给出时是**断言**(不符即 `E_CHARSET_MISMATCH`),缺省自动探测。返回带行号文本 + `truncated` 标记 |
|
|
110
|
-
|
|
111
|
-
**编码铁律(治 BOM 问题的核心)**
|
|
112
|
-
|
|
113
|
-
1. `file charset` 必须把 `utf-8`(无 BOM)与 `utf-8-bom`(有 BOM)**区分报告**。
|
|
114
|
-
2. `file create` 若目标已存在则报错而非覆盖,因此不存在"覆盖时如何处理原 BOM"的歧义。
|
|
115
|
-
3. `file append` / `file insert` / `file encode` 必须**保留原文件的 BOM 与行尾状态**。
|
|
116
|
-
4. 读取时剥离 BOM、把 `\r\n` 归一为 `\n` 用于输出,但**任何写回操作都必须还原原文件的 BOM 与行尾**。
|
|
117
|
-
> 这是对 DSH 现有不一致的修正:`dsh-fs-local` 的 `editText` 会复原 CRLF、`writeText` **不会**,且两者都不保留 BOM。
|
|
118
|
-
|
|
119
|
-
### 3.2 execute
|
|
120
|
-
|
|
121
|
-
| 命令 | 权限 | 参数与语义 |
|
|
122
|
-
|---|---|---|
|
|
123
|
-
| `execute command arguments` | W | `arguments` 为**数组**,逐元素作为独立 argv 直传,**不经 shell**。需要管道/重定向时显式 `--shell`,并在描述里警告调用方自行负责引用 |
|
|
124
|
-
| `execute query pid` | R | 进程是否存在、退出码、CPU/内存峰值(平台能力不足时字段为 `null`) |
|
|
125
|
-
| `execute kill pid [--force]` | W | 缺省温和终止(WM_CLOSE/SIGTERM),`--force` 强杀 |
|
|
126
|
-
|
|
127
|
-
通用标志:`--workdir d`、`--env K=V`(可重复)、`--stdin file`、`--timeout ms`、`--encoding c`、`--background`。
|
|
128
|
-
|
|
129
|
-
**编码**:子进程 stdout/stderr 以**字节**收集后按显式 `--encoding`(缺省 utf-8,含 BOM 时剥离)解码;Windows 侧先钉死 `[Console]::OutputEncoding`/`$OutputEncoding` 为无 BOM UTF-8 再 spawn。
|
|
130
|
-
|
|
131
|
-
### 3.3 mouse
|
|
132
|
-
|
|
133
|
-
坐标缺省为**虚拟桌面逻辑像素**(全部显示器并集的左上为原点);`--space physical` 切换为物理像素。
|
|
134
|
-
Windows 上进程**必须先声明 DPI 感知**(`SetProcessDpiAwarenessContext` / 清单),否则 125%/150% 缩放下点击整体偏移。
|
|
135
|
-
|
|
136
|
-
| 命令 | 权限 | 参数与语义 |
|
|
137
|
-
|---|---|---|
|
|
138
|
-
| `mouse click x y [--button left\|right\|middle] [--count n] [--clearmodifiers]` | W | 等价 down+up 的原子语义;`--count 2` 为双击 |
|
|
139
|
-
| `mouse context x y [--clearmodifiers]` | W | 等价 `mouse click --button right`(保留独立动词以贴合 `command.txt`) |
|
|
140
|
-
| `mouse over x y [--hold]` | W | 移动光标;`--hold` 表示"按住左键移动"(拖动必需,避免 down/up 之间被中断) |
|
|
141
|
-
| `mouse wheel x y` | W | `x`/`y` 为**相对位移**(像素),模拟滚轮 |
|
|
142
|
-
| `mouse down x y [--button ...]` | W | 按下并保持 |
|
|
143
|
-
| `mouse up [--button ...]` | W | 释放 |
|
|
144
|
-
| `mouse cursor` | R | 读回 `{x,y,screen,space}`,用于点击后校验 |
|
|
145
|
-
|
|
146
|
-
`--clearmodifiers`:注入前查询并释放所有活动修饰键(含 CapsLock/鼠标键),注入后恢复。**这是防"按键卡住"和"Shift 污染"的必需项**(PowerToys 有专门修复此类故障的 PR;xdotool 亦提供同名标志)。
|
|
147
|
-
|
|
148
|
-
### 3.4 keyboard / clipboard
|
|
149
|
-
|
|
150
|
-
| 命令 | 权限 | 参数与语义 |
|
|
151
|
-
|---|---|---|
|
|
152
|
-
| `keyboard code action [--clearmodifiers]` | W | `action ∈ {down, up, press}`;`press` = 单次按下+释放的**原子**语义(缺它则"按一次键"要两条命令,中途中断会留下卡住的键) |
|
|
153
|
-
| `keyboard chord keys [--clearmodifiers]` | W | `keys` 为**数组**(`["alt","tab"]`),语义为**同时按下、逆序释放**。取代原 `combination` 的变长位置参数(DSH 的 schema DSL 中 `array` 只允许 `items`,变长位置参数无法表达) |
|
|
154
|
-
| `keyboard type text [--method auto\|keys\|paste]` | W | 任意文本输入。**非 ASCII 缺省走剪贴板粘贴**(`SendInput` 的 `KEYEVENTF_UNICODE` 在 IME/中文场景不可靠) |
|
|
155
|
-
| `clipboard read` | R | 读剪贴板文本 |
|
|
156
|
-
| `clipboard write text` | W | 写剪贴板文本 |
|
|
157
|
-
| `clipboard paste` | W | 写剪贴板后按 Ctrl+V(`keyboard type --method paste` 的显式形式) |
|
|
158
|
-
|
|
159
|
-
**键码方言**(`code` 参数,带前缀):`dom:KeyA` / `vk:0x53`(Windows 虚拟键)/ `keycode:0`(macOS CGKeyCode)/ `keysym:a`(X11)。无前缀按平台默认;同时提供 `keyboard codes` 子命令列出可用键名,避免模型猜键名。
|
|
160
|
-
|
|
161
|
-
### 3.5 window
|
|
162
|
-
|
|
163
|
-
| 命令 | 权限 | 参数与语义 |
|
|
164
|
-
|---|---|---|
|
|
165
|
-
| `window find name [--regex] [--pid n] [--visible]` | R | 返回**全部**匹配窗口,含 `handle/title/class/pid/rect/z` |
|
|
166
|
-
| `window list [--z-order]` | R | 枚举顶层窗口 |
|
|
167
|
-
| `window active` | R | 当前前台窗口(避免必须按名字再找一次) |
|
|
168
|
-
| `window close name` | W | 请求关闭(非强杀) |
|
|
169
|
-
| `window top name` | W | 置前(改 **z-order**) |
|
|
170
|
-
| `window bottom name` | W | 置底(改 z-order) |
|
|
171
|
-
| `window move name x y` | W | 移动窗口 |
|
|
172
|
-
| `window resize name width height` | W | 改变窗口尺寸 |
|
|
173
|
-
|
|
174
|
-
**已删除 `window next` / `window previous`**:Alt+Tab 的"下一个窗口"由窗口管理器决定,脚本无法确定,只能**合成 `Alt+Tab` 按键**——即等价于 `keyboard chord ["alt","tab"]`,且原 `name` 参数在语义上无意义(无法"只在同名窗口间循环")。按原子化原则删去,保留组合键原语。xdotool 亦警告窗口栈顺序"may not be consistent across invocations"。
|
|
175
|
-
|
|
176
|
-
### 3.6 wait(新增:UI 时序必需)
|
|
177
|
-
|
|
178
|
-
| 命令 | 权限 | 参数与语义 |
|
|
179
|
-
|---|---|---|
|
|
180
|
-
| `wait window name [--timeout ms] [--interval ms] [--absent]` | R | 等待窗口出现(`--absent` 等待消失);超时退出码 6 |
|
|
181
|
-
| `wait ms` | R | 显式等待。**这是"未知时长"场景唯一的确定性手段**,模型不应靠反复轮询 `window find` 猜测 |
|
|
182
|
-
|
|
183
|
-
### 3.7 观察面
|
|
184
|
-
|
|
185
|
-
| 命令 | 权限 | 参数与语义 |
|
|
186
|
-
|---|---|---|
|
|
187
|
-
| `mouse screenshot [x y width height] [--screen n] [--out file]` | R | 缺省全屏(当前显示器)。**参数序采用 `x y width height`**,与 `BitBlt`/xdotool/`CGWindowListCreateImage` 取值习惯一致;原 `top right bottom left` 既缺 `left` 又与任何平台 API 顺序不符,已废止 |
|
|
188
|
-
| `structure x y` | R | 取该点**视图结构**:控件类型/名称/包围盒/父子链/是否可点击聚焦 |
|
|
189
|
-
| `text x y` | R | 取该点**完整文本** |
|
|
190
|
-
|
|
191
|
-
**`text` / `structure` 的实现层次(关键设计决定)**:这两者是本层**唯一不确定**的原语,必须"无障碍树优先、OCR 兜底":
|
|
192
|
-
|
|
193
|
-
| | Windows | macOS | Linux |
|
|
194
|
-
|---|---|---|---|
|
|
195
|
-
| `structure` | UI Automation `ElementFromPoint` → `CurrentControlType`/`CurrentName`/`CurrentBoundingRectangle` + `TreeWalker` 父链 | AXAPI `AXUIElementCopyElementAtPosition` → `AXRole`/`AXTitle`/`AXPosition`/`AXSize` | AT-SPI2 `getAccessibleAtPoint` → `getRoleName`/`getName`/`getExtents` |
|
|
196
|
-
| `text` | UIA `TextPattern`/`ValuePattern` 优先;OCR 用 `Windows.Media.Ocr` 或捆绑 Tesseract | AXAPI `AXValue`/`AXSelectedText`;OCR 用 Vision `VNRecognizeTextRequest` | AT-SPI `Text` 接口;OCR 用 Tesseract |
|
|
197
|
-
|
|
198
|
-
返回**必须**包含 `source: "uia" | "ax" | "atspi" | "ocr"` 与 `confidence`。失败必须区分报错,**不得以空文本表示"无文本"**:
|
|
199
|
-
|
|
200
|
-
* `E_ACCESSIBILITY_DENIED`(macOS 未授予辅助功能权限)
|
|
201
|
-
* `E_ELEVATION_REQUIRED`(Windows 提权窗口不可读/不可注入)
|
|
202
|
-
* `E_ACCESSIBILITY_UNAVAILABLE`(Wayland 下 AT-SPI 不可用)
|
|
203
|
-
|
|
204
|
-
### 3.8 自检
|
|
205
|
-
|
|
206
|
-
| 命令 | 权限 | 参数与语义 |
|
|
207
|
-
|---|---|---|
|
|
208
|
-
| `selfcheck [--json]` | R | 校验 `spec/verbs.tsv` 与实现一致、报告平台能力矩阵(DPI 感知可用性、UIA/AT-SPI 可用性、会话类型、可用键码方言),并逐项列出**不支持**的原语及原因 |
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
|
-
## 4. 错误码表(权威,调用方按此分支)
|
|
213
|
-
|
|
214
|
-
| code | 退出码 | 触发条件 |
|
|
215
|
-
|---|---|---|
|
|
216
|
-
| `E_USAGE` | 1 | 未知动词、缺参、参数互斥 |
|
|
217
|
-
| `E_TYPE` | 2 | 参数类型/取值非法 |
|
|
218
|
-
| `E_NOT_SUPPORTED` | 4 | 本平台无此能力(含 Wayland 无 XTest) |
|
|
219
|
-
| `E_PERMISSION` | 3 | 需要 `--allow` 而用户未授权 |
|
|
220
|
-
| `E_ACCESSIBILITY_DENIED` | 4 | 系统级辅助功能权限未授予(macOS) |
|
|
221
|
-
| `E_ACCESSIBILITY_UNAVAILABLE` | 4 | 无障碍树不可用(Wayland / 服务未运行 / 提权窗口) |
|
|
222
|
-
| `E_ELEVATION_REQUIRED` | 3 | UIPI 阻止向高完整性窗口注入或读取 |
|
|
223
|
-
| `E_NOT_FOUND` | 5 | 路径不存在 |
|
|
224
|
-
| `E_NOT_A_FILE` | 5 | 路径存在但不是普通文件(`file charset`/`encode` 的主要失败) |
|
|
225
|
-
| `E_NOT_A_DIR` | 5 | 期望目录 |
|
|
226
|
-
| `E_IS_A_DIR` | 5 | 期望文件 |
|
|
227
|
-
| `E_EXISTS` | 5 | 目标已存在(create/move/copy) |
|
|
228
|
-
| `E_NO_PARENT` | 5 | 父目录不存在且未给 `--parents` |
|
|
229
|
-
| `E_CHARSET_UNKNOWN` | 2 | charset 不在支持表内 |
|
|
230
|
-
| `E_CHARSET_MISMATCH` | 5 | `file view` 的 charset 断言不符 |
|
|
231
|
-
| `E_ENCODING_INVALID` | 5 | 字节序列在指定编码下非法 |
|
|
232
|
-
| `E_BOM_INVALID` | 5 | BOM 与声明编码矛盾 |
|
|
233
|
-
| `E_BINARY_CONTENT` | 5 | 内容判定为二进制 |
|
|
234
|
-
| `E_BOUNDARY` | 2 | 字节偏移落在多字节字符中间 |
|
|
235
|
-
| `E_RANGE` | 2 | 偏移/区间越界 |
|
|
236
|
-
| `E_TRUNCATED` | 5 | 内容超过上限被截断(结果内含 `truncated:true`) |
|
|
237
|
-
| `E_PARTIAL_WRITE` | 5 | 写入未完成(原子写失败后的状态) |
|
|
238
|
-
| `E_NO_DISPLAY` | 4 | 无图形会话(纯终端/无头环境) |
|
|
239
|
-
| `E_WINDOW_NOT_FOUND` | 5 | 未匹配到窗口 |
|
|
240
|
-
| `E_WINDOW_AMBIGUOUS` | 2 | 匹配到多个窗口而需要唯一目标 |
|
|
241
|
-
| `E_WINDOW_CLOSED` | 5 | 操作过程中窗口已消失 |
|
|
242
|
-
| `E_TIMEOUT` | 6 | `wait` 超时 |
|
|
243
|
-
| `E_ABORTED` | 7 | 调用方中止(`AbortSignal`) |
|
|
244
|
-
| `E_IO` | 5 | 其他 IO 失败 |
|
|
245
|
-
| `E_INTERNAL` | 7 | 内部错误 |
|
|
246
|
-
|
|
247
|
-
---
|
|
248
|
-
|
|
249
|
-
## 5. 各脚本的实现约束(写代码时的硬要求)
|
|
250
|
-
|
|
251
|
-
### 5.1 `bin/dsh-os.bat`(薄壳)
|
|
252
|
-
* **只允许 ASCII 字节**(否则 `cmd.exe` 的 OEM 代码页会破坏 `%*`)。
|
|
253
|
-
* CRLF 行尾;首行 `@echo off`;随后 `chcp 65001 >nul`。
|
|
254
|
-
* **只做一件事**:把 `%*` 原样转发给 `dsh-os.ps1`,并透传退出码(`exit /b %ERRORLEVEL%`)。
|
|
255
|
-
* 用 `-NoProfile -NonInteractive -ExecutionPolicy Bypass -File`;**不加 `-NoLogo` 之外的多余开关**。
|
|
256
|
-
* 逻辑一律不在 `.bat` 里实现(`.bat` 的引号处理不可靠,是故障源)。
|
|
257
|
-
|
|
258
|
-
### 5.2 `bin/dsh-os.ps1`(Windows 实现)
|
|
259
|
-
* 文件自身保存为 **UTF-8 with BOM**;若保存为无 BOM,PowerShell 5.1 会按 ANSI 解析脚本内的非 ASCII 字面量——这正是 BOM 问题的反噬。
|
|
260
|
-
* 顶部固定:
|
|
261
|
-
* `$ErrorActionPreference = 'Stop'`;`Set-StrictMode -Version Latest`
|
|
262
|
-
* `[Console]::InputEncoding = [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)`
|
|
263
|
-
* `$OutputEncoding = New-Object System.Text.UTF8Encoding($false)`
|
|
264
|
-
* **禁止**用 `Get-Content`/`Set-Content` 处理文件内容——它们在 5.1 下编码行为不一致;一律走 `[System.IO.File]::ReadAllBytes` / `WriteAllBytes` + 显式 `Encoding` 对象,以便精确控制 BOM 与行尾。
|
|
265
|
-
* **原子写**:同目录临时文件 → `[System.IO.File]::Replace`(同卷)或 `MoveFileEx`;保证 `file create/append/insert/encode` 不产生半写状态。
|
|
266
|
-
* 原生调用用 `koffi`(本机安装体已带 `@koromix/koffi-win32-x64`;`user32.dll` 已被 DSH 内 3 处加载,写法可参照 `dsh-host-directory-picker-native/lib/worker.cjs:93` 的 `keybd_event`)。
|
|
267
|
-
* **惰性 `await import('koffi')`**:只在真正需要注入时加载,且仅在 Windows 分支触达(DSH 内既有同模式)。
|
|
268
|
-
* 中文路径:调用方除非显式要求,一律走 `\\?\` 长路径前缀内部规范化,**不得把前缀回显给模型**。
|
|
269
|
-
|
|
270
|
-
### 5.3 `bin/dsh-os.sh`(Linux/Unix/macOS 实现)
|
|
271
|
-
* LF 行尾;`#!/usr/bin/env bash`;`set -euo pipefail`;`chmod +x`。
|
|
272
|
-
* 首部 `export LC_ALL=C.UTF-8`(回退 `en_US.UTF-8`),并校验当前 locale 确实是 UTF-8——**这是 Unix 侧空格/中文路径问题的根因所在**。若 locale 非 UTF-8,必须报 `E_NOT_SUPPORTED` 而不是静默乱码。
|
|
273
|
-
* `IFS=$'\n'` 级别的谨慎处理:所有参数用数组 `"$@"` 传递,**禁止 `eval`、禁止未加引号的变量展开**。
|
|
274
|
-
* 编码探测/转换用 `iconv`(映射表见 `spec/charsets.tsv`);`iconv` 缺失时报 `E_NOT_SUPPORTED`。
|
|
275
|
-
* Linux 输入注入:X11 用 XTest(`xdotool` 存在则优先复用);**Wayland 检测到即 `E_NOT_SUPPORTED`**(`XTest` 在 Wayland 无效,uinput 需 root)。macOS 用 `CGEvent`(`osascript`/`cliclick` 兜底),缺辅助功能权限报 `E_ACCESSIBILITY_DENIED`。
|
|
276
|
-
|
|
277
|
-
### 5.4 授权模型(`--allow`)
|
|
278
|
-
* 首行权限组:`execute / delete / create / edit / window / keyboard / clipboard / text / structure`。
|
|
279
|
-
* **判定在 harness 侧**(DSH 已有 `ctx.tools.guard()` 与 `tools/pre-execute` 的 allow/deny/ask 门禁);脚本侧只检查 `--allow` 是否存在,缺失即以 `E_PERMISSION`(退出码 3)拒绝。**避免两套互不知情的权限系统。**
|
|
280
|
-
* `R` 类命令(charset/length/information/list/view/find/list-active/wait/cursor/clipboard read/selfcheck)永不要求 `--allow`。
|
|
281
|
-
|
|
282
|
-
---
|
|
283
|
-
|
|
284
|
-
## 6. 黄金测试矩阵(三平台同一批用例,逐字比对)
|
|
285
|
-
|
|
286
|
-
| # | 用例 | 断言要点 |
|
|
287
|
-
|---|---|---|
|
|
288
|
-
| 1 | 路径含空格:`...\a b\c.txt` | create→charset→view→delete 全链路成功;**空格不被拆分** |
|
|
289
|
-
| 2 | 路径含全角字符:`...\中文 目录\文件.txt` | 同上;输出 JSON 为合法 UTF-8 |
|
|
290
|
-
| 3 | UTF-8 **带 BOM** 文件 | `charset` 报 `utf-8-bom`;`append`/`insert` 后 **BOM 仍在**;`view` 首行**不含** `\uFEFF` |
|
|
291
|
-
| 4 | UTF-8 无 BOM 文件 | `charset` 报 `utf-8`;**不得**擅自加 BOM |
|
|
292
|
-
| 5 | CRLF 文件 | `information.eol == "crlf"`;`append` 后仍 CRLF;`view` 输出行尾归一为 `\n` |
|
|
293
|
-
| 6 | GBK 文件 | `charset` 报 `gbk`;`encode` 转 `utf-8` 后内容语义不变;转回再比对字节级一致 |
|
|
294
|
-
| 7 | UTF-16LE/带 BOM | `charset` 报 `utf-16le`;`view` 正确解码 |
|
|
295
|
-
| 8 | 偏移落在多字节字符中间 | `file view` 多字节文件时给中间偏移 → `E_BOUNDARY`(退出码 2) |
|
|
296
|
-
| 9 | 二进制文件 | `file charset` → `E_BINARY_CONTENT`;`file view` 同 |
|
|
297
|
-
| 10 | 目录当文件用 | `file charset <dir>` → `E_NOT_A_FILE` |
|
|
298
|
-
| 11 | `file create` 目标已存在 | `E_EXISTS`,且**文件内容未被改动** |
|
|
299
|
-
| 12 | `file delete` 缺省 | 进入回收站/垃圾桶而非真删;`--permanent` 才消失 |
|
|
300
|
-
| 13 | 非 ASCII 参数:`execute printf` 传中文参数 | argv 直传,**不经 shell**,参数边界与内容完全一致 |
|
|
301
|
-
| 14 | 非 ASCII 输出:子进程打印中文 | 正确解码,无乱码、无 BOM 泄漏到结果 |
|
|
302
|
-
| 15 | 输出 JSON 合法性 | 每个成功/失败结果都能被解析器解析(含路径带引号、换行、制表符的转义) |
|
|
303
|
-
| 16 | 缺少 `--allow` 的 W 类命令 | `E_PERMISSION`(退出码 3),且**未产生任何副作用** |
|
|
304
|
-
| 17 | 未知动词/缺参 | `E_USAGE`(退出码 1),stdout 仍是合法 JSON |
|
|
305
|
-
| 18 | `selfcheck` | 三平台都能输出能力矩阵;不支持项以 code 列出而非静默省略 |
|
|
306
|
-
| 19 | 规范一致性 | `tests/conformance.ps1` 校验 `spec/verbs.tsv` 与 `command.txt` 动词集完全一致(防止规范漂移) |
|
|
307
|
-
| 20 | 幂等/可重复 | 连续两次 `file charset`、`file information` 结果一致(无隐藏状态污染) |
|
|
308
|
-
|
|
309
|
-
**平台专属(各自跑,标记为 `platform-only`)**:`click`/`keyboard` 的注入效果(需真实图形会话)、`window` 五连、`wait window`、`screenshot`、`text`/`structure`(需 UIA/AT-SPI 可用)。这些**不进 CI 的强制门槛**,但必须提供"能力不可用时返回正确错误码"的断言——CI 里可强制验证的就是这一条。
|
|
310
|
-
|
|
311
|
-
---
|
|
312
|
-
|
|
313
|
-
## 7. 验收标准
|
|
314
|
-
|
|
315
|
-
1. `tests/conformance.ps1` 通过:脚本动词集 == `command.txt` 动词集,无多、无缺。
|
|
316
|
-
2. 用例 1–20 在 Windows 全绿;用例 1–10、13–20 在任何 POSIX(含非 UTF-8 locale 的降级行为)上全绿。
|
|
317
|
-
3. 用户环境相关用例(11–17 类)在**变更失败时不留下半写文件**(原子写验证)。
|
|
318
|
-
4. 三平台 `selfcheck` 输出的能力矩阵互相可比对(同名字段、同语义),差异只体现在受支持项上。
|
|
319
|
-
5. 不引入除 `koffi`(Windows 注入)与系统工具(`iconv`、`xdotool`、`osascript`)之外的任何依赖。
|
|
320
|
-
|
|
321
|
-
---
|
|
322
|
-
|
|
323
|
-
## 8. 尚未闭合的技术风险(实现时需先行验证)
|
|
324
|
-
|
|
325
|
-
| 风险 | 说明 | 处置 |
|
|
326
|
-
|---|---|---|
|
|
327
|
-
| koffi 版本与 Electron ABI | 安装体内 koffi 服务于 Electron 主进程;脚本由 `pwsh` 加载的是 Node 侧 koffi,版本可能不同 | 首步先跑一个最小 `SendInput` 冒烟测试,确认加载与注入均可用;不可用则降级为 `keybd_event`/`mouse_event`(同样经 koffi),或 `WScript.Shell.SendKeys`(仅 ASCII) |
|
|
328
|
-
| DPI 感知 | 未声明 DPI 感知时坐标整体偏移 | 在 `.ps1` 启动即声明;`selfcheck` 报告当前 DPI 与缩放因子 |
|
|
329
|
-
| UIPI | 提权窗口既不可注入也不可读 | 明确 `E_ELEVATION_REQUIRED`;不静默失败 |
|
|
330
|
-
| UIA 在部分应用不可用 | 空格/自绘控件无 UIA 树 | `text`/`structure` 必须能回退 OCR,并在返回中标注 `source` |
|
|
331
|
-
| Wayland | XTest 无效 | 检测会话类型,明确 `E_NOT_SUPPORTED`;不尝试 uinput(需 root) |
|
|
332
|
-
| 回收站 API | Windows 侧 `SHFileOperation` 需经 shell;Unix 侧无标准回收站 | Windows 走 `SHFileOperation`/`IFileOperation`;Unix 缺 `trash-cli` 时**降级为报错**而非静默真删,或经 `--permanent` 显式确认 |
|
|
333
|
-
| `.bat` 的 `%*` 边界 | 极端引号组合可能被 `cmd.exe` 改写 | 文档明确"harness 必须用 argv 数组直调 `.ps1`/`.sh`";`.bat` 仅服务人类手敲 |
|
|
334
|
-
|
|
335
|
-
---
|
|
336
|
-
|
|
337
|
-
## 9. 落地进度与实现偏差
|
|
338
|
-
|
|
339
|
-
### 9.1 已完成(第 1 轮,Windows `file` 族 + 公共设施)
|
|
340
|
-
|
|
341
|
-
| 文件 | 状态 | 说明 |
|
|
342
|
-
|---|---|---|
|
|
343
|
-
| `command.txt` | 已按批准结论修订 | 保留用户全部命名;仅做加法修正(见 9.2) |
|
|
344
|
-
| `docs/command-original.txt` | 原始草稿备份 | 修订前的 26 行版本 |
|
|
345
|
-
| `spec/verbs.tsv` | 完成 | 46 条动词的参数/标志/权限/错误码单一真源 |
|
|
346
|
-
| `spec/charsets.tsv` | 完成 | 15 种编码的 IANA 名 → .NET 码页 → iconv 名 → BOM 字节 |
|
|
347
|
-
| `bin/dsh-os.ps1` | 完成 `file.*` + `selfcheck` | 1450 行;其余动词以 `E_NOT_SUPPORTED` 明确拒绝,绝不静默 |
|
|
348
|
-
| `bin/dsh-os.bat` | 完成 | ASCII-only、CRLF、`chcp 65001`、`%*` 原样转发、透传退出码 |
|
|
349
|
-
| `tests/golden.ps1` | 完成 | 23 条黄金用例,全部通过 |
|
|
350
|
-
| `tests/encoding.ps1` | 完成 | 编码契约守卫(BOM/ASCII/行尾/stdout 无 BOM) |
|
|
351
|
-
|
|
352
|
-
**验证证据**:`tests/golden.ps1` → `passed: 23 failed: 0`;`tests/encoding.ps1` → `all encoding contracts hold`。
|
|
353
|
-
|
|
354
|
-
### 9.2 对 `command.txt` 的加法修正(用户命名全部保留)
|
|
355
|
-
|
|
356
|
-
| 项 | 处理 | 理由 |
|
|
357
|
-
|---|---|---|
|
|
358
|
-
| `keyboard press code` | **新增** | 原设计只有 `down`/`up` 两半,"按一次键"必须两条命令,中途中断会留下卡住的按键 |
|
|
359
|
-
| `keyboard type text` | **新增** | 只有键码无法输入中文等任意文本;非 ASCII 走剪贴板粘贴 |
|
|
360
|
-
| `mouse down/up/cursor`、`clipboard` 三条、`wait` 两条、`window list/active/move/resize`、`execute query/kill`、`file list/move/copy/mkdir`、`selfcheck` | **新增** | 见评审结论;均为填补缺口而非扩张范围 |
|
|
361
|
-
| `screenshot` 注释拼写 | **修正** | 原文 `rectange` → `rectangle`;参数序确定为 `x y width height`(原文 `top right bottom left` 缺 `left` 且与任何平台 API 顺序不符) |
|
|
362
|
-
| `mose over` / `mose wheel` | **修正** | 拼写 → `mouse over` / `mouse wheel` |
|
|
363
|
-
| `keyboard` / `clipboard` 加入权限组 | **修正** | 原首行权限组遗漏这两族;键鼠注入的风险不低于改文件 |
|
|
364
|
-
| `window next/previous` | **删除** | Alt+Tab 的"下一个"由窗口管理器决定,脚本只能合成 `Alt+Tab` = `keyboard combination alt tab press`,原 `name` 参数无法实现 |
|
|
365
|
-
|
|
366
|
-
### 9.3 实现细节与规范的偏差(已在实现中落地,需回写规范)
|
|
367
|
-
|
|
368
|
-
| 项 | 规范原写法 | 实现采用 | 理由 |
|
|
369
|
-
|---|---|---|---|
|
|
370
|
-
| `file append` 的 `charset` | 位置参数(必需) | **可选**(省略即用文件自身编码) | 追加时断言编码几无意义,且实测必需会让 `file append p content` 失败 |
|
|
371
|
-
| `file insert` 的 `charset` | 第三个位置参数 | **`--charset` 标志** | 它夹在 `content` 与 `offset` 之间,作为可选位置参数会**静默移位 offset**,属危险设计 |
|
|
372
|
-
| `file create` 的 `--encoding` | 标志 | 未实现(重复了 `charset` 位置参数) | 保留为后续清理项 |
|
|
373
|
-
| `file.delete` 回收站 | 规范要求"Unix 缺 `trash-cli` 时降级为报错" | Windows 用 `Microsoft.VisualBasic.FileIO.FileSystem` + `SendToRecycleBin`(实测可用) | Unix 侧待实现时按规范处理 |
|
|
374
|
-
| 原子替换 | 规范写 `File.Replace` | **`MoveFileExW(REPLACE_EXISTING\|WRITE_THROUGH)`** | .NET Framework 上 `File.Replace(tmp, dst, $null)` 抛"路径格式非法",且 `File.Move` 无覆盖重载 |
|
|
375
|
-
| 原生调用 | 规范建议 koffi | **`Add-Type` P/Invoke,零外部依赖** | `koffi` 是 Node 侧模块,`pwsh` 脚本内不可用;P/Invoke 由 .NET Framework 内建,反而更稳 |
|
|
376
|
-
|
|
377
|
-
### 9.4 本轮踩到并修掉的 PowerShell 5.1 陷阱(教训,务必遵守)
|
|
378
|
-
|
|
379
|
-
1. **脚本文件自身必须 UTF-8 with BOM。** 本轮 `tests/golden.ps1` 含中文面量却无 BOM,被 PS 5.1 按 ANSI(936) 解码 ⇒ 测试数据在运行前就已损坏,表现为"脚本明明对、断言却离奇失败"。这正是本项目要消灭的那类 bug,反过来咬了自己一口。**结论:`.ps1` 含非 ASCII 保存为 UTF-8 with BOM;纯 ASCII 文件(`golden.ps1`)用码点构造非 ASCII 数据。**
|
|
380
|
-
2. **`New-Object byte[] $a + $b` 是 `$a + ($b * 4)`。** 运算符优先级导致 `byte[]` 尺寸错误。全部改为 `New-Object byte[] ($a + $b)`。
|
|
381
|
-
3. **函数返回空数组会被 PowerShell 展开成 `$null`。** `return @()` ⇒ 调用方 `$bom.Length` 抛"找不到 Length 属性"(GBK 等无 BOM 编码全中)。用 `return , $array` 或 `Write-Output -NoEnumerate`。
|
|
382
|
-
4. **PS 5.1 无法绑定 `Decoder.GetChars(bytes, 0, n)` 三参重载。** 用它做编码校验 ⇒ 每个候选编码都抛异常 ⇒ 检测静默退化到 `latin1`(GBK 文件被静默损坏)。改用 `Encoding.GetString`。
|
|
383
|
-
5. **`System.Text.Encoding.CodePages` 在 .NET Framework 上不存在**(那是 .NET Core API),但 936/54936/950/932/1200/1201/28591/1252/65001 都已内建。
|
|
384
|
-
6. **`File.Move` 在 .NET Framework 只有 2 参重载**,没有覆盖版本。
|
|
385
|
-
7. **PowerShell 的 `>` 重定向会解码再编码子进程输出**,用它采样 stdout 字节会**制造** U+FFFD。测字节必须用 `ProcessStartInfo` + `RedirectStandardOutput.BaseStream`。
|
|
386
|
-
8. **行为契约:先做参数校验,再检查权限。** 否则畸形调用返回 `E_PERMISSION`,既掩盖真实问题、又像是在为一次根本无法执行的调用索要授权。
|
|
387
|
-
|
|
388
|
-
### 9.5 第 2 轮完成(授权层 + 新增动词族)
|
|
389
|
-
|
|
390
|
-
新增 `docs/permission-model.md`:四种决定语义、`permission.json` 模式、A+B 强制模型、写入委托、残余风险。
|
|
391
|
-
|
|
392
|
-
**已实现(全部实测通过)**
|
|
393
|
-
|
|
394
|
-
| 组 | 动词 | 状态 |
|
|
395
|
-
|---|---|---|
|
|
396
|
-
| `file.verify.*` | charset / encoding / completion | 完成。`encoding` 用"解码再编码后逐字节比对"判定合法性,能区分"文件不合法"与"我要求错了" |
|
|
397
|
-
| `system.*` | brand / cpu / gpu / version / info | 完成。逐字段降级(无 GPU 驱动返回空列表而非整体失败) |
|
|
398
|
-
| `task.*` | list / query | 完成。`show` / `kill` / `launch` 待下一轮 |
|
|
399
|
-
| `service.*` | list / query | 完成。`start/stop/restart` 待下一轮(需提权路径) |
|
|
400
|
-
| `environment.*` | list / get / add / set / delete + `attr` 同名五操作 | 完成。见 §9.6 |
|
|
401
|
-
| `permission.*` | query / list / audit | 完成(只读) |
|
|
402
|
-
| **保护路径(B 层)** | — | 完成。8 个变更类动词全部接入;`permission.json`、脚本自身、`spec/*.tsv`、`command.txt` 不可被任何动词修改 |
|
|
403
|
-
|
|
404
|
-
**验证证据**:`tests/golden.ps1` → **passed: 30 failed: 0**(新增 7 条用例);`tests/encoding.ps1` → **all encoding contracts hold**;动词表 82 条。
|
|
405
|
-
|
|
406
|
-
**本轮修掉的两个真实缺陷(均由测试暴露)**
|
|
407
|
-
|
|
408
|
-
1. **`file.move` / `file.copy` 只保护了目标端**,于是 `file move permission.json <外部路径>` 把授权文件搬走了 —— 移动同样会删除源文件。测试因此发现 `permission.json` 真的被移走(已从副本恢复,最终确认 0 字节、哈希 `E3B0C442…`)。现已改为**两端都检查**:移动/复制受保护文件属泄露与破坏,源与目标同等对待。
|
|
409
|
-
2. **`E_PERMISSION_PROTECTED_PATH` 未登记退出码**,被兜底成 7(E_INTERNAL)而非 3(授权类)。现已把 5 个授权类错误码统一登记为退出码 3。
|
|
410
|
-
|
|
411
|
-
**另修**:三级动词解析(`file verify charset`、`environment path add`)—— 原解析器只支持两级;`Get-CimSafe` 单行结果被 PowerShell 展开导致 `.Count` 抛异常;`--filter` 缺省被强转为 `''` 导致过滤掉全部结果。
|
|
412
|
-
|
|
413
|
-
### 9.6 第 3 轮:environment 重构为「变量 + 属性」两层
|
|
414
|
-
|
|
415
|
-
`path` 不再是独立命令,而是 environment 的**属性**;变量与属性各自暴露同一组五个操作。
|
|
416
|
-
|
|
417
|
-
| 层 | 动词 | 语义 |
|
|
418
|
-
|---|---|---|
|
|
419
|
-
| 变量 | `environment list` | 列出变量的名字与原始值 |
|
|
420
|
-
| | `environment get name` | 取变量的原始字符串 |
|
|
421
|
-
| | `environment add name value` | 仅创建;已存在报 `E_EXISTS` |
|
|
422
|
-
| | `environment set name value` | 仅覆盖;不存在报 `E_NOT_FOUND` |
|
|
423
|
-
| | `environment delete name` | 删除;返回被删掉的值 |
|
|
424
|
-
| 属性 | `environment attr list` | 发现哪些变量是列表型属性(PATH / PATHEXT / LIB / …),含分隔符与条目数 |
|
|
425
|
-
| | `environment attr get name [--attr A]` | 属性的**有序条目列表** |
|
|
426
|
-
| | `environment attr add name value` | 追加一条;重复条目报 `E_EXISTS` |
|
|
427
|
-
| | `environment attr set name index value` | 按索引替换;越界报 `E_RANGE` |
|
|
428
|
-
| | `environment attr delete name [--index n \| --value v]` | 按索引或按值删除;两者都不给报 `E_USAGE` |
|
|
429
|
-
|
|
430
|
-
**关键语义(已实测并写入测试)**
|
|
431
|
-
|
|
432
|
-
1. **`--scope process` 的改动不跨调用存活**:每次调用都是新进程,因此该作用域只能验证单次调用内的行为。**只有 `user` / `machine` 作用域能证明持久化**,黄金用例据此分工。
|
|
433
|
-
2. **每个写入动词都回报 `visibleToRunningProcesses`**:`user`/`machine` 写入对已运行进程不可见,这个字段让调用方不必靠猜——正是这类"set 了却没生效"的困惑要在接口层消灭。
|
|
434
|
-
3. **`machine` 作用域在未提权时报 `E_ELEVATION_REQUIRED`**,绝不静默失败(本机会话恰好是提权的,所以该分支由代码审查而非运行验证)。
|
|
435
|
-
4. **属性是通用列表,不是 PATH 特例**:分隔符按平台取(Windows `;`、POSIX `:`),`--attr` 默认 `PATH`。
|
|
436
|
-
|
|
437
|
-
**本轮修掉的三个真实缺陷**
|
|
438
|
-
|
|
439
|
-
1. **`Get-AttributeEntries` 的数组包装被调用方二次 `@()`**:函数用一元逗号返回(保证空结果仍是数组),而两处调用又套了 `@(...)`,于是数组被**嵌套成单元素数组**,`.Count` 恒为 1、范围检查误判——表现为"文件里明明是 `a;b;c`,却报 index 1 越界 (0..0)"。已统一所有调用点并写明契约。
|
|
440
|
-
2. **黄金用例的夹具不生效**:`$env:NAME=` 只改提供程序视图,子进程继承的是进程环境块,必须用 `[System.Environment]::SetEnvironmentVariable(..., 'Process')`。夹具失效会让所有计数断言变得**看似通过实则无意义**。
|
|
441
|
-
3. **`--attr` 未登记为取值标志**,会被当成布尔标志从而吞掉属性名。
|
|
442
|
-
|
|
443
|
-
**验证证据**:`tests/golden.ps1` → **passed: 32 failed: 0**;`tests/encoding.ps1` → **all encoding contracts hold**;动词表 **82 条**。
|
|
444
|
-
|
|
445
|
-
### 9.7 第 4 轮:目录重构、POSIX 实现与契约对齐
|
|
446
|
-
|
|
447
|
-
**目录重构("移动到 src 的 host/client 下")**
|
|
448
|
-
|
|
449
|
-
| 旧位置 | 新位置 |
|
|
450
|
-
|---|---|
|
|
451
|
-
| `bin/` | `src/host/bin/` |
|
|
452
|
-
| `spec/` | `src/host/spec/` |
|
|
453
|
-
| `command.txt`(仓库根) | `src/host/command.txt` |
|
|
454
|
-
| `docs/` | `src/host/docs/` |
|
|
455
|
-
| `tests/` | 保持在仓库根(面向贡献者,非运行时产物) |
|
|
456
|
-
|
|
457
|
-
**本轮修掉的三个真实缺陷(每个都曾产生实际错误结果)**
|
|
458
|
-
|
|
459
|
-
1. **受保护路径检查被静默解除(最严重)。** 拆分目录后,`$MyInvocation.MyCommand.Path` 在 `-File` 子进程里是**相对路径**,`Split-Path` 于是从调用者 CWD 向上走,把 `RootDir` 算成 `<repo>\src` 而非 `<repo>`;保护清单被解析到 `<repo>\src\src\host\...`,**一条都不匹配**。后果不是报错而是**保护失效**:`permission.json`、`src\host\bin\dsh-os.ps1`、`command.txt`、`verbs.tsv` 全被真正删除(进入回收站)。已按原始路径从回收站恢复,并改为**向上搜索标记**(同时存在 `package.json` 与 `src/host`)定位仓库根,不再数 `..` 层数。
|
|
460
|
-
> 教训:安全边界依赖路径推导时,推导失败必须 **fail-closed**。"算错了所以不匹配"等价于"放行"。
|
|
461
|
-
2. **`environment attr` 与契约漂移。** `command.txt` 已改名为 `environment attribute`,而实现与动词表仍是 `attr`,于是**文档里的命令实际不可用**(`E_NOT_SUPPORTED`)。已把 `spec/verbs.tsv`、`.ps1`、`.sh`、`tests/golden.ps1` 全部对齐到 `attribute`(`--attr` 标志名保留)。
|
|
462
|
-
3. **POSIX 实现的三个独立缺陷**(新建 `src/host/bin/dsh-os.sh`):
|
|
463
|
-
* `resolve_verb` 被 `CURRENT_VERB="$(resolve_verb)"` 包成子 shell,其中的 `POSITIONALS` 重赋值被丢弃 —— 每个动词都把 `file` 当成首个参数,报出"cannot read 'file': not found"这种**看似合理但完全错误**的结果。改为在当前 shell 内解析。
|
|
464
|
-
* 动词体内的失败被 `set -e` 在 `result="$(cmd_x)"` 处直接终止,**信封来不及输出**。改为用 `dispatch` 包装并显式处理状态。
|
|
465
|
-
* 二进制检测把 `od` 的十六进制**拼接成整串**后找 `00`,于是 `b0 0a` 拼成 `b00a`,把普通 UTF-8 文本误判为二进制。改为按空白分隔逐字节匹配。
|
|
466
|
-
|
|
467
|
-
**POSIX 实现现状(在 git-bash 实测)**
|
|
468
|
-
|
|
469
|
-
* **成功路径全部可用**:`file charset/length/list/view`、`file verify charset|encoding`、`system brand/version`、`task list`、`environment get`、`environment attribute list|get`、`permission list`、`selfcheck`。
|
|
470
|
-
* **失败路径仍是缺陷**:退出码正确,但**错误信封未出现在 stdout**(fd 8 中转方案在 git-bash 下未生效)。**未解决**,是 POSIX 进入可用状态前的最后一关。
|
|
471
|
-
* 未实现:`execute`、`mouse`、`keyboard`、`window`、`wait`、`screenshot`、`text`、`structure`、`service`、环境写入类。
|
|
472
|
-
|
|
473
|
-
### 9.8 待办(第 5 轮起,按第 4 轮要求排队)
|
|
474
|
-
|
|
475
|
-
1. **修复 POSIX 失败信封**(阻塞 POSIX 可用性)
|
|
476
|
-
2. **新增动词**:`file search name`(返回 JSON 数组)、`execute synchronous` / `execute asynchronous`(命令数组,分别顺序与并发执行并汇总结果)
|
|
477
|
-
3. **输出改为 agent-first**:结果一律面向机器可解析,取消 `file view` 的人类行号渲染(行号改为结构化字段)
|
|
478
|
-
4. **按模块拆分**:`command/charset/file/window/execute/json/task` 等,`.ps1` 与 `.sh` **成对**提供
|
|
479
|
-
5. **DSH 插件入口**(`dsh.bundle.patch` + Cordis 插件),否则 npm 安装后 harness 不会加载
|
|
480
|
-
6. **npm 发布 0.1.0**(用户已选"先补齐再发布")
|
|
481
|
-
|
|
482
|
-
### 9.9 本轮完成的交付动作
|
|
483
|
-
|
|
484
|
-
* **GitHub 已推送**:`https://github.com/eloyhere/deepseek-coordination`,提交 `c2dae0c`,26 个文件;已用远端 API 校验 SHA 与目录内容一致。
|
|
485
|
-
* **`.gitattributes`**:把行尾纳入契约(`.ps1`/`.bat` 检出 CRLF,`.tsv`/`command.txt`/`.md` 为 LF),避免 `core.autocrlf=true` 把 LF 写回仓库而破坏编码契约。
|
|
486
|
-
* **package.json**:`private:false`、`0.1.0`、`repository`/`bugs`/`homepage`、`files` 白名单、`os`/`engines`、`bin`、测试脚本;`npm pack --dry-run` 通过(11 文件 / 54.9 kB)。
|
|
487
|
-
* **README.md**:新增。
|
|
488
|
-
* **npm**:身份 `eloyhere` 已登录,包名 `deepseek-coordination` **未被占用**;按用户决定**尚未发布**。
|
|
489
|
-
|
|
490
|
-
### 9.10 原始第 4 轮计划(已并入 9.8)
|
|
491
|
-
|
|
492
|
-
1. `execute` 族(argv 直传、非 ASCII 输出解码、超时、`--background`)
|
|
493
|
-
2. `bin/dsh-os.sh` 对齐 `file` 族 + `tests/golden.sh`;locale 非 UTF-8 时必须 `E_NOT_SUPPORTED` 而非乱码
|
|
494
|
-
3. `permission-consent` 插件(Service Definition + Consumer + `approval/request` answerer)与 A 层 ACL 初始化
|
|
495
|
-
4. 剩余动词:`task.show/kill/launch`、`window.minimize/maximize/restore`、`service.start/stop/restart`、`environment.*` 写入侧、`mouse`/`keyboard`/`clipboard`/`wait`/`screenshot`/`text`/`structure`
|
|
496
|
-
5. `tests/conformance.ps1`(动词集与 `command.txt` 一致性)
|
|
497
|
-
|
|
498
|
-
## Round 19 - execute.command / query / kill and task.launch (Windows)
|
|
499
|
-
|
|
500
|
-
Windows now implements 82 of the 86 declared verbs. Four were added: `execute.command`,
|
|
501
|
-
`execute.query`, `execute.kill` and `task.launch`.
|
|
502
|
-
|
|
503
|
-
**`execute.command` takes an argv array and uses no shell.** The batch verbs
|
|
504
|
-
(`execute.synchronous` / `.asynchronous`) take command STRINGS and run them through `cmd.exe`; this one
|
|
505
|
-
parses nothing, so a value containing spaces, quotes or metacharacters reaches the child exactly as
|
|
506
|
-
given. `--shell` switches to the batch form explicitly rather than silently.
|
|
507
|
-
|
|
508
|
-
Three defects were found while building it, each producing a wrong result rather than an error:
|
|
509
|
-
|
|
510
|
-
1. **`ProcessStartInfo.ArgumentList` does not exist on .NET Framework**, which is what Windows
|
|
511
|
-
PowerShell 5.1 runs on, so every call failed with "property not found". The Win32 process API takes
|
|
512
|
-
a single command line string, so it is now built with the quoting rules `CommandLineToArgvW`
|
|
513
|
-
parses: backslashes are doubled before a quote and at the end of an argument, and an embedded quote
|
|
514
|
-
is escaped. This is the one place in the surface where a command line is assembled, and the reason
|
|
515
|
-
is that the API leaves no alternative.
|
|
516
|
-
2. **A timeout waited for the grandchildren, not for the deadline.** Killing the direct child does not
|
|
517
|
-
close the output pipes, because descendants inherit those handles; the output await then blocked
|
|
518
|
-
until they exited on their own. A 1.2s timeout on `cmd /c ping -n 20` took **20 seconds**. The
|
|
519
|
-
process TREE is now terminated, which also matters to the caller: those pipes are the ones this
|
|
520
|
-
script was launched with, so the caller's own process stayed alive until they closed. The same case
|
|
521
|
-
now returns in about 3.6s including PowerShell startup.
|
|
522
|
-
3. **`task.launch` reported `started: true` for a process that was already dead.** PowerShell runs its
|
|
523
|
-
children inside a job object that is terminated when PowerShell exits, so the launched program was
|
|
524
|
-
killed the moment the verb returned. It is now created through WMI, which asks a service that
|
|
525
|
-
outlives this process; the result names the mechanism used, and falls back to `Process.Start` with
|
|
526
|
-
an explicit warning on a machine without WMI.
|
|
527
|
-
|
|
528
|
-
`execute.kill` delegates to the `task kill` implementation so the two cannot drift apart.
|