dsh-vscode-mode 0.1.21 → 0.1.23

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/lib/index.js CHANGED
@@ -378,6 +378,7 @@ function detectExternal(ctx, depsAvailable) {
378
378
  const mcpCount = entriesOf(ctx).length;
379
379
  const settings = ctx.get("settings");
380
380
  const hasSettings = Boolean(settings?.describe || settings?.update);
381
+ const sub = ctx.get("subprocess");
381
382
  return [
382
383
  {
383
384
  name: MCP_PACKAGE,
@@ -393,6 +394,11 @@ function detectExternal(ctx, depsAvailable) {
393
394
  name: "settings 服务",
394
395
  active: hasSettings,
395
396
  note: hasSettings ? "可读写设置" : "不可用(设置读写走配置回退)"
397
+ },
398
+ {
399
+ name: "文件浏览器打开(subprocess 服务)",
400
+ active: typeof sub?.spawn === "function",
401
+ note: typeof sub?.spawn === "function" ? "可定位/打开 OS 文件浏览器" : "不可用(右键「在文件浏览器中打开」将提示失败)"
396
402
  }
397
403
  ];
398
404
  }
@@ -2451,6 +2457,73 @@ function toTreeEntries(rel, children) {
2451
2457
  return out.sort(entryOrder);
2452
2458
  }
2453
2459
  //#endregion
2460
+ //#region src/reveal.ts
2461
+ /**
2462
+ * dsh-vscode-mode host — 在 OS 文件浏览器中打开/定位路径(reveal 能力)。
2463
+ * 纯函数 revealCommand 平台分发 + revealInExplorer 经 ctx.subprocess.spawn 发射
2464
+ * (argv 数组、无 shell 插值,沿 workspace/revert 的 subprocess 契约)。
2465
+ * 文件 → 资源管理器选中定位;目录 → 打开目录;Linux 无通用定位协议 → 打开所在目录。
2466
+ * 作者 ddj 2026-08-27
2467
+ */
2468
+ /**
2469
+ * 构造平台 opener 的 argv(纯函数,platform 可注入便于单测)。
2470
+ * @author ddj 2026年08月27号
2471
+ * @param absPath 绝对路径
2472
+ * @param isDir 是否为目录
2473
+ * @param platform 目标平台(缺省当前进程平台)
2474
+ * @returns opener argv(无 shell 插值)
2475
+ */
2476
+ function revealCommand(absPath, isDir, platform = process.platform) {
2477
+ switch (platform) {
2478
+ case "darwin": return { argv: [
2479
+ "open",
2480
+ "-R",
2481
+ absPath
2482
+ ] };
2483
+ case "win32": return isDir ? { argv: ["explorer.exe", absPath] } : { argv: [
2484
+ "explorer.exe",
2485
+ "/select,",
2486
+ absPath
2487
+ ] };
2488
+ default: return { argv: ["xdg-open", isDir ? absPath : dirname(absPath)] };
2489
+ }
2490
+ }
2491
+ /**
2492
+ * 经 host subprocess 服务发射 opener(fire-and-forget 语义)。
2493
+ * Explorer 为 GUI 分离进程,非零退出码不视为失败;仅 spawn 级错误回失败。
2494
+ * @author ddj 2026年08月27号
2495
+ * @param ctx DSH host 上下文
2496
+ * @param absPath 绝对路径
2497
+ * @param isDir 是否为目录
2498
+ * @returns 成功或失败原因
2499
+ */
2500
+ async function revealInExplorer(ctx, absPath, isDir) {
2501
+ const sub = ctx.get("subprocess");
2502
+ if (!sub || typeof sub.spawn !== "function") return {
2503
+ ok: false,
2504
+ error: "打开文件浏览器不可用:缺少 subprocess 服务"
2505
+ };
2506
+ const { argv } = revealCommand(absPath, isDir);
2507
+ try {
2508
+ await sub.spawn({
2509
+ argv,
2510
+ cwd: dirname(absPath),
2511
+ stdio: {
2512
+ stdout: { maxBytes: 65536 },
2513
+ stderr: { maxBytes: 65536 },
2514
+ stdin: "ignore"
2515
+ },
2516
+ graceMs: 1e4
2517
+ }).done;
2518
+ return { ok: true };
2519
+ } catch (error) {
2520
+ return {
2521
+ ok: false,
2522
+ error: "打开失败:" + String(error)
2523
+ };
2524
+ }
2525
+ }
2526
+ //#endregion
2454
2527
  //#region src/rpc.ts
2455
2528
  /** cwd → Promise 链:串行化 debug 日志追加(fs read+write 非原子,避免并发丢行)。 */
2456
2529
  const debugWriteQueues = /* @__PURE__ */ new Map();
@@ -2925,6 +2998,46 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx)) {
2925
2998
  };
2926
2999
  }
2927
3000
  },
3001
+ "edrv.revealInExplorer": async (args) => {
3002
+ const sc = await requireSession(ctx, args.sessionId);
3003
+ if ("err" in sc) return {
3004
+ ok: false,
3005
+ error: sc.err
3006
+ };
3007
+ const fs = ctx.get("fs");
3008
+ if (!fs) return {
3009
+ ok: false,
3010
+ error: "缺少 fs"
3011
+ };
3012
+ try {
3013
+ const rel = normalizeRel(args.path);
3014
+ if (rel === null) return {
3015
+ ok: false,
3016
+ error: "路径不合法"
3017
+ };
3018
+ const target = await fs.resolve(rel || ".", { cwd: sc.cwd });
3019
+ const info = await fs.stat(target);
3020
+ if (!info) return {
3021
+ ok: false,
3022
+ error: "路径不存在"
3023
+ };
3024
+ const abs = fs.processPath(target);
3025
+ const outcome = await revealInExplorer(ctx, abs, info.type === "directory");
3026
+ if (!outcome.ok) return {
3027
+ ok: false,
3028
+ error: outcome.error
3029
+ };
3030
+ return {
3031
+ ok: true,
3032
+ revealed: abs
3033
+ };
3034
+ } catch (error) {
3035
+ return {
3036
+ ok: false,
3037
+ error: "打开失败:" + String(error)
3038
+ };
3039
+ }
3040
+ },
2928
3041
  "mcp.list": async () => ({
2929
3042
  ok: true,
2930
3043
  ...listMcp(ctx)