dsh-crwu-workbench 0.0.12 → 0.0.13

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/lib/index.js +110 -39
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,34 @@
7
7
  `cordis_define` + `cordis_run` 装配,版本号用 DSH 的 `pkg-N`);它已在本仓收尾时删除
8
8
  (见 `0.0.1` 一节),下面 `legacy · pkg-43` 及更早的记录是它的历史。
9
9
 
10
+ ## package · 0.0.13 · 2026-09-28
11
+
12
+ **修复 Windows 上的平台方言:命令串按 PowerShell 拼,不再按 `cmd.exe` 拼。**
13
+ 0.0.12 及更早的版本在 Windows 上,环境页的「氚云员工会话」「钉钉认证」两行会一起报
14
+ `表达式或语句中包含意外的标记"h3yun"`(PowerShell `ParserError` / `FullyQualifiedErrorId:
15
+ UnexpectedToken`),相关按钮点下去也不会有结果;macOS 上完全正常。
16
+
17
+ - **根因**:DSH 在 Windows 挂的执行器是 `@deepseek-ai/dsh-pwsh-local` —— 整条命令作为**一个
18
+ argv 元素**交给 `pwsh -NoLogo -NoProfile -NonInteractive -Command <整串>`,由 PowerShell 自己
19
+ 解析。插件此前按 `cmd.exe` 的规矩拼命令,于是在 PowerShell 里「引号包住可执行文件」是
20
+ **字符串表达式**、不是命令调用,紧随其后的第一个参数就成了意外标记。
21
+ - **命令位置改用 PowerShell 的调用运算符 `&`**:新增 `shellInvoke(exe, args, platform)`,
22
+ 由它在 Windows 上拼 `& 'C:\…\crwu.exe' 'h3yun' 'session'`。所有「第一条 token 是可执行文件」
23
+ 的调用点(`crwu` / `dws` / DSH 自带 Python / `ossutil` / 清单里的探测模板)全部改走它;
24
+ POSIX 上仍是原样的 `bash -c` 命令串(那里 `&` 是后台作业,绝不能加)。
25
+ - **参数引用改用 PowerShell 单引号字面量**:Windows 上 `shellQuote` 不再用 cmd 式的双引号
26
+ (在 PowerShell 里会插值 `$` 与反引号),改为单引号 + 内部单引号翻倍。
27
+ - **清掉同一类「POSIX-only」写法**:Windows 上不再执行 `chmod`(Windows 没有这个命令,
28
+ 凭据文件由用户 ACL 保护,这一项在 Windows 上不适用、如实标注而不是伪造失败);
29
+ 建目录用幂等的 `New-Item -ItemType Directory -Force`;删文件用
30
+ `Remove-Item -LiteralPath … -Force -ErrorAction SilentlyContinue`
31
+ (`rm -f` 的 `-f` 在 `Remove-Item` 上同时前缀匹配 `-Force` 与 `-Filter`,会直接报「参数名不明确」)。
32
+ - **平台方言跟着 `platform` 键走,不读 `process.platform`**:否则在 Windows CI 上跑
33
+ stubbed `darwin-arm64` 的用例也会被补上 `&`。
34
+ - **回归测试**:`host-environment-probe.test.mjs`(引用与命令位置)、`host-shell-fs.test.mjs`
35
+ (一条静态守卫,钉死「模板以 `${shellQuote(` 开头」与「`argv.map(…shellQuote…).join(' ')`」
36
+ 这两种会复发的写法)、`host-crwu-h3yun.test.mjs`(按员工报的原文断言 Windows 命令形状)。
37
+
10
38
  ## package · 0.0.12 · 2026-09-28
11
39
 
12
40
  **首个包含自更新能力的正式版本。** 0.0.10 / 0.0.11 用户需要**手动完成一次**引导升级
package/lib/index.js CHANGED
@@ -229,7 +229,7 @@ const PLUGIN_INJECT = [
229
229
  * 还会让打包器的 JSON 插件成为隐式依赖。代价是升版本时要同时改这里 ——
230
230
  * `tests/unit/host-package.test.mjs` 有一条断言盯着它必须等于 `package.json` 的 version。
231
231
  */
232
- const PLUGIN_VERSION = "0.0.12";
232
+ const PLUGIN_VERSION = "0.0.13";
233
233
  /**
234
234
  * `ping` / `boot` 应答里的版本指纹,形如 `pkg-0.0.5`。
235
235
  *
@@ -1554,9 +1554,51 @@ function sanitizeOssError(value) {
1554
1554
  }
1555
1555
  //#endregion
1556
1556
  //#region src/host/environment/probe.ts
1557
+ /**
1558
+ * 把一个参数安全地放进**该平台真实使用的 shell** 的命令串里。
1559
+ *
1560
+ * POSIX 上就是 `bash -c`:能不加引号就不加(`quoteArg`),需要时用单引号 + `'\''`。
1561
+ *
1562
+ * **Windows 上是 PowerShell,不是 `cmd.exe`**(DSH 在 Windows 挂 `@deepseek-ai/dsh-pwsh-local`,
1563
+ * 整串命令交给 `pwsh -Command`)。所以这里用 PowerShell 的单引号字面量、内部单引号翻倍:
1564
+ * 双引号在 PowerShell 里会做 `$` / 反引号插值(路径或 URL 里出现 `$` 就会被当变量展开),
1565
+ * 而单引号是纯字面量。cmd 那套 `"…"` + `""` 转义在这里既没必要也不安全。
1566
+ *
1567
+ * 以引号开头的命令还需要 PowerShell 的调用运算符 `&` —— 那是**命令位置**的事,由下面的
1568
+ * `shellInvoke()` 负责;`shellQuote` 只管「一个参数」。
1569
+ */
1557
1570
  function shellQuote$1(value, platform) {
1558
1571
  if (!isWindowsPlatform(text(platform))) return quoteArg(value);
1559
- return `"${String(value).replace(/"/g, "\"\"")}"`;
1572
+ return `'${String(value).replace(/'/g, "''")}'`;
1573
+ }
1574
+ /**
1575
+ * 拼一条**可执行文件 + 参数**的命令串(命令位置)。
1576
+ *
1577
+ * 为什么不能直接 `${shellQuote(exe, platform)} 参数…`(2026-09-28 修,员工在 Windows 上实测报错):
1578
+ * DSH 在 Windows 挂的执行器是 `@deepseek-ai/dsh-pwsh-local`,它把整条命令作为**一个 argv 元素**
1579
+ * 交给 `pwsh -NoLogo -NoProfile -NonInteractive -Command <整串>`(该包自己的 README:*the command
1580
+ * string is passed as ONE argv element to `-Command`; PowerShell itself parses the text*)。
1581
+ * 也就是说插件拼的是 **PowerShell 脚本**,不是 `cmd.exe` 的批处理行。
1582
+ *
1583
+ * 后果很硬:**以引号开头的 token 在 PowerShell 里是字符串表达式,不是命令调用**。
1584
+ *
1585
+ * ```text
1586
+ * PS> "C:\…\crwu.exe" "h3yun" "session" "status"
1587
+ * 表达式或语句中包含意外的标记"h3yun"。
1588
+ * ```
1589
+ *
1590
+ * 员工看到的正是这个:环境页「氚云员工会话」「钉钉认证」两行一起红,因为它们的命令都以包内绝对
1591
+ * 路径开头。PowerShell 的调用运算符 `&` 才是「把这段字符串当命令执行」,所以 Windows 上必须写
1592
+ * `& 'C:\…\crwu.exe' 'h3yun' 'session' 'status'`。
1593
+ *
1594
+ * POSIX 上**绝不能**加 `&`:`bash -c` 里它是后台作业,会把前台命令变成异步执行。
1595
+ *
1596
+ * 所有「第一条 token 是可执行文件」的调用点都必须走这里,而不是自己 join ——
1597
+ * `tests/unit/host-shell-fs.test.mjs` 里有静态守卫钉着这一点。
1598
+ */
1599
+ function shellInvoke(executable, args, platform) {
1600
+ const parts = [executable, ...args].map((item) => shellQuote$1(item, platform));
1601
+ return isWindowsPlatform(text(platform)) ? `& ${parts.join(" ")}` : parts.join(" ");
1560
1602
  }
1561
1603
  /**
1562
1604
  * 包内路径:`<包根>` 与 `<包根>/bin/manifest.json`。
@@ -1853,10 +1895,16 @@ async function probeOss(ctx, oss, platform, options = {}) {
1853
1895
  */
1854
1896
  function buildOssProbeCommand(oss, ossutil, platform) {
1855
1897
  const { bucket, prefix, target } = ossProbeTarget(oss);
1856
- const endpointArg = oss.endpoint === "" ? "" : ` --endpoint ${shellQuote$1(oss.endpoint, platform)}`;
1857
- const extra = oss.extraArgs.map((arg) => ` ${shellQuote$1(arg, platform)}`).join("");
1858
- if (oss.probeCommand !== "") return oss.probeCommand.split("{ossutil}").join(shellQuote$1(ossutil, platform)).split("{bucket}").join(bucket).split("{prefix}").join(prefix).split("{target}").join(target).split("{endpoint}").join(oss.endpoint);
1859
- return `${shellQuote$1(ossutil, platform)} ls ${shellQuote$1(target, platform)}${endpointArg} --limited-num 1${extra}`;
1898
+ const executable = shellInvoke(ossutil, [], platform);
1899
+ if (oss.probeCommand !== "") return oss.probeCommand.split("{ossutil}").join(executable).split("{bucket}").join(bucket).split("{prefix}").join(prefix).split("{target}").join(target).split("{endpoint}").join(oss.endpoint);
1900
+ return shellInvoke(ossutil, [
1901
+ "ls",
1902
+ target,
1903
+ ...oss.endpoint === "" ? [] : ["--endpoint", oss.endpoint],
1904
+ "--limited-num",
1905
+ "1",
1906
+ ...oss.extraArgs
1907
+ ], platform);
1860
1908
  }
1861
1909
  /**
1862
1910
  * OSS 失败 → 人话归因。
@@ -2000,8 +2048,8 @@ async function runCrwu(ctx, argv, options) {
2000
2048
  const allowed = escalationAllowed(clean);
2001
2049
  if (options.escalate === true && !allowed) return failed(`该 crwu 子命令不允许无沙箱执行:${clean.slice(0, 3).join(" ")}`);
2002
2050
  const canEscalate = (options.trusted === true && allowed || options.escalate === true) && (options.workdir ?? "") !== "";
2003
- const quote = (value) => shellQuote$1(value, options.platform ?? "");
2004
- const result = await runShell(ctx, [await resolveBundledCommand(ctx, options.platform ?? "", "crwu"), ...clean.slice(1)].map(quote).join(" "), {
2051
+ const platform = options.platform ?? "";
2052
+ const result = await runShell(ctx, shellInvoke(await resolveBundledCommand(ctx, platform, "crwu"), clean.slice(1), platform), {
2005
2053
  ...options.workdir === void 0 ? {} : { workdir: options.workdir },
2006
2054
  timeoutMs: options.timeoutMs ?? 6e4,
2007
2055
  escalate: canEscalate,
@@ -3343,8 +3391,7 @@ async function runDws(ctx, platform, argv, options) {
3343
3391
  const resolved = await requireBundledCommand(ctx, platform, "dws");
3344
3392
  if (!resolved.ok) return failure(resolved.errorKind, resolved.error, shown);
3345
3393
  const canEscalate = options.credentialOperation === true && options.trusted === true && options.workdir !== "" && dwsEscalationAllowed(argv);
3346
- const quote = (value) => shellQuote$1(value, platform);
3347
- const result = await runShell(ctx, [resolved.path, ...argv.map((item) => text(item))].map(quote).join(" "), {
3394
+ const result = await runShell(ctx, shellInvoke(resolved.path, argv.map((item) => text(item)), platform), {
3348
3395
  ...options.workdir === "" ? {} : { workdir: options.workdir },
3349
3396
  timeoutMs: options.timeoutMs ?? 18e4,
3350
3397
  stdoutMaxBytes: options.stdoutMaxBytes ?? 16777216,
@@ -5030,7 +5077,7 @@ async function writeIfindSecret(ctx, home, rawSecret, options = {}) {
5030
5077
  if (fs === void 0) return writeFailure("infrastructure", "Host 文件服务不可用,无法保存同花顺 iFinD 凭据", path);
5031
5078
  const platform = text(options.platform) || process.platform;
5032
5079
  const dir = ifindStateDir(home);
5033
- const mkdir = await runShell(ctx, `mkdir -p ${shellQuote(dir, platform)}`, {
5080
+ const mkdir = await runShell(ctx, isWindowsPlatform(platform) ? `New-Item -ItemType Directory -Force -Path ${shellQuote(dir, platform)} | Out-Null` : `mkdir -p ${shellQuote(dir, platform)}`, {
5034
5081
  workdir: home,
5035
5082
  timeoutMs: 15e3,
5036
5083
  escalate: true
@@ -5046,18 +5093,23 @@ async function writeIfindSecret(ctx, home, rawSecret, options = {}) {
5046
5093
  } catch (error) {
5047
5094
  return writeFailure("infrastructure", `写入 ${path} 失败:${error instanceof Error ? error.message : String(error)}`, path);
5048
5095
  }
5049
- const chmod = await runShell(ctx, `chmod 600 ${shellQuote(path, platform)}`, {
5050
- workdir: home,
5051
- timeoutMs: 15e3,
5052
- escalate: true
5053
- });
5054
- const chmodError = chmod.ok ? "" : (text(chmod.stderr) || text(chmod.error) || "权限设置命令没有跑起来").slice(0, 200);
5096
+ let chmodOk = true;
5097
+ let chmodError = "";
5098
+ if (!isWindowsPlatform(platform)) {
5099
+ const chmod = await runShell(ctx, `chmod 600 ${shellQuote(path, platform)}`, {
5100
+ workdir: home,
5101
+ timeoutMs: 15e3,
5102
+ escalate: true
5103
+ });
5104
+ chmodOk = chmod.ok;
5105
+ chmodError = chmod.ok ? "" : (text(chmod.stderr) || text(chmod.error) || "权限设置命令没有跑起来").slice(0, 200);
5106
+ }
5055
5107
  return {
5056
5108
  ok: true,
5057
5109
  errorKind: "",
5058
5110
  error: "",
5059
5111
  dirReady,
5060
- chmodOk: chmod.ok,
5112
+ chmodOk,
5061
5113
  chmodError,
5062
5114
  mode: "file",
5063
5115
  view: {
@@ -5095,7 +5147,7 @@ async function clearIfindSecret(ctx, home, options = {}) {
5095
5147
  mode: "file"
5096
5148
  };
5097
5149
  const platform = text(options.platform) || process.platform;
5098
- const result = await runShell(ctx, `rm -f ${shellQuote(ifindCredentialPath(home), platform)}`, {
5150
+ const result = await runShell(ctx, isWindowsPlatform(platform) ? `Remove-Item -LiteralPath ${shellQuote(ifindCredentialPath(home), platform)} -Force -ErrorAction SilentlyContinue` : `rm -f ${shellQuote(ifindCredentialPath(home), platform)}`, {
5099
5151
  workdir: home,
5100
5152
  timeoutMs: 15e3,
5101
5153
  escalate: true
@@ -5116,14 +5168,15 @@ async function clearIfindSecret(ctx, home, options = {}) {
5116
5168
  /**
5117
5169
  * 平台引用方式。
5118
5170
  *
5119
- * 与 `environment/probe.ts` 的 `shellQuote` 同一判据(POSIX 单引号 / Windows 双引号),
5171
+ * 与 `environment/probe.ts` 的 `shellQuote` 同一判据(POSIX 单引号 / Windows 也是单引号 ——
5172
+ * Windows 上 DSH 跑的是 PowerShell,单引号才是纯字面量,双引号会插值),
5120
5173
  * 这里各写一份是因为 `probe.ts` 已经反向依赖本模块(`checkIfindSecret` 由它复用),
5121
- * 再互相 import 会成环。两处都只有三行,且都有单测钉着 Windows 分支。
5174
+ * 再互相 import 会成环。两处都只有两行,且都有单测钉着 Windows 分支。
5122
5175
  */
5123
5176
  function shellQuote(value, platform) {
5124
5177
  const raw = text(value);
5125
5178
  if (!isWindowsPlatform(text(platform))) return `'${raw.replace(/'/g, "'\\''")}'`;
5126
- return `"${raw.replace(/"/g, "\"\"")}"`;
5179
+ return `'${raw.replace(/'/g, "''")}'`;
5127
5180
  }
5128
5181
  //#endregion
5129
5182
  //#region src/host/ifind/mcp.ts
@@ -7278,7 +7331,7 @@ function ossTools(deps) {
7278
7331
  ];
7279
7332
  if (oss.endpoint !== "") argv.push("--endpoint", oss.endpoint);
7280
7333
  for (const extra of oss.extraArgs) argv.push(extra);
7281
- const run = await runShell(ctx, argv.map((item) => shellQuote$1(item, platform)).join(" "), {
7334
+ const run = await runShell(ctx, shellInvoke(argv[0] ?? "", argv.slice(1), platform), {
7282
7335
  workdir: caseDir,
7283
7336
  timeoutMs: 18e4,
7284
7337
  signal: exec.signal
@@ -7329,11 +7382,12 @@ function ossTools(deps) {
7329
7382
  }
7330
7383
  /** 列举前缀并核对目标对象的字节数。 */
7331
7384
  async function verifyObject(ctx, platform, bucket, prefix, key, localSize, workdir, ossutil, signal) {
7332
- const run = await runShell(ctx, [
7385
+ const argv = [
7333
7386
  ossutil,
7334
7387
  "ls",
7335
7388
  `oss://${bucket}/${prefix}/`
7336
- ].map((item) => shellQuote$1(item, platform)).join(" "), {
7389
+ ];
7390
+ const run = await runShell(ctx, shellInvoke(argv[0] ?? "", argv.slice(1), platform), {
7337
7391
  workdir,
7338
7392
  timeoutMs: 9e4,
7339
7393
  stdoutMaxBytes: 4194304,
@@ -8965,7 +9019,7 @@ async function ossIndex(deps, args = {}) {
8965
9019
  `oss://${oss.bucket}/${listedPrefix}/`
8966
9020
  ];
8967
9021
  if (oss.endpoint !== "") argv.push("--endpoint", oss.endpoint);
8968
- const run = await runShell(deps.ctx, argv.map((item) => shellQuote$1(item, deps.platform)).join(" "), {
9022
+ const run = await runShell(deps.ctx, shellInvoke(argv[0] ?? "", argv.slice(1), deps.platform), {
8969
9023
  workdir: await shellWorkdir(deps),
8970
9024
  timeoutMs: 9e4,
8971
9025
  stdoutMaxBytes: 4194304
@@ -9010,7 +9064,7 @@ async function ossResult(deps, args) {
9010
9064
  `oss://${oss.bucket}/${key}`
9011
9065
  ];
9012
9066
  if (oss.endpoint !== "") argv.push("--endpoint", oss.endpoint);
9013
- const run = await runShell(deps.ctx, argv.map((item) => shellQuote$1(item, deps.platform)).join(" "), {
9067
+ const run = await runShell(deps.ctx, shellInvoke(argv[0] ?? "", argv.slice(1), deps.platform), {
9014
9068
  workdir: await shellWorkdir(deps),
9015
9069
  timeoutMs: 6e4,
9016
9070
  stdoutMaxBytes: 8388608
@@ -9058,7 +9112,7 @@ async function ossLink(deps, args) {
9058
9112
  String(oss.linkTtl)
9059
9113
  ];
9060
9114
  if (oss.endpoint !== "") argv.push("--endpoint", oss.endpoint);
9061
- const run = await runShell(deps.ctx, argv.map((item) => shellQuote$1(item, deps.platform)).join(" "), {
9115
+ const run = await runShell(deps.ctx, shellInvoke(argv[0] ?? "", argv.slice(1), deps.platform), {
9062
9116
  workdir: await shellWorkdir(deps),
9063
9117
  timeoutMs: 3e4
9064
9118
  });
@@ -9115,7 +9169,7 @@ async function uploadArtifacts(deps, item, projectId, ossutil, oss) {
9115
9169
  ];
9116
9170
  if (oss.endpoint !== "") argv.push("--endpoint", oss.endpoint);
9117
9171
  for (const extra of oss.extraArgs) argv.push(extra);
9118
- const run = await runShell(deps.ctx, argv.map((item2) => shellQuote$1(item2, deps.platform)).join(" "), {
9172
+ const run = await runShell(deps.ctx, shellInvoke(argv[0] ?? "", argv.slice(1), deps.platform), {
9119
9173
  workdir: await shellWorkdir(deps),
9120
9174
  timeoutMs: 18e4
9121
9175
  });
@@ -9271,17 +9325,23 @@ async function writeOssCred(deps, input) {
9271
9325
  chmodError: ""
9272
9326
  };
9273
9327
  }
9274
- const chmod = await runShell(deps.ctx, `chmod 600 ${shellQuote$1(path, deps.platform)}`, {
9275
- workdir: await shellWorkdir(deps),
9276
- timeoutMs: 15e3
9277
- });
9328
+ let chmodOk = true;
9329
+ let chmodError = "";
9330
+ if (!isWindowsPlatform(deps.platform)) {
9331
+ const chmod = await runShell(deps.ctx, `chmod 600 ${shellQuote$1(path, deps.platform)}`, {
9332
+ workdir: await shellWorkdir(deps),
9333
+ timeoutMs: 15e3
9334
+ });
9335
+ chmodOk = chmod.ok;
9336
+ chmodError = chmod.ok ? "" : (text(chmod.stderr) || text(chmod.error) || "权限设置失败").slice(0, 200);
9337
+ }
9278
9338
  return {
9279
9339
  ok: true,
9280
9340
  error: "",
9281
9341
  path,
9282
9342
  operation,
9283
- chmodOk: chmod.ok,
9284
- chmodError: chmod.ok ? "" : (text(chmod.stderr) || text(chmod.error) || "权限设置失败").slice(0, 200)
9343
+ chmodOk,
9344
+ chmodError
9285
9345
  };
9286
9346
  }
9287
9347
  /** 生成 ossutil 配置内容(`[Credentials]` 段)。 */
@@ -9676,7 +9736,7 @@ async function dwsLogin(deps, args) {
9676
9736
  "login"
9677
9737
  ];
9678
9738
  if (args.device === true) argv.push("--device");
9679
- const run = await runShell(deps.ctx, argv.map((item) => shellQuote$1(item, deps.platform)).join(" "), {
9739
+ const run = await runShell(deps.ctx, shellInvoke(argv[0] ?? "", argv.slice(1), deps.platform), {
9680
9740
  workdir: await deps.workdir(),
9681
9741
  timeoutMs: 3e5,
9682
9742
  escalate: true
@@ -9767,7 +9827,13 @@ function readSelfDocument(payload) {
9767
9827
  }
9768
9828
  async function dwsSelf(deps) {
9769
9829
  const dws = await resolveBundledCommand(deps.ctx, deps.platform ?? "", "dws");
9770
- const run = await runShell(deps.ctx, `${shellQuote$1(dws, deps.platform ?? "")} contact user get-self --format json`, {
9830
+ const run = await runShell(deps.ctx, shellInvoke(dws, [
9831
+ "contact",
9832
+ "user",
9833
+ "get-self",
9834
+ "--format",
9835
+ "json"
9836
+ ], deps.platform ?? ""), {
9771
9837
  workdir: await deps.workdir(),
9772
9838
  timeoutMs: deps.timeoutMs ?? 3e4,
9773
9839
  escalate: true
@@ -10291,7 +10357,12 @@ async function loadEnvironment(deps, args) {
10291
10357
  });
10292
10358
  const trustedCredentials = state.trustCredentials === true;
10293
10359
  const dwsCommand = await resolveBundledCommand(ctx, platform, "dws");
10294
- const dwsRun = trustedCredentials ? await runShell(ctx, `${shellQuote$1(dwsCommand, platform)} auth status --format json`, {
10360
+ const dwsRun = trustedCredentials ? await runShell(ctx, shellInvoke(dwsCommand, [
10361
+ "auth",
10362
+ "status",
10363
+ "--format",
10364
+ "json"
10365
+ ], platform), {
10295
10366
  workdir: await deps.sessionRoot(),
10296
10367
  timeoutMs: 3e4,
10297
10368
  escalate: true
@@ -12776,7 +12847,7 @@ function createPythonRuntimeResolver(deps) {
12776
12847
  } catch (error) {
12777
12848
  return failed("failed", `无法访问 DSH Python 路径:${python}(${error instanceof Error ? error.message : String(error)})`);
12778
12849
  }
12779
- const probe = await runShell(ctx, `${shellQuote$1(python, platform)} -c ${shellQuote$1("import sys;print(sys.version.split()[0])", platform)}`, {
12850
+ const probe = await runShell(ctx, shellInvoke(python, ["-c", "import sys;print(sys.version.split()[0])"], platform), {
12780
12851
  workdir: await deps.world.workdir(),
12781
12852
  timeoutMs: 3e4
12782
12853
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-crwu-workbench",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "中瑞世联工作台 / CRWU audit workbench for DeepSeek Harness: pick a pending audit report, dispatch one AI audit subagent, watch and stop/restart it, auto-upload deliverables to Aliyun OSS.",
5
5
  "keywords": [
6
6
  "deepseek-harness",