dsh-crwu-workbench 0.0.13 → 0.0.14
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 +166 -0
- package/README.en.md +33 -4
- package/README.md +34 -5
- 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 +193 -18
- package/lib/index.js +1040 -583
- package/package.json +17 -9
- package/scripts/lib/cli-entry.mjs +112 -0
- package/scripts/prepare.mjs +56 -16
- 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/lib/client.js
CHANGED
|
@@ -2962,6 +2962,9 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
2962
2962
|
envOssErrPermission: "AccessKey 可以登录,但这个账号没有交付目录的访问权限,请联系管理员开通。",
|
|
2963
2963
|
envOssErrConfig: "交付目录的配置有问题(Bucket 或 Endpoint),请联系管理员核对。",
|
|
2964
2964
|
envOssErrInfrastructure: "暂时无法连接 OSS,请稍后重新验证;这不代表 AccessKey 有误。",
|
|
2965
|
+
envCredInheritedWindowsAcl: "使用当前 Windows 账户 ACL;POSIX 0600 不适用。",
|
|
2966
|
+
envCredInherited: "凭据权限由现有机制负责,没有执行额外收紧。",
|
|
2967
|
+
envCredHardeningFailed: "凭据已保存,但权限没有收紧成功,请检查文件权限。",
|
|
2965
2968
|
envIfindCardTitle: "同花顺 iFinD API-Key",
|
|
2966
2969
|
envIfindCardHint: "用于查询外部金融数据。API-Key 由插件保存在本机,页面不回显;每次检查都会真的读取一条测试数据。",
|
|
2967
2970
|
envIfindSecretLabel: "同花顺 iFinD API-Key",
|
|
@@ -3903,6 +3906,128 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
3903
3906
|
})] });
|
|
3904
3907
|
}
|
|
3905
3908
|
//#endregion
|
|
3909
|
+
//#region src/shared/utils/value.ts
|
|
3910
|
+
/** 将可空外部值转换为稳定字符串。 */
|
|
3911
|
+
function text(value) {
|
|
3912
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
3913
|
+
}
|
|
3914
|
+
//#endregion
|
|
3915
|
+
//#region src/shared/utils/local-path.ts
|
|
3916
|
+
/**
|
|
3917
|
+
* **本地展示路径**的纯函数适配器(不引用 `node:path`)。
|
|
3918
|
+
*
|
|
3919
|
+
* ## 为什么需要它
|
|
3920
|
+
*
|
|
3921
|
+
* Host 与 Client 都要算「案例目录」「文件名」这类**展示路径**,而共享层不能引入 Node 模块
|
|
3922
|
+
* (Client 产物是要进浏览器的)。于是历史实现各处都用 `/` 硬拼,在 Windows 上产生三类错误:
|
|
3923
|
+
*
|
|
3924
|
+
* 1. **拼接**:`C:\Work` + `/` + `S1` 得到 `C:\Work/S1`,进 shell(PowerShell 的 `-Path`)
|
|
3925
|
+
* 与进提示词(模型照着写命令)都是混用分隔符;
|
|
3926
|
+
* 2. **取末段**:`'C:\\Work\\S1'.split('/')` 得到整串,界面上的案例名变成一整条路径;
|
|
3927
|
+
* 3. **裁剪**:`replace(/\/+$/, '')` 对 `C:\Work\` 无效;对 `\\server\share\` 稍有不慎就裁成
|
|
3928
|
+
* 无法解析的 `\\server`。
|
|
3929
|
+
*
|
|
3930
|
+
* ## 边界(改这个文件前先读)
|
|
3931
|
+
*
|
|
3932
|
+
* - 这里处理的是**本地路径**:盘符、UNC、POSIX 根。**OSS object key 永远用 `/`**,
|
|
3933
|
+
* 不属于这里(见 `host/oss/ops.ts` 里 `objectKey` 的命名)。
|
|
3934
|
+
* - **安全性判断不要用这里的函数**:`..`、符号链接、Windows 盘符大小写都会让字符串比较失效,
|
|
3935
|
+
* 父目录/子目录判定一律走 `ctx.fs.contains`(后端 realpath 之后比较)。
|
|
3936
|
+
* 这里只负责「拼一条给人看、给命令用的路径」。
|
|
3937
|
+
* - `joinLocalPath` 对**不受信任的片段**是拒绝而不是清洗:绝对路径、`..` 片段都抛错。
|
|
3938
|
+
* 静默清洗会让调用方以为拿到了一条安全路径(最危险的形态)。
|
|
3939
|
+
*/
|
|
3940
|
+
/**
|
|
3941
|
+
* 盘符**风格**:`C:\` / `C:/`,也接受裸 `C:`。
|
|
3942
|
+
*
|
|
3943
|
+
* `C:` 是「盘符相对路径」(这个盘上的当前目录),它**不是**绝对路径 —— 风格与绝对必须分开判,
|
|
3944
|
+
* 见 `isAbsoluteLocalPath`。
|
|
3945
|
+
*/
|
|
3946
|
+
const DRIVE_STYLE = /^[A-Za-z]:([\\/]|$)/;
|
|
3947
|
+
/** 盘符**绝对**:盘符后必须有分隔符(`C:\` / `C:/`)。裸 `C:` 不算。 */
|
|
3948
|
+
const DRIVE_ROOTED = /^[A-Za-z]:[\\/]/;
|
|
3949
|
+
/** UNC / 双分隔符开头:`\\server\share` 或 `//server/share`。 */
|
|
3950
|
+
const UNC = /^[\\/]{2}[^\\/]/;
|
|
3951
|
+
/** 这个字符串看起来是 Windows 风格路径(盘符或 UNC)。**不表示它是绝对路径。** */
|
|
3952
|
+
function isWindowsStylePath(value) {
|
|
3953
|
+
const raw = text(value);
|
|
3954
|
+
return DRIVE_STYLE.test(raw) || UNC.test(raw);
|
|
3955
|
+
}
|
|
3956
|
+
/**
|
|
3957
|
+
* 是否是绝对本地路径(POSIX `/…`、Windows 盘符根、UNC)。
|
|
3958
|
+
*
|
|
3959
|
+
* **裸 `C:` 不是绝对路径**(2026-09-28 复查修):早先这里复用了「Windows 风格」的判断,
|
|
3960
|
+
* 于是 `requireCaseDir('C:')` 会把盘符相对路径当绝对路径放行 —— 案例目录随后随进程 cwd 漂移,
|
|
3961
|
+
* 产物落点不可预测。`C:` 与 `C:\` 是完全不同的位置,两条正则不能共用。
|
|
3962
|
+
*/
|
|
3963
|
+
function isAbsoluteLocalPath(value) {
|
|
3964
|
+
const raw = text(value);
|
|
3965
|
+
return raw.startsWith("/") || DRIVE_ROOTED.test(raw) || UNC.test(raw);
|
|
3966
|
+
}
|
|
3967
|
+
/**
|
|
3968
|
+
* 去掉尾部路径分隔符,但**保留根**:
|
|
3969
|
+
*
|
|
3970
|
+
* - `'/work/'` → `'/work'`;`'/'` → `'/'`;
|
|
3971
|
+
* - `'C:\\Work\\'` → `'C:\\Work'`;`'C:\\'` → `'C:\\'`(裁成 `'C:'` 会变成「盘符相对路径」);
|
|
3972
|
+
* - `'\\\\server\\share\\'` → `'\\\\server\\share'`;
|
|
3973
|
+
* `'\\\\server\\'` → `'\\\\server\\'`(裁掉尾部分隔符就只剩服务器名,不是一个可解析的路径)。
|
|
3974
|
+
*/
|
|
3975
|
+
function trimTrailingSeparators(value) {
|
|
3976
|
+
const raw = text(value);
|
|
3977
|
+
if (raw === "") return "";
|
|
3978
|
+
if (/^\/+$/.test(raw)) return "/";
|
|
3979
|
+
if (/^[A-Za-z]:[\\/]$/.test(raw)) return raw;
|
|
3980
|
+
if (/^[\\/]{2}[^\\/]+[\\/]$/.test(raw)) return raw;
|
|
3981
|
+
let end = raw.length;
|
|
3982
|
+
while (end > 0 && (raw[end - 1] === "/" || raw[end - 1] === "\\")) end -= 1;
|
|
3983
|
+
return end === 0 ? raw : raw.slice(0, end);
|
|
3984
|
+
}
|
|
3985
|
+
/** 该路径风格用哪个分隔符;非 Windows 风格一律 `/`。 */
|
|
3986
|
+
function localSeparator(value) {
|
|
3987
|
+
return isWindowsStylePath(value) ? "\\" : "/";
|
|
3988
|
+
}
|
|
3989
|
+
/** 一个片段是不是 `..`(只看整段,不看 `a..b` 这种文件名)。 */
|
|
3990
|
+
function isParentSegment(segment) {
|
|
3991
|
+
return segment.split(/[\\/]+/).some((part) => part === "..");
|
|
3992
|
+
}
|
|
3993
|
+
/**
|
|
3994
|
+
* 拼接一条本地路径:分隔符随 `root` 的风格。
|
|
3995
|
+
*
|
|
3996
|
+
* - `joinLocalPath('C:\\Work', 'a', 'b')` → `'C:\\Work\\a\\b'`
|
|
3997
|
+
* - `joinLocalPath('/work', 'a', 'b')` → `'/work/a/b'`
|
|
3998
|
+
* - `joinLocalPath('/work/', 'a')` → `'/work/a'`(根上的尾分隔符不产生双斜杠)
|
|
3999
|
+
*
|
|
4000
|
+
* **拒绝**(抛 `RangeError`)而不是清洗:
|
|
4001
|
+
* - 片段是绝对路径(`/x`、`C:\x`、`\\server\x`)—— 那会静默替换掉整条路径;
|
|
4002
|
+
* - 片段含 `..` 整段 —— 那会静默越出调用方以为的目录。
|
|
4003
|
+
*
|
|
4004
|
+
* 空片段(`''`)跳过;片段内部的另一种分隔符会被规范成目标分隔符。
|
|
4005
|
+
*
|
|
4006
|
+
* 裸盘符根(`'C:'`)会被规范成 `'C:\\'` —— 「往 `C:` 后面接一段」在 Windows 上的实际含义
|
|
4007
|
+
* 就是该盘的根,而不是「这个盘上的当前目录」;要判「是不是绝对路径」用 `isAbsoluteLocalPath`,
|
|
4008
|
+
* 那一条对裸 `C:` 明确返回 `false`。
|
|
4009
|
+
*/
|
|
4010
|
+
function joinLocalPath(root, ...segments) {
|
|
4011
|
+
const base = trimTrailingSeparators(root);
|
|
4012
|
+
const sep = localSeparator(base);
|
|
4013
|
+
const parts = [];
|
|
4014
|
+
if (base !== "") parts.push(base);
|
|
4015
|
+
for (const segment of segments) {
|
|
4016
|
+
const raw = text(segment);
|
|
4017
|
+
if (raw === "") continue;
|
|
4018
|
+
if (isAbsoluteLocalPath(raw)) throw new RangeError(`joinLocalPath 拒绝绝对路径片段:${raw}`);
|
|
4019
|
+
if (isParentSegment(raw)) throw new RangeError(`joinLocalPath 拒绝含 .. 的片段:${raw}`);
|
|
4020
|
+
const cleaned = raw.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "");
|
|
4021
|
+
if (cleaned === "") continue;
|
|
4022
|
+
parts.push(sep === "\\" ? cleaned.replace(/\//g, "\\") : cleaned.replace(/\\/g, "/"));
|
|
4023
|
+
}
|
|
4024
|
+
if (parts.length === 0) return "";
|
|
4025
|
+
if (parts.length === 1) return parts[0];
|
|
4026
|
+
const [head, ...tail] = parts;
|
|
4027
|
+
const joint = head !== void 0 && /[\\/]$/.test(head) ? "" : sep;
|
|
4028
|
+
return `${head ?? ""}${joint}${tail.join(sep)}`;
|
|
4029
|
+
}
|
|
4030
|
+
//#endregion
|
|
3906
4031
|
//#region src/client/features/workbench/open-session.ts
|
|
3907
4032
|
/** 可尝试的 mode 顺序:记录里的优先,然后是两个已知取值。 */
|
|
3908
4033
|
function modeCandidates(recorded) {
|
|
@@ -4287,9 +4412,12 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
4287
4412
|
canAuto: missing || (workspace?.source ?? "") === "manual"
|
|
4288
4413
|
};
|
|
4289
4414
|
}
|
|
4415
|
+
/** 提示文案里的占位段:员工看到的是「<工作空间>\<报告流水号>\」。 */
|
|
4416
|
+
const ARTIFACT_HINT_PLACEHOLDER = "<报告流水号>";
|
|
4290
4417
|
/** 审核产物的落盘位置说明(用户最常问的问题:东西写到哪了)。 */
|
|
4291
4418
|
function artifactHint(path) {
|
|
4292
|
-
|
|
4419
|
+
const sep = localSeparator(path);
|
|
4420
|
+
return `${zhCN.wsArtifactPrefix}${joinLocalPath(path, ARTIFACT_HINT_PLACEHOLDER)}${sep}。${zhCN.wsCwdNote}`;
|
|
4293
4421
|
}
|
|
4294
4422
|
//#endregion
|
|
4295
4423
|
//#region src/client/features/workbench/WorkspaceCard.tsx
|
|
@@ -4491,6 +4619,49 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
4491
4619
|
});
|
|
4492
4620
|
}
|
|
4493
4621
|
//#endregion
|
|
4622
|
+
//#region src/client/features/environment/credential-permission.ts
|
|
4623
|
+
/**
|
|
4624
|
+
* 把 Host 的结构化权限结论翻成**一句给员工看的话**(协议 17)。
|
|
4625
|
+
*
|
|
4626
|
+
* 三种结局各自对应不同的动作,混在一起说就会误导:
|
|
4627
|
+
*
|
|
4628
|
+
* - `verified`:真的收紧了(POSIX 0600 已生效)→ 不需要说什么;
|
|
4629
|
+
* - `inherited`:**没有**执行收紧命令(Windows 上 POSIX 0600 不适用,由当前账户 ACL 负责)
|
|
4630
|
+
* → 必须说清楚,否则员工会以为这份文件被 0600 保护着;
|
|
4631
|
+
* - `failed`:试图收紧但失败(chmod 报错 / 被沙箱拒绝)→ 这是安全边界没守住,逐字回原因。
|
|
4632
|
+
*
|
|
4633
|
+
* 界面**不得**把 `inherited` 显示成成功,也不得把「不适用」显示成失败。
|
|
4634
|
+
*
|
|
4635
|
+
* `onlyFailures: true` 用在「本来就有更重要的提示」的分支(例如 OSS 连通性失败):
|
|
4636
|
+
* 那种时候再叠一句 ACL 说明只会把真正要处理的问题挤掉。
|
|
4637
|
+
*/
|
|
4638
|
+
function credentialPermissionHint(raw, options = {}) {
|
|
4639
|
+
const permission = asPermission(raw);
|
|
4640
|
+
if (permission === null) return "";
|
|
4641
|
+
if (permission.status === "failed") return permission.message !== "" ? permission.message : zhCN.envCredHardeningFailed;
|
|
4642
|
+
if (options.onlyFailures === true) return "";
|
|
4643
|
+
if (permission.status === "inherited") return permission.mechanism === "windows-acl" ? zhCN.envCredInheritedWindowsAcl : permission.message !== "" ? permission.message : zhCN.envCredInherited;
|
|
4644
|
+
return "";
|
|
4645
|
+
}
|
|
4646
|
+
/** 边界收窄:Host 应答是外部输入,字段缺失或形状不对时按「没有结论」处理。 */
|
|
4647
|
+
function asPermission(raw) {
|
|
4648
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
4649
|
+
const record = raw;
|
|
4650
|
+
const status = record.status;
|
|
4651
|
+
if (status !== "verified" && status !== "inherited" && status !== "failed") return null;
|
|
4652
|
+
const mechanism = record.mechanism;
|
|
4653
|
+
if (mechanism !== "posix-0600" && mechanism !== "windows-acl" && mechanism !== "host-store") return {
|
|
4654
|
+
status,
|
|
4655
|
+
mechanism: "posix-0600",
|
|
4656
|
+
message: typeof record.message === "string" ? record.message : ""
|
|
4657
|
+
};
|
|
4658
|
+
return {
|
|
4659
|
+
status,
|
|
4660
|
+
mechanism,
|
|
4661
|
+
message: typeof record.message === "string" ? record.message : ""
|
|
4662
|
+
};
|
|
4663
|
+
}
|
|
4664
|
+
//#endregion
|
|
4494
4665
|
//#region src/client/features/environment/IfindAuthCard.tsx
|
|
4495
4666
|
/** Host 回的状态 → 中文标签。**认不出的状态按未配置处理**(不假装已认证)。 */
|
|
4496
4667
|
function ifindStateLabel(state) {
|
|
@@ -4598,8 +4769,9 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
4598
4769
|
return;
|
|
4599
4770
|
}
|
|
4600
4771
|
setReplacing(false);
|
|
4601
|
-
if (result.chmodOk === false) setHint(result.chmodError);
|
|
4602
4772
|
showProbe(result.probe);
|
|
4773
|
+
const permissionHint = credentialPermissionHint(result.permission);
|
|
4774
|
+
if (permissionHint !== "") setHint(permissionHint);
|
|
4603
4775
|
}).catch((cause) => {
|
|
4604
4776
|
if (mounted.current) {
|
|
4605
4777
|
setError(cause instanceof Error ? cause.message : String(cause));
|
|
@@ -4843,11 +5015,13 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
4843
5015
|
if (result.ok !== true) {
|
|
4844
5016
|
setError(String(result.error ?? outcome?.detail ?? zhCN.envOssNotReady));
|
|
4845
5017
|
setHint(ossErrorHint(String(outcome?.errorKind ?? result.errorKind ?? "")));
|
|
4846
|
-
|
|
5018
|
+
const failed = credentialPermissionHint(result.permission, { onlyFailures: true });
|
|
5019
|
+
if (failed !== "") setHint(failed);
|
|
4847
5020
|
return;
|
|
4848
5021
|
}
|
|
4849
5022
|
setReplacing(false);
|
|
4850
|
-
|
|
5023
|
+
const permissionHint = credentialPermissionHint(result.permission);
|
|
5024
|
+
if (permissionHint !== "") setHint(permissionHint);
|
|
4851
5025
|
if (outcome?.ok === true) {
|
|
4852
5026
|
setMessage(zhCN.envOssSaved);
|
|
4853
5027
|
props.onSaved();
|
|
@@ -7586,12 +7760,6 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
7586
7760
|
});
|
|
7587
7761
|
}
|
|
7588
7762
|
//#endregion
|
|
7589
|
-
//#region src/shared/utils/value.ts
|
|
7590
|
-
/** 将可空外部值转换为稳定字符串。 */
|
|
7591
|
-
function text(value) {
|
|
7592
|
-
return value === void 0 || value === null ? "" : String(value);
|
|
7593
|
-
}
|
|
7594
|
-
//#endregion
|
|
7595
7763
|
//#region src/client/features/report-audit/row.ts
|
|
7596
7764
|
/**
|
|
7597
7765
|
* 报告页的判定规则。
|
|
@@ -7844,7 +8012,7 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
7844
8012
|
//#endregion
|
|
7845
8013
|
//#region src/shared/utils/case-dir.ts
|
|
7846
8014
|
/**
|
|
7847
|
-
*
|
|
8015
|
+
* 一条报告的**案例目录**:`<工作空间>/<流水号>`(Windows 上是 `<工作空间>\<流水号>`)。
|
|
7848
8016
|
*
|
|
7849
8017
|
* 这是 Host 与 Client 都依赖的唯一约定,所以只在这里定义一次:
|
|
7850
8018
|
* - Host 的审核提示词拿它告诉审核子代理「本案例目录必须是 …」(`host/audit/prompt.ts`);
|
|
@@ -7855,15 +8023,22 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
7855
8023
|
* 猜成了 `<工作空间>/cases/<流水号>`:既扫了用户的磁盘,又把材料下到了错的地方。
|
|
7856
8024
|
* 两处各写一份拼接逻辑就是这种漂移的温床,所以宁可多一个共享模块。
|
|
7857
8025
|
*
|
|
7858
|
-
*
|
|
7859
|
-
*
|
|
7860
|
-
*
|
|
8026
|
+
* **拼接交给 `joinLocalPath`**(2026-09-28 Windows 适配):分隔符随工作空间的风格走 ——
|
|
8027
|
+
* 在 Windows 上硬拼 `/` 会得到 `C:\Work/S1` 这种混用形式,而这条路径既进提示词
|
|
8028
|
+
* (模型照着写 PowerShell 命令)又进 `ctx.shell` 的 `workdir`。
|
|
8029
|
+
*
|
|
8030
|
+
* 任一侧为空、或流水号不是安全的一段(绝对路径 / 含 `..`)时返回空串,
|
|
8031
|
+
* 调用方据此**不写这一段**(宁可不给,也不给一个半截或越界的路径)。
|
|
7861
8032
|
*/
|
|
7862
8033
|
function caseDirOf(workspacePath, seqNo) {
|
|
7863
|
-
const workspace = text(workspacePath)
|
|
8034
|
+
const workspace = text(workspacePath);
|
|
7864
8035
|
const seq = text(seqNo).trim();
|
|
7865
8036
|
if (workspace === "" || seq === "") return "";
|
|
7866
|
-
|
|
8037
|
+
try {
|
|
8038
|
+
return joinLocalPath(workspace, seq);
|
|
8039
|
+
} catch (error) {
|
|
8040
|
+
return "";
|
|
8041
|
+
}
|
|
7867
8042
|
}
|
|
7868
8043
|
//#endregion
|
|
7869
8044
|
//#region src/client/features/report-audit/ReportPane.tsx
|
|
@@ -9796,7 +9971,7 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
9796
9971
|
}
|
|
9797
9972
|
/** 宿主与客户端是不是同一代(不同代时**必须**拦住发起审核,见 AGENTS.md §7.12)。 */
|
|
9798
9973
|
function hostIsStale(snapshot) {
|
|
9799
|
-
return snapshot.protocol !== null && snapshot.protocol !==
|
|
9974
|
+
return snapshot.protocol !== null && snapshot.protocol !== 17;
|
|
9800
9975
|
}
|
|
9801
9976
|
/**
|
|
9802
9977
|
* 当前运行的版本号(**不带 `v` 前缀**):`0.0.11` / dev 形态回空串。
|
|
@@ -10966,7 +11141,7 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
10966
11141
|
setNotice("这条记录还没有本地 HTML");
|
|
10967
11142
|
return;
|
|
10968
11143
|
}
|
|
10969
|
-
workbenchApi.openPath({ path:
|
|
11144
|
+
workbenchApi.openPath({ path: joinLocalPath(record.casePath, record.htmlFile) }).then((result) => {
|
|
10970
11145
|
if (!result.ok) setNotice(result.error);
|
|
10971
11146
|
});
|
|
10972
11147
|
},
|