dsh-vscode-mode 0.4.3 → 0.4.4
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.md +18 -1
- package/lib/client.js +681 -26
- package/lib/client.js.map +1 -1
- package/lib/index.js +240 -21
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/events.ts +11 -0
- package/src/client/sidebar/panels/FileExplorer.ts +47 -0
- package/src/client/ui/EditorView.ts +285 -25
- package/src/client/ui/useFileWatch.ts +214 -0
- package/src/client/watchDecision.ts +274 -0
- package/src/compat.ts +1 -1
- package/src/dshVersion.ts +42 -10
- package/src/fileOpenSettings.ts +26 -2
- package/src/fileVersions.ts +167 -0
- package/src/index.ts +6 -1
- package/src/rpc.ts +50 -7
- package/src/shared/rpc.ts +23 -3
package/lib/index.js
CHANGED
|
@@ -259,6 +259,15 @@ function bindHostLog(ctx) {
|
|
|
259
259
|
}
|
|
260
260
|
//#endregion
|
|
261
261
|
//#region src/fileOpenSettings.ts
|
|
262
|
+
/**
|
|
263
|
+
* dsh-vscode-mode host — 文件链接打开工具 + 快捷键的持久化设置。
|
|
264
|
+
* 依赖守卫:schemastery 动态加载;@deepseek-ai/dsh-settings 仅作 legacy 探测——
|
|
265
|
+
* rc 线导出 installSettingsSection free function,0.1.2-alpha 起移除(改由 settings
|
|
266
|
+
* 服务的 installSection 方法承载,见 runSettingsInstall 三策略)。
|
|
267
|
+
* 缺失/任一策略失败时插件仍可加载(fileOpenTool 降级为配置值,compat 报告可见),
|
|
268
|
+
* 全程 try/catch,不产生未捕获 rejection。
|
|
269
|
+
* 作者 ddj 2026年08月24号 / 2026年08月26号 / 2026年09月02号
|
|
270
|
+
*/
|
|
262
271
|
const FILE_OPEN_SETTINGS_NS = "dsh-vscode-mode";
|
|
263
272
|
const FILE_OPEN_DEFAULT = "auto";
|
|
264
273
|
/** AI 内联补全配置默认值(默认关闭;路由空 = 自动;档位空 = 跟随模型默认)。 */
|
|
@@ -270,14 +279,31 @@ const AI_CONFIG_DEFAULT = {
|
|
|
270
279
|
};
|
|
271
280
|
let depsPromise;
|
|
272
281
|
/**
|
|
282
|
+
* 宿主锚点动态导入:先经 process.argv[1](DSH 启动脚本所在树)解析并加载目标包,
|
|
283
|
+
* 避开本插件自身 node_modules 的 dev 依赖副本(dev-link 下 import() 相对插件位置
|
|
284
|
+
* 解析,会命中 rc 线旧包 dsh-settings@0.1.0-rc.8);argv[1] 缺失或解析失败回退
|
|
285
|
+
* 普通 specifier import。
|
|
286
|
+
* @author ddj 2026年09月15号
|
|
287
|
+
* @param specifier 包名
|
|
288
|
+
* @returns 加载的模块命名空间
|
|
289
|
+
*/
|
|
290
|
+
async function hostImport(specifier) {
|
|
291
|
+
const entry = process.argv[1];
|
|
292
|
+
if (entry) try {
|
|
293
|
+
const resolved = createRequire(entry).resolve(specifier);
|
|
294
|
+
return await import(pathToFileURL(resolved).href);
|
|
295
|
+
} catch {}
|
|
296
|
+
return import(specifier);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
273
299
|
* 动态加载设置依赖(模块级缓存;任一缺失/失败返回 null 而非抛错)。
|
|
274
300
|
* installSettingsSection 仅在 rc 线 dsh-settings 中存在时提供(alpha 起移除,
|
|
275
301
|
* 属性探测得到 undefined,不抛错)。
|
|
276
|
-
* @author ddj 2026年08月24号
|
|
302
|
+
* @author ddj 2026年08月24号 / 2026年09月15号
|
|
277
303
|
* @returns 设置依赖或 null
|
|
278
304
|
*/
|
|
279
305
|
function loadSettingsDeps() {
|
|
280
|
-
if (!depsPromise) depsPromise = Promise.all([
|
|
306
|
+
if (!depsPromise) depsPromise = Promise.all([hostImport("@deepseek-ai/dsh-settings"), hostImport("schemastery")]).then(([dshSettings, schemastery]) => ({
|
|
281
307
|
installSettingsSection: dshSettings.installSettingsSection,
|
|
282
308
|
z: schemastery.default
|
|
283
309
|
})).catch(() => null);
|
|
@@ -736,6 +762,8 @@ function inDshRange(version, range) {
|
|
|
736
762
|
function familyLabel(input) {
|
|
737
763
|
const version = parseDshVersion(input);
|
|
738
764
|
if (!version) return "未知";
|
|
765
|
+
if (inDshRange(version, { from: "0.1.6-alpha.1" })) return "0.1.6-alpha 及更新(MCP SDK v2 + Web 侧边栏终端 + 文件链接默认侧栏预览)";
|
|
766
|
+
if (inDshRange(version, { from: "0.1.5-alpha.1" })) return "0.1.5-alpha 及更新(官方右侧 Sidebar 编辑区 + sidebar.panellist)";
|
|
739
767
|
if (inDshRange(version, { from: "0.1.3-alpha.1" })) return "0.1.3-alpha 及更新(对话文件链接=remote.session.openWorkspacePath)";
|
|
740
768
|
if (inDshRange(version, { from: "0.1.2-alpha.1" })) return "0.1.2-alpha 及更新(设置 API=settings.installSection)";
|
|
741
769
|
return "0.1.0/0.1.1 rc 线(设置 API=installSettingsSection)";
|
|
@@ -748,19 +776,48 @@ const VERSION_CANDIDATES = [
|
|
|
748
776
|
"@deepseek-ai/dsh/package.json"
|
|
749
777
|
];
|
|
750
778
|
let detectedVersion;
|
|
779
|
+
/**
|
|
780
|
+
* 宿主入口 require 锚点:process.argv[1] 指向运行中 DSH 的启动脚本
|
|
781
|
+
* (安装树 node_modules/@deepseek-ai/dsh/... 或 profile 树),从其所在目录
|
|
782
|
+
* 向上解析能命中宿主的 @deepseek-ai 包——避开本插件自身 node_modules 里的
|
|
783
|
+
* dev 依赖副本(dev-link 安装下 import.meta.url 锚点会先命中 rc 线副本,
|
|
784
|
+
* 曾致版本探测与 deps 加载错位到 0.1.0-rc.8,见 fileOpenSettings 的 hostImport)。
|
|
785
|
+
* @author ddj 2026年09月15号
|
|
786
|
+
* @returns 宿主锚点 require;argv[1] 缺失或不可用时 undefined
|
|
787
|
+
*/
|
|
788
|
+
function hostRequire() {
|
|
789
|
+
const entry = process.argv[1];
|
|
790
|
+
if (!entry) return void 0;
|
|
791
|
+
try {
|
|
792
|
+
return createRequire(entry);
|
|
793
|
+
} catch {
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
/** 用指定 resolver 解析一个候选包版本(解析/读取失败返回 null)。 */
|
|
798
|
+
function resolveVersionOf(specifier, resolver) {
|
|
799
|
+
try {
|
|
800
|
+
const file = resolver.resolve(specifier);
|
|
801
|
+
const pkg = JSON.parse(readFileSync(file, "utf8"));
|
|
802
|
+
if (typeof pkg.version === "string" && parseDshVersion(pkg.version) !== null) return pkg.version;
|
|
803
|
+
} catch {}
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
751
806
|
/** 探测运行中 DSH 核心版本(模块级缓存;失败空串,不抛错)。 */
|
|
752
807
|
function detectDshVersion() {
|
|
753
808
|
if (detectedVersion !== void 0) return detectedVersion;
|
|
754
809
|
detectedVersion = "";
|
|
755
|
-
const
|
|
756
|
-
for (const
|
|
757
|
-
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
810
|
+
const resolvers = [hostRequire(), createRequire(import.meta.url)];
|
|
811
|
+
for (const resolver of resolvers) {
|
|
812
|
+
if (!resolver) continue;
|
|
813
|
+
for (const specifier of VERSION_CANDIDATES) {
|
|
814
|
+
const version = resolveVersionOf(specifier, resolver);
|
|
815
|
+
if (version !== null) {
|
|
816
|
+
detectedVersion = version;
|
|
817
|
+
return detectedVersion;
|
|
818
|
+
}
|
|
762
819
|
}
|
|
763
|
-
}
|
|
820
|
+
}
|
|
764
821
|
return detectedVersion;
|
|
765
822
|
}
|
|
766
823
|
//#endregion
|
|
@@ -1642,7 +1699,7 @@ function detectGuards(ctx) {
|
|
|
1642
1699
|
}];
|
|
1643
1700
|
}
|
|
1644
1701
|
/** 已实测覆盖的最高 DSH 版本(适配矩阵上界,超过则提示,见 buildReport)。 */
|
|
1645
|
-
const TESTED_DSH_MAX = "0.1.
|
|
1702
|
+
const TESTED_DSH_MAX = "0.1.6-alpha.1";
|
|
1646
1703
|
/** 版本适配机制状态行:DSH 版本探测 + 设置 section 安装策略。 */
|
|
1647
1704
|
function versionAdapters(dshVersion) {
|
|
1648
1705
|
const adapters = [];
|
|
@@ -1688,7 +1745,7 @@ async function buildReport(ctx, options) {
|
|
|
1688
1745
|
if (settingsInstallStrategy() === "none") warnings.push("设置 section 安装不可用:" + settingsInstallNote());
|
|
1689
1746
|
const parsed = parseDshVersion(dshVersion);
|
|
1690
1747
|
const testedMax = parseDshVersion(TESTED_DSH_MAX);
|
|
1691
|
-
if (parsed && testedMax && compareDshVersions(parsed, testedMax) > 0) warnings.push("DSH " + dshVersion + " 高于已实测版本(0.1.
|
|
1748
|
+
if (parsed && testedMax && compareDshVersions(parsed, testedMax) > 0) warnings.push("DSH " + dshVersion + " 高于已实测版本(0.1.6-alpha.1):设置 API 按能力探测运行,异常时请回报适配矩阵");
|
|
1692
1749
|
for (const guard of guards) if (!guard.active && guard.note) warnings.push(guard.note);
|
|
1693
1750
|
return {
|
|
1694
1751
|
pluginVersion: options?.version ?? pluginVersionOf(),
|
|
@@ -6966,6 +7023,29 @@ function snippetTargetOf(path) {
|
|
|
6966
7023
|
return isSnippetFilePath(path) ? path.replace(/\\/g, "/") : null;
|
|
6967
7024
|
}
|
|
6968
7025
|
/**
|
|
7026
|
+
* 本地文件系统版本令牌(mtime+size):与 ctx.fs 的不透明版本串用途相同,
|
|
7027
|
+
* 仅供全局片段文件(走 node:fs 直读、不经 ctx.fs)在 edrv.read 时回带。
|
|
7028
|
+
* @author ddj 2026年09月15号
|
|
7029
|
+
* @param info node fs.Stats(stat 失败传 null)
|
|
7030
|
+
* @returns 版本令牌;取不到 → 空串(不启用护栏)
|
|
7031
|
+
*/
|
|
7032
|
+
function fileVersionOf(info) {
|
|
7033
|
+
if (!info || typeof info.mtimeMs !== "number" || typeof info.size !== "number") return "";
|
|
7034
|
+
return info.mtimeMs + ":" + info.size;
|
|
7035
|
+
}
|
|
7036
|
+
/**
|
|
7037
|
+
* 判定错误是否为「读取后文件已被外部修改」(版本守卫拒绝写入)。
|
|
7038
|
+
* 不依赖 host fs 的 FsError 类(外置模块,instanceof 不可靠),按错误码与文案双重判定。
|
|
7039
|
+
* @author ddj 2026年09月15号
|
|
7040
|
+
* @param error 捕获到的异常
|
|
7041
|
+
* @returns 是否为版本冲突
|
|
7042
|
+
*/
|
|
7043
|
+
function isStaleVersion(error) {
|
|
7044
|
+
const err = error;
|
|
7045
|
+
if (err && err.code === "FS_STALE_VERSION") return true;
|
|
7046
|
+
return (err && typeof err.message === "string" ? err.message : String(error)).includes("changed since it was read");
|
|
7047
|
+
}
|
|
7048
|
+
/**
|
|
6969
7049
|
* 手动保存收尾(edrv.save / edrv.saveBinary 共用):目录树失效 + 该路径 pending
|
|
6970
7050
|
* 文本差异记录标记 superseded 并归档(旧文本 diff 不得再应用到新内容上)。
|
|
6971
7051
|
* @author ddj 2026年09月22号
|
|
@@ -7072,7 +7152,7 @@ async function integrationBaseUrlOf(ctx) {
|
|
|
7072
7152
|
return (typeof value?.integrationBaseUrl === "string" ? value.integrationBaseUrl.trim() : "") || "http://127.0.0.1:3080";
|
|
7073
7153
|
}
|
|
7074
7154
|
/** 各方法 handler 表(类型由 shared/rpc 的 RpcHandlerMap 约束)。 */
|
|
7075
|
-
function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearcher = newContentSearcher(ctx), lspHandlers, aiHandlers) {
|
|
7155
|
+
function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearcher = newContentSearcher(ctx), lspHandlers, aiHandlers, fileVersions) {
|
|
7076
7156
|
return {
|
|
7077
7157
|
...lspHandlers ?? {},
|
|
7078
7158
|
...aiHandlers ?? {},
|
|
@@ -7163,10 +7243,12 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
|
|
|
7163
7243
|
const snippetTarget = snippetTargetOf(args.path);
|
|
7164
7244
|
if (snippetTarget && args.encoding !== "base64") try {
|
|
7165
7245
|
const content = await readFile(snippetTarget, "utf8");
|
|
7246
|
+
const info = await stat(snippetTarget).catch(() => null);
|
|
7166
7247
|
return {
|
|
7167
7248
|
ok: true,
|
|
7168
7249
|
content,
|
|
7169
|
-
size: content.length
|
|
7250
|
+
size: content.length,
|
|
7251
|
+
version: fileVersionOf(info)
|
|
7170
7252
|
};
|
|
7171
7253
|
} catch (error) {
|
|
7172
7254
|
return {
|
|
@@ -7204,7 +7286,8 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
|
|
|
7204
7286
|
content: Buffer.from(bytes).toString("base64"),
|
|
7205
7287
|
size: bytes.byteLength,
|
|
7206
7288
|
encoding: "base64",
|
|
7207
|
-
mime: binaryMimeOf(args.path)
|
|
7289
|
+
mime: binaryMimeOf(args.path),
|
|
7290
|
+
version: String(info.version ?? "")
|
|
7208
7291
|
};
|
|
7209
7292
|
}
|
|
7210
7293
|
if ((info.size ?? 0) > 8388608) return {
|
|
@@ -7215,7 +7298,8 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
|
|
|
7215
7298
|
return {
|
|
7216
7299
|
ok: true,
|
|
7217
7300
|
content,
|
|
7218
|
-
size: content.length
|
|
7301
|
+
size: content.length,
|
|
7302
|
+
version: String(info.version ?? "")
|
|
7219
7303
|
};
|
|
7220
7304
|
} catch (error) {
|
|
7221
7305
|
return {
|
|
@@ -7224,6 +7308,13 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
|
|
|
7224
7308
|
};
|
|
7225
7309
|
}
|
|
7226
7310
|
},
|
|
7311
|
+
"edrv.versions": async (args) => {
|
|
7312
|
+
if (!fileVersions) return {
|
|
7313
|
+
ok: false,
|
|
7314
|
+
error: "文件版本观察器未装配"
|
|
7315
|
+
};
|
|
7316
|
+
return fileVersions.versions(args);
|
|
7317
|
+
},
|
|
7227
7318
|
"edrv.original": async (args) => {
|
|
7228
7319
|
const sc = await requireSession(ctx, args.sessionId);
|
|
7229
7320
|
if ("err" in sc) return {
|
|
@@ -7300,10 +7391,23 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
|
|
|
7300
7391
|
};
|
|
7301
7392
|
try {
|
|
7302
7393
|
const target = await resolveTarget(ctx, sc.session, args.path);
|
|
7303
|
-
|
|
7394
|
+
const rev = typeof args.rev === "string" && args.rev ? args.rev : null;
|
|
7395
|
+
const intent = rev ? {
|
|
7396
|
+
kind: "replaceIfVersion",
|
|
7397
|
+
version: rev
|
|
7398
|
+
} : void 0;
|
|
7399
|
+
const outcome = await fs.writeText(target, args.content, intent, void 0, policyOf(ctx, sc.session));
|
|
7304
7400
|
await afterManualSave(ctx, registry, sc, args.path);
|
|
7305
|
-
return {
|
|
7401
|
+
return {
|
|
7402
|
+
ok: true,
|
|
7403
|
+
rev: outcome?.version ? String(outcome.version) : rev ?? void 0
|
|
7404
|
+
};
|
|
7306
7405
|
} catch (error) {
|
|
7406
|
+
if (isStaleVersion(error)) return {
|
|
7407
|
+
ok: false,
|
|
7408
|
+
conflict: true,
|
|
7409
|
+
error: "文件已被外部修改,请先重新加载(或用编辑器内容覆盖)"
|
|
7410
|
+
};
|
|
7307
7411
|
return {
|
|
7308
7412
|
ok: false,
|
|
7309
7413
|
error: "保存失败:" + String(error)
|
|
@@ -8231,8 +8335,8 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
|
|
|
8231
8335
|
* 统一入口:按方法分发到 handler 表。
|
|
8232
8336
|
* @author ddj 2026年08月20号 / 2026年08月26号
|
|
8233
8337
|
*/
|
|
8234
|
-
async function handleRpc(ctx, registry, method, args, searcher = newSearcher(ctx), contentSearcher = newContentSearcher(ctx), lspHandlers, aiHandlers) {
|
|
8235
|
-
const handler = buildHandlers(ctx, registry, searcher, contentSearcher, lspHandlers, aiHandlers)[method];
|
|
8338
|
+
async function handleRpc(ctx, registry, method, args, searcher = newSearcher(ctx), contentSearcher = newContentSearcher(ctx), lspHandlers, aiHandlers, fileVersions) {
|
|
8339
|
+
const handler = buildHandlers(ctx, registry, searcher, contentSearcher, lspHandlers, aiHandlers, fileVersions)[method];
|
|
8236
8340
|
if (!handler) return {
|
|
8237
8341
|
ok: false,
|
|
8238
8342
|
error: "未知方法: " + String(method)
|
|
@@ -11665,6 +11769,118 @@ function createAiRpc(deps) {
|
|
|
11665
11769
|
})
|
|
11666
11770
|
} };
|
|
11667
11771
|
}
|
|
11772
|
+
/** 基准表键:工作区根 + 归一化路径。 */
|
|
11773
|
+
function memoKey(cwd, path) {
|
|
11774
|
+
return String(cwd).replace(/\\/g, "/") + "\0" + String(path).replace(/\\/g, "/");
|
|
11775
|
+
}
|
|
11776
|
+
/** 清理超期/超量记忆(每次写入前调用,摊还 O(1))。 */
|
|
11777
|
+
function sweep(memos, now) {
|
|
11778
|
+
if (memos.size === 0) return;
|
|
11779
|
+
for (const [key, memo] of memos) if (now - memo.at > 3e5) memos.delete(key);
|
|
11780
|
+
while (memos.size > 500) {
|
|
11781
|
+
const oldest = memos.keys().next().value;
|
|
11782
|
+
if (oldest === void 0) break;
|
|
11783
|
+
memos.delete(oldest);
|
|
11784
|
+
}
|
|
11785
|
+
}
|
|
11786
|
+
/**
|
|
11787
|
+
* 创建文件版本观察器(host 单例,随插件装配创建、卸载 dispose)。
|
|
11788
|
+
* @author ddj 2026年09月15号
|
|
11789
|
+
* @param ctx DSH host 上下文
|
|
11790
|
+
* @returns 观察器实例
|
|
11791
|
+
*/
|
|
11792
|
+
function createFileVersions(ctx) {
|
|
11793
|
+
/** 基准表:memoKey → 上次观察到的版本(仅用于变化判定与树失效)。 */
|
|
11794
|
+
const memos = /* @__PURE__ */ new Map();
|
|
11795
|
+
/**
|
|
11796
|
+
* 记录一次观察并按需失效目录树;返回本次是否为「变化」。
|
|
11797
|
+
* @author ddj 2026年09月15号
|
|
11798
|
+
* @param cwd 工作区根
|
|
11799
|
+
* @param path 请求路径
|
|
11800
|
+
* @param version 当前版本令牌(空串 = 后端不提供)
|
|
11801
|
+
* @param present 磁盘上是否存在
|
|
11802
|
+
* @returns 是否检测到变化
|
|
11803
|
+
*/
|
|
11804
|
+
const syncTree = (cwd, path, version, present) => {
|
|
11805
|
+
const now = Date.now();
|
|
11806
|
+
const key = memoKey(cwd, path);
|
|
11807
|
+
const prev = memos.get(key);
|
|
11808
|
+
sweep(memos, now);
|
|
11809
|
+
const changed = prev !== void 0 && (prev.version !== version || !present);
|
|
11810
|
+
memos.set(key, {
|
|
11811
|
+
version,
|
|
11812
|
+
at: now
|
|
11813
|
+
});
|
|
11814
|
+
if (changed) log.debug("检测到磁盘变化(已失效目录树):" + path);
|
|
11815
|
+
if (changed) invalidateIndex(ctx, cwd, path);
|
|
11816
|
+
return changed;
|
|
11817
|
+
};
|
|
11818
|
+
/**
|
|
11819
|
+
* 单条路径观察:解析 → stat → 组装条目(异常一律降级为 missing,不整批失败)。
|
|
11820
|
+
* @author ddj 2026年09月15号
|
|
11821
|
+
* @param fs host fs 服务
|
|
11822
|
+
* @param cwd 工作区根(为空表示会话无工作区,跳过变化判定)
|
|
11823
|
+
* @param path 请求路径
|
|
11824
|
+
* @returns 版本条目
|
|
11825
|
+
*/
|
|
11826
|
+
const observe = async (fs, cwd, path) => {
|
|
11827
|
+
try {
|
|
11828
|
+
const target = await fs.resolve(path, cwd ? { cwd } : {});
|
|
11829
|
+
const info = await fs.stat(target);
|
|
11830
|
+
if (!info || info.type === "other") {
|
|
11831
|
+
if (cwd) syncTree(cwd, path, "", false);
|
|
11832
|
+
return {
|
|
11833
|
+
path,
|
|
11834
|
+
version: "",
|
|
11835
|
+
type: "missing"
|
|
11836
|
+
};
|
|
11837
|
+
}
|
|
11838
|
+
const version = info.version ? String(info.version) : "";
|
|
11839
|
+
if (cwd) syncTree(cwd, path, version, true);
|
|
11840
|
+
return {
|
|
11841
|
+
path,
|
|
11842
|
+
version,
|
|
11843
|
+
size: typeof info.size === "number" ? info.size : void 0,
|
|
11844
|
+
type: info.type
|
|
11845
|
+
};
|
|
11846
|
+
} catch (error) {
|
|
11847
|
+
if (cwd) syncTree(cwd, path, "", false);
|
|
11848
|
+
return {
|
|
11849
|
+
path,
|
|
11850
|
+
version: "",
|
|
11851
|
+
type: "missing",
|
|
11852
|
+
error: String(error)
|
|
11853
|
+
};
|
|
11854
|
+
}
|
|
11855
|
+
};
|
|
11856
|
+
/**
|
|
11857
|
+
* 批查入口(RPC handler 直接委托):同一会话的路径共享一次会话解析。
|
|
11858
|
+
* @author ddj 2026年09月15号
|
|
11859
|
+
* @param args 会话 id 与路径列表
|
|
11860
|
+
* @returns 始终 ok 的逐条结果
|
|
11861
|
+
*/
|
|
11862
|
+
const versions = async (args) => {
|
|
11863
|
+
const fs = ctx.get("fs");
|
|
11864
|
+
const paths = (Array.isArray(args.paths) ? args.paths : []).filter((p) => typeof p === "string" && p.length > 0).slice(0, 200);
|
|
11865
|
+
if (!fs || !paths.length) return {
|
|
11866
|
+
ok: true,
|
|
11867
|
+
items: []
|
|
11868
|
+
};
|
|
11869
|
+
const cwd = cwdOf(sessionOf(ctx, args.sessionId));
|
|
11870
|
+
const items = [];
|
|
11871
|
+
for (const path of paths) items.push(await observe(fs, cwd, path));
|
|
11872
|
+
return {
|
|
11873
|
+
ok: true,
|
|
11874
|
+
items
|
|
11875
|
+
};
|
|
11876
|
+
};
|
|
11877
|
+
return {
|
|
11878
|
+
versions,
|
|
11879
|
+
dispose() {
|
|
11880
|
+
memos.clear();
|
|
11881
|
+
}
|
|
11882
|
+
};
|
|
11883
|
+
}
|
|
11668
11884
|
//#endregion
|
|
11669
11885
|
//#region src/index.ts
|
|
11670
11886
|
/**
|
|
@@ -11721,6 +11937,8 @@ function apply(ctx, config) {
|
|
|
11721
11937
|
ctx,
|
|
11722
11938
|
settings: openSettings
|
|
11723
11939
|
}).handlers;
|
|
11940
|
+
/** 文件磁盘新鲜度观察器(客户端轮询 edrv.versions;变化时顺带失效目录树缓存)。 */
|
|
11941
|
+
const fileVersions = createFileVersions(ctx);
|
|
11724
11942
|
ctx.on("tools/result", (exec, result) => {
|
|
11725
11943
|
captureToolResult(ctx, registry, exec, result);
|
|
11726
11944
|
});
|
|
@@ -11736,7 +11954,7 @@ function apply(ctx, config) {
|
|
|
11736
11954
|
const sid = session?.id;
|
|
11737
11955
|
if (typeof sid === "string") lspRpc.disposeSession(sid);
|
|
11738
11956
|
});
|
|
11739
|
-
registerRoutes(ctx, config, (method, args) => handleRpc(ctx, registry, method, args, searcher, contentSearcher, lspHandlers, aiHandlers), (warning) => warnings.push(warning));
|
|
11957
|
+
registerRoutes(ctx, config, (method, args) => handleRpc(ctx, registry, method, args, searcher, contentSearcher, lspHandlers, aiHandlers, fileVersions), (warning) => warnings.push(warning));
|
|
11740
11958
|
installIsolation(ctx);
|
|
11741
11959
|
ctx.effect(() => shellMenuLifecycle(ctx));
|
|
11742
11960
|
sweepTreeCache();
|
|
@@ -11745,6 +11963,7 @@ function apply(ctx, config) {
|
|
|
11745
11963
|
lspManager.disposeAll().catch(() => {});
|
|
11746
11964
|
disposeAllServers();
|
|
11747
11965
|
});
|
|
11966
|
+
ctx.effect(() => () => fileVersions.dispose());
|
|
11748
11967
|
hookExitReclaim();
|
|
11749
11968
|
log.info("编辑差异审查已装配(/edrv/rpc 路由就绪,项目 MCP 隔离已启用,语言服务器 LSP 已接入,规则注入" + (rulesInstalled ? "已接入" : "未接入") + ",技能组" + (skillsDispatched ? "装配中" : "未装配") + ")");
|
|
11750
11969
|
}
|