dsh-crwu-workbench 0.0.13 → 0.0.15
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 +418 -0
- package/README.en.md +53 -13
- package/README.md +69 -12
- package/common/skills/crwu-dws/SKILL.md +4 -4
- package/common/skills/crwu-h3yun-login/SKILL.md +3 -3
- package/common/skills/crwu-h3yun-query/SKILL.md +5 -5
- package/lib/client.js +979 -109
- package/lib/index.js +5470 -1389
- package/package.json +21 -9
- package/scripts/lib/cli-entry.mjs +112 -0
- package/scripts/prepare.mjs +56 -16
- package/skills/README.md +19 -0
- package/skills/crwu/crwu-audit/references/14-orchestration-workflow.md +15 -0
- package/skills/crwu/crwu-audit/scripts/README.md +2 -2
- package/skills/crwu/crwu-dev-audit-optimize/references/03-AI/344/272/272/345/267/245/345/267/256/350/267/235/345/210/206/346/236/220/346/265/201/347/250/213.md +1 -1
- package/skills/crwu/crwu-dev-audit-skill-maintainer/references/01-kb-source-discovery.md +1 -1
- package/skills/crwu/crwu-dev-audit-skill-maintainer/references/02-child-skill-contract.md +1 -1
- package/skills/crwu/crwu-dev-audit-skill-maintainer/references/04-registry-and-mapping-update.md +1 -1
- package/skills/crwu/crwu-dev-audit-skill-maintainer/references/05-validation-and-delivery.md +2 -2
- package/skills/crwu/crwu-dev-audit-skill-maintainer/references/06-live-routing-reconciliation.md +1 -1
- package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/README.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,254 @@
|
|
|
7
7
|
`cordis_define` + `cordis_run` 装配,版本号用 DSH 的 `pkg-N`);它已在本仓收尾时删除
|
|
8
8
|
(见 `0.0.1` 一节),下面 `legacy · pkg-43` 及更早的记录是它的历史。
|
|
9
9
|
|
|
10
|
+
## package · 0.0.15 · 2026-09-28
|
|
11
|
+
|
|
12
|
+
**协议 18:本机访问从「调用方声明提权」改成「具名操作 + 版本化授权收据」,
|
|
13
|
+
审核会话固定收敛到 `workspace-write` + `approval=never`。**
|
|
14
|
+
|
|
15
|
+
这是**跨进程契约的语义破坏**,不只是加字段:旧的 `trust` 布尔不再被接受(协议不匹配直接失败,
|
|
16
|
+
不会被当成"已授权"),`crwu` 直通 RPC 不再接受 `escalate` 参数,提权改由**操作身份**决定。
|
|
17
|
+
装上升级后需要**完整退出并重启** DSH(客户端与宿主是两个进程,只刷新页面会留下旧宿主)。
|
|
18
|
+
|
|
19
|
+
### 为什么要换形态(员工实测的三段报错)
|
|
20
|
+
|
|
21
|
+
| 员工看到的 | 真正的原因 | 旧形态为什么分辨不出 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| 写 `%USERPROFILE%\.ossutilconfig` 报 `file access denied under workspace-write mode` | DSH 沙箱拒绝 | `escalate: true` 只是一个布尔,看不出"这一步到底放行了什么" |
|
|
24
|
+
| 氚云报 `secret not found in keyring` | 受限沙箱读不到钥匙串,**不是**没登录 | 未授权时会先探一次凭据,把假结论当真相 |
|
|
25
|
+
| DWS 报 `.data.lock: Access is denied` | 沙箱写不了 / 真 NTFS ACL / 锁被占用(三种) | 三者文本一样,只能靠猜 |
|
|
26
|
+
|
|
27
|
+
三段表象不同、原因相同;而同**一句** `Access is denied` 又可能是三种完全不同的原因。
|
|
28
|
+
所以这一版把"提权"变成一个**具名操作的属性**,把"为什么失败"变成**结构化事实的推理**。
|
|
29
|
+
|
|
30
|
+
### A · 版本化同意 + 安全的环境引导(协议 18)
|
|
31
|
+
|
|
32
|
+
- 授权收据带 `schemaVersion` + `grantedAt` + **固定的五项能力**;旧的 `trustCredentials: true`
|
|
33
|
+
一律判为 `outdated`(**永远不是**一次授权)。
|
|
34
|
+
- **未授权时一个凭据进程都不起、一个凭据字节都不读**(原来会先探一次,于是"未登录/密钥错误"
|
|
35
|
+
这种假结论会被写进交付件)。未授权在环境自检里是**阻塞项**,不是"未登录"。
|
|
36
|
+
- 授权入口在环境 workflow 的「账号连接」里(不是遮住整页的模态框),并带一次明确的「暂不允许」。
|
|
37
|
+
|
|
38
|
+
### B · 本机访问代理(Local Access Broker)
|
|
39
|
+
|
|
40
|
+
- `src/host/access/{operations,diagnostics,broker}.ts`:**封闭的操作表**(27 个操作,
|
|
41
|
+
每个固定 capability / 通道 / 是否提权 / 允许的来源)+ 唯一能声明 `danger-full-access` 的地方。
|
|
42
|
+
- `runShell` **没有** `escalate` 参数;调用方只能选一个操作名,不能提交命令、二进制路径、
|
|
43
|
+
沙箱模式或提权开关。文件写入还要求"目标种类 + 绝对路径"逐字对上。
|
|
44
|
+
- 全量迁移:状态文件、iFinD 凭据、OSS 配置与**每一次** `ossutil`、氚云全部子命令、
|
|
45
|
+
`dws` 全部白名单前缀、浏览器/剪贴板/案例目录、13 处审核 Tool 调用点。
|
|
46
|
+
- **归因只看结构化事实**:`requested` / `resolved` / `ran` / `denied` / `runnerFailed`。
|
|
47
|
+
`ran` 也要看 —— 请求活过了 `resolve()` 却在执行时被降级是最难发现的一种。
|
|
48
|
+
- 迁移守卫(`host-access-migration.test.mjs`):业务 CLI 只能在登记的执行器里拼命令、
|
|
49
|
+
且必须经 Broker;这条守卫在开发中真的抓到两处自己拼 `dws` 命令的调用点。
|
|
50
|
+
|
|
51
|
+
### C · 审核会话收敛
|
|
52
|
+
|
|
53
|
+
- 审核根与审核子会话**永远** `workspace-write` + `approval=never`:用 DSH 自己的
|
|
54
|
+
`setSandboxMode` / `setApprovalPolicy` 写入,再**回读**,并按 DSH 的委派捕获口径确认
|
|
55
|
+
"子代理将要继承到什么"。**不依赖部署默认**(默认可能是 `danger-full-access`)。
|
|
56
|
+
- 带 `auto` / `danger-full-access` preset 的根**不可复用**;创建子代理前还有一道环境就绪门禁;
|
|
57
|
+
子会话发布后按它自己的 scope 复查策略与工具可见性,不对就**停掉它**。
|
|
58
|
+
- 登录类操作(`h3yun.session.login` / `dws.auth.login`)的 `allowedSources` **只有面板**:
|
|
59
|
+
审核链路连登录这个动作都拿不到。提示词里明令"未登录就停下、要员工回工作台"。
|
|
60
|
+
|
|
61
|
+
### D · 桌面归因与修复指引
|
|
62
|
+
|
|
63
|
+
- `src/host/access/classify.ts`:九档优先级的**纯函数**分类器,把三段表象分开成
|
|
64
|
+
`os-credential-store` / `file-lock` / `os-filesystem-permission` / `sandbox-denied` /
|
|
65
|
+
`sandbox-downgraded` / `infrastructure` / `cli`。
|
|
66
|
+
`sandbox-exec: sandbox_apply: Operation not permitted` **永远**是基础设施故障,
|
|
67
|
+
不会被翻译成"命令缺失 / 没登录 / 凭据无效";锁必须有**正向探测**才定性。
|
|
68
|
+
- 新 Host 操作 `dws-local-doctor`(只读、零参数、目录由 `<home>/.dws` 推导)与
|
|
69
|
+
`dws-local-permission-repair`(**只给面板**、必须 `{ confirm: true }`、要求体检**正向确诊**
|
|
70
|
+
"本机文件权限问题")。修复只动 `.dws`(700)与 `.data.lock`(600):不 `chown` / `takeown` /
|
|
71
|
+
`sudo`、不删锁、不碰父目录、拒符号链接,修完**重新体检**。
|
|
72
|
+
- 界面:修复按钮**只在确诊本机文件权限问题时渲染**,并且要**二次确认**(改权限 ≠ 允许读本机凭据)。
|
|
73
|
+
沙箱拒绝 / 降级 / 钥匙串 / 认证失败 / 所有者不对 / 文件锁都不渲染。
|
|
74
|
+
- `@deepseek-ai/dsh-sandbox-policy` 与 `@deepseek-ai/dsh-user-approval` 加入 peer + dev
|
|
75
|
+
(与其余 7 个 peer 同口径;宿主产物会 `import` 它们,接收方由 DSH 运行时提供)。
|
|
76
|
+
|
|
77
|
+
### 操作清单:33 → 38
|
|
78
|
+
|
|
79
|
+
新增 `local-access-grant` / `local-access-revoke`(A)、`access-diagnostics`(B,只读)、
|
|
80
|
+
`dws-local-doctor` / `dws-local-permission-repair`(D)。`crwu` 直通入口的 `escalate` 参数已删除。
|
|
81
|
+
冻结清单在 `tests/helpers/frozen-inventory.mjs`,漏登记会直接红。
|
|
82
|
+
|
|
83
|
+
### 复查期间加固(2026-09-29,用户复查逐条点出后修)
|
|
84
|
+
|
|
85
|
+
下面这些**都在本版里**(0.0.15 未发布,所以按最终形态记),每一条都能被具名用例证伪:
|
|
86
|
+
|
|
87
|
+
- **授权/撤销的状态迁移只由 `host/access/consent.ts` 负责**:调用方不得把返回的收据写回活状态 ——
|
|
88
|
+
写盘失败时那份视图可能来自**磁盘上的旧授权**,照抄等于一次失败的「重新允许」把权限重新打开。
|
|
89
|
+
- **案例目录必须落在选定工作空间的信任域内**:此前这条要求只写在注释里。`caseDir` 是模型参数,
|
|
90
|
+
只校验"绝对 + 存在 + 是目录 + 没有 `..`"等于没校验 —— `crwu_audit_oss_publish` 会把**任意可读文件**
|
|
91
|
+
传到 OSS(读不受沙箱限制),`crwu_h3yun_record_get` / `files_list` 更把它**原样当特权命令的 cwd**。
|
|
92
|
+
现在 `allowedRoot` 是**必填参数**(编译器保证没有调用点能漏传),运行时漏传 fail closed。
|
|
93
|
+
- **信任域未知一律拒绝,不许"跳过检查继续做"**:`openPath` 曾经是后者,那天唯一挡住它的是
|
|
94
|
+
"提权必须有 workdir"与案例根兜底**恰好相等**;判据现在落在任何 fs 探测之前。
|
|
95
|
+
- **锁归因要两个条件**(原始失败与锁有关 **且** 正向探测证明有人持有);Windows 的锁探测必须沿
|
|
96
|
+
`InnerException` 解包到根异常再读 HResult(旧写法让"真有进程持锁"也落 `Unknown`,W-05 在代码上不可达)。
|
|
97
|
+
- **目录与 `.data.lock` 分别探权限**:只探目录会让"目录正常、锁文件不可写"退化成 `cli`、修复入口永不出现;
|
|
98
|
+
修复只动被证明有问题的对象,回读也只核对修过的那些。
|
|
99
|
+
- **Windows ACL 算有效权限**:`Modify = 197055` / `FullControl = 2032127`(夹具曾把后者标成 Modify);
|
|
100
|
+
判据是"所需**写**权限"是否被完整覆盖,**部分 Deny**(`(W)`、Delete)同样算不可写 —— 旧实现整个忽略。
|
|
101
|
+
- **脱敏声明要有端到端证据**:取数摘要的净化此前只在失败路径被喂过 token,成功路径只试过 `{"v":1}`;
|
|
102
|
+
现在按"零件级 + 接线级"两条分别取证(对象路径的密钥字段整条丢 ≠ 文本的值级净化)。
|
|
103
|
+
- **卸载路径此前从未被执行过**:`smoke:built` 现在调用每个 `ctx.effect` 的 disposer,断言同源路由被摘、
|
|
104
|
+
工具被逐个注销(忘 `return` disposer 是 Cordis 的经典坑,本地以前完全看不见)。
|
|
105
|
+
|
|
106
|
+
### 复查第二轮:**审核 scope 绑定**(协议 19)
|
|
107
|
+
|
|
108
|
+
第一轮修的是"案例目录必须在工作空间之下"。用户第二轮复查指出:这**不够** ——
|
|
109
|
+
一个工作空间里通常有很多案例目录,于是 S1 的子会话可以传 `<工作空间>/S2`、
|
|
110
|
+
或传工作空间根再读 `S2/文件`(把别的案例读出来传上 OSS、或往别的案例里写);
|
|
111
|
+
氚云的 `objectId` / `fileId` 也是模型提交的,Host 没有把任何东西绑到**本轮**审核上。
|
|
112
|
+
|
|
113
|
+
- **Host 权威 scope 落盘**:创建记录时写死 `casePath`(= `<工作空间>/<流水号>`)、`attemptId`
|
|
114
|
+
与 `allowedAttachmentIds`(来自**可信输入快照**),跨重启保留。认领来的旧记录 scope 不完整 →
|
|
115
|
+
案例内 Tool 一律拒绝(不拿工作空间兜底)。
|
|
116
|
+
- **统一门禁 `requireAuditScope`**:按 `exec.agent.id → childId → 记录` 找本轮 scope,
|
|
117
|
+
`caseDir` 必须与 `casePath` **规范解析后精确相等**(工作空间根、兄弟案例、案例目录的子目录一律拒绝;
|
|
118
|
+
Windows 盘符/UNC 按风格大小写不敏感,POSIX 大小写敏感),`seqNo` / `objectId` 必须一致,
|
|
119
|
+
无身份 / 未知 childId / 已结束 / scope 不完整一律 fail closed。通过时**返回 Host 的路径**,
|
|
120
|
+
调用方不再使用模型给的字符串。
|
|
121
|
+
- **氚云边界收紧**:`crwu_h3yun_record_get` / `files_list` 移出审核子会话的必需集
|
|
122
|
+
(注册面与审核能力集拆成 `CRWU_BUSINESS_TOOLS` / `REQUIRED_AUDIT_TOOLS`),
|
|
123
|
+
审核子会话调用它们直接拒绝且**零进程**;`crwu_h3yun_file_get` 只接受快照里登记的 `fileId`,
|
|
124
|
+
表外 id 在起进程之前拒绝。`case_bootstrap` 也要求 `caseDir` 精确等于 Host 约定算出的那一个。
|
|
125
|
+
- **真正的写边界是案例目录**:审核根的 `cwd` 与 `workspace-write.workspaceRoot` **都**改成
|
|
126
|
+
本轮案例目录(回读核对),一条根只服务一个案例,工作空间级旧根(`casePath` 为空)判**过期**不复用;
|
|
127
|
+
占用门禁提前到"建目录 / 建根"之前,所以被拒的发起**零副作用**(不建空目录、不起根会话)。
|
|
128
|
+
于是通用 shell / fs 也只能写自己的案例目录 —— 这一条与设计文档"只能写自己的案例目录"终于一致。
|
|
129
|
+
- **协议 18 → 19**:这是跨进程权限语义变化,旧宿主仍按工作空间级边界跑审核,
|
|
130
|
+
必须靠协议号把"界面新、宿主旧"拦下来(客户端会要求完整重启)。
|
|
131
|
+
### F 段:可观察的停止状态机 + 当前审核会话入口(2026-09-29)
|
|
132
|
+
|
|
133
|
+
- **两阶段停止协议**:`audit-stop` 立刻把阶段写成 `requested` 并落盘后返回"已接受",
|
|
134
|
+
abort → dispose → 静默复查在后台继续,阶段逐段落盘;`audit-status` 每条记录带
|
|
135
|
+
`stop`(`phase` / `requestedAt` / `elapsedMs` / `quiesced` / `aborted` / `disposed` /
|
|
136
|
+
`error` / `notes` / `canStartNext`),并加一个顶层 `canStartNext`。
|
|
137
|
+
重复点停止**幂等**(`alreadyStopping`,不启动第二条流程、不重复 abort)。
|
|
138
|
+
- **阶段枚举**:`idle` / `requested` / `aborting` / `waiting-quiescence` / `quiesced` / `timeout` / `failed`。
|
|
139
|
+
`canStartNext` **只由 Host 判定**:没确认静默(timeout / failed / 进行中)一律 false,
|
|
140
|
+
且**不依赖占用锁在不在**(重启后锁可能没恢复,"没确认停下"仍然成立)。客户端缺字段按未知处理。
|
|
141
|
+
- **启动门禁**:停止流程在跑、或上一条留下 timeout / failed 时,`audit-start` 直接拒绝;
|
|
142
|
+
界面同时把行上的「AI 审核 / 重新审核」置灰(与 Host 的 `canStartNext` 取交)。
|
|
143
|
+
- **界面(F2/F3)**:顶部「当前审核」摘要卡(流水号 / 项目 / 状态 / attempt / 开始时间 /
|
|
144
|
+
childId 脱敏尾部 / 停止阶段 / 观察),**在表格之外**,所以当前审核不在当前分页或筛选结果里时
|
|
145
|
+
依然可见;阶段文案用 `aria-live="polite"` 播报、等待期间显示"已等待 N 秒 · 最多等待约 8 秒"、
|
|
146
|
+
停止按钮禁用;timeout 给「继续等待 / 再次停止 / 复制诊断」,failed 给「打开当前会话 / 复制诊断」
|
|
147
|
+
并显示真实错误与"当前审核仍被保留,未释放占用"。
|
|
148
|
+
- **「打开审核会话」**:用**被点那一行**的 `childId` / `parentSessionId`(`openSessionTarget` 是唯一解析点),
|
|
149
|
+
`childId` 为空时按钮显示「会话正在建立」并禁用;它与「AI 审核结果分析(`audit_analysis`)」
|
|
150
|
+
是两个入口、两个目标会话(文案与目标都有断言)。
|
|
151
|
+
- **客户端停止口径是纯函数**(`features/report-audit/stop-view.ts`):阶段文案 / 语气 / 可用动作 /
|
|
152
|
+
`canStartNext` 都能被单测穷举(7 条),组件只负责渲染。
|
|
153
|
+
- **协议号仍是 19**:新增的 `stop` / `canStartNext` 都是**可选**字段 —— 旧客户端忽略它们照常工作,
|
|
154
|
+
新客户端在旧宿主上把它们当"未知"(不显示"已停止"、也不放行下一条)。没有破坏性契约变化。
|
|
155
|
+
|
|
156
|
+
### 第三轮复查:4 个 P1 + 2 个 P2(全部已修,逐条有用例与缺陷注入)
|
|
157
|
+
|
|
158
|
+
- **子会话必需集与 deny 集重叠(P1)**:`crwu_audit_case_bootstrap` 既是"根必需"又是"子会话 deny",
|
|
159
|
+
而子会话复查用的是根必需集 —— 真实 `toolFilter` 生效后**每条正常子会话都会被判成缺工具并停掉**。
|
|
160
|
+
现在拆成 `REQUIRED_AUDIT_TOOLS`(根必需)与 `REQUIRED_AUDIT_CHILD_TOOLS`(子会话必需 = 根必需 − deny),
|
|
161
|
+
并有一条门禁断言两个集合**不相交**;子会话复查专用后者。
|
|
162
|
+
- **pending scope 按父会话认领会允许 sibling 冒领(P1)**:父会话 id 只能证明"属于同一个 root",
|
|
163
|
+
证明不了"就是本次 `start()` 创建的那一个"。one-shot `start()` 没有预留 child id 的参数、
|
|
164
|
+
拿不到不可伪造的 launch token,所以窗口内**案例内 Tool 一律 fail closed**(
|
|
165
|
+
`auditScopeFor` 只认与记录逐字相等的 childId);父会话判据只保留在**拒绝方向**(`isAuditChild`)。
|
|
166
|
+
- **失败分支忽略 `quiesced`(P1)**:子会话可见性/策略复查失败、最终落盘失败这三条路径此前直接
|
|
167
|
+
回滚记录与句柄;dispose 超时且 Agent 仍 running 时,会形成"旧子会话继续写案例目录、Host 已抹掉身份"。
|
|
168
|
+
现在检查 `quiesced`:没确认静默就写成**退役记录**(`retired: true`,只拒绝不放行)、
|
|
169
|
+
**保留** run 句柄与占用,并提示去工作台重试停止。
|
|
170
|
+
- **读不到 child Agent 仍继续审核(P1)**:`SubagentRun.localAgent` 对远程 provider 是 `undefined`,
|
|
171
|
+
而边界复查全靠它。现在保留并优先使用 `localAgent`,**读不到就停掉并失败**(不再只记 warning);
|
|
172
|
+
`pickProvider` 也**不再回退到"第一个注册的"**,只接受本地可验证的 provider(`spawn` / `fork`)。
|
|
173
|
+
- **orphan pending 跨重启可冒领(P2)**:创建失败且回滚写盘也失败时,磁盘会留下 `pending: true` 的记录。
|
|
174
|
+
恢复时把它**退役**(不发放 scope),并且**不恢复指向空 childId 的占用锁**。
|
|
175
|
+
- **`auditStop` 返回写死的停止结果(P2)**:现在透传真实的 `StopOutcome`
|
|
176
|
+
(`aborted` / `disposed` / `quiesced` / `interrupted` / `agentCancelled` / `notes`)。
|
|
177
|
+
|
|
178
|
+
### 第二轮复查:5 个 P1 + 2 个 P2(全部已修,逐条有用例与缺陷注入)
|
|
179
|
+
|
|
180
|
+
- **子会话策略复查用了错的边界**(P1):协议 19 已把子会话的边界/cwd 收紧到案例目录,
|
|
181
|
+
但复查仍拿整个工作空间当期望值 —— **正常可见的子会话会被稳定判错并停掉**。
|
|
182
|
+
现在传 `caseDir`,并新增"真实可见 child + 正确案例目录能正常启动"的**正向**用例
|
|
183
|
+
(旧夹具的 `agents.get()` 只返回根 Agent,整段复查被跳过,所以这条一直没被抓到)。
|
|
184
|
+
- **发布后的子会话没有被真正限制工具**(P1):`record_get` / `files_list` / `case_bootstrap`
|
|
185
|
+
仍可见可执行 —— "必需集名单"只是预检名单,不是边界。现在按 provider 的
|
|
186
|
+
`capabilities.toolFilter` 显式传 `toolFilter.deny`(DSH 在子会话创建窗口里做 scoped
|
|
187
|
+
`tools.restrict()`:**既不进 prompt、也拒绝执行**),**不支持过滤的 provider 在创建子会话之前拒绝**。
|
|
188
|
+
- **子会话发布早于 scope 建立**(P1):`subagents.start()` 返回前子会话已在跑,那段窗口里
|
|
189
|
+
身份拒绝失效、案例内 Tool 随机失败。现在**两阶段握手**:创建子会话**之前**先落一条
|
|
190
|
+
pending 记录(父会话 = 审核根),窗口内按父会话认领本轮 scope;`start()` 返回后写真正 childId;
|
|
191
|
+
创建失败 / 子会话复查失败一律回滚 pending。
|
|
192
|
+
窗口内的"父会话"有**两条来源**:会话头的 `meta.parentSession`,读不到就问 `subagents.listChildren`
|
|
193
|
+
(会话存储驱动,Host 权威)"审核根的孩子里有没有这个调用者" —— 两条都拿不到才 fail closed。
|
|
194
|
+
这样一来该判据不再依赖另一个组件是否写了元数据。
|
|
195
|
+
- **dispose 超时被当成停止成功**(P1):计时器是**正常 resolve** 的,旧实现在超时时
|
|
196
|
+
`disposed=false` 但 `errors=[]`,上层据此继续重启。现在区分"谁赢了",并以**静默判据**
|
|
197
|
+
`quiesced`(dispose 完成 **或** Agent 已不在 running;问不到状态 = fail closed)为准:
|
|
198
|
+
没确认停下来就**不覆盖旧记录、不启动下一条**,句柄与占用一并保留。等待也改成**有界**
|
|
199
|
+
(没有 timer 服务时退化成全局定时器,绝不无界 await)。
|
|
200
|
+
- **返回记录的身份没有核对**(P1):`records get --id B` 取回一条记录 ≠ 取回的是 B。
|
|
201
|
+
现在**在请求附件与任何写盘之前**核对返回记录的 `ObjectId` / `SeqNo`:缺字段或与请求不一致
|
|
202
|
+
一律 fail closed(不再用调用方提交的值兜底)。
|
|
203
|
+
- **把不透明的 `targetKey` 当路径解析**(P2):`FsTarget.targetKey` 是 `Branded<'FsTargetKey'>`,
|
|
204
|
+
合同只允许等值比较;旧实现做分隔符/大小写归一化,形似 Windows 路径但区分大小写的后端会被错误放行。
|
|
205
|
+
现在只做 `===`(`displayPath` 不再当身份用);我们自己产生的路径字符串另有 `samePathText`。
|
|
206
|
+
- **审核记录持久化失败仍返回成功**(P2):启动/停止/释放三条路径都处理 `persistAudits=false` ——
|
|
207
|
+
启动失败会停掉刚起的子会话并回滚内存锁;停止/释放如实返回失败(内存放开了,但磁盘上可能还留着)。
|
|
208
|
+
|
|
209
|
+
(实现方在交付前自己找的,均已用缺陷注入证伪):
|
|
210
|
+
① `record_get` / `files_list` 的拒绝条件一度写成"这轮 scope 是否可用" —— 一轮审核
|
|
211
|
+
`ended: true` 之后 scope 变为不可用,那个还活着的子会话就能以"没有可用 scope"为由绕过拒绝、
|
|
212
|
+
去读**任意** `objectId`;判据已改成**调用者身份**(`isAuditChild`,不要求 scope 可用)。
|
|
213
|
+
② `crwu_audit_case_bootstrap` 的 `caseDir` 虽被钉在本轮案例目录,但 `objectId` 仍是提交进来的 ——
|
|
214
|
+
子会话可以用**别的** objectId 把别人的记录取进自己的案例目录;现在审核子会话调用它一律拒绝。
|
|
215
|
+
|
|
216
|
+
### 测试
|
|
217
|
+
|
|
218
|
+
新增/扩写的测试文件:`host-access-consent` / `host-access-broker` / `host-access-classify` /
|
|
219
|
+
`host-access-migration` / `host-access-rpc-gate` / `host-audit-policy` / `host-dws-local` /
|
|
220
|
+
`client-dws-local` / `host-case-dir-gate` / `host-design-traceability`(**共 1242 条**,
|
|
221
|
+
本机 `1242 / 1238 pass / 1 skip`;两条 posix 子用例只在"用 DSH 自带二进制当 `node`"时红,
|
|
222
|
+
机制见 `docs/review-0.0.15.md` §5)。
|
|
223
|
+
|
|
224
|
+
每一条新断言都用**缺陷注入**证伪过(注入 → 变红 → `cp` 还原 → 转绿);
|
|
225
|
+
开发与复查过程中被抓到的真实缺陷包括:OSS 未授权仍然报「还没有填写 AccessKey」、
|
|
226
|
+
环境自检与身份查询自己拼 `dws` 命令绕开白名单、审核根的诊断被体检自己挤掉、
|
|
227
|
+
"没确认"的修复调用照样跑了一串探测、撤销写盘失败反而把权限重新打开、
|
|
228
|
+
案例目录可以指到工作空间之外、Windows 锁探测读错异常层。
|
|
229
|
+
|
|
230
|
+
## package · 0.0.14 · 2026-09-28
|
|
231
|
+
|
|
232
|
+
**兼容 DSH `0.2.0-rc.1`:peer 区间从「一条线」改成「两条线并列」。**
|
|
233
|
+
0.0.13 及更早的版本在 DSH `0.2.0-rc.1` 上会被整体判为不兼容 —— 插件管理器的原文是
|
|
234
|
+
「`dsh-crwu-workbench@0.0.13` 与 DSH `0.2.0-rc.1` 不兼容(要求 `@deepseek-ai/dsh-tools@^0.1.7-rc.2`…),
|
|
235
|
+
运行它可能导致崩溃或数据丢失。请安装与当前 DSH 兼容的插件版本。」,装上了也会被禁用。
|
|
236
|
+
|
|
237
|
+
- **根因**:`^0.1.7-rc.2` 的语义是 `>=0.1.7-rc.2 <0.2.0-0`,**不含** `0.2.0-rc.1`。DSH 从 0.1.7 起把
|
|
238
|
+
peer 当**硬门禁**:加载 profile 的 bundle 层时拿 `getDshRuntimeVersion()` 与每个
|
|
239
|
+
`@deepseek-ai/dsh` / `@deepseek-ai/dsh-*` 的**区间**比对(预发布参与匹配),不匹配的整行被跳过;
|
|
240
|
+
插件管理器在安装前还会拿候选包自己的 `peerDependencies` 先判一次。
|
|
241
|
+
- **peer 改成 `^0.1.7-rc.2 || ^0.2.0-rc.1`**(7 个 `@deepseek-ai/dsh-*` 与 `engines.dsh` 一致)。
|
|
242
|
+
**一条线一个 `^` 区间并列,不用 `>=0.1.7-rc.2 <0.3.0`** —— 后者会顺带放行还没验证过的 0.3 线。
|
|
243
|
+
- **加线之前逐包比对过 API**,比对结论(`npm pack` 两条线的全部 `@deepseek-ai/dsh-*` 后逐文件 diff):
|
|
244
|
+
7 个 peer 里 `dsh-tools` / `dsh-plugin-manager` / `dsh-host-webserver` / `dsh-client-ui-renderer` /
|
|
245
|
+
`dsh-skill-filesystem` **字节相同**;`dsh-client-ui-sidebar` 只多了 1 行埋点调用与 1 行文档注释;
|
|
246
|
+
`dsh-client-ui-layout` 只多了一段 Windows 标题栏的 CSS 变量。插件用到的类型与运行时契约没有变化,
|
|
247
|
+
0.1.7-rc.2 上的一切行为不变。
|
|
248
|
+
- **`devDependencies` 补全为全部 7 个 peer**(原先只有 4 个):npm 的 peer 自动安装会为「只有 peer、
|
|
249
|
+
树里没有具体实例」的包去解析**最新**匹配版本,于是 `@deepseek-ai/dsh-skill-filesystem@0.2.0-rc.1`
|
|
250
|
+
的精确 peer(`@deepseek-ai/dsh-fs@0.2.0-rc.1`)会和开发树里的 `0.1.7-rc.2` 撞成 `ERESOLVE`。
|
|
251
|
+
列出实例即把开发树钉在我们开发所对的那条线上。**接收方不受影响**:profile 的 pnpm 配了
|
|
252
|
+
`autoInstallPeers: false`,`@deepseek-ai/dsh-*` 从来不由包管理器安装(DSH 运行时自己提供)。
|
|
253
|
+
- **新的回归测试**(`host-package.test.mjs`):按 DSH 自己的判据
|
|
254
|
+
(`semver.satisfies(runtime, range, { includePrerelease: true })`)断言 7 个 peer 覆盖
|
|
255
|
+
`0.1.7-rc.2` / `0.1.7` / `0.2.0-rc.1`,并反向断言**没有**顺带放行 `0.3.0-rc.1`,同时要求
|
|
256
|
+
`engines.dsh` 与 peer 口径一致。这条测试在旧区间上会直接红(已用注入缺陷证伪)。
|
|
257
|
+
|
|
10
258
|
## package · 0.0.13 · 2026-09-28
|
|
11
259
|
|
|
12
260
|
**修复 Windows 上的平台方言:命令串按 PowerShell 拼,不再按 `cmd.exe` 拼。**
|
|
@@ -35,6 +283,176 @@ UnexpectedToken`),相关按钮点下去也不会有结果;macOS 上完全
|
|
|
35
283
|
(一条静态守卫,钉死「模板以 `${shellQuote(` 开头」与「`argv.map(…shellQuote…).join(' ')`」
|
|
36
284
|
这两种会复发的写法)、`host-crwu-h3yun.test.mjs`(按员工报的原文断言 Windows 命令形状)。
|
|
37
285
|
|
|
286
|
+
### Windows 全面适配(同一版本的第二批,2026-09-28)
|
|
287
|
+
|
|
288
|
+
0.0.13 只修好了「最直接的那一类」PowerShell 调用(命令位置与参数引用)。这一批把剩下的四个
|
|
289
|
+
风险层一起收掉:安装兼容、命令方言不统一、本地路径按 POSIX 规则处理、CI 与发布门禁没有真的
|
|
290
|
+
模拟 DSH 的 Windows 语义。**一个版本的完整说明必须能回答「Windows 上到底能不能用」**,
|
|
291
|
+
所以下面按风险层分组。
|
|
292
|
+
|
|
293
|
+
#### 安装兼容
|
|
294
|
+
|
|
295
|
+
- `22b31b9` 的 peer 双线区间之外,新增 `scripts/check-dsh-compat.mjs`(`npm run compat:dsh`):
|
|
296
|
+
在干净临时工程里**真的**装齐 DSH `0.1.7-rc.2` 与 `0.2.0-rc.1` 的完整 peer 集(依赖表由
|
|
297
|
+
peer + dependencies + devDependencies + `src/` 里真实 import 的 `@deepseek-ai/*` 共同生成,
|
|
298
|
+
逐个钉版本),把 `npm pack` 出的 tarball 装进去,再调用 DSH 自己的
|
|
299
|
+
`evaluatePluginCompatibility()` 断言**不会被 skip / disable**,并用**该版本**的 DSH 类型跑
|
|
300
|
+
一次 `tsc --noEmit`。显式关掉 `legacy-peer-deps`,并反向断言 `0.3.0-rc.1` 会被判不兼容。
|
|
301
|
+
- 补上漏声明的 `@deepseek-ai/dsh-util-values`(`src/` 里 5 处 `import type { JsonValue }`,
|
|
302
|
+
此前只能靠传递依赖拿类型)。
|
|
303
|
+
|
|
304
|
+
#### 命令方言(Shell)
|
|
305
|
+
|
|
306
|
+
- 新增 `src/host/platform/shell.ts`:**唯一**的方言适配器 —— `shellDialect` / `shellQuote` /
|
|
307
|
+
`shellInvoke` / `mkdirCommand` / `removeFileCommand` / `openExternalCommand` /
|
|
308
|
+
`clipboardCommand` / `homeProbeCommand` / `privateFileCommand` / `privateFileMechanism`。
|
|
309
|
+
NUL 明确拒绝,换行等控制字符由单引号安全表示;平台一律由调用方注入。
|
|
310
|
+
- 迁移全部调用点:`tools/case-files.ts`、`system/ops.ts`、`oss/ops.ts`、`ifind/store.ts`、
|
|
311
|
+
`platform/home.ts`、`audit/root.ts`、`environment/probe.ts`,以及 `dws`/`crwu`/`oss`/Python/
|
|
312
|
+
身份查询的执行入口。**删除** `environment/manifest.ts` 的 `quoteArg` 与 `ifind/store.ts` 的
|
|
313
|
+
本地 `shellQuote`(第二、第三个引用实现就是绕过 Windows 的后门)。
|
|
314
|
+
- 不再出现 `cmd /c …`、`Start-Process` 与建目录/删除/权限命令的手工拼接(有静态门禁)。
|
|
315
|
+
Windows 打开文件改用 `Start-Process -FilePath`;删除改用
|
|
316
|
+
`if (Test-Path -LiteralPath …) { Remove-Item … -ErrorAction Stop }` —— **不再用
|
|
317
|
+
`SilentlyContinue` 把真实失败吞成成功**。
|
|
318
|
+
- `tools/case-files.ts` 不再吞错:只把「目标已经是我们要的状态」视为成功,并用 `ctx.fs.stat`
|
|
319
|
+
**回读后置条件**(命令返回 0 不等于目录真的在 / 文件真的没了)。
|
|
320
|
+
- `oss.probeCommand` 模板降级为 deprecated 兼容路径:结构化 `executable/args` 是默认形状,
|
|
321
|
+
模板的每个占位符**逐个按平台引用**,未知占位符 / 换行 / 空可执行文件一律拒绝。
|
|
322
|
+
- `platform/home.ts` 按平台只跑一条探测:Windows 只问 PowerShell 的 `$env:USERPROFILE`,
|
|
323
|
+
不再先试 `python3` / `printf`(那两条在 Windows 上只是两次 command-not-found)。
|
|
324
|
+
|
|
325
|
+
#### 本地路径
|
|
326
|
+
|
|
327
|
+
- 新增 `src/shared/utils/local-path.ts`(不引用 Node 模块,Client 也能用):
|
|
328
|
+
`isWindowsStylePath` / `isAbsoluteLocalPath` / `trimTrailingSeparators` / `localSeparator` /
|
|
329
|
+
`joinLocalPath` / `basenameLocalPath`,另有仅供展示层前缀匹配的 `isLocalPathUnder`
|
|
330
|
+
(**明确标注不是安全边界**)。`joinLocalPath` 对绝对片段与 `..` 片段**抛错而不是静默清洗**。
|
|
331
|
+
- `shared/utils/case-dir.ts` 的 `caseDirOf` 改走它(Windows 上给出 `C:\Work\<流水号>`),
|
|
332
|
+
新增 `caseDirName`;`host/audit/case.ts` 的 `caseNameOf` 改走 `basenameLocalPath`
|
|
333
|
+
(旧实现 `split('/')` 在 Windows 上把整条路径当目录名)。
|
|
334
|
+
- `host/report/files.ts` 的拼接走 `joinLocalPath`,工作空间**包含判断从字符串前缀改为
|
|
335
|
+
`ctx.fs.contains`**;`host/workspace/resolve.ts` 的最长前缀匹配改 `isLocalPathUnder`。
|
|
336
|
+
- **本地路径与 OSS object key 分开命名**:`host/oss/ops.ts` 里 `localPath` 随平台、
|
|
337
|
+
`objectKey` 永远 `/`。
|
|
338
|
+
- **UNC 策略**:不自行判死 —— 交给底层 `fs` 解析(DSH 在 Windows 上原生支持 `\\server\share`),
|
|
339
|
+
解析不了时回稳定的「案例目录不可解析:…(原因)」,**不生成损坏路径**。
|
|
340
|
+
|
|
341
|
+
#### audit 预检与凭据权限
|
|
342
|
+
|
|
343
|
+
- 审核根预检的提示词不再写死「用 bash 执行 `pwd`」(Windows 上 DSH 挂的是 PowerShell,
|
|
344
|
+
宿主自检全绿而 Agent 预检必然失败):改为按平台生成(Windows `Get-Location`、POSIX `pwd`),
|
|
345
|
+
并把平台事实逐层传到 `probeMessage` / `preflight`。
|
|
346
|
+
- 技能正文与公共层里的 ```bash 代码块改为 ```text;`crwu-audit` 增加 §1.1「命令块按当前平台
|
|
347
|
+
shell 执行」。vendored `skills/dws/**` 不动(上游正文,由 `dws:check` 按 provenance 守)。
|
|
348
|
+
- **凭据权限结论结构化**(协议 16 → 17):`chmodOk: boolean` / `chmodError: string` 换成
|
|
349
|
+
`permission: CredentialPermission`(`status: verified|inherited|failed` + `mechanism` +
|
|
350
|
+
`message`)。Windows 上没有 POSIX 权限位、也没有 `chmod`,跳过之后旧字段只能报 `true`,
|
|
351
|
+
字段名读起来是「chmod 成功了」—— 现在如实报 `inherited / windows-acl`
|
|
352
|
+
(「使用当前 Windows 账户 ACL;POSIX 0600 不适用」)。**本轮不调用 `icacls`**。
|
|
353
|
+
|
|
354
|
+
#### 构建与发布门禁
|
|
355
|
+
|
|
356
|
+
- `scripts/prepare.mjs` 不再 `spawnSync('tsdown', { shell: process.platform === 'win32' })`:
|
|
357
|
+
新增 `scripts/lib/cli-entry.mjs`(解析包的 JS 入口,命中 `.cmd`/`.ps1` shim 时明确报错、
|
|
358
|
+
**不退回 shell**),用 `process.execPath` 直接执行。**过 shell 会让项目路径里的空格与单引号
|
|
359
|
+
被第二套规则改写** —— 这是安装链路的第一跳。`scripts/lib/cli-entry.mjs` 随包发布
|
|
360
|
+
(`files` + `pack:assert` 的 REQUIRED 都钉住了)。
|
|
361
|
+
- `tests/windows/powershell-contract.test.mjs`:把适配器生成的命令真的交给 shell 执行
|
|
362
|
+
(与 DSH 同形:整串命令作为**一个** argv 元素给 `pwsh -Command`),覆盖命令位置(可执行文件
|
|
363
|
+
路径含空格/引号/`$`/方括号/中文)、参数原样往返(含 `;` 与 `$(…)` 注入金丝雀)、
|
|
364
|
+
mkdir/remove 幂等与后置条件、**真实失败必须非零退出**。POSIX 侧本机跑,Windows 侧在 CI 跑。
|
|
365
|
+
- `scripts/smoke-windows-binaries.mjs`(`npm run bin:smoke`):在 Windows runner 上把
|
|
366
|
+
`crwu.exe` / `ossutil.exe` / `dws.exe` 真的启动一次(核对 manifest 与 SHA-256、
|
|
367
|
+
断言发布树无运行残留、对未声明平台明确失败)。
|
|
368
|
+
- CI:新增 `windows-powershell` job(**不设 job 级 `shell: bash`**,runner 默认 pwsh,
|
|
369
|
+
先断言 PowerShell 7,再跑静态门禁 + 原生合同 + build + smoke + 全量 `npm test`);
|
|
370
|
+
plugin 矩阵的 Node 22 上跑 `npm run compat:dsh`;`release.yml` 新增
|
|
371
|
+
`windows-binary-smoke`,`publish` 改为 `needs: [binaries, windows-binary-smoke]`。
|
|
372
|
+
|
|
373
|
+
#### 已知限制
|
|
374
|
+
|
|
375
|
+
- **`win32-arm64` 明确不支持**:随包二进制只有 `darwin-arm64` 与 `win32-x64`。arm64 Windows 上
|
|
376
|
+
插件如实回 capability gap,**不会**静默用 x64 顶上(有单测钉住文案与「不发任何命令」)。
|
|
377
|
+
- **UNC 未在真实共享上做过端到端验收**:策略是「交给底层 fs 解析,失败给可读错误」,
|
|
378
|
+
真实 `\\server\share` 的读写仍未在 CI 或真机覆盖。
|
|
379
|
+
- **没有 `icacls` 收紧 ACL**:Windows 上凭据文件的权限继承自当前账户 ACL,插件不擅自重写
|
|
380
|
+
(企业域策略下这不该由插件决定)。真要「收紧 ACL」需要另做设计与域环境测试。
|
|
381
|
+
- **干净 Windows 用户配置的端到端验收(装插件 → 选工作空间 → 建案例 → 跑审核 → 回传)
|
|
382
|
+
尚未执行**:本机是 macOS,可机器验证的部分已交给上面的三个 Windows job;
|
|
383
|
+
人工验收按 `docs/windows-acceptance.md` 的清单逐项记录。
|
|
384
|
+
|
|
385
|
+
#### 复查修复(同一版本的第三批,2026-09-28)
|
|
386
|
+
|
|
387
|
+
分支合并前的一次定向复查发现了三个阻塞项与三个中等问题,全部已修,并各自配了会变红的用例。
|
|
388
|
+
|
|
389
|
+
- **[P1] 发布工作流会稳定失败**:`release.yml` 里「未声明平台必须报错」那一步**故意**让
|
|
390
|
+
`node` 返回非零,但 GitHub 的 pwsh 壳在脚本末尾用 `$LASTEXITCODE` 作为步骤退出码,
|
|
391
|
+
`Write-Host` 不会把它重置成 0 —— 于是这一步真的判红,依赖它的 `publish` 永远起不来。
|
|
392
|
+
现在写成「存退出码 → 断言 → 末尾显式 `exit 0`」;`windows-powershell` job 里那条
|
|
393
|
+
`npm test` 也改成同样的形状。新增 `tests/unit/host-ci-workflows.test.mjs`:用 `yaml` 解析两个
|
|
394
|
+
工作流,**任何引用 `$LASTEXITCODE` 的 `run` 块都必须以显式 `exit` 收尾**,并钉住
|
|
395
|
+
「原生 pwsh job 不得设 job 级 shell / 必须先断言 PowerShell 7 / publish 必须 needs Windows 冒烟」。
|
|
396
|
+
- **[P1] 裸盘符被当成绝对路径**:`isAbsoluteLocalPath` 复用了「Windows 风格」的判断,
|
|
397
|
+
而风格判断为了兼容 `C:` 这种写法接受了裸盘符 —— 于是 `requireCaseDir('C:')` 会放行一条
|
|
398
|
+
「盘符相对路径」,案例目录随进程 cwd 漂移。现在风格(`DRIVE_STYLE`)与绝对(`DRIVE_ROOTED`)
|
|
399
|
+
是两条正则,并新增 `tests/unit/host-case-dir-gate.test.mjs` 覆盖门禁全部分支
|
|
400
|
+
(裸盘符 / 相对路径 / `..` / UNC 策略 / `fs.contains` 而非字符串前缀)。
|
|
401
|
+
- **[P1] 本地路径迁移不完整**:`bootstrap.ts`(输入快照目录与三个快照文件)、`knowledge.ts`
|
|
402
|
+
(knowledge 目录 / 下载目标 / manifest)、`dingtalk.ts`(通知幂等状态文件)、
|
|
403
|
+
`audit/state.ts`(案例目录候选)、`WorkbenchPanel.tsx`(打开本地 HTML)、
|
|
404
|
+
`workspace-view.ts`(界面上的落盘位置说明)仍在用 `/` 拼 Windows 本地路径 ——
|
|
405
|
+
其中快照、知识库与本地报告打开都属于核心审核链路。全部改走 `joinLocalPath`/
|
|
406
|
+
`localSeparator`;新增 Windows 行为测试(bootstrap 的三条快照路径与真实落盘目标、
|
|
407
|
+
knowledge 目录、`assessAudit` 的案例目录候选、`artifactHint`),
|
|
408
|
+
并在 `host-platform-shell.test.mjs` 加一条**本地路径载体名单**的静态门禁
|
|
409
|
+
(名单短、每条写得出理由;对象键与 URL 不在名单里)。
|
|
410
|
+
- **[P2] `fs.stat` 异常被当成「文件不存在」**:`case-files.ts` 把 `resolve/stat` 的所有异常
|
|
411
|
+
都折叠成「没什么可删的」,删除后的回读也一样 —— 于是 `Access denied` 会被回报成删除成功。
|
|
412
|
+
现在探测是**三态**(存在 / 不存在 / 查不出来),「查不出来」一律按基础设施失败上报;
|
|
413
|
+
`ensureDirectory` 在确认不了目标状态时也不许报成功。
|
|
414
|
+
- **[P2] `verified` 没有真正回读**:协议与注释都把 `verified` 定义为「执行并回读确认」,
|
|
415
|
+
而实现只看了 `chmod` 的退出码 —— 有些文件系统会静默忽略 chmod。现在收紧之后**必须回读模式位**:
|
|
416
|
+
新增 `readFileModeCommand()`(GNU `stat -c %a` / BSD `stat -f %Lp` / Windows 不适用,
|
|
417
|
+
分歧只在适配器里出现一次)与 `parseFileMode()`;回读失败、模式对不上都算 `failed` 并带原因。
|
|
418
|
+
- **[P2] Windows 的 OSS 输入错误仍报 POSIX 机制**:`ossCredSave` 的输入校验失败信封硬编码了
|
|
419
|
+
`mechanism: 'posix-0600'`,在 `win32-x64` 上说错了「谁在负责权限」。改为复用
|
|
420
|
+
`failedPermission(error, deps.platform)`。
|
|
421
|
+
- 文档:`docs/windows-acceptance.md` 的收尾步骤在 PowerShell 里错用了 `rm -rf`,
|
|
422
|
+
改为 `Remove-Item -LiteralPath … -Recurse -Force`。
|
|
423
|
+
|
|
424
|
+
#### 工作区外路径与沙箱归因(同一版本的第四批,2026-09-28 员工 Windows 实测)
|
|
425
|
+
|
|
426
|
+
员工在 Windows 上报了三段看起来无关的错误,实际是同一个原因:**受限沙箱(`workspace-write`)
|
|
427
|
+
不允许碰工作区之外的路径**(`%USERPROFILE%` 下的 `.ossutilconfig`、`.dws\`、操作系统凭据存储)。
|
|
428
|
+
当时插件有两处该提权却没提权,还有一处把沙箱下的假结论当成了真结论。
|
|
429
|
+
|
|
430
|
+
- **修①|写 `~/.ossutilconfig` 被沙箱拒绝**:`oss/ops.ts` 的 `fs.writeText` 没声明
|
|
431
|
+
`sandboxPolicy`(同仓的 `ifind/store.ts`、`state/persist.ts` 都声明了),员工实测原文
|
|
432
|
+
`cannot write "C:\Users\<用户>\.ossutilconfig": file access denied under workspace-write mode`。
|
|
433
|
+
现在与那两处同一形态。
|
|
434
|
+
- **修②|`crwu h3yun session status` 拿不到真结论**:提权白名单只放行了 `session login`,
|
|
435
|
+
而 `status` / `bind` 才是**读**钥匙串的那两条。受限沙箱下读不到,crwu 如实回
|
|
436
|
+
`secret not found in keyring` —— 面板照着显示成「未登录」,把人指去重新扫码。
|
|
437
|
+
现在 `h3yun session` 整段放行(凭据存储的读与写都要在沙箱外)。
|
|
438
|
+
- **修③|未授权时不再去问氚云**:环境探测原来无条件跑一次 `h3yun session status`,
|
|
439
|
+
既是假结论、又永远拿不到真值。现在与钉钉那条同一条纪律:未授权就报「需要授权」并进阻塞项,
|
|
440
|
+
**一条凭据命令都不发**;授权后才带 `sandboxPolicy` 去拿真结论。
|
|
441
|
+
- **新增归因**:`shell/run.ts` 的 `sandboxDenialNote()` 统一认三种表象 ——
|
|
442
|
+
DSH 的标记 `file access denied under <mode> mode`(确定)、
|
|
443
|
+
以及 `Access is denied` + 工作区外路径线索(**疑似**,措辞不把猜测说成结论)。
|
|
444
|
+
`runDws` / `runCrwu` 会把这句话补在错误最前面,于是 `dws` 的
|
|
445
|
+
`acquiring file lock: … .dws\.data.lock: Access is denied.` 不再看起来像 dws 自己的 bug。
|
|
446
|
+
测试样本**逐字抄自员工贴回来的原文**。
|
|
447
|
+
- **收下 DSH 的沙箱事实**:`ShellRunResult.sandbox = { mode, denied, runnerFailed }` 会说明命令
|
|
448
|
+
**实际**跑在哪个模式、沙箱是否真的拒绝过;`resolve()` 回来的 `spec.sandboxPolicy` 还能看出
|
|
449
|
+
提权请求有没有被降级。`ShellResult` 现在把这四项(请求 / 解析 / 实际 / 是否被拒)一起带出来,
|
|
450
|
+
`sandboxDenialNote()` **以事实为准、事实在手就不再猜文本** ——
|
|
451
|
+
于是「沙箱拒了」与「文件真被占用/ACL 异常」不会再被混为一谈(后者按「去授权」处理是修不好的)。
|
|
452
|
+
- 文档:`README.md` 的 Windows 故障排查与 `docs/development-notes.md` §5.1/§5.2 补上
|
|
453
|
+
「工作区外的路径清单」「三种表象→同一个原因」「要全局关沙箱只能改 DSH 侧
|
|
454
|
+
(`DSH_PERMISSION_MODE=danger-full-access` 或 profile 的 `dsh-sandbox-policy.mode`)」。
|
|
455
|
+
|
|
38
456
|
## package · 0.0.12 · 2026-09-28
|
|
39
457
|
|
|
40
458
|
**首个包含自更新能力的正式版本。** 0.0.10 / 0.0.11 用户需要**手动完成一次**引导升级
|
package/README.en.md
CHANGED
|
@@ -71,9 +71,11 @@ Building the tarball yourself (from the `crwu-ai` repository): `make plugin-pack
|
|
|
71
71
|
**② Bundled components** only ever reports "package incomplete / platform unsupported" if one is missing.
|
|
72
72
|
Section **③ DSH script runtime** reports the **DSH-bundled** Python (with its `openpyxl` and other
|
|
73
73
|
package versions); the system `python3` is not a dependency and is never used as a fallback.
|
|
74
|
-
3. To dispatch an audit,
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
3. To dispatch an audit, just click **AI audit** on the report-audit page. The plugin creates its own
|
|
75
|
+
**audit subagent root session** inside the selected workspace and parents the audit child to *that*
|
|
76
|
+
root — not to whichever chat session you happen to be looking at. The session-header
|
|
77
|
+
"register as sub-session parent" button now only passes the **preset of the session you are using**
|
|
78
|
+
to the audit root (so the audit toolchain matches your manual runs); it is **not** a prerequisite.
|
|
77
79
|
|
|
78
80
|
> **The Skills ship with the package, organized in layers**: the tarball carries `skills/crwu/`
|
|
79
81
|
> (27 in-repo Skills), `skills/dws/` (14 vendored `dingtalk-workspace-cli` Skills) and `common/skills/`
|
|
@@ -85,12 +87,21 @@ Building the tarball yourself (from the `crwu-ai` repository): `make plugin-pack
|
|
|
85
87
|
> layer is upgraded.
|
|
86
88
|
|
|
87
89
|
> **Permissions**: no startup parameters are needed (`DSH_PERMISSION_MODE` stays untouched), but the
|
|
88
|
-
> **first run requires one authorization** — the "
|
|
89
|
-
> the
|
|
90
|
-
>
|
|
91
|
-
>
|
|
92
|
-
>
|
|
93
|
-
> the
|
|
90
|
+
> **first run requires one authorization** — the "allow the workbench to reach local accounts and
|
|
91
|
+
> configuration" switch in the account-connection step. It is written to the workbench state file as a
|
|
92
|
+
> **versioned receipt** (`localAccess`: `schemaVersion` + timestamp + five fixed capabilities).
|
|
93
|
+
> Protocol 18 treats the old `trustCredentials: true` as `outdated` — **it is not a grant**. Upgrading
|
|
94
|
+
> from 0.0.14 therefore needs a **full quit and relaunch** (protocol 17 → 18 is a semantic break);
|
|
95
|
+
> refreshing the page only leaves the old host running. Without a receipt the plugin cannot read the
|
|
96
|
+
> H3Yun session or the DingTalk login state, the self-check blocks the gate, and **no credential
|
|
97
|
+
> process is started at all** — so it can never report a false "not logged in". Once authorized, each
|
|
98
|
+
> **named operation** decides for itself whether it needs per-call `danger-full-access`
|
|
99
|
+
> (`src/host/access/operations.ts`); callers cannot submit an escalation switch and neither can the
|
|
100
|
+
> model. Only operations that genuinely touch paths outside the workspace escalate (H3Yun session,
|
|
101
|
+
> DingTalk `~/.dws`, `~/.ossutilconfig`, the iFinD credential file, plugin state); package integrity
|
|
102
|
+
> checks, case-directory writes and opening the browser stay in the profile's default sandbox. Audit
|
|
103
|
+
> root and child sessions are always `workspace-write` with `approval=never`, and the account-connection
|
|
104
|
+
> step also offers a read-only `.dws` checkup with a second-confirmed permission repair.
|
|
94
105
|
|
|
95
106
|
### Updating CRWU (self-update, from 0.0.12)
|
|
96
107
|
|
|
@@ -206,13 +217,42 @@ it builds when the sources are present and skips with an explanation when they a
|
|
|
206
217
|
`tests/unit/host-package.test.mjs` pins this with a real `npm pack` + `npm install` + import regression —
|
|
207
218
|
both failure modes were invisible to local gates and only surfaced for the person installing the package.
|
|
208
219
|
|
|
209
|
-
`peerDependencies`
|
|
210
|
-
|
|
211
|
-
|
|
220
|
+
`peerDependencies` cover **two DSH lines**: `@deepseek-ai/dsh-*@^0.1.7-rc.2 || ^0.2.0-rc.1` (cordis `^4.0.4`).
|
|
221
|
+
One `^` range per line, joined with `||`, rather than a single `>=… <…` — the latter would also admit the
|
|
222
|
+
unverified 0.3 line. The DSH plugin API is a developer preview: **before adding a line**, diff the
|
|
223
|
+
`@deepseek-ai/dsh-*` packages of both lines file by file, re-run the gate, and record the supported DSH
|
|
224
|
+
version in `CHANGELOG.md`.
|
|
212
225
|
|
|
213
226
|
> **Since 0.1.7 this is a hard gate.** DSH compares the runtime version against every `@deepseek-ai/dsh*` peer
|
|
214
227
|
> range and **skips the whole bundle** on any mismatch (recorded in `skippedBundles`) — the symptom is "the plugin
|
|
215
|
-
> is installed but nothing appears", with a clean startup log
|
|
228
|
+
> is installed but nothing appears", with a clean startup log; the plugin manager also marks the row
|
|
229
|
+
> "incompatible with DSH \<version\>" and disables it. The check reads `peerDependencies`, not `engines.dsh`.
|
|
230
|
+
> Note that `^0.1.7-rc.2` means `>=0.1.7-rc.2 <0.2.0-0` and therefore **excludes** `0.2.0-rc.1`.
|
|
231
|
+
|
|
232
|
+
### Supported platforms
|
|
233
|
+
|
|
234
|
+
| Dimension | Supported | Notes |
|
|
235
|
+
| --- | --- | --- |
|
|
236
|
+
| Windows | **10 / 11 x64** | DSH passes the whole command as **one argv element** to `pwsh -NoLogo -NoProfile -NonInteractive -Command <string>`. The plugin therefore builds PowerShell (call operator `&`, single-quoted literals) and never assumes `cmd.exe` or Git Bash. PowerShell 7 (`pwsh`) required. |
|
|
237
|
+
| macOS / Linux | darwin-arm64 / linux-x64 | POSIX dialect; only `darwin-arm64` ships bundled binaries. |
|
|
238
|
+
| CPU | `win32-x64` / `darwin-arm64` | **`win32-arm64` is explicitly unsupported**: the plugin reports a capability gap and never silently falls back to x64. |
|
|
239
|
+
| Node.js | `^22.19.0 \|\| >=24` | CI covers 22 and 24 on both Linux and Windows. |
|
|
240
|
+
| DSH | `0.1.7-rc.2` line, `0.2.0-rc.1` line | `npm run compat:dsh` installs each line's full peer set into a clean project and calls DSH's own compatibility check; `0.3.x` is out of scope. |
|
|
241
|
+
| Local paths | drive-letter absolute paths, spaces, CJK, single quotes | e.g. `C:\Users\Jane\Case's Work`. |
|
|
242
|
+
| UNC | `\\server\share\…` | **Delegated to the underlying DSH `fs`**; when it cannot resolve, the plugin returns a readable "cannot resolve case directory: … (reason)" instead of building a broken path. Not yet accepted end-to-end on a real share. |
|
|
243
|
+
|
|
244
|
+
### Windows troubleshooting
|
|
245
|
+
|
|
246
|
+
| Symptom | Check first | Fix |
|
|
247
|
+
| --- | --- | --- |
|
|
248
|
+
| Plugin installed, nothing appears, clean startup log | Whether DSH rejected it as **incompatible** (`skippedBundles`, or the plugin manager marking it "incompatible with DSH `<version>`") | The check reads `peerDependencies` (**not** `engines.dsh`); this package declares `^0.1.7-rc.2 \|\| ^0.2.0-rc.1`. Upgrade the plugin after a DSH upgrade; `npm run compat:dsh` reproduces the verdict |
|
|
249
|
+
| `ParserError` / `UnexpectedToken` / "unexpected token" | A command starting with a quoted path (in PowerShell that is a string expression, not a command invocation) | Upgrade to 0.0.14+: every command is generated by `src/host/platform/shell.ts` (with `&` on Windows). If it persists, a call site bypassed the adapter — the static gate in `host-platform-shell.test.mjs` goes red first |
|
|
250
|
+
| "parameter name is ambiguous" / `mkdir` is not recognized | POSIX-only syntax leaking in (`mkdir -p`, `chmod`, `rm -f`, `cmd /c …`) | Same as above: those literals may only appear inside the adapter |
|
|
251
|
+
| A command "succeeded" but the file was not touched | A swallowed failure (`-ErrorAction SilentlyContinue`) | Removed in 0.0.14+; `case-files.ts` also re-reads post-conditions with `ctx.fs.stat` |
|
|
252
|
+
| Truncated paths, a directory name that is a whole path | Local paths handled with POSIX string rules | 0.0.14+ routes them through `shared/utils/local-path.ts`; containment uses `fs.contains`, never string prefixes |
|
|
253
|
+
| Bundled binary fails with "not a valid Win32 application" / missing DLL | Release artifact architecture (`win32-x64` vs `win32-arm64`) | `win32-arm64` is unsupported by design; `npm run bin:smoke` actually launches the release binaries on a Windows runner |
|
|
254
|
+
| Credential file never shows mode `600` | Windows has no POSIX permission bits and no `chmod` | The UI says "using the current Windows account ACL; POSIX 0600 does not apply" (`permission.status = inherited`) — neither a failure nor "verified" |
|
|
255
|
+
| `prepare` cannot find `tsdown`, or a path gets mangled | Whether the build command goes through a shell | 0.0.14+ runs tsdown's JS entry with `process.execPath` (`scripts/lib/cli-entry.mjs`) and **never through a shell**, so spaces and single quotes survive |
|
|
216
256
|
|
|
217
257
|
## Development
|
|
218
258
|
|