dsh-code-ui 1.6.0 → 1.6.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/lib/index.js +54 -10
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { Remote, bindTypertRemote } from "@deepseek-ai/dsh-typert-protocol";
|
|
|
8
8
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
9
9
|
import z from "@deepseek-ai/schemastery";
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
11
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, watch, writeFileSync } from "node:fs";
|
|
11
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
12
12
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
|
|
@@ -696,7 +696,48 @@ function unwatchAllFiles() {
|
|
|
696
696
|
}
|
|
697
697
|
|
|
698
698
|
function rpcHandler(ctx) {
|
|
699
|
-
|
|
699
|
+
// fs 服务延迟获取:插件 apply 可能早于 base bundle 的 fs-sandbox 行挂载,
|
|
700
|
+
// apply 时一次性 ctx.get("fs") 会永久取到 undefined(0.2.0-rc.2 实测:
|
|
701
|
+
// list-dir/stat-path/read-file 全部抛 "Cannot read properties of
|
|
702
|
+
// undefined (reading 'resolve')")。改为 inject 等待 + 调用时解析。
|
|
703
|
+
let fsSvc = null;
|
|
704
|
+
try { fsSvc = ctx.get("fs") || null; } catch (_) {}
|
|
705
|
+
try {
|
|
706
|
+
ctx.inject(["fs"], (c) => { fsSvc = c.fs || fsSvc; });
|
|
707
|
+
} catch (_) {}
|
|
708
|
+
// 宿主组合始终无 fs 服务时的原生兜底:与 fs-mkdir/write-file 等磁盘端点
|
|
709
|
+
// 同一权限模型(编辑器是用户显式操作面,本就读写真实磁盘)。
|
|
710
|
+
const nativeFsShim = {
|
|
711
|
+
resolve: async (p) => p,
|
|
712
|
+
stat: async (p) => {
|
|
713
|
+
try {
|
|
714
|
+
const s = statSync(p);
|
|
715
|
+
return { type: s.isDirectory() ? "directory" : s.isFile() ? "file" : "other" };
|
|
716
|
+
} catch (_) { return null; }
|
|
717
|
+
},
|
|
718
|
+
listDir: async (p) => {
|
|
719
|
+
const sep = String(p).includes("\\") ? "\\" : "/";
|
|
720
|
+
return readdirSync(p, { withFileTypes: true }).map((d) => ({
|
|
721
|
+
name: d.name,
|
|
722
|
+
path: String(p).replace(/[\\/]+$/, "") + sep + d.name,
|
|
723
|
+
isDir: d.isDirectory(),
|
|
724
|
+
}));
|
|
725
|
+
},
|
|
726
|
+
readBytes: async (p, _signal, maxBytes) => {
|
|
727
|
+
const b = readFileSync(p);
|
|
728
|
+
if (b.length > maxBytes) {
|
|
729
|
+
const e = new Error("file too large");
|
|
730
|
+
e.code = "FS_TOO_LARGE";
|
|
731
|
+
throw e;
|
|
732
|
+
}
|
|
733
|
+
return new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
|
|
734
|
+
},
|
|
735
|
+
readByteRange: async (p, range) => {
|
|
736
|
+
const b = readFileSync(p);
|
|
737
|
+
return new Uint8Array(b.buffer, b.byteOffset, b.byteLength).slice(range.offset, range.offset + range.length);
|
|
738
|
+
},
|
|
739
|
+
};
|
|
740
|
+
const getFs = () => fsSvc || nativeFsShim;
|
|
700
741
|
let currentRootRef = detectWorkspaceRoot(ctx); // 最近一次解析的根:list-dir 的兜底
|
|
701
742
|
// 每会话根目录记忆:sessionId -> 手动设置的根(「以此文件夹为根」)。
|
|
702
743
|
// 按会话隔离——手动根只影响设置它的那个会话,不再全局污染其它会话的自动解析;
|
|
@@ -792,8 +833,9 @@ function rpcHandler(ctx) {
|
|
|
792
833
|
if (endpoint === "list-dir") {
|
|
793
834
|
const path = args.path || currentRootRef;
|
|
794
835
|
if (!path) return ok({ error: "no-root" });
|
|
795
|
-
const
|
|
796
|
-
const
|
|
836
|
+
const fsvc = getFs();
|
|
837
|
+
const target = await fsvc.resolve(path);
|
|
838
|
+
const entries = await fsvc.listDir(target);
|
|
797
839
|
const sep = String(path).includes("\\") ? "\\" : "/";
|
|
798
840
|
const list = entries.map((e) => {
|
|
799
841
|
let name = null;
|
|
@@ -820,8 +862,9 @@ function rpcHandler(ctx) {
|
|
|
820
862
|
|
|
821
863
|
if (endpoint === "stat-path") {
|
|
822
864
|
if (!args.path) return ok({ error: "no-path" });
|
|
823
|
-
const
|
|
824
|
-
const
|
|
865
|
+
const fsvc = getFs();
|
|
866
|
+
const target = await fsvc.resolve(args.path);
|
|
867
|
+
const info = await fsvc.stat(target);
|
|
825
868
|
// DSH fs.stat 返回 FsInfo { type: 'file' | 'directory' | 'other' },
|
|
826
869
|
// 没有 isDirectory/isFile 布尔字段;必须按 type 判定(原实现恒为 false,
|
|
827
870
|
// 导致目录路径被误报为「不存在」)。保留遗留字段兼容。
|
|
@@ -833,19 +876,20 @@ function rpcHandler(ctx) {
|
|
|
833
876
|
|
|
834
877
|
if (endpoint === "read-file") {
|
|
835
878
|
if (!args.path) return ok({ error: "no-path" });
|
|
836
|
-
const
|
|
879
|
+
const fsvc = getFs();
|
|
880
|
+
const target = await fsvc.resolve(args.path);
|
|
837
881
|
// 对齐 dsh ≥ 0.1.7-rc.1 的二进制 Remote 结果契约:字节经 Uint8Array 字段返回,
|
|
838
882
|
// gateway 自动投影为 multipart 附件下发;0.1.5/0.1.6 老宿主无投影则走 JSON
|
|
839
883
|
// (Uint8Array 落成下标对象,客户端负责重建),同一份代码两端兼容。
|
|
840
884
|
// 取代旧的 60 万字符文本截断——截断内容若被 Ctrl+S 原样写回会写坏大文件。
|
|
841
885
|
const MAX = 4 * 1024 * 1024; // 单次读取上限 4MB
|
|
842
886
|
try {
|
|
843
|
-
const bytes = await
|
|
887
|
+
const bytes = await fsvc.readBytes(target, undefined, MAX);
|
|
844
888
|
return ok({ path: args.path, bytes, truncated: false, size: bytes.length });
|
|
845
889
|
} catch (err) {
|
|
846
|
-
if (!err || err.code !== "FS_TOO_LARGE" || typeof
|
|
890
|
+
if (!err || err.code !== "FS_TOO_LARGE" || typeof fsvc.readByteRange !== "function") throw err;
|
|
847
891
|
// 超限文件读前 4MB,标记 truncated;客户端据此禁写并提示
|
|
848
|
-
const bytes = await
|
|
892
|
+
const bytes = await fsvc.readByteRange(target, { offset: 0, length: MAX });
|
|
849
893
|
return ok({ path: args.path, bytes, truncated: true });
|
|
850
894
|
}
|
|
851
895
|
}
|