dsh-vscode-mode 0.15.7 → 0.15.9
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 +5 -3
- package/lib/client.js +377 -70
- package/lib/client.js.map +1 -1
- package/lib/index.js +210 -6
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +4 -0
- package/src/client/sessionError.ts +38 -0
- package/src/client/sidebar/panels/FileExplorer.ts +7 -1
- package/src/client/state/editorFindState.ts +15 -10
- package/src/client/state/editorViewState.ts +7 -4
- package/src/client/styles/editor.css +3 -0
- package/src/client/ui/EditorPane.ts +23 -3
- package/src/client/ui/EditorView.ts +130 -15
- package/src/client/ui/McpSettings.ts +36 -0
- package/src/client/updateNotice.ts +64 -0
- package/src/lsp/rpc.ts +3 -2
- package/src/rpc.ts +12 -3
- package/src/selfUpdate.ts +188 -0
- package/src/shared/rpc.ts +15 -0
- package/src/svn.ts +3 -2
package/lib/index.js
CHANGED
|
@@ -2423,6 +2423,15 @@ function imageMimeOf(path) {
|
|
|
2423
2423
|
/** PDF 统一 MIME(host base64 响应与 client PDF 面板同源,避免两处漂移)。 */
|
|
2424
2424
|
const PDF_MIME = "application/pdf";
|
|
2425
2425
|
/**
|
|
2426
|
+
* 会话级前置错误的规范文案:host requireSession 系与 client 错误识别共用同一常量,
|
|
2427
|
+
* 避免文案漂移导致 client 端分类失效。跨端场景(桌面端/Web 端各自运行独立 DSH 实例、
|
|
2428
|
+
* 会话列表互通但会话仅在一端激活)下,本端 host 取不到会话即返回「会话不存在」。
|
|
2429
|
+
* @author ddj 2026年10月16号
|
|
2430
|
+
*/
|
|
2431
|
+
const SESSION_MISSING_ERROR = "会话不存在";
|
|
2432
|
+
/** 会话存在但无工作区 cwd 时的规范文案(与 SESSION_MISSING_ERROR 同源共用)。 */
|
|
2433
|
+
const SESSION_NO_WORKSPACE_ERROR = "会话无工作区";
|
|
2434
|
+
/**
|
|
2426
2435
|
* 路径 → 二进制预览 MIME:PDF 特判 {@link PDF_MIME},其余走图片表回退 octet-stream。
|
|
2427
2436
|
* @author ddj 2026年09月22号
|
|
2428
2437
|
* @param path 文件路径(`/` 或 `\` 分隔均可)
|
|
@@ -10297,6 +10306,181 @@ async function storedOpenBase(dir, fallback) {
|
|
|
10297
10306
|
return fallback;
|
|
10298
10307
|
}
|
|
10299
10308
|
}
|
|
10309
|
+
//#endregion
|
|
10310
|
+
//#region src/selfUpdate.ts
|
|
10311
|
+
/**
|
|
10312
|
+
* dsh-vscode-mode host — 自我更新:registry 最新版本检查与官方 pluginManager「先删后装」编排。
|
|
10313
|
+
* 职责:compareVersion 语义化版本比较(纯函数可单测)、latestVersionOf 经 subprocess 运行
|
|
10314
|
+
* `pnpm view <pkg> version --json`(与 installBundle 同一套 pnpm/registry 配置)、
|
|
10315
|
+
* startSelfUpdate 把 removeBundle→installBundle 编排放进宿主级 setTimeout(先回 RPC 响应,
|
|
10316
|
+
* 定时器回调不受本插件卸载影响,规避「删除自身后 install 不再执行」的自愈陷阱)。
|
|
10317
|
+
* 注意:不 import @deepseek-ai/dsh-plugin-manager(鸭子类型引用 ctx.get('pluginManager')),
|
|
10318
|
+
* 老宿主缺失该服务时返回明确错误引导官方插件管理页。
|
|
10319
|
+
* 作者 ddj 2026-10-16
|
|
10320
|
+
*/
|
|
10321
|
+
/** 防并发重入标志:一旦启动编排,本次进程内不再接受第二次更新请求。 */
|
|
10322
|
+
let updating = false;
|
|
10323
|
+
/**
|
|
10324
|
+
* 语义化版本比较:核心段按 `.` 数值比较、段数不足补 0;预发布版(-beta 等)低于同号正式版,
|
|
10325
|
+
* 构建元数据(+)与预发布粒度不参与排序。
|
|
10326
|
+
* @private
|
|
10327
|
+
* @author ddj 2026年10月16号
|
|
10328
|
+
* @param a 左版本号
|
|
10329
|
+
* @param b 右版本号
|
|
10330
|
+
* @returns a>b 为 1,a<b 为 -1,相等为 0
|
|
10331
|
+
*/
|
|
10332
|
+
function compareVersion$1(a, b) {
|
|
10333
|
+
const parse = (text) => {
|
|
10334
|
+
const value = String(text ?? "").trim().replace(/^[^\d]*/, "");
|
|
10335
|
+
const cut = value.search(/[-+]/);
|
|
10336
|
+
return {
|
|
10337
|
+
nums: (cut >= 0 ? value.slice(0, cut) : value).split(".").map((part) => {
|
|
10338
|
+
const num = Number.parseInt(part, 10);
|
|
10339
|
+
return Number.isFinite(num) ? num : 0;
|
|
10340
|
+
}),
|
|
10341
|
+
pre: cut >= 0 && value[cut] === "-"
|
|
10342
|
+
};
|
|
10343
|
+
};
|
|
10344
|
+
const left = parse(a);
|
|
10345
|
+
const right = parse(b);
|
|
10346
|
+
const total = Math.max(left.nums.length, right.nums.length);
|
|
10347
|
+
for (let index = 0; index < total; index += 1) {
|
|
10348
|
+
const diff = (left.nums[index] ?? 0) - (right.nums[index] ?? 0);
|
|
10349
|
+
if (diff !== 0) return diff > 0 ? 1 : -1;
|
|
10350
|
+
}
|
|
10351
|
+
if (left.pre !== right.pre) return left.pre ? -1 : 1;
|
|
10352
|
+
return 0;
|
|
10353
|
+
}
|
|
10354
|
+
/**
|
|
10355
|
+
* 用 profileContext.packageManager(缺省 pnpm)查询 npm registry 上本包最新版本。
|
|
10356
|
+
* @private
|
|
10357
|
+
* @author ddj 2026年10月16号
|
|
10358
|
+
* @param ctx DSH host 上下文
|
|
10359
|
+
* @returns registry 最新版本号
|
|
10360
|
+
* @throws pnpm 不存在、退出非零或输出不可解析
|
|
10361
|
+
*/
|
|
10362
|
+
async function latestVersionOf(ctx) {
|
|
10363
|
+
const dir = findProfileDir(ctx);
|
|
10364
|
+
if (!dir) throw new Error("无法定位当前 profile,无法查询最新版本");
|
|
10365
|
+
const runner = ctx.get("profileContext")?.packageManager;
|
|
10366
|
+
return parseVersionJson(await new Promise((resolvePromise, rejectPromise) => {
|
|
10367
|
+
let spawned = false;
|
|
10368
|
+
try {
|
|
10369
|
+
const handle = ctx.get("subprocess").spawn({
|
|
10370
|
+
argv: [
|
|
10371
|
+
runner?.command ?? "pnpm",
|
|
10372
|
+
...runner?.args ?? [],
|
|
10373
|
+
"view",
|
|
10374
|
+
PLUGIN_NAME,
|
|
10375
|
+
"version",
|
|
10376
|
+
"--json"
|
|
10377
|
+
],
|
|
10378
|
+
cwd: dir,
|
|
10379
|
+
env: childEnv(runner?.env),
|
|
10380
|
+
stdio: {
|
|
10381
|
+
stdout: "pipe",
|
|
10382
|
+
stderr: "pipe",
|
|
10383
|
+
stdin: "ignore"
|
|
10384
|
+
},
|
|
10385
|
+
graceMs: 2e4
|
|
10386
|
+
});
|
|
10387
|
+
spawned = true;
|
|
10388
|
+
readSpawnText(handle, resolvePromise, rejectPromise);
|
|
10389
|
+
} catch (error) {
|
|
10390
|
+
if (!spawned) rejectPromise(error instanceof Error ? error : new Error(String(error)));
|
|
10391
|
+
}
|
|
10392
|
+
}));
|
|
10393
|
+
}
|
|
10394
|
+
/**
|
|
10395
|
+
* 读取 spawn 句柄的 stdout 文本并等待退出(非零退出或空输出抛错)。
|
|
10396
|
+
* @private
|
|
10397
|
+
* @author ddj 2026年10月16号
|
|
10398
|
+
* @param handle subprocess.spawn 返回句柄
|
|
10399
|
+
* @param resolvePromise 成功回传 stdout 全文
|
|
10400
|
+
* @param rejectPromise 失败回传错误
|
|
10401
|
+
*/
|
|
10402
|
+
function readSpawnText(handle, resolvePromise, rejectPromise) {
|
|
10403
|
+
handle.done.then((outcome) => {
|
|
10404
|
+
const text = handle.collected?.stdout?.readFrom(0)?.text ?? "";
|
|
10405
|
+
if (outcome?.exitCode !== 0 || !text.trim()) {
|
|
10406
|
+
rejectPromise(/* @__PURE__ */ new Error("pnpm view 失败(exit " + String(outcome?.exitCode) + "):" + text.slice(0, 200)));
|
|
10407
|
+
return;
|
|
10408
|
+
}
|
|
10409
|
+
resolvePromise(text);
|
|
10410
|
+
}, rejectPromise);
|
|
10411
|
+
}
|
|
10412
|
+
/**
|
|
10413
|
+
* 解析 `pnpm view --json` 输出:JSON 字符串("1.2.3")或直接版本字面量均接受。
|
|
10414
|
+
* @private
|
|
10415
|
+
* @author ddj 2026年10月16号
|
|
10416
|
+
* @param text stdout 全文
|
|
10417
|
+
* @returns 版本号
|
|
10418
|
+
* @throws 输出不含可识别版本
|
|
10419
|
+
*/
|
|
10420
|
+
function parseVersionJson(text) {
|
|
10421
|
+
const trimmed = text.trim();
|
|
10422
|
+
try {
|
|
10423
|
+
const value = JSON.parse(trimmed);
|
|
10424
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
10425
|
+
} catch {}
|
|
10426
|
+
if (/^\d+(\.\d+){0,3}([-+].*)?$/.test(trimmed)) return trimmed;
|
|
10427
|
+
throw new Error("无法解析 pnpm view 输出:" + trimmed.slice(0, 120));
|
|
10428
|
+
}
|
|
10429
|
+
/**
|
|
10430
|
+
* 检查 registry 是否有比当前安装版本更新的版本。
|
|
10431
|
+
* @public
|
|
10432
|
+
* @author ddj 2026年10月16号
|
|
10433
|
+
* @param ctx DSH host 上下文
|
|
10434
|
+
* @returns 当前版本 / 最新版本 / 是否有更新
|
|
10435
|
+
*/
|
|
10436
|
+
async function checkUpdate(ctx) {
|
|
10437
|
+
const current = pluginVersionOf();
|
|
10438
|
+
try {
|
|
10439
|
+
const latest = await latestVersionOf(ctx);
|
|
10440
|
+
return {
|
|
10441
|
+
ok: true,
|
|
10442
|
+
current,
|
|
10443
|
+
latest,
|
|
10444
|
+
hasUpdate: latest !== "" && compareVersion$1(latest, current) > 0
|
|
10445
|
+
};
|
|
10446
|
+
} catch (error) {
|
|
10447
|
+
return {
|
|
10448
|
+
ok: false,
|
|
10449
|
+
current,
|
|
10450
|
+
error: String(error)
|
|
10451
|
+
};
|
|
10452
|
+
}
|
|
10453
|
+
}
|
|
10454
|
+
/**
|
|
10455
|
+
* 启动「先删后装」自我更新编排:校验通过后把编排放进宿主级 setTimeout(延迟 1s 让 RPC 响应
|
|
10456
|
+
* 先送达客户端),立即返回 started;定时器回调不受本插件卸载影响。install 失败时
|
|
10457
|
+
* pluginManager 会恢复 profile 的 package.json/pnpm-lock.yaml,插件保持未安装状态,
|
|
10458
|
+
* 由用户在官方插件管理页重装恢复。
|
|
10459
|
+
* @public
|
|
10460
|
+
* @author ddj 2026年10月16号
|
|
10461
|
+
* @param ctx DSH host 上下文
|
|
10462
|
+
* @returns started 是否已启动编排;失败时 ok=false
|
|
10463
|
+
* @throws 无(失败走返回值 error)
|
|
10464
|
+
*/
|
|
10465
|
+
async function startSelfUpdate(ctx) {
|
|
10466
|
+
if (updating) return {
|
|
10467
|
+
ok: false,
|
|
10468
|
+
error: "更新流程已在进行中,请重启 DSH 后观察结果"
|
|
10469
|
+
};
|
|
10470
|
+
const manager = ctx.get("pluginManager");
|
|
10471
|
+
if (!manager || typeof manager.removeBundle !== "function" || typeof manager.installBundle !== "function") return {
|
|
10472
|
+
ok: false,
|
|
10473
|
+
error: "当前宿主无 pluginManager 服务,请在官方插件管理页卸载后重新安装以升级"
|
|
10474
|
+
};
|
|
10475
|
+
updating = true;
|
|
10476
|
+
setTimeout(() => {
|
|
10477
|
+
manager.removeBundle(PLUGIN_NAME).then(() => manager.installBundle(PLUGIN_NAME)).catch(() => {});
|
|
10478
|
+
}, 1e3);
|
|
10479
|
+
return {
|
|
10480
|
+
ok: true,
|
|
10481
|
+
started: true
|
|
10482
|
+
};
|
|
10483
|
+
}
|
|
10300
10484
|
/**
|
|
10301
10485
|
* 路径归一:去首尾空白 + 反斜杠转 '/'(Windows 输入等价处理)。
|
|
10302
10486
|
* @author ddj 2026年09月22号
|
|
@@ -11198,9 +11382,9 @@ function recView(record, ws = null) {
|
|
|
11198
11382
|
/** 会话/工作区公共前置:返回 {session,cwd} 或错误文案。 */
|
|
11199
11383
|
async function requireSession(ctx, sessionId) {
|
|
11200
11384
|
const session = sessionOf(ctx, sessionId);
|
|
11201
|
-
if (!session) return { err:
|
|
11385
|
+
if (!session) return { err: SESSION_MISSING_ERROR };
|
|
11202
11386
|
const cwd = cwdOf(session);
|
|
11203
|
-
if (!cwd) return { err:
|
|
11387
|
+
if (!cwd) return { err: SESSION_NO_WORKSPACE_ERROR };
|
|
11204
11388
|
return {
|
|
11205
11389
|
session,
|
|
11206
11390
|
cwd
|
|
@@ -12818,6 +13002,26 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
|
|
|
12818
13002
|
};
|
|
12819
13003
|
}
|
|
12820
13004
|
},
|
|
13005
|
+
"vscode.updateCheck": async () => {
|
|
13006
|
+
try {
|
|
13007
|
+
return await checkUpdate(ctx);
|
|
13008
|
+
} catch (error) {
|
|
13009
|
+
return {
|
|
13010
|
+
ok: false,
|
|
13011
|
+
error: String(error)
|
|
13012
|
+
};
|
|
13013
|
+
}
|
|
13014
|
+
},
|
|
13015
|
+
"vscode.updateSelf": async () => {
|
|
13016
|
+
try {
|
|
13017
|
+
return await startSelfUpdate(ctx);
|
|
13018
|
+
} catch (error) {
|
|
13019
|
+
return {
|
|
13020
|
+
ok: false,
|
|
13021
|
+
error: String(error)
|
|
13022
|
+
};
|
|
13023
|
+
}
|
|
13024
|
+
},
|
|
12821
13025
|
"edrv.integration.status": async () => {
|
|
12822
13026
|
try {
|
|
12823
13027
|
return {
|
|
@@ -16706,8 +16910,8 @@ function createLspRpc(deps) {
|
|
|
16706
16910
|
/** 解析会话与工作区根(失败 → null + error)。 */
|
|
16707
16911
|
const rootOf = async (sessionId) => {
|
|
16708
16912
|
const session = sessionOf(ctx, sessionId);
|
|
16709
|
-
if (!session) return { err:
|
|
16710
|
-
if (!cwdOf(session)) return { err:
|
|
16913
|
+
if (!session) return { err: SESSION_MISSING_ERROR };
|
|
16914
|
+
if (!cwdOf(session)) return { err: SESSION_NO_WORKSPACE_ERROR };
|
|
16711
16915
|
try {
|
|
16712
16916
|
return {
|
|
16713
16917
|
root: await searchRoot(ctx, session),
|
|
@@ -19960,9 +20164,9 @@ function foregroundHelperArgv(processName) {
|
|
|
19960
20164
|
/** host 会话/工作区前置(对齐 rpc.ts requireSession 的错误文案)。 */
|
|
19961
20165
|
async function requireSvnSession(ctx, sessionId) {
|
|
19962
20166
|
const session = sessionOf(ctx, sessionId);
|
|
19963
|
-
if (!session) return { err:
|
|
20167
|
+
if (!session) return { err: SESSION_MISSING_ERROR };
|
|
19964
20168
|
const cwd = cwdOf(session);
|
|
19965
|
-
if (!cwd) return { err:
|
|
20169
|
+
if (!cwd) return { err: SESSION_NO_WORKSPACE_ERROR };
|
|
19966
20170
|
return { cwd };
|
|
19967
20171
|
}
|
|
19968
20172
|
/** 工作区相对路径归一('' 合法 = 工作区根;非法含越界/绝对路径返回 null)。 */
|