dsh-crwu-workbench 0.0.11 → 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.
- package/CHANGELOG.md +64 -0
- package/README.en.md +45 -0
- package/README.md +51 -1
- package/lib/client.js +1549 -10
- package/lib/index.js +110 -39
- package/package.json +1 -1
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.
|
|
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 `
|
|
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
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
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
|
|
2004
|
-
const result = await runShell(ctx,
|
|
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
|
|
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
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
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
|
|
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
|
|
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 `
|
|
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
|
|
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
|
|
7385
|
+
const argv = [
|
|
7333
7386
|
ossutil,
|
|
7334
7387
|
"ls",
|
|
7335
7388
|
`oss://${bucket}/${prefix}/`
|
|
7336
|
-
]
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
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
|
|
9284
|
-
chmodError
|
|
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.
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
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",
|