dsh-vscode-mode 0.15.9 → 0.17.0
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 +14 -9
- package/lib/client.js +376 -84
- package/lib/client.js.map +1 -1
- package/lib/index.js +4545 -4454
- package/lib/index.js.map +1 -1
- package/locale/en.json +4 -2
- package/locale/zh.json +4 -2
- package/package.json +4 -2
- package/src/capture.ts +2 -2
- package/src/client/compat.ts +2 -1
- package/src/client/index.ts +22 -7
- package/src/client/localeClient.ts +97 -0
- package/src/client/officialSidebar.ts +4 -1
- package/src/client/state/recordsFeed.ts +241 -0
- package/src/client/ui/ConversationDiffDock.ts +10 -19
- package/src/client/ui/DiffBadge.ts +12 -19
- package/src/client/ui/EditorView.ts +38 -33
- package/src/client/ui/PerfSizeBadge.ts +7 -4
- package/src/client/ui/PluginDetailSection.ts +82 -0
- package/src/dap/discovery.ts +5 -3
- package/src/dap/provider.ts +26 -0
- package/src/dshVersion.ts +4 -2
- package/src/fileVersions.ts +2 -2
- package/src/lsp/providers.ts +9 -5
- package/src/lsp/rpc.ts +2 -2
- package/src/lsp/server.ts +26 -1
- package/src/mcpRuntime.ts +2 -1
- package/src/registry.ts +15 -2
- package/src/rpc.ts +2 -2
- package/src/rules.ts +9 -4
- package/src/search/fallback.ts +2 -2
- package/src/search/ripgrep.ts +2 -2
- package/src/store.ts +26 -1
- package/src/svn.ts +2 -2
package/README.md
CHANGED
|
@@ -460,25 +460,28 @@ DSH 每次启动监听随机端口,`http://127.0.0.1:<port>` 就是浏览器 o
|
|
|
460
460
|
|
|
461
461
|
官方 `dsh plugin` 方式,三选一。下面以 `web` profile 为例;Desktop 或其他命名
|
|
462
462
|
profile 请替换为实际目标,不要把示例目录当成当前运行 profile。升级前可阅读
|
|
463
|
-
[0.
|
|
463
|
+
[0.17.0 发布说明](docs/release-0.17.0.md);从 0.14.2 或更旧版本升级还须阅读
|
|
464
464
|
[0.14.3 数据迁移与回退说明](docs/release-0.14.3.md)。
|
|
465
465
|
|
|
466
466
|
```bash
|
|
467
467
|
# ① Git 安装(clone + prepare 构建;推荐打固定 tag)
|
|
468
|
-
dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.
|
|
468
|
+
dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.17.0
|
|
469
469
|
|
|
470
470
|
# ② npm 注册表(发布到 npm 后)
|
|
471
|
-
dsh plugin --profile web add dsh-vscode-mode@0.
|
|
471
|
+
dsh plugin --profile web add dsh-vscode-mode@0.17.0
|
|
472
472
|
|
|
473
473
|
# ③ GitHub Release tgz 直装(Release asset 可用后)
|
|
474
|
-
dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.
|
|
474
|
+
dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.17.0/dsh-vscode-mode-0.17.0.tgz
|
|
475
475
|
```
|
|
476
476
|
|
|
477
|
-
> **DSH 版本政策(0.
|
|
478
|
-
>
|
|
479
|
-
>
|
|
480
|
-
>
|
|
481
|
-
>
|
|
477
|
+
> **DSH 版本政策(0.17.0 起)**:六项 DSH peer 仍统一声明为 `*`,另以
|
|
478
|
+
> `engines.dsh: ">=0.1.0-rc.1 <0.3.0-0"` 显式声明支持的 DSH 运行时区间——
|
|
479
|
+
> `engines.dsh` 一旦声明就**单独决定**兼容性(官方实现:`An own engines.dsh range
|
|
480
|
+
> decides alone and peerDependencies are ignored`),不满足时安装会被直接拒绝。
|
|
481
|
+
> 该区间接受 0.1.0-rc.1 起的全部 0.1.x / 0.2.x,拒绝 0.3.0 及以后的主版本;
|
|
482
|
+
> DSH 自身以 `includePrerelease` 求值,故 alpha、rc 与正式版同等对待。
|
|
483
|
+
> 运行时仍按实际能力检查;允许安装不代表该版本已通过全功能验收。
|
|
484
|
+
> 已发布包仍以其实际声明为准;npm 安装前应确认注册表中版本可查询。
|
|
482
485
|
> 替换已有包后需重启目标 DSH Host 并强制刷新原页面;仅刷新不会更新 Host。
|
|
483
486
|
> 从 0.14.2 或更旧版本升级时,首次访问工作区会执行 0.14.3 引入的 SQLite 活跃记录迁移。
|
|
484
487
|
>
|
|
@@ -809,6 +812,8 @@ Keep All / Undo All 却是亮的。修复:单文件 Keep / Undo 覆盖冲突
|
|
|
809
812
|
完整变更见 [GitHub Releases](https://github.com/Lenonss/DSH_VsCodeMode/releases)。
|
|
810
813
|
近期关键版本:
|
|
811
814
|
|
|
815
|
+
- **v0.17.0**:**DSH 0.2.1 吸收与运行期优化**——① 修复插件元数据一直失效:`locale/*.json` 改 `{"meta":{...}}` 结构并导出 `./locale/*.json`,插件管理器标题与描述不再回退 `package.json.name`;② 新增 `engines.dsh: ">=0.1.0-rc.1 <0.3.0-0"` 显式声明支持区间(此前仅靠官方 `includePrerelease` 宽容);③ 跟随会话内 `cd`:13 处 `header.cwd` 直读改走 `workingDirectory` 投影,服务缺失/抛错时回落原始目录;④ 轮询收敛:差异角标 / 输入区 dock / 编辑器 / 体积角标原本各自 5s 拉一次 `edrv.list`,改为会话级共享订阅(单轮询 + 退订到 0 才释放);⑤ 界面壳层接入 `ctx.locale` 中英双语(slot 名与官方页签标题随语言切换);⑥ 新增插件管理器详情页「VSCodeMode」状态段。详见 [0.17.0 发布说明](docs/release-0.17.0.md)。
|
|
816
|
+
- **v0.16.0**:**C# 智能体体验增强**——C# provider DotRush 优先于 ms-Roslyn(自带 sln 加载与反编译跳转;Roslyn 无 C# Dev Kit 时不加载项目);csharp 握手后自动找工作区根 `.sln` 发 Roslyn 专有 `solution/open`,元数据符号(Unity 引擎/NuGet 程序集)可跳转与 hover;DAP 调试支持 `runtime='dotnet'` 适配器(DotRush monodbg 形态),按 dll runtimeconfig 要求的运行时大版本选择 dotnet。
|
|
812
817
|
- **v0.15.9**:**插件自我更新与可用更新提醒**——设置页「文件与安装」新增更新行:显示当前版本,一键检查 npm 上的最新版本,发现新版后可确认执行自我更新(host 端先卸载再安装插件包,安装失败自动恢复 profile package.json/pnpm-lock.yaml,更新期间页面会短暂断开)。客户端启动后延迟探测新版本,发现可用更新时弹 toast 提醒(每个版本只提醒一次),devForm 开发形态不提醒。详见 [0.15.9 发布说明](docs/release-0.15.9.md)。
|
|
813
818
|
- **v0.15.8**:**跨端「会话不存在」兼容**——桌面端与 Web 端各自运行独立 DSH 实例、会话列表互通但会话仅在一端激活时,另一端文件编辑器不再卡在「文件加载失败:会话不存在 0%」:中央区显示「会话在当前端不可用」终态提示(说明成因、保留重试),资源管理器红条同样替换为友好文案。host 三处会话前置错误文案收敛为 shared 常量,client 按常量识别归类。详见 [0.15.8 发布说明](docs/release-0.15.8.md)。
|
|
814
819
|
- **v0.15.7**:**Lua 成员链跳转与枚举引用**——规避 EmmyLua 标识符词边界解析问题,从引用结果推导跨文件声明;Lua 表字面量枚举键 Ctrl+点击打开引用 Peek,使用处仍按原生定义跳转。真实 GUI 点击尚未完成验收,用户接受此缺口;发布不自动安装或重启当前实例,详见 [0.15.7 发布说明](docs/release-0.15.7.md)。
|
package/lib/client.js
CHANGED
|
@@ -3019,6 +3019,167 @@ window.__ModuleLoader__.load({
|
|
|
3019
3019
|
} catch (e) {}
|
|
3020
3020
|
}
|
|
3021
3021
|
//#endregion
|
|
3022
|
+
//#region src/client/state/recordsFeed.ts
|
|
3023
|
+
/** 兜底轮询周期(毫秒):仅作事件丢失时的兜底,正常由 edrv:refresh 驱动。 */
|
|
3024
|
+
const FEED_INTERVAL_MS = 5e3;
|
|
3025
|
+
const feeds = /* @__PURE__ */ new Map();
|
|
3026
|
+
/** 取全局 window(Node 测试环境无 window)。 */
|
|
3027
|
+
function win() {
|
|
3028
|
+
return typeof window === "undefined" ? void 0 : window;
|
|
3029
|
+
}
|
|
3030
|
+
/**
|
|
3031
|
+
* 取或建会话订阅槽位。
|
|
3032
|
+
* @author ddj 2026年10月19号
|
|
3033
|
+
* @param sessionId 会话 id
|
|
3034
|
+
* @returns 订阅槽位
|
|
3035
|
+
*/
|
|
3036
|
+
function entryOf(sessionId) {
|
|
3037
|
+
let entry = feeds.get(sessionId);
|
|
3038
|
+
if (!entry) {
|
|
3039
|
+
entry = {
|
|
3040
|
+
records: null,
|
|
3041
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
3042
|
+
tickers: /* @__PURE__ */ new Set(),
|
|
3043
|
+
errorListeners: /* @__PURE__ */ new Set(),
|
|
3044
|
+
timer: null,
|
|
3045
|
+
onRefresh: null,
|
|
3046
|
+
seq: 0,
|
|
3047
|
+
inflight: false
|
|
3048
|
+
};
|
|
3049
|
+
feeds.set(sessionId, entry);
|
|
3050
|
+
}
|
|
3051
|
+
return entry;
|
|
3052
|
+
}
|
|
3053
|
+
/**
|
|
3054
|
+
* 把最新记录分发给订阅者(快照相等时也分发,交由消费方自行比对)。
|
|
3055
|
+
* @author ddj 2026年10月19号
|
|
3056
|
+
* @param entry 订阅槽位
|
|
3057
|
+
*/
|
|
3058
|
+
function notifyRecords(entry) {
|
|
3059
|
+
const snapshot = entry.records;
|
|
3060
|
+
if (!snapshot) return;
|
|
3061
|
+
for (const listener of Array.from(entry.listeners)) try {
|
|
3062
|
+
listener(snapshot);
|
|
3063
|
+
} catch {}
|
|
3064
|
+
}
|
|
3065
|
+
/**
|
|
3066
|
+
* 拉取一次该会话的差异记录。
|
|
3067
|
+
* @author ddj 2026年10月19号
|
|
3068
|
+
* @param sessionId 会话 id
|
|
3069
|
+
* @param skipStale 是否跳过过期判定(与 edrv.list 参数同名同义)
|
|
3070
|
+
* @returns 拉取完成 Promise
|
|
3071
|
+
*/
|
|
3072
|
+
function refreshFeed(sessionId, skipStale = false) {
|
|
3073
|
+
const entry = entryOf(sessionId);
|
|
3074
|
+
const current = ++entry.seq;
|
|
3075
|
+
entry.inflight = true;
|
|
3076
|
+
const payload = { sessionId };
|
|
3077
|
+
if (skipStale) payload.skipStale = true;
|
|
3078
|
+
return rpc("edrv.list", payload).then((res) => {
|
|
3079
|
+
if (current !== entry.seq) return;
|
|
3080
|
+
if (!res || !res.ok || !Array.isArray(res.records)) return;
|
|
3081
|
+
entry.records = res.records;
|
|
3082
|
+
notifyRecords(entry);
|
|
3083
|
+
}).catch((e) => {
|
|
3084
|
+
if (current !== entry.seq) return;
|
|
3085
|
+
const message = String(e);
|
|
3086
|
+
for (const listener of Array.from(entry.errorListeners)) try {
|
|
3087
|
+
listener(message);
|
|
3088
|
+
} catch {}
|
|
3089
|
+
}).finally(() => {
|
|
3090
|
+
if (current === entry.seq) entry.inflight = false;
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
3093
|
+
/**
|
|
3094
|
+
* 确保该会话的定时器与 refresh 事件监听已就绪(首个订阅者到来时启动)。
|
|
3095
|
+
* @author ddj 2026年10月19号
|
|
3096
|
+
* @param sessionId 会话 id
|
|
3097
|
+
* @param entry 订阅槽位
|
|
3098
|
+
*/
|
|
3099
|
+
function ensureTimer$1(sessionId, entry) {
|
|
3100
|
+
if (entry.timer === null) entry.timer = setInterval(() => {
|
|
3101
|
+
const tickers = Array.from(entry.tickers);
|
|
3102
|
+
for (const tick of tickers) try {
|
|
3103
|
+
tick();
|
|
3104
|
+
} catch {}
|
|
3105
|
+
if (entry.listeners.size > 0 || entry.errorListeners.size > 0) refreshFeed(sessionId);
|
|
3106
|
+
}, FEED_INTERVAL_MS);
|
|
3107
|
+
if (entry.onRefresh === null) {
|
|
3108
|
+
const handler = () => {
|
|
3109
|
+
refreshFeed(sessionId);
|
|
3110
|
+
};
|
|
3111
|
+
const target = win();
|
|
3112
|
+
if (target) target.addEventListener("edrv:refresh", handler);
|
|
3113
|
+
entry.onRefresh = handler;
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
/**
|
|
3117
|
+
* 会话无任何订阅者时释放定时器、事件监听与缓存快照。
|
|
3118
|
+
* @author ddj 2026年10月19号
|
|
3119
|
+
* @param sessionId 会话 id
|
|
3120
|
+
* @param entry 订阅槽位
|
|
3121
|
+
*/
|
|
3122
|
+
function releaseIfIdle(sessionId, entry) {
|
|
3123
|
+
if (entry.listeners.size || entry.tickers.size || entry.errorListeners.size) return;
|
|
3124
|
+
if (entry.timer !== null) clearInterval(entry.timer);
|
|
3125
|
+
if (entry.onRefresh !== null) {
|
|
3126
|
+
const target = win();
|
|
3127
|
+
if (target) target.removeEventListener("edrv:refresh", entry.onRefresh);
|
|
3128
|
+
}
|
|
3129
|
+
feeds.delete(sessionId);
|
|
3130
|
+
}
|
|
3131
|
+
/**
|
|
3132
|
+
* 订阅会话的差异记录。
|
|
3133
|
+
* 首个订阅者触发一次立即拉取;同会话多订阅者共享一次 RPC。
|
|
3134
|
+
* @author ddj 2026年10月19号
|
|
3135
|
+
* @param sessionId 会话 id
|
|
3136
|
+
* @param listener 记录变化回调
|
|
3137
|
+
* @returns 取消订阅函数
|
|
3138
|
+
*/
|
|
3139
|
+
function subscribeRecords(sessionId, listener) {
|
|
3140
|
+
const entry = entryOf(sessionId);
|
|
3141
|
+
entry.listeners.add(listener);
|
|
3142
|
+
ensureTimer$1(sessionId, entry);
|
|
3143
|
+
if (entry.records) listener(entry.records);
|
|
3144
|
+
else if (!entry.inflight) refreshFeed(sessionId);
|
|
3145
|
+
return () => {
|
|
3146
|
+
entry.listeners.delete(listener);
|
|
3147
|
+
releaseIfIdle(sessionId, entry);
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
/**
|
|
3151
|
+
* 订阅会话的周期心跳(复用同一定时器,不额外制造定时器)。
|
|
3152
|
+
* @author ddj 2026年10月19号
|
|
3153
|
+
* @param sessionId 会话 id
|
|
3154
|
+
* @param listener 心跳回调
|
|
3155
|
+
* @returns 取消订阅函数
|
|
3156
|
+
*/
|
|
3157
|
+
function subscribeTick(sessionId, listener) {
|
|
3158
|
+
const entry = entryOf(sessionId);
|
|
3159
|
+
entry.tickers.add(listener);
|
|
3160
|
+
ensureTimer$1(sessionId, entry);
|
|
3161
|
+
return () => {
|
|
3162
|
+
entry.tickers.delete(listener);
|
|
3163
|
+
releaseIfIdle(sessionId, entry);
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3166
|
+
/**
|
|
3167
|
+
* 订阅会话记录拉取失败(错误提示用;同样复用该会话的共享通道)。
|
|
3168
|
+
* @author ddj 2026年10月19号
|
|
3169
|
+
* @param sessionId 会话 id
|
|
3170
|
+
* @param listener 失败回调,参数为已字符串化的错误
|
|
3171
|
+
* @returns 取消订阅函数
|
|
3172
|
+
*/
|
|
3173
|
+
function subscribeErrors(sessionId, listener) {
|
|
3174
|
+
const entry = entryOf(sessionId);
|
|
3175
|
+
entry.errorListeners.add(listener);
|
|
3176
|
+
ensureTimer$1(sessionId, entry);
|
|
3177
|
+
return () => {
|
|
3178
|
+
entry.errorListeners.delete(listener);
|
|
3179
|
+
releaseIfIdle(sessionId, entry);
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
//#endregion
|
|
3022
3183
|
//#region src/client/monaco/theme.ts
|
|
3023
3184
|
/**
|
|
3024
3185
|
* dsh-vscode-mode client — Monaco 主题:官方令牌跟随 + 现役双套回落。
|
|
@@ -17816,6 +17977,7 @@ window.__ModuleLoader__.load({
|
|
|
17816
17977
|
*/
|
|
17817
17978
|
function installOfficial(options) {
|
|
17818
17979
|
const { tabs, service, slots, renderTab, guide } = options;
|
|
17980
|
+
const titleOf = typeof options.title === "function" ? options.title : () => OFFICIAL_TAB_TITLE;
|
|
17819
17981
|
const open = (path, focusDiff) => {
|
|
17820
17982
|
try {
|
|
17821
17983
|
service.openTab(OFFICIAL_TAB_KIND, { params: {
|
|
@@ -17834,7 +17996,7 @@ window.__ModuleLoader__.load({
|
|
|
17834
17996
|
id: OFFICIAL_TAB_ID,
|
|
17835
17997
|
kind: OFFICIAL_TAB_KIND,
|
|
17836
17998
|
priority: "extension",
|
|
17837
|
-
title: () =>
|
|
17999
|
+
title: () => titleOf(),
|
|
17838
18000
|
guide: [{
|
|
17839
18001
|
order: 20,
|
|
17840
18002
|
title: guide.title,
|
|
@@ -20392,7 +20554,7 @@ window.__ModuleLoader__.load({
|
|
|
20392
20554
|
* @param state 任务快照
|
|
20393
20555
|
* @returns 可读的状态行
|
|
20394
20556
|
*/
|
|
20395
|
-
function countText(state) {
|
|
20557
|
+
function countText$1(state) {
|
|
20396
20558
|
if (!state) return "等待 SVN 任务…";
|
|
20397
20559
|
const c = state.counts;
|
|
20398
20560
|
return "合并 " + c.G + " · 新增 " + c.A + " · 删除 " + c.D + " · 更新 " + c.U + " · 冲突 " + c.C + (c.R || c.E ? " · 其他 " + (c.R + c.E) : "");
|
|
@@ -20792,7 +20954,7 @@ window.__ModuleLoader__.load({
|
|
|
20792
20954
|
*/
|
|
20793
20955
|
function updateFoot(props, state, closable, finished, cancel, dismiss, confirmClose, cleaned, cleanup, remaining, jump, refresh) {
|
|
20794
20956
|
const locked = updateLocked(state);
|
|
20795
|
-
return react.default.createElement("footer", { className: "edrv-update-foot" }, react.default.createElement("span", { className: "edrv-update-counts" }, countText(state)), remaining > 0 ? react.default.createElement("button", {
|
|
20957
|
+
return react.default.createElement("footer", { className: "edrv-update-foot" }, react.default.createElement("span", { className: "edrv-update-counts" }, countText$1(state)), remaining > 0 ? react.default.createElement("button", {
|
|
20796
20958
|
type: "button",
|
|
20797
20959
|
className: "edrv-svn-act edrv-update-jump",
|
|
20798
20960
|
onClick: jump
|
|
@@ -22875,31 +23037,36 @@ window.__ModuleLoader__.load({
|
|
|
22875
23037
|
}
|
|
22876
23038
|
return true;
|
|
22877
23039
|
};
|
|
23040
|
+
/**
|
|
23041
|
+
* 把共享订阅拿到的记录落进组件状态(内容一致时保持引用不变,避免整树重渲染)。
|
|
23042
|
+
* @private @author ddj 2026年10月19号
|
|
23043
|
+
* @param list 差异记录数组
|
|
23044
|
+
*/
|
|
23045
|
+
const applyRecords = (list) => {
|
|
23046
|
+
const trace = isDebug();
|
|
23047
|
+
const compareStart = trace ? performance.now() : 0;
|
|
23048
|
+
const map = {};
|
|
23049
|
+
for (const r of list) {
|
|
23050
|
+
if (r.archived === true) continue;
|
|
23051
|
+
map[r.callId] = r;
|
|
23052
|
+
}
|
|
23053
|
+
setRecords((prev) => {
|
|
23054
|
+
const equal = sameRecords(prev, map);
|
|
23055
|
+
if (trace) try {
|
|
23056
|
+
const compareMs = performance.now() - compareStart;
|
|
23057
|
+
if (compareMs > 20) dbg(sessionId, `[DEBUG diffList] count=${list.length} compareMs=${compareMs.toFixed(1)} changed=${!equal}`);
|
|
23058
|
+
} catch (e) {}
|
|
23059
|
+
return equal ? prev : map;
|
|
23060
|
+
});
|
|
23061
|
+
};
|
|
23062
|
+
/**
|
|
23063
|
+
* 主动刷新一次差异记录(走会话级共享通道,同会话多消费方共享一次 RPC)。
|
|
23064
|
+
* @private @author ddj 2026年10月19号
|
|
23065
|
+
* @param skipStale 是否跳过过期判定
|
|
23066
|
+
*/
|
|
22878
23067
|
const refreshRecords = (skipStale) => {
|
|
22879
23068
|
if (!sessionId) return;
|
|
22880
|
-
|
|
22881
|
-
const start = trace ? performance.now() : 0;
|
|
22882
|
-
rpc("edrv.list", {
|
|
22883
|
-
sessionId,
|
|
22884
|
-
...skipStale ? { skipStale: true } : {}
|
|
22885
|
-
}).then((res) => {
|
|
22886
|
-
const received = trace ? performance.now() : 0;
|
|
22887
|
-
if (!res || !res.ok || !Array.isArray(res.records)) return;
|
|
22888
|
-
const map = {};
|
|
22889
|
-
for (const r of res.records) {
|
|
22890
|
-
if (r.archived === true) continue;
|
|
22891
|
-
map[r.callId] = r;
|
|
22892
|
-
}
|
|
22893
|
-
setRecords((prev) => {
|
|
22894
|
-
const compareStart = trace ? performance.now() : 0;
|
|
22895
|
-
const equal = sameRecords(prev, map);
|
|
22896
|
-
if (trace) try {
|
|
22897
|
-
const compareMs = performance.now() - compareStart;
|
|
22898
|
-
if (compareMs > 20 || received - start > 100) dbg(sessionId, `[DEBUG diffList] count=${res.records.length} skipStale=${skipStale === true} rpcMs=${(received - start).toFixed(1)} compareMs=${compareMs.toFixed(1)} changed=${!equal}`);
|
|
22899
|
-
} catch (e) {}
|
|
22900
|
-
return equal ? prev : map;
|
|
22901
|
-
});
|
|
22902
|
-
}).catch((e) => setError("list异常:" + String(e)));
|
|
23069
|
+
refreshFeed(sessionId, skipStale === true);
|
|
22903
23070
|
};
|
|
22904
23071
|
/**
|
|
22905
23072
|
* Reject callbacks for a replaced file or loader generation.
|
|
@@ -23224,14 +23391,13 @@ window.__ModuleLoader__.load({
|
|
|
23224
23391
|
});
|
|
23225
23392
|
};
|
|
23226
23393
|
react.default.useEffect(() => {
|
|
23227
|
-
if (!sessionId) return;
|
|
23394
|
+
if (!sessionId) return void 0;
|
|
23395
|
+
const offRecords = subscribeRecords(sessionId, applyRecords);
|
|
23396
|
+
const offErrors = subscribeErrors(sessionId, (message) => setError("list异常:" + message));
|
|
23228
23397
|
refreshRecords();
|
|
23229
|
-
const t = setInterval(refreshRecords, 5e3);
|
|
23230
|
-
const onRefresh = () => refreshRecords();
|
|
23231
|
-
window.addEventListener("edrv:refresh", onRefresh);
|
|
23232
23398
|
return () => {
|
|
23233
|
-
|
|
23234
|
-
|
|
23399
|
+
offRecords();
|
|
23400
|
+
offErrors();
|
|
23235
23401
|
};
|
|
23236
23402
|
}, [sessionId]);
|
|
23237
23403
|
/**
|
|
@@ -27313,34 +27479,24 @@ window.__ModuleLoader__.load({
|
|
|
27313
27479
|
//#region src/client/ui/DiffBadge.ts
|
|
27314
27480
|
/**
|
|
27315
27481
|
* dsh-vscode-mode client — DiffBadge:header 差异角标(仅工作区有差异时显示)。
|
|
27316
|
-
*
|
|
27482
|
+
* 轮询已收敛到 state/recordsFeed.ts 的会话级共享订阅,本组件不再自建定时器。
|
|
27317
27483
|
* 作者 ddj 2026-08-20
|
|
27318
27484
|
*/
|
|
27319
27485
|
/**
|
|
27320
|
-
* 会话 header
|
|
27486
|
+
* 会话 header 差异角标:订阅会话差异记录,有差异时显示数字徽章,点击打开编辑区并拉起 DiffLauncher。
|
|
27321
27487
|
*/
|
|
27322
27488
|
function DiffBadge(props) {
|
|
27323
27489
|
const sessionId = props?.sessionId;
|
|
27324
27490
|
const [summary, setSummary] = react.default.useState(null);
|
|
27325
|
-
const seq = react.default.useRef(0);
|
|
27326
|
-
const load = react.default.useCallback(() => {
|
|
27327
|
-
if (!sessionId) return;
|
|
27328
|
-
const s = ++seq.current;
|
|
27329
|
-
rpc("edrv.list", { sessionId }).then((res) => {
|
|
27330
|
-
if (s !== seq.current || !res || !res.ok || !Array.isArray(res.records)) return;
|
|
27331
|
-
setSummary(summarize(res.records));
|
|
27332
|
-
}).catch(() => {});
|
|
27333
|
-
}, [sessionId]);
|
|
27334
27491
|
react.default.useEffect(() => {
|
|
27335
|
-
|
|
27336
|
-
|
|
27337
|
-
|
|
27338
|
-
|
|
27339
|
-
return () => {
|
|
27340
|
-
|
|
27341
|
-
|
|
27342
|
-
|
|
27343
|
-
}, [load]);
|
|
27492
|
+
if (!sessionId) {
|
|
27493
|
+
setSummary(null);
|
|
27494
|
+
return;
|
|
27495
|
+
}
|
|
27496
|
+
return subscribeRecords(sessionId, (records) => {
|
|
27497
|
+
setSummary(summarize(records));
|
|
27498
|
+
});
|
|
27499
|
+
}, [sessionId]);
|
|
27344
27500
|
const n = summary?.totalFiles ?? 0;
|
|
27345
27501
|
if (!n) return null;
|
|
27346
27502
|
const click = () => {
|
|
@@ -27394,7 +27550,8 @@ window.__ModuleLoader__.load({
|
|
|
27394
27550
|
//#region src/client/ui/PerfSizeBadge.ts
|
|
27395
27551
|
/**
|
|
27396
27552
|
* dsh-vscode-mode client — PerfSizeBadge:对话 header 会话体积指示器。
|
|
27397
|
-
*
|
|
27553
|
+
* 定时器复用 state/recordsFeed.ts 的会话级心跳(单会话只保留 1 个定时器),
|
|
27554
|
+
* 轮询当前会话持久化体积(host 仅 stat,近零成本),≥1MB 显示、超阈值变色,
|
|
27398
27555
|
* 引导"一次任务一个短会话":会话过大时建议 /compact 或新开会话。
|
|
27399
27556
|
* 作者 ddj 2026-09-02
|
|
27400
27557
|
*/
|
|
@@ -27428,12 +27585,10 @@ window.__ModuleLoader__.load({
|
|
|
27428
27585
|
}).catch(() => {});
|
|
27429
27586
|
}, [sessionId, sessions]);
|
|
27430
27587
|
react.default.useEffect(() => {
|
|
27588
|
+
if (!sessionId) return void 0;
|
|
27431
27589
|
load();
|
|
27432
|
-
|
|
27433
|
-
|
|
27434
|
-
clearInterval(t);
|
|
27435
|
-
};
|
|
27436
|
-
}, [load]);
|
|
27590
|
+
return subscribeTick(sessionId, load);
|
|
27591
|
+
}, [load, sessionId]);
|
|
27437
27592
|
if (!size || size < SHOW_MIN) return null;
|
|
27438
27593
|
const label = size >= 1048576 ? (size / 1048576).toFixed(1) + "MB" : Math.round(size / 1024) + "KB";
|
|
27439
27594
|
const color = size >= HARD ? "#dc2626" : size >= SOFT ? "#d97706" : "var(--dsw-text-3,#6b7280)";
|
|
@@ -27460,6 +27615,7 @@ window.__ModuleLoader__.load({
|
|
|
27460
27615
|
/**
|
|
27461
27616
|
* dsh-vscode-mode client — 会话级差异 dock。
|
|
27462
27617
|
* conversation.input.dock 是唯一实例:普通对话显示摘要,文件编辑页显示完整操作条。
|
|
27618
|
+
* 轮询已收敛到 state/recordsFeed.ts 的会话级共享订阅,本组件不再自建定时器。
|
|
27463
27619
|
* 作者 ddj 2026-08-26
|
|
27464
27620
|
*/
|
|
27465
27621
|
const nextIndexBySession = /* @__PURE__ */ new Map();
|
|
@@ -27473,25 +27629,15 @@ window.__ModuleLoader__.load({
|
|
|
27473
27629
|
const sessionId = props?.sessionId;
|
|
27474
27630
|
const [summary, setSummary] = react.default.useState(null);
|
|
27475
27631
|
const [, setStoreRevision] = react.default.useState(0);
|
|
27476
|
-
const seq = react.default.useRef(0);
|
|
27477
|
-
const load = react.default.useCallback(() => {
|
|
27478
|
-
if (!sessionId) return;
|
|
27479
|
-
const current = ++seq.current;
|
|
27480
|
-
rpc("edrv.list", { sessionId }).then((res) => {
|
|
27481
|
-
if (current !== seq.current || !res || !res.ok || !Array.isArray(res.records)) return;
|
|
27482
|
-
setSummary(summarize(res.records));
|
|
27483
|
-
}).catch(() => {});
|
|
27484
|
-
}, [sessionId]);
|
|
27485
27632
|
react.default.useEffect(() => {
|
|
27486
|
-
|
|
27487
|
-
|
|
27488
|
-
|
|
27489
|
-
|
|
27490
|
-
return () => {
|
|
27491
|
-
|
|
27492
|
-
|
|
27493
|
-
|
|
27494
|
-
}, [load]);
|
|
27633
|
+
if (!sessionId) {
|
|
27634
|
+
setSummary(null);
|
|
27635
|
+
return;
|
|
27636
|
+
}
|
|
27637
|
+
return subscribeRecords(sessionId, (records) => {
|
|
27638
|
+
setSummary(summarize(records));
|
|
27639
|
+
});
|
|
27640
|
+
}, [sessionId]);
|
|
27495
27641
|
react.default.useEffect(() => {
|
|
27496
27642
|
if (!sessionId) return void 0;
|
|
27497
27643
|
const sync = () => setStoreRevision((value) => value + 1);
|
|
@@ -30584,6 +30730,73 @@ window.__ModuleLoader__.load({
|
|
|
30584
30730
|
}, "保存配置"))));
|
|
30585
30731
|
}
|
|
30586
30732
|
//#endregion
|
|
30733
|
+
//#region src/client/ui/PluginDetailSection.ts
|
|
30734
|
+
/**
|
|
30735
|
+
* dsh-vscode-mode client — 官方插件管理器详情页的只读状态段。
|
|
30736
|
+
* 挂在 DSH 0.2.1 新增的 `plugins.detail.section`(list / root)扩展位上,
|
|
30737
|
+
* 只在「本插件自己」的详情页渲染,其余插件页返回 null。
|
|
30738
|
+
* 作者 ddj 2026年10月19号
|
|
30739
|
+
*/
|
|
30740
|
+
/** 本插件标识(与 package.json name / bundle 行 id 一致)。 */
|
|
30741
|
+
const SELF_ID = "dsh-vscode-mode";
|
|
30742
|
+
/**
|
|
30743
|
+
* 判断详情页 subject 是否指向本插件。
|
|
30744
|
+
* 官方有两种形状:官方内置项 `{kind:'item', id}`、安装包 `{kind:'bundle', pkg:{name}}`。
|
|
30745
|
+
* @author ddj 2026年10月19号
|
|
30746
|
+
* @param subject 详情页主题
|
|
30747
|
+
* @returns 是否为本插件
|
|
30748
|
+
*/
|
|
30749
|
+
function isSelfSubject(subject) {
|
|
30750
|
+
if (!subject || typeof subject !== "object") return false;
|
|
30751
|
+
if (subject.kind === "item") return subject.id === SELF_ID;
|
|
30752
|
+
if (subject.kind === "bundle") return subject.pkg?.name === SELF_ID;
|
|
30753
|
+
return false;
|
|
30754
|
+
}
|
|
30755
|
+
/**
|
|
30756
|
+
* 把适配项列表压成「正常 / 未生效」计数文案。
|
|
30757
|
+
* @author ddj 2026年10月19号
|
|
30758
|
+
* @param list 适配项数组
|
|
30759
|
+
* @returns 计数文案
|
|
30760
|
+
*/
|
|
30761
|
+
function countText(list) {
|
|
30762
|
+
const items = Array.isArray(list) ? list : [];
|
|
30763
|
+
return items.filter((item) => item?.active === true).length + " / " + items.length + " 项生效";
|
|
30764
|
+
}
|
|
30765
|
+
/**
|
|
30766
|
+
* 插件详情页的只读状态段。
|
|
30767
|
+
* @author ddj 2026年10月19号
|
|
30768
|
+
* @param props DSH 注入的 { subject }
|
|
30769
|
+
* @returns 状态段元素或 null(非本插件页)
|
|
30770
|
+
*/
|
|
30771
|
+
function PluginDetailSection(props) {
|
|
30772
|
+
const subject = props?.subject;
|
|
30773
|
+
const isSelf = isSelfSubject(subject);
|
|
30774
|
+
const [report, setReport] = react.default.useState(null);
|
|
30775
|
+
react.default.useEffect(() => {
|
|
30776
|
+
if (!isSelf) return void 0;
|
|
30777
|
+
let alive = true;
|
|
30778
|
+
rpc("compat", {}).then((result) => {
|
|
30779
|
+
if (!alive || !result || !result.ok) return;
|
|
30780
|
+
setReport(result.report ?? null);
|
|
30781
|
+
}).catch(() => {});
|
|
30782
|
+
return () => {
|
|
30783
|
+
alive = false;
|
|
30784
|
+
};
|
|
30785
|
+
}, [isSelf]);
|
|
30786
|
+
if (!isSelf) return null;
|
|
30787
|
+
const row = (name, note) => react.default.createElement("div", {
|
|
30788
|
+
key: name,
|
|
30789
|
+
className: "vsm-compat-item ok"
|
|
30790
|
+
}, react.default.createElement("span", { className: "vsm-compat-name" }, name), react.default.createElement("span", { className: "vsm-compat-note" }, note));
|
|
30791
|
+
const versionText = report?.dshVersion ? "DSH " + report.dshVersion : "未探测到 DSH 版本";
|
|
30792
|
+
const pluginText = report?.pluginVersion ? "插件 " + report.pluginVersion : "插件版本未上报";
|
|
30793
|
+
const warnCount = Array.isArray(report?.warnings) ? report.warnings.length : 0;
|
|
30794
|
+
return react.default.createElement("section", {
|
|
30795
|
+
className: "vsm-panel",
|
|
30796
|
+
"data-edrv-plugin-status": "1"
|
|
30797
|
+
}, react.default.createElement("h3", { className: "vsm-panel-title" }, "VSCodeMode 运行状态"), react.default.createElement("div", { className: "vsm-panel-body" }, row("版本", versionText + " · " + pluginText), row("版本适配", countText(report?.adapters)), row("外部插件与服务", countText(report?.external)), row("护栏", countText(report?.guards)), row("兼容性警告", warnCount ? warnCount + " 条(设置页「兼容性」可查看详情)" : "未检测到")));
|
|
30798
|
+
}
|
|
30799
|
+
//#endregion
|
|
30587
30800
|
//#region src/client/openPathRouter.ts
|
|
30588
30801
|
/**
|
|
30589
30802
|
* dsh-vscode-mode client — DSH 对话文件链接的统一打开路由。
|
|
@@ -37914,6 +38127,78 @@ window.__ModuleLoader__.load({
|
|
|
37914
38127
|
};
|
|
37915
38128
|
}
|
|
37916
38129
|
//#endregion
|
|
38130
|
+
//#region src/client/localeClient.ts
|
|
38131
|
+
/**
|
|
38132
|
+
* dsh-vscode-mode client — UI 壳层文案的 locale 接入。
|
|
38133
|
+
* 此前 `locale` 写在 inject 里却零调用,界面文案全为硬编码中文;
|
|
38134
|
+
* 这里只覆盖壳层(slot label、编辑 Tab 标题、侧栏引导),编辑器内部大段文案不动。
|
|
38135
|
+
* 所有对外函数都对 locale 服务做可选探测:旧版 DSH 无该服务时回落中文词典。
|
|
38136
|
+
* 作者 ddj 2026年10月19号
|
|
38137
|
+
*/
|
|
38138
|
+
/** locale 命名空间(与包名一致,避免与其他插件串词条)。 */
|
|
38139
|
+
const LOCALE_NS = "dsh-vscode-mode";
|
|
38140
|
+
/** 壳层文案词典(键名即 slot label / Tab 标题的稳定标识)。 */
|
|
38141
|
+
const LOCALE_DICTS = {
|
|
38142
|
+
zh: {
|
|
38143
|
+
"slot.fileEditor": "文件编辑",
|
|
38144
|
+
"slot.diff": "差异",
|
|
38145
|
+
"slot.sessionSize": "会话体积",
|
|
38146
|
+
"slot.settings": "VSCodeMode",
|
|
38147
|
+
"guide.title": "文件编辑",
|
|
38148
|
+
"guide.description": "Monaco 文件编辑器:对话与编辑同屏(差异审查/跳转/大纲)"
|
|
38149
|
+
},
|
|
38150
|
+
en: {
|
|
38151
|
+
"slot.fileEditor": "File editor",
|
|
38152
|
+
"slot.diff": "Diff",
|
|
38153
|
+
"slot.sessionSize": "Session size",
|
|
38154
|
+
"slot.settings": "VSCodeMode",
|
|
38155
|
+
"guide.title": "File editor",
|
|
38156
|
+
"guide.description": "Monaco file editor: chat and editing side by side (diff review, jump, outline)"
|
|
38157
|
+
}
|
|
38158
|
+
};
|
|
38159
|
+
/**
|
|
38160
|
+
* 取词条:先当前语言、再中文、最后键本身。
|
|
38161
|
+
* @author ddj 2026年10月19号
|
|
38162
|
+
* @param lang 语言 id
|
|
38163
|
+
* @param key 词条键
|
|
38164
|
+
* @returns 文案
|
|
38165
|
+
*/
|
|
38166
|
+
function lookup(lang, key) {
|
|
38167
|
+
return LOCALE_DICTS[lang]?.[key] ?? LOCALE_DICTS["zh"]?.[key] ?? key;
|
|
38168
|
+
}
|
|
38169
|
+
/**
|
|
38170
|
+
* 生成不依赖 locale 服务的直读翻译函数(服务缺失/注册失败时使用)。
|
|
38171
|
+
* @author ddj 2026年10月19号
|
|
38172
|
+
* @param lang 语言 id
|
|
38173
|
+
* @returns 翻译函数
|
|
38174
|
+
*/
|
|
38175
|
+
function directTranslate(lang = "zh") {
|
|
38176
|
+
return (key) => lookup(lang, key);
|
|
38177
|
+
}
|
|
38178
|
+
/**
|
|
38179
|
+
* 注册本插件词典并返回翻译函数。
|
|
38180
|
+
* 服务缺失、重复注册或 bind 抛错都不影响插件其余部分:一律回落直读词典。
|
|
38181
|
+
* @author ddj 2026年10月19号
|
|
38182
|
+
* @param ctx DSH 客户端上下文
|
|
38183
|
+
* @returns 翻译函数(读取时才解析当前语言,可直接放进 slot label thunk)
|
|
38184
|
+
*/
|
|
38185
|
+
function installLocale(ctx) {
|
|
38186
|
+
let service;
|
|
38187
|
+
try {
|
|
38188
|
+
service = ctx.get("locale");
|
|
38189
|
+
} catch {}
|
|
38190
|
+
try {
|
|
38191
|
+
if (service && typeof service.register === "function") service.register(LOCALE_NS, LOCALE_DICTS);
|
|
38192
|
+
} catch {}
|
|
38193
|
+
try {
|
|
38194
|
+
if (service && typeof service.bind === "function") {
|
|
38195
|
+
const bound = service.bind(LOCALE_NS);
|
|
38196
|
+
if (typeof bound === "function") return (key) => bound(key);
|
|
38197
|
+
}
|
|
38198
|
+
} catch {}
|
|
38199
|
+
return directTranslate();
|
|
38200
|
+
}
|
|
38201
|
+
//#endregion
|
|
37917
38202
|
//#region src/client/planBridge.ts
|
|
37918
38203
|
/** Optional DSH plan integration; no provider or approval UI replacement. @author ddj 2026年10月08号 */
|
|
37919
38204
|
const CLAIM_ID = "dsh-vscode-mode/plan";
|
|
@@ -38155,7 +38440,7 @@ window.__ModuleLoader__.load({
|
|
|
38155
38440
|
clearPlanState();
|
|
38156
38441
|
};
|
|
38157
38442
|
}
|
|
38158
|
-
createLogger();
|
|
38443
|
+
createLogger().child("debug");
|
|
38159
38444
|
//#endregion
|
|
38160
38445
|
//#region src/client/updateNotice.ts
|
|
38161
38446
|
/**
|
|
@@ -38284,6 +38569,7 @@ window.__ModuleLoader__.load({
|
|
|
38284
38569
|
*/
|
|
38285
38570
|
async function apply(ctx) {
|
|
38286
38571
|
const schedule = (fn, ms) => ctx.timeout(fn, ms);
|
|
38572
|
+
const t = installLocale(ctx);
|
|
38287
38573
|
/**
|
|
38288
38574
|
* 无门控上报界面状态镜像事件(绕过客户端 edrv.debug 开关,直接落 host 诊断日志)。
|
|
38289
38575
|
* 开关关闭时 dbg 直接 return,「重启后没恢复」这类问题会在客户端侧彻底无痕,故另开一条通路。
|
|
@@ -38637,7 +38923,7 @@ window.__ModuleLoader__.load({
|
|
|
38637
38923
|
name: "conversation.view",
|
|
38638
38924
|
id: "edrv-editor",
|
|
38639
38925
|
order: 5,
|
|
38640
|
-
label: "
|
|
38926
|
+
label: () => t("slot.fileEditor"),
|
|
38641
38927
|
inject: (sessionId) => ({ sessionId })
|
|
38642
38928
|
}, (props) => react.default.createElement(EditorView, Object.assign({}, props, {
|
|
38643
38929
|
layout: "tab",
|
|
@@ -38670,9 +38956,10 @@ window.__ModuleLoader__.load({
|
|
|
38670
38956
|
service: official.service,
|
|
38671
38957
|
slots: ctx.slots,
|
|
38672
38958
|
renderTab: officialRenderTab,
|
|
38959
|
+
title: () => t("slot.fileEditor"),
|
|
38673
38960
|
guide: {
|
|
38674
|
-
title: () =>
|
|
38675
|
-
description: () => "
|
|
38961
|
+
title: () => t("guide.title"),
|
|
38962
|
+
description: () => t("guide.description")
|
|
38676
38963
|
}
|
|
38677
38964
|
});
|
|
38678
38965
|
if (outcome === null) {
|
|
@@ -38786,31 +39073,36 @@ window.__ModuleLoader__.load({
|
|
|
38786
39073
|
name: "conversation.input.dock",
|
|
38787
39074
|
id: "edrv-diff-dock",
|
|
38788
39075
|
order: 30,
|
|
38789
|
-
label: "
|
|
39076
|
+
label: () => t("slot.diff"),
|
|
38790
39077
|
inject: (sessionId) => ({ sessionId })
|
|
38791
39078
|
}, (props) => react.default.createElement(ConversationDiffDock, Object.assign({}, props)));
|
|
38792
39079
|
registerSlotSafely(ctx, {
|
|
38793
39080
|
name: "conversation.session.header.utilities",
|
|
38794
39081
|
id: "edrv-diff-badge",
|
|
38795
39082
|
order: 90,
|
|
38796
|
-
label: "
|
|
39083
|
+
label: () => t("slot.diff")
|
|
38797
39084
|
}, (props) => react.default.createElement(DiffBadge, Object.assign({}, props)));
|
|
38798
39085
|
registerSlotSafely(ctx, {
|
|
38799
39086
|
name: "conversation.session.header.utilities",
|
|
38800
39087
|
id: "edrv-perf-size",
|
|
38801
39088
|
order: 89,
|
|
38802
|
-
label: "
|
|
39089
|
+
label: () => t("slot.sessionSize")
|
|
38803
39090
|
}, (props) => react.default.createElement(PerfSizeBadge, Object.assign({}, props, { sessions })));
|
|
38804
39091
|
registerSlotSafely(ctx, {
|
|
38805
39092
|
name: "settings.section",
|
|
38806
39093
|
id: "vscode-mode",
|
|
38807
39094
|
order: 30,
|
|
38808
|
-
label: "
|
|
39095
|
+
label: () => t("slot.settings")
|
|
38809
39096
|
}, () => react.default.createElement(SettingsContext.Provider, { value: bridge.scope() }, react.default.createElement(McpSettings, {
|
|
38810
39097
|
openerRegistry: registry,
|
|
38811
39098
|
compatSummary,
|
|
38812
39099
|
getShortcuts: () => detectShortcuts(ctx)
|
|
38813
39100
|
})));
|
|
39101
|
+
registerSlotSafely(ctx, {
|
|
39102
|
+
name: "plugins.detail.section",
|
|
39103
|
+
id: "edrv-plugin-status",
|
|
39104
|
+
order: 40
|
|
39105
|
+
}, (props) => react.default.createElement(PluginDetailSection, Object.assign({}, props)));
|
|
38814
39106
|
ctx.effect(() => () => {
|
|
38815
39107
|
try {
|
|
38816
39108
|
disposeSnippets();
|