@xiaoso/dsh-tool-plus 0.1.0-beta.6 → 0.1.1
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/README.en.md +9 -5
- package/README.md +9 -5
- package/lib/client.js +372 -120
- package/lib/index.mjs +300 -16
- package/lib/{scrapers-BlWQMGjU.mjs → scrapers-DI8yWlu_.mjs} +1 -1
- package/lib/trash-cli.mjs +281 -0
- package/lib/{utils-D1QPFbRk.mjs → utils-D1p8-H8L.mjs} +1 -1
- package/package.json +148 -147
- package/presets/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ __installBunShim();
|
|
|
3
3
|
import { n as fs_promises_shim_exports, t as exists } from "./fs-promises-shim-Bx0fpxuk.mjs";
|
|
4
4
|
import { i as setRuntimeLogger, n as systemChromiumCandidatesForTest, r as runtimeLogger } from "./launch-DwpE70Pv.mjs";
|
|
5
5
|
import { $ as peekFile, A as formatAge, C as error, F as $envpos, I as $flag, M as formatCount, P as $env, Q as isProbablyBinary, S as debug, T as warn, U as getAutoQaDbPath, V as VERSION, X as getRemoteDir, Y as getPuppeteerDir, b as isEnotdir, c as combineSignals, d as $which, j as formatBytes$1, k as installBunShim, l as exec, p as readImageMetadata, q as getInstallId, tt as untilAborted, u as render, v as hasFsCode, y as isEnoent } from "./src-D_e1qMwi.mjs";
|
|
6
|
-
import { C as truncateToWidth$1, S as replaceTabs, _ as formatCodeFrameLine, a as fetchBinary, b as LRUCache, c as getParallelExtractContent, d as convertFileWithMarkit, f as Settings, g as capParseErrors, h as OMP_DEFAULTS, i as convertWithMarkit, l as ensureTool, m as settings, o as extractWithParallel, p as isSettingsInitialized, s as findParallelApiKey, u as CONVERTIBLE_EXTENSIONS, v as formatParseErrors, x as padding, y as shortenPath } from "./utils-
|
|
6
|
+
import { C as truncateToWidth$1, S as replaceTabs, _ as formatCodeFrameLine, a as fetchBinary, b as LRUCache, c as getParallelExtractContent, d as convertFileWithMarkit, f as Settings, g as capParseErrors, h as OMP_DEFAULTS, i as convertWithMarkit, l as ensureTool, m as settings, o as extractWithParallel, p as isSettingsInitialized, s as findParallelApiKey, u as CONVERTIBLE_EXTENSIONS, v as formatParseErrors, x as padding, y as shortenPath } from "./utils-D1p8-H8L.mjs";
|
|
7
7
|
import { t as sanitizeText } from "./sanitize-text-CfcXzw5X.mjs";
|
|
8
8
|
import { n as isRecord$2 } from "./type-guards-B9VVjRDK.mjs";
|
|
9
9
|
import { n as ToolError, r as throwIfAborted, t as ToolAbortError } from "./tool-errors-DoyxivAR.mjs";
|
|
@@ -719,7 +719,26 @@ async function getOrCreateSnapshot(shell, env, timeoutMs = SNAPSHOT_TIMEOUT_MS)
|
|
|
719
719
|
const cached = cachedSnapshotPaths.get(cacheKey);
|
|
720
720
|
if (cached && fs.existsSync(cached)) return cached;
|
|
721
721
|
if (cached) cachedSnapshotPaths.delete(cacheKey);
|
|
722
|
-
if (process.platform === "win32")
|
|
722
|
+
if (process.platform === "win32") {
|
|
723
|
+
const uid = process.getuid?.();
|
|
724
|
+
const snapshotDir = path.join(os.tmpdir(), uid === void 0 ? "omp-shell-snapshots" : `omp-shell-snapshots-${uid}`);
|
|
725
|
+
const snapshotPath = path.join(snapshotDir, `snapshot-bash-${crypto.randomUUID()}.sh`);
|
|
726
|
+
try {
|
|
727
|
+
fs.mkdirSync(snapshotDir, {
|
|
728
|
+
recursive: true,
|
|
729
|
+
mode: 448
|
|
730
|
+
});
|
|
731
|
+
fs.writeFileSync(snapshotPath, "# Shell snapshot - generated by omp agent (windows minimal)\n", { mode: 384 });
|
|
732
|
+
cachedSnapshotPaths.set(cacheKey, snapshotPath);
|
|
733
|
+
return snapshotPath;
|
|
734
|
+
} catch (err) {
|
|
735
|
+
runtimeLogger().debug("shell-snapshot: windows snapshot dir unusable", {
|
|
736
|
+
dir: snapshotDir,
|
|
737
|
+
err: String(err)
|
|
738
|
+
});
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
723
742
|
const rcFile = getShellConfigFile(shell, env);
|
|
724
743
|
const uid = process.getuid?.();
|
|
725
744
|
const snapshotDir = path.join(os.tmpdir(), uid === void 0 ? "omp-shell-snapshots" : `omp-shell-snapshots-${uid}`);
|
|
@@ -788,6 +807,85 @@ function cleanupSnapshots() {
|
|
|
788
807
|
cachedSnapshotPaths.clear();
|
|
789
808
|
}
|
|
790
809
|
//#endregion
|
|
810
|
+
//#region src/tools/bash/rm-safe.ts
|
|
811
|
+
/**
|
|
812
|
+
* rmSafe: rm 重定义脚本的生成与快照注入。
|
|
813
|
+
*
|
|
814
|
+
* 生成一个 bash 函数定义脚本(rm → `node <trash-cli.mjs> "$@"`),
|
|
815
|
+
* 通过快照文件(会话 shell 创建时 source 一次)注入:
|
|
816
|
+
* - POSIX:追加到用户环境快照末尾(幂等,带标记行);
|
|
817
|
+
* - Windows:getOrCreateSnapshot 生成"仅注入"快照文件后同样追加。
|
|
818
|
+
*
|
|
819
|
+
* rmSafe 关闭时调用方不注入,快照保持原样,系统 rm 生效。
|
|
820
|
+
* @module @xiaoso/dsh-tool-plus/bash/rm-safe
|
|
821
|
+
*/
|
|
822
|
+
/** 注入标记行:injectRmSafe 以此判断是否已注入(幂等)。 */
|
|
823
|
+
const RM_SAFE_MARKER = "# dsh-tool-plus rmSafe";
|
|
824
|
+
/** rm-safe 脚本缓存目录(tmpdir 下,跨会话复用)。 */
|
|
825
|
+
function rmSafeScriptDir() {
|
|
826
|
+
return path.join(os.tmpdir(), "dsh-bash-plus");
|
|
827
|
+
}
|
|
828
|
+
/** trash-cli 构建产物路径(lib/trash-cli.mjs,与主 bundle 同目录)。 */
|
|
829
|
+
function rmSafeCliPath() {
|
|
830
|
+
return fileURLToPath(new URL("./trash-cli.mjs", import.meta.url));
|
|
831
|
+
}
|
|
832
|
+
/** 单引号转义(bash 单引号内 `'\''` 表示一个字面单引号)。 */
|
|
833
|
+
function quote(s) {
|
|
834
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* 生成 rm 重定义脚本内容:bash 函数 `rm()` 把参数原样转交 trash-cli。
|
|
838
|
+
* 逃生口:`command rm`(bash 的 command 关键字跳过函数/alias 查找)或
|
|
839
|
+
* 绝对路径(/usr/bin/rm)走系统真删除。注意 `\rm` 反斜杠只抑制 alias
|
|
840
|
+
* 展开、不抑制函数查找,对函数重定义无效——不是逃生口。
|
|
841
|
+
*/
|
|
842
|
+
function rmSafeScript(nodePath, cliPath) {
|
|
843
|
+
return [
|
|
844
|
+
RM_SAFE_MARKER,
|
|
845
|
+
"rm() {",
|
|
846
|
+
` ${quote(nodePath)} ${quote(cliPath)} "$@"`,
|
|
847
|
+
"}",
|
|
848
|
+
""
|
|
849
|
+
].join("\n");
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* 确保 rm-safe 脚本存在(内容不变时幂等,不重写文件)。
|
|
853
|
+
* 返回脚本路径;目录/写入失败时返回 null(不抛异常,调用方降级并告警)。
|
|
854
|
+
*/
|
|
855
|
+
function ensureRmSafeScript(dir, nodePath, cliPath) {
|
|
856
|
+
const target = path.join(dir, "rm-safe.sh");
|
|
857
|
+
const content = rmSafeScript(nodePath, cliPath);
|
|
858
|
+
try {
|
|
859
|
+
if (fs.readFileSync(target, "utf8") === content) return target;
|
|
860
|
+
} catch {}
|
|
861
|
+
try {
|
|
862
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
863
|
+
fs.writeFileSync(target, content, { mode: 384 });
|
|
864
|
+
return target;
|
|
865
|
+
} catch {
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* 向快照文件追加 `source <rm-safe.sh>`(幂等:已有标记则跳过)。
|
|
871
|
+
* 返回是否注入成功;快照文件不可读/不可写时返回 false(不抛异常)。
|
|
872
|
+
*/
|
|
873
|
+
function injectRmSafe(snapshotPath, scriptPath) {
|
|
874
|
+
let content;
|
|
875
|
+
try {
|
|
876
|
+
content = fs.readFileSync(snapshotPath, "utf8");
|
|
877
|
+
} catch {
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
if (content.includes("# dsh-tool-plus rmSafe")) return true;
|
|
881
|
+
try {
|
|
882
|
+
fs.appendFileSync(snapshotPath, `\n${RM_SAFE_MARKER}\nsource ${quote(scriptPath)}\n`);
|
|
883
|
+
return true;
|
|
884
|
+
} catch {
|
|
885
|
+
return false;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
//#endregion
|
|
791
889
|
//#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/path.ts
|
|
792
890
|
const WINDOWS_DRIVE_EXTENDED_PREFIX = /^\\\\[?]\\([A-Za-z]:[\\/].*)$/;
|
|
793
891
|
const WINDOWS_UNC_EXTENDED_PREFIX = /^\\\\[?]\\UNC[\\/]([^\\/]+)[\\/](.+)$/i;
|
|
@@ -2325,7 +2423,19 @@ async function executeBash(command, options) {
|
|
|
2325
2423
|
const baseShellConfig = getShellConfig();
|
|
2326
2424
|
const { shell, args, env: shellEnv, prefix } = options?.useUserShell === true ? resolveUserShellConfig(baseShellConfig) : baseShellConfig;
|
|
2327
2425
|
const bashShell = isBashShell(shell);
|
|
2328
|
-
|
|
2426
|
+
let snapshotPath = bashShell && options?.snapshotEnabled !== false ? await getOrCreateSnapshot(shell, shellEnv) : null;
|
|
2427
|
+
let rmSafeInjectionFailed = false;
|
|
2428
|
+
if (snapshotPath !== null && options?.rmSafe === true) {
|
|
2429
|
+
const scriptPath = ensureRmSafeScript(rmSafeScriptDir(), process.execPath, rmSafeCliPath());
|
|
2430
|
+
if (scriptPath === null || !injectRmSafe(snapshotPath, scriptPath)) {
|
|
2431
|
+
rmSafeInjectionFailed = true;
|
|
2432
|
+
runtimeLogger().warn("rmSafe: injection failed, rm will delete permanently", {
|
|
2433
|
+
scriptPath: scriptPath ?? null,
|
|
2434
|
+
snapshotPath
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
const injectionFailedMarker = rmSafeInjectionFailed ? { rmSafeInjectionFailed: true } : {};
|
|
2329
2439
|
const minimizerSettings = options?.minimizerSettings ?? DEFAULT_MINIMIZER_SETTINGS;
|
|
2330
2440
|
const minimizer = options?.minimizerEnabled ?? minimizerSettings.enabled ? buildMinimizerOptions(minimizerSettings) : void 0;
|
|
2331
2441
|
const commandCwd = resolveShellCwd(options?.cwd);
|
|
@@ -2354,6 +2464,7 @@ async function executeBash(command, options) {
|
|
|
2354
2464
|
if (options?.signal?.aborted) return {
|
|
2355
2465
|
exitCode: void 0,
|
|
2356
2466
|
cancelled: true,
|
|
2467
|
+
...injectionFailedMarker,
|
|
2357
2468
|
...await settle("Command cancelled")
|
|
2358
2469
|
};
|
|
2359
2470
|
const shellOptions = {
|
|
@@ -2432,6 +2543,7 @@ async function executeBash(command, options) {
|
|
|
2432
2543
|
return {
|
|
2433
2544
|
exitCode: void 0,
|
|
2434
2545
|
cancelled: true,
|
|
2546
|
+
...injectionFailedMarker,
|
|
2435
2547
|
...winner.kind === "timeout" ? { timedOut: true } : {},
|
|
2436
2548
|
...await settle(winner.kind === "timeout" && deadlineTimeoutMs !== void 0 ? `Command timed out after ${Math.round(deadlineTimeoutMs / 1e3)} seconds` : "Command cancelled")
|
|
2437
2549
|
};
|
|
@@ -2448,6 +2560,7 @@ async function executeBash(command, options) {
|
|
|
2448
2560
|
exitCode: void 0,
|
|
2449
2561
|
cancelled: true,
|
|
2450
2562
|
timedOut: true,
|
|
2563
|
+
...injectionFailedMarker,
|
|
2451
2564
|
...await settle(annotation)
|
|
2452
2565
|
};
|
|
2453
2566
|
}
|
|
@@ -2457,6 +2570,7 @@ async function executeBash(command, options) {
|
|
|
2457
2570
|
return {
|
|
2458
2571
|
exitCode: void 0,
|
|
2459
2572
|
cancelled: true,
|
|
2573
|
+
...injectionFailedMarker,
|
|
2460
2574
|
...await settle("Command cancelled")
|
|
2461
2575
|
};
|
|
2462
2576
|
}
|
|
@@ -2473,6 +2587,7 @@ async function executeBash(command, options) {
|
|
|
2473
2587
|
return {
|
|
2474
2588
|
exitCode: winner.result.exitCode,
|
|
2475
2589
|
cancelled: false,
|
|
2590
|
+
...injectionFailedMarker,
|
|
2476
2591
|
minimized: minimized && minimized.text !== minimized.originalText ? {
|
|
2477
2592
|
filter: minimized.filter,
|
|
2478
2593
|
inputBytes: minimized.inputBytes,
|
|
@@ -3166,6 +3281,7 @@ function renderBashResult(value) {
|
|
|
3166
3281
|
const truncation = formatTruncationNotice(value.output);
|
|
3167
3282
|
if (truncation !== void 0) notices.push(...truncation);
|
|
3168
3283
|
else if (value.output.spillPath !== void 0) notices.push(`[full raw stream saved: ${value.output.spillPath}]`);
|
|
3284
|
+
if (value.rmSafeInjectionFailed === true) notices.push("[Safe rm injection failed: rm deletes permanently — use `command rm` to bypass]");
|
|
3169
3285
|
const markers = [];
|
|
3170
3286
|
if (value.timedOut) markers.push(`[timed out after ${value.timeoutMs ?? "?"}ms]`);
|
|
3171
3287
|
else if (value.exitCode !== 0) markers.push(`[exit code: ${value.exitCode}]`);
|
|
@@ -3229,6 +3345,15 @@ const TOOL_PLUS_FIELDS = [
|
|
|
3229
3345
|
group: "behavior",
|
|
3230
3346
|
tool: "bash"
|
|
3231
3347
|
},
|
|
3348
|
+
{
|
|
3349
|
+
name: "rmSafe",
|
|
3350
|
+
kind: "boolean",
|
|
3351
|
+
default: true,
|
|
3352
|
+
labelKey: "rmSafe",
|
|
3353
|
+
hintKey: "rmSafeHint",
|
|
3354
|
+
group: "behavior",
|
|
3355
|
+
tool: "bash"
|
|
3356
|
+
},
|
|
3232
3357
|
{
|
|
3233
3358
|
name: "useShellCommandWrapper",
|
|
3234
3359
|
kind: "boolean",
|
|
@@ -3895,6 +4020,7 @@ const Config = z.object({
|
|
|
3895
4020
|
interceptorEnabled: z.boolean().default(fieldDefault("interceptorEnabled", true)),
|
|
3896
4021
|
nonInteractiveEnv: z.boolean().default(fieldDefault("nonInteractiveEnv", true)),
|
|
3897
4022
|
snapshotEnabled: z.boolean().default(fieldDefault("snapshotEnabled", true)),
|
|
4023
|
+
rmSafe: z.boolean().default(fieldDefault("rmSafe", true)),
|
|
3898
4024
|
useShellCommandWrapper: z.boolean().default(fieldDefault("useShellCommandWrapper", false)),
|
|
3899
4025
|
maxBackgroundJobs: z.number().default(fieldDefault("maxBackgroundJobs", 15)),
|
|
3900
4026
|
outputTruncateStrategy: z.union(["bytes", "lines"]).default(fieldDefault("outputTruncateStrategy", "bytes")),
|
|
@@ -3987,6 +4113,7 @@ function resolveConfig(config) {
|
|
|
3987
4113
|
interceptorEnabled: config.interceptorEnabled ?? fieldDefault("interceptorEnabled", true),
|
|
3988
4114
|
nonInteractiveEnv: config.nonInteractiveEnv ?? fieldDefault("nonInteractiveEnv", true),
|
|
3989
4115
|
snapshotEnabled: config.snapshotEnabled ?? fieldDefault("snapshotEnabled", true),
|
|
4116
|
+
rmSafe: config.rmSafe ?? fieldDefault("rmSafe", true),
|
|
3990
4117
|
useShellCommandWrapper: config.useShellCommandWrapper ?? fieldDefault("useShellCommandWrapper", false),
|
|
3991
4118
|
maxBackgroundJobs: config.maxBackgroundJobs ?? fieldDefault("maxBackgroundJobs", 15),
|
|
3992
4119
|
outputTruncate: {
|
|
@@ -4196,27 +4323,145 @@ function probeBrowsers(env = defaultBrowserDetectEnv()) {
|
|
|
4196
4323
|
return found;
|
|
4197
4324
|
}
|
|
4198
4325
|
//#endregion
|
|
4326
|
+
//#region src/tools/bash/rm-safe-status.ts
|
|
4327
|
+
/**
|
|
4328
|
+
* rmSafe 注入状态查询:文件层(快照标记 + 脚本 + trash-cli 产物)与
|
|
4329
|
+
* 运行时层(一次性 bash 会话 source 快照后探测 `rm` 是否被重定义)。
|
|
4330
|
+
*
|
|
4331
|
+
* 分层语义:
|
|
4332
|
+
* - 文件层成功只证明"注入已写入快照",不证明"会话里生效"——快照是会话
|
|
4333
|
+
* 创建时 source 的,注入只对之后创建的会话生效;trash-cli 产物缺失或
|
|
4334
|
+
* node 路径不可用时函数调用会直接失败。
|
|
4335
|
+
* - 运行时层用一次性 bash 会话模拟"新会话创建"(source 快照 + `type rm`),
|
|
4336
|
+
* 验证的正是注入承诺的语义:新会话里 `rm` 是函数而非系统命令。探测
|
|
4337
|
+
* 无副作用,不触碰任何真实会话。
|
|
4338
|
+
* @module @xiaoso/dsh-tool-plus/bash/rm-safe-status
|
|
4339
|
+
*/
|
|
4340
|
+
/**
|
|
4341
|
+
* 解析 `type rm` / `declare -f rm` 探测输出。
|
|
4342
|
+
* `rm is a function` → 函数重定义生效;其余(`rm is /usr/bin/rm`、
|
|
4343
|
+
* `rm is hashed (...)`、空输出)→ 系统命令。
|
|
4344
|
+
*/
|
|
4345
|
+
function parseProbeOutput(stdout) {
|
|
4346
|
+
return /rm is a function/.test(stdout) ? "function" : "system";
|
|
4347
|
+
}
|
|
4348
|
+
/**
|
|
4349
|
+
* Windows 路径转 Git Bash 形式(`C:\Users\x` → `/c/Users/x`)。
|
|
4350
|
+
* 非 Windows 原样返回;非盘符路径仅把反斜杠转正斜杠。
|
|
4351
|
+
*/
|
|
4352
|
+
function toGitBashPath(p) {
|
|
4353
|
+
if (process.platform !== "win32") return p;
|
|
4354
|
+
const drive = /^([A-Za-z]):[\\/](.*)$/.exec(p);
|
|
4355
|
+
if (drive === null) return p.replace(/\\/g, "/");
|
|
4356
|
+
return `/${drive[1].toLowerCase()}/${drive[2].replace(/\\/g, "/")}`;
|
|
4357
|
+
}
|
|
4358
|
+
/** 默认 spawn 实现:收集 stdout,超时 kill 并标记 timedOut。 */
|
|
4359
|
+
const defaultSpawnDeps = { spawnShell: (shell, args, timeoutMs) => new Promise((resolve, reject) => {
|
|
4360
|
+
const child = spawn(shell, args, { stdio: [
|
|
4361
|
+
"ignore",
|
|
4362
|
+
"pipe",
|
|
4363
|
+
"pipe"
|
|
4364
|
+
] });
|
|
4365
|
+
let stdout = "";
|
|
4366
|
+
let timedOut = false;
|
|
4367
|
+
const timer = setTimeout(() => {
|
|
4368
|
+
timedOut = true;
|
|
4369
|
+
child.kill();
|
|
4370
|
+
}, timeoutMs);
|
|
4371
|
+
child.stdout.on("data", (chunk) => {
|
|
4372
|
+
stdout += chunk.toString();
|
|
4373
|
+
});
|
|
4374
|
+
child.on("error", (error) => {
|
|
4375
|
+
clearTimeout(timer);
|
|
4376
|
+
reject(error);
|
|
4377
|
+
});
|
|
4378
|
+
child.on("close", () => {
|
|
4379
|
+
clearTimeout(timer);
|
|
4380
|
+
resolve({
|
|
4381
|
+
stdout,
|
|
4382
|
+
timedOut
|
|
4383
|
+
});
|
|
4384
|
+
});
|
|
4385
|
+
}) };
|
|
4386
|
+
/**
|
|
4387
|
+
* 运行时探测:在一次性 bash 会话里 source 快照(模拟新会话创建),
|
|
4388
|
+
* 然后 `type rm` + `declare -f rm` 看 `rm` 是否被重定义为函数。
|
|
4389
|
+
* spawn 失败或超时 → 'unknown'(探测不可用,不代表注入失败)。
|
|
4390
|
+
*/
|
|
4391
|
+
async function probeRmSafeRuntime(shell, snapshotPath, deps = defaultSpawnDeps, timeoutMs = 5e3) {
|
|
4392
|
+
const script = `source ${quote(toGitBashPath(snapshotPath))}; type rm; declare -f rm`;
|
|
4393
|
+
try {
|
|
4394
|
+
const { stdout, timedOut } = await deps.spawnShell(shell, ["-c", script], timeoutMs);
|
|
4395
|
+
if (timedOut) return "unknown";
|
|
4396
|
+
return parseProbeOutput(stdout);
|
|
4397
|
+
} catch {
|
|
4398
|
+
return "unknown";
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
/**
|
|
4402
|
+
* 查询 rmSafe 注入状态(查询即确保注入:幂等,与 executeBash 的注入
|
|
4403
|
+
* 路径一致)。rmSafe 关闭时调用方不应调用本函数(返回 disabled 由
|
|
4404
|
+
* 调用方短路);此处仍兜底返回 disabled 语义由调用方保证。
|
|
4405
|
+
*/
|
|
4406
|
+
async function queryRmSafeStatus(deps) {
|
|
4407
|
+
const snapshotPath = await deps.getOrCreateSnapshot();
|
|
4408
|
+
if (snapshotPath === null) return {
|
|
4409
|
+
status: "failed",
|
|
4410
|
+
reason: "snapshot-unavailable"
|
|
4411
|
+
};
|
|
4412
|
+
if (!deps.cliExists()) return {
|
|
4413
|
+
status: "failed",
|
|
4414
|
+
reason: "cli-missing"
|
|
4415
|
+
};
|
|
4416
|
+
const scriptPath = deps.ensureScript();
|
|
4417
|
+
if (scriptPath === null) return {
|
|
4418
|
+
status: "failed",
|
|
4419
|
+
reason: "script-write-failed"
|
|
4420
|
+
};
|
|
4421
|
+
if (!deps.inject(snapshotPath, scriptPath)) return {
|
|
4422
|
+
status: "failed",
|
|
4423
|
+
reason: "snapshot-write-failed"
|
|
4424
|
+
};
|
|
4425
|
+
const runtime = await deps.probe(snapshotPath);
|
|
4426
|
+
if (runtime === "system") return {
|
|
4427
|
+
status: "failed",
|
|
4428
|
+
reason: "runtime-not-effective"
|
|
4429
|
+
};
|
|
4430
|
+
return {
|
|
4431
|
+
status: "injected",
|
|
4432
|
+
runtime
|
|
4433
|
+
};
|
|
4434
|
+
}
|
|
4435
|
+
//#endregion
|
|
4199
4436
|
//#region src/host/browser-probe-rpc.ts
|
|
4200
4437
|
/**
|
|
4201
|
-
*
|
|
4438
|
+
* Host half of the tool-plus RPC channel: registers the `/tool-plus` logical
|
|
4439
|
+
* channel on the official Connection transport and answers
|
|
4440
|
+
* `browser/detect` (probe the machine for usable browsers) and
|
|
4441
|
+
* `rmSafe/status` (query = ensure + verify the rmSafe injection). The
|
|
4442
|
+
* settings panel (client half) calls these through
|
|
4443
|
+
* `createWebConnectionRpc().call(...)` — the documented public RPC surface
|
|
4444
|
+
* of `@deepseek-ai/dsh-client-connection`, not a private plugin hook.
|
|
4445
|
+
*
|
|
4446
|
+
* Trust policy is `loopback`: the probe only reports local filesystem paths,
|
|
4447
|
+
* so it must never be reachable from a non-loopback host. Registration is
|
|
4448
|
+
* best-effort — if `connection` is missing (non-web deployments) or the
|
|
4449
|
+
* channel is already taken, the endpoints are simply unavailable and the
|
|
4450
|
+
* panel shows "Unavailable".
|
|
4451
|
+
* @module @xiaoso/dsh-tool-plus/host/browser-probe-rpc
|
|
4452
|
+
*/
|
|
4453
|
+
/**
|
|
4454
|
+
* Install the tool-plus RPC endpoints. Returns a disposer that removes
|
|
4202
4455
|
* the registration (call it from the plugin's `ctx.effect` teardown).
|
|
4203
4456
|
* Never throws for "unavailable" — the return value tells the caller whether
|
|
4204
4457
|
* the channel is actually live.
|
|
4205
4458
|
*/
|
|
4206
|
-
function installBrowserProbeRpc(ctx) {
|
|
4459
|
+
function installBrowserProbeRpc(ctx, deps) {
|
|
4207
4460
|
let disposer;
|
|
4208
4461
|
ctx.inject(["connection"], (connectionCtx) => {
|
|
4209
4462
|
try {
|
|
4210
4463
|
const handler = async (endpoint, _payload, _signal) => {
|
|
4211
|
-
if (endpoint
|
|
4212
|
-
ok: false,
|
|
4213
|
-
error: {
|
|
4214
|
-
code: "bad-request",
|
|
4215
|
-
message: `Unknown tool-plus endpoint: ${endpoint}`,
|
|
4216
|
-
details: { issues: [] }
|
|
4217
|
-
}
|
|
4218
|
-
};
|
|
4219
|
-
try {
|
|
4464
|
+
if (endpoint === "browser/detect") try {
|
|
4220
4465
|
return {
|
|
4221
4466
|
ok: true,
|
|
4222
4467
|
value: { found: probeBrowsers() }
|
|
@@ -4231,6 +4476,42 @@ function installBrowserProbeRpc(ctx) {
|
|
|
4231
4476
|
}
|
|
4232
4477
|
};
|
|
4233
4478
|
}
|
|
4479
|
+
if (endpoint === "rmSafe/status") {
|
|
4480
|
+
if (!deps.getRmSafe()) return {
|
|
4481
|
+
ok: true,
|
|
4482
|
+
value: { status: "disabled" }
|
|
4483
|
+
};
|
|
4484
|
+
try {
|
|
4485
|
+
const { shell, env } = getShellConfig();
|
|
4486
|
+
return {
|
|
4487
|
+
ok: true,
|
|
4488
|
+
value: await queryRmSafeStatus({
|
|
4489
|
+
getOrCreateSnapshot: () => getOrCreateSnapshot(shell, env),
|
|
4490
|
+
cliExists: () => fs.existsSync(rmSafeCliPath()),
|
|
4491
|
+
ensureScript: () => ensureRmSafeScript(rmSafeScriptDir(), process.execPath, rmSafeCliPath()),
|
|
4492
|
+
inject: (snapshotPath, scriptPath) => injectRmSafe(snapshotPath, scriptPath),
|
|
4493
|
+
probe: (snapshotPath) => probeRmSafeRuntime(shell, snapshotPath)
|
|
4494
|
+
})
|
|
4495
|
+
};
|
|
4496
|
+
} catch (error) {
|
|
4497
|
+
return {
|
|
4498
|
+
ok: false,
|
|
4499
|
+
error: {
|
|
4500
|
+
code: "internal",
|
|
4501
|
+
message: error instanceof Error ? error.message : String(error),
|
|
4502
|
+
details: {}
|
|
4503
|
+
}
|
|
4504
|
+
};
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
return {
|
|
4508
|
+
ok: false,
|
|
4509
|
+
error: {
|
|
4510
|
+
code: "bad-request",
|
|
4511
|
+
message: `Unknown tool-plus endpoint: ${endpoint}`,
|
|
4512
|
+
details: { issues: [] }
|
|
4513
|
+
}
|
|
4514
|
+
};
|
|
4234
4515
|
};
|
|
4235
4516
|
disposer = connectionCtx.connection.rpc.handle(TOOL_PLUS_RPC_CHANNEL, handler, { authority: "loopback" });
|
|
4236
4517
|
} catch {
|
|
@@ -14555,7 +14836,7 @@ let specialHandlersPromise;
|
|
|
14555
14836
|
* requires a special handler, so we keep them out of the cold-startup graph.
|
|
14556
14837
|
*/
|
|
14557
14838
|
function loadSpecialHandlers() {
|
|
14558
|
-
specialHandlersPromise ??= import("./scrapers-
|
|
14839
|
+
specialHandlersPromise ??= import("./scrapers-DI8yWlu_.mjs").then((m) => m.specialHandlers);
|
|
14559
14840
|
return specialHandlersPromise;
|
|
14560
14841
|
}
|
|
14561
14842
|
/**
|
|
@@ -25704,6 +25985,7 @@ function buildForeground(result, wallTimeMs, timeoutMs) {
|
|
|
25704
25985
|
wallTimeMs,
|
|
25705
25986
|
...result.workingDir !== void 0 ? { workingDir: result.workingDir } : {},
|
|
25706
25987
|
...result.minimized !== void 0 ? { minimized: result.minimized } : {},
|
|
25988
|
+
...result.rmSafeInjectionFailed === true ? { rmSafeInjectionFailed: true } : {},
|
|
25707
25989
|
output: {
|
|
25708
25990
|
text: result.output,
|
|
25709
25991
|
truncated: result.truncated,
|
|
@@ -25772,7 +26054,7 @@ function apply(ctx, config = {}) {
|
|
|
25772
26054
|
cfg = current();
|
|
25773
26055
|
registerModeSensitive();
|
|
25774
26056
|
});
|
|
25775
|
-
const disposeBrowserProbe = installBrowserProbeRpc(ctx);
|
|
26057
|
+
const disposeBrowserProbe = installBrowserProbeRpc(ctx, { getRmSafe: () => cfg.rmSafe });
|
|
25776
26058
|
ctx.systemPrompt.section({
|
|
25777
26059
|
name: "tool:bash",
|
|
25778
26060
|
order: 105,
|
|
@@ -26028,6 +26310,7 @@ function apply(ctx, config = {}) {
|
|
|
26028
26310
|
headBytes: cfg.outputSinkHeadBytes,
|
|
26029
26311
|
useShellCommandWrapper: cfg.useShellCommandWrapper,
|
|
26030
26312
|
snapshotEnabled: cfg.snapshotEnabled,
|
|
26313
|
+
rmSafe: cfg.rmSafe,
|
|
26031
26314
|
nonInteractiveEnv: cfg.nonInteractiveEnv,
|
|
26032
26315
|
artifactPath: allocateSpillFile()
|
|
26033
26316
|
});
|
|
@@ -26085,6 +26368,7 @@ function apply(ctx, config = {}) {
|
|
|
26085
26368
|
headBytes: cfg.outputSinkHeadBytes,
|
|
26086
26369
|
useShellCommandWrapper: cfg.useShellCommandWrapper,
|
|
26087
26370
|
snapshotEnabled: cfg.snapshotEnabled,
|
|
26371
|
+
rmSafe: cfg.rmSafe,
|
|
26088
26372
|
nonInteractiveEnv: cfg.nonInteractiveEnv,
|
|
26089
26373
|
artifactPath: allocateSpillFile()
|
|
26090
26374
|
});
|
|
@@ -3,7 +3,7 @@ __installBunShim();
|
|
|
3
3
|
import { n as fs_promises_shim_exports } from "./fs-promises-shim-Bx0fpxuk.mjs";
|
|
4
4
|
import { B as USER_AGENT, G as getDocsRsCacheDir, N as formatNumber, P as $env, T as warn, c as combineSignals, j as formatBytes, l as exec, s as Snowflake, x as parseFrontmatter, y as isEnoent } from "./src-D_e1qMwi.mjs";
|
|
5
5
|
import { V as tryParseJson } from "./utils-fnV2zzp3.mjs";
|
|
6
|
-
import { a as fetchBinary, c as getParallelExtractContent, i as convertWithMarkit, l as ensureTool, m as settings, n as asRecord, o as extractWithParallel, r as asString, s as findParallelApiKey, t as asNumber } from "./utils-
|
|
6
|
+
import { a as fetchBinary, c as getParallelExtractContent, i as convertWithMarkit, l as ensureTool, m as settings, n as asRecord, o as extractWithParallel, r as asString, s as findParallelApiKey, t as asNumber } from "./utils-D1p8-H8L.mjs";
|
|
7
7
|
import { n as isRecord } from "./type-guards-B9VVjRDK.mjs";
|
|
8
8
|
import { r as throwIfAborted, t as ToolAbortError } from "./tool-errors-DoyxivAR.mjs";
|
|
9
9
|
import { c as getLocalizedText, d as looksLikeHtml, i as decodeHtmlEntities, l as htmlToBasicMarkdown, o as formatIsoDate, r as buildResult, s as formatMediaDuration, u as loadPage } from "./types-BUO0JXO-.mjs";
|