dsh-vscode-mode 0.1.59 → 0.1.61

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/client.js CHANGED
@@ -3058,7 +3058,7 @@ window.__ModuleLoader__.load({
3058
3058
  * 浏览器不涉 ~/.dsh 文件系统,这里只管键名 + shared 路由常量。
3059
3059
  * 作者 ddj 2026-09-01
3060
3060
  */
3061
- /** localStorage 键前缀表(按功能隔离;均按会话拼接)。 */
3061
+ /** localStorage 键前缀表(按功能隔离;与 scopeStore 作用域键拼接:工作区优先,无 cwd 回退会话)。 */
3062
3062
  const CACHE_KEY = {
3063
3063
  /** 展开状态(explorerCache v1,当前使用)。 */
3064
3064
  expanded: "edrv.cache.explorer.v1.",
@@ -3072,7 +3072,7 @@ window.__ModuleLoader__.load({
3072
3072
  sidebar: "edrv.sidebar.",
3073
3073
  /** 搜索面板条件(SearchPanel v1)。 */
3074
3074
  search: "edrv.search.v1.",
3075
- /** 侧边栏提示已关闭标记(全局,无会话)。 */
3075
+ /** 侧边栏提示已关闭标记(全局,无作用域)。 */
3076
3076
  sideHint: "edrv.side-hint-dismissed",
3077
3077
  /** 「性能优化」页工作区栏目展开状态(workspaceFoldCache v1,全局不按会话)。 */
3078
3078
  workspaceFold: "edrv.ws-fold.v1.",
@@ -3088,7 +3088,7 @@ window.__ModuleLoader__.load({
3088
3088
  * 键前缀统一走 paths.ts PathConst(CACHE_KEY.viewstate)。
3089
3089
  * 作者 ddj 2026-08-28
3090
3090
  */
3091
- /** localStorage 键前缀(按会话隔离)。 */
3091
+ /** localStorage 键前缀(按工作区作用域隔离,无 cwd 会话回退会话键)。 */
3092
3092
  const KEY_PREFIX$2 = CACHE_KEY.viewstate;
3093
3093
  /**
3094
3094
  * 判断对象是否为合法的 Monaco view state(保守形状校验,非深校验)。
@@ -3160,29 +3160,173 @@ window.__ModuleLoader__.load({
3160
3160
  return next;
3161
3161
  }
3162
3162
  /**
3163
- * 读取某会话的视图状态缓存(localStorage 不可用 → 空映射)。
3163
+ * 读取某作用域的视图状态缓存(localStorage 不可用 → 空映射)。
3164
3164
  * @author ddj 2026年08月28号
3165
- * @param sessionId 会话 id
3165
+ * @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
3166
3166
  * @returns path → viewState 映射
3167
3167
  */
3168
- function viewStatesLoad(sessionId) {
3168
+ function viewStatesLoad(scope) {
3169
3169
  try {
3170
- return parseViewStates(window.localStorage.getItem(KEY_PREFIX$2 + String(sessionId))) ?? {};
3170
+ return parseViewStates(window.localStorage.getItem(KEY_PREFIX$2 + String(scope))) ?? {};
3171
3171
  } catch (error) {
3172
3172
  return {};
3173
3173
  }
3174
3174
  }
3175
3175
  /**
3176
- * 写入某会话的视图状态缓存(损坏/配额满 → 忽略)。
3176
+ * 写入某作用域的视图状态缓存(损坏/配额满 → 忽略)。
3177
3177
  * @author ddj 2026年08月28号
3178
- * @param sessionId 会话 id
3178
+ * @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
3179
3179
  * @param states path → viewState 映射
3180
3180
  */
3181
- function viewStatesSave(sessionId, states) {
3181
+ function viewStatesSave(scope, states) {
3182
3182
  try {
3183
- window.localStorage.setItem(KEY_PREFIX$2 + String(sessionId), serializeViewStates(states));
3183
+ window.localStorage.setItem(KEY_PREFIX$2 + String(scope), serializeViewStates(states));
3184
3184
  } catch (error) {}
3185
3185
  }
3186
+ //#endregion
3187
+ //#region src/client/state/scopeStore.ts
3188
+ /**
3189
+ * dsh-vscode-mode client — 编辑区状态作用域键(scope key)。
3190
+ * 编辑区 UI 状态(页签/展开树/视图状态/侧边栏等)按「工作区」隔离:同一工作区下
3191
+ * 切换对话恢复同一份状态,无体感差别;会话没有 cwd 时回退按会话隔离(旧行为)。
3192
+ * 另提供旧「会话键」数据到「工作区键」的一次性迁移(只复制不删除,幂等)。
3193
+ * 键前缀统一走 paths.ts PathConst(CACHE_KEY),本模块只产出拼接用的作用域串。
3194
+ * 作者 ddj 2026-09-09
3195
+ */
3196
+ /** Windows 盘符绝对路径(如 D:/ 或 d:\),大小写不敏感。 */
3197
+ const WIN_DRIVE_RE = /^[a-z]:\//i;
3198
+ /**
3199
+ * 归一化工作区路径:trim、反斜杠→斜杠、去尾部斜杠;
3200
+ * Windows 盘符绝对路径整体小写(文件系统大小写不敏感),POSIX 路径保留大小写。
3201
+ * @author ddj 2026年09月09号
3202
+ * @param cwd 工作区目录(sessions 快照 byId[sessionId].cwd)
3203
+ * @returns 归一化路径;空值返回 ''
3204
+ */
3205
+ function normalizeWsPath(cwd) {
3206
+ const s = String(cwd ?? "").trim().replace(/\\/g, "/").replace(/\/+$/, "");
3207
+ if (!s) return "";
3208
+ return WIN_DRIVE_RE.test(s) ? s.toLowerCase() : s;
3209
+ }
3210
+ /**
3211
+ * 计算编辑区状态作用域键:有 cwd → 'ws:' + 归一化路径(同工作区共享);
3212
+ * 无 cwd → 'sid:' + sessionId(回退按会话隔离,对齐旧行为)。
3213
+ * @author ddj 2026年09月09号
3214
+ * @param cwd 工作区目录(可为 null/undefined)
3215
+ * @param sessionId 会话 id(cwd 缺失时的隔离键)
3216
+ * @returns 作用域键字符串
3217
+ */
3218
+ function workspaceScopeOf(cwd, sessionId) {
3219
+ const ws = normalizeWsPath(cwd);
3220
+ if (ws) return "ws:" + ws;
3221
+ return "sid:" + String(sessionId ?? "");
3222
+ }
3223
+ /** 参与迁移的 localStorage 键前缀(编辑区全部按会话持久化的旧键)。 */
3224
+ const MIGRATE_PREFIXES = [
3225
+ CACHE_KEY.editor,
3226
+ CACHE_KEY.viewstate,
3227
+ CACHE_KEY.sidebar + "side.",
3228
+ CACHE_KEY.sidebar,
3229
+ CACHE_KEY.search,
3230
+ CACHE_KEY.expanded,
3231
+ CACHE_KEY.entries
3232
+ ];
3233
+ /** 已迁移过的 scope+sessionId 组合(按 storage 实例分组;每页生命周期只跑一次)。 */
3234
+ const migratedPairs = /* @__PURE__ */ new WeakMap();
3235
+ /**
3236
+ * 把旧「前缀+sessionId」键的数据一次性复制到「前缀+scope」键(目标已存在则跳过)。
3237
+ * 只复制不删除旧键;幂等,可安全重复调用。升级后首个工作区键为空时兜底恢复旧数据。
3238
+ * @author ddj 2026年09月09号
3239
+ * @param scope 目标作用域键
3240
+ * @param sessionId 旧数据所属会话 id
3241
+ * @param storage localStorage 注入口(缺省 window.localStorage,测试可传内存桩)
3242
+ */
3243
+ function migrateScopedKeys(scope, sessionId, storage) {
3244
+ if (!scope || !sessionId) return;
3245
+ let store = null;
3246
+ try {
3247
+ store = storage ?? window.localStorage;
3248
+ } catch (error) {
3249
+ return;
3250
+ }
3251
+ if (!store) return;
3252
+ let pairs = migratedPairs.get(store);
3253
+ if (!pairs) {
3254
+ pairs = /* @__PURE__ */ new Set();
3255
+ migratedPairs.set(store, pairs);
3256
+ }
3257
+ const pair = scope + "|" + sessionId;
3258
+ if (pairs.has(pair)) return;
3259
+ pairs.add(pair);
3260
+ const legacy = String(sessionId);
3261
+ for (const prefix of MIGRATE_PREFIXES) {
3262
+ const target = prefix + scope;
3263
+ try {
3264
+ if (store.getItem(target) !== null) continue;
3265
+ const raw = store.getItem(prefix + legacy);
3266
+ if (raw === null) continue;
3267
+ store.setItem(target, raw);
3268
+ } catch (error) {}
3269
+ }
3270
+ }
3271
+ /** scope →(path → model)缓存表。 */
3272
+ const caches = /* @__PURE__ */ new Map();
3273
+ /** 当前激活作用域(切换时释放上一个)。 */
3274
+ let activeScope = null;
3275
+ /** 模型是否仍附着在某个编辑器实例上(缺方法视为未附着)。 */
3276
+ function isAttached(model) {
3277
+ const fn = model?.isAttachedToEditor;
3278
+ return typeof fn === "function" ? fn.call(model) === true : false;
3279
+ }
3280
+ /** 释放模型(仅未附着编辑器时真正 dispose;附着中的保留给编辑器自毁)。 */
3281
+ function disposeIfFree(model) {
3282
+ if (!model || isAttached(model)) return;
3283
+ try {
3284
+ model.dispose();
3285
+ } catch {}
3286
+ }
3287
+ /**
3288
+ * 取(或切)某作用域的 model 缓存:同作用域重复调用复用同一 Map(跨挂载存活);
3289
+ * 作用域变化时释放上一作用域的模型(未附着的 dispose,附着中的仅移出缓存)。
3290
+ * @author ddj 2026年09月09号
3291
+ * @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
3292
+ * @returns 该作用域的 path → model Map
3293
+ */
3294
+ function modelsForScope(scope) {
3295
+ const existing = caches.get(scope);
3296
+ if (existing && activeScope === scope) return existing;
3297
+ if (activeScope !== null && activeScope !== scope) {
3298
+ const prev = caches.get(activeScope);
3299
+ if (prev) {
3300
+ for (const model of prev.values()) disposeIfFree(model);
3301
+ prev.clear();
3302
+ caches.delete(activeScope);
3303
+ }
3304
+ }
3305
+ activeScope = scope;
3306
+ if (existing) return existing;
3307
+ const map = /* @__PURE__ */ new Map();
3308
+ caches.set(scope, map);
3309
+ return map;
3310
+ }
3311
+ /**
3312
+ * 记入模型并刷新 LRU 序(先删后插保证最新),超上限逐出最旧(未附着才 dispose)。
3313
+ * @author ddj 2026年09月09号
3314
+ * @param map 作用域缓存 Map
3315
+ * @param path 工作区相对路径
3316
+ * @param model Monaco model(或测试桩)
3317
+ */
3318
+ function rememberModel(map, path, model) {
3319
+ if (!path || !model) return;
3320
+ map.delete(path);
3321
+ map.set(path, model);
3322
+ while (map.size > 40) {
3323
+ const oldest = map.keys().next().value;
3324
+ if (oldest === void 0) break;
3325
+ const evicted = map.get(oldest);
3326
+ map.delete(oldest);
3327
+ disposeIfFree(evicted);
3328
+ }
3329
+ }
3186
3330
  let currentMin = 300;
3187
3331
  /**
3188
3332
  * 归一化最小宽度:非法(NaN/非有限/负数)回退默认,越界夹取到 [180, 560]。
@@ -3213,6 +3357,8 @@ window.__ModuleLoader__.load({
3213
3357
  function getSidebarMinWidth() {
3214
3358
  return currentMin;
3215
3359
  }
3360
+ /** scope → 导航历史实例(同工作区跨会话复用同一份历史)。 */
3361
+ const scopeHistories = /* @__PURE__ */ new Map();
3216
3362
  /** 路径一致(容忍相对路径大小写差异,对齐既有 sameFile 语义常用写法)。 */
3217
3363
  function samePath(a, b) {
3218
3364
  if (a === b) return true;
@@ -3276,6 +3422,26 @@ window.__ModuleLoader__.load({
3276
3422
  }
3277
3423
  };
3278
3424
  }
3425
+ /**
3426
+ * 取(或建)某工作区作用域的导航历史实例:同 scope 跨会话复用同一实例,
3427
+ * 后退/前进历史在切换对话后保留(对齐「同一工作区同一编辑区」体感)。
3428
+ * 超出 NAV_SCOPE_CAP 时逐出最旧的实例(Map 插入序近似 LRU)。
3429
+ * @author ddj 2026年09月09号
3430
+ * @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
3431
+ * @param cap 后退栈容量上限(透传 createNavHistory)
3432
+ * @returns 导航历史实例
3433
+ */
3434
+ function navHistoryFor(scope, cap = 200) {
3435
+ const existing = scopeHistories.get(scope);
3436
+ if (existing) return existing;
3437
+ const created = createNavHistory(cap);
3438
+ scopeHistories.set(scope, created);
3439
+ if (scopeHistories.size > 6) {
3440
+ const oldest = scopeHistories.keys().next().value;
3441
+ if (typeof oldest === "string") scopeHistories.delete(oldest);
3442
+ }
3443
+ return created;
3444
+ }
3279
3445
  //#endregion
3280
3446
  //#region src/client/addToConversation.ts
3281
3447
  /** 把追加引用结果映射为可读文案(ok 用 okText;busy 提示已降级纯文本;unavailable 提示不可用)。
@@ -4399,6 +4565,8 @@ window.__ModuleLoader__.load({
4399
4565
  * dsh-vscode-mode client — EditorView:中央 VSCode 式文件编辑器(编排层)。
4400
4566
  * 迁移自原 src/client/index.ts 的 EditorView,语义不改;差异自绘已抽到 monaco/diffRender。
4401
4567
  * 职责:页签/QuickOpen/Monaco 编辑器/差异审查(DiffBox/Launcher/Badge 事件装配)/状态栏/自动保存。
4568
+ * 状态作用域:页签/视图状态/侧边栏/展开树等 UI 状态按工作区作用域(cwd)隔离,
4569
+ * 同一工作区切换对话恢复同一份状态;无 cwd 会话回退按会话隔离(scopeStore)。
4402
4570
  * 作者 ddj 2026-08-20
4403
4571
  */
4404
4572
  /**
@@ -4415,6 +4583,13 @@ window.__ModuleLoader__.load({
4415
4583
  const addToConversation = props.addToConversation;
4416
4584
  const layout = props?.layout === "side" ? "side" : "tab";
4417
4585
  const sideHint = props?.sideHint;
4586
+ const sessionsList = props?.sessions?.list;
4587
+ const cwd = react.default.useSyncExternalStore(react.default.useCallback((onStoreChange) => typeof sessionsList?.subscribe === "function" ? sessionsList.subscribe(onStoreChange) : () => {}, [sessionsList]), () => sessionsList?.getSnapshot?.()?.byId?.[sessionId]?.cwd ?? null, () => null);
4588
+ const scope = react.default.useMemo(() => {
4589
+ const s = workspaceScopeOf(cwd, sessionId);
4590
+ migrateScopedKeys(s, sessionId);
4591
+ return s;
4592
+ }, [cwd, sessionId]);
4418
4593
  const [monaco, setMonaco] = react.default.useState(null);
4419
4594
  const [monacoErr, setMonacoErr] = react.default.useState(null);
4420
4595
  const [records, setRecords] = react.default.useState({});
@@ -4469,15 +4644,16 @@ window.__ModuleLoader__.load({
4469
4644
  const editorRef = react.default.useRef(null);
4470
4645
  const viewRootRef = react.default.useRef(null);
4471
4646
  const monacoRef = react.default.useRef(null);
4472
- const modelsRef = react.default.useRef(/* @__PURE__ */ new Map());
4647
+ const modelsRef = react.default.useRef(null);
4648
+ if (!modelsRef.current) modelsRef.current = modelsForScope(scope);
4473
4649
  const viewStatesRef = react.default.useRef({});
4474
4650
  const saveTimerRef = react.default.useRef(null);
4475
4651
  const loadSeqRef = react.default.useRef(0);
4476
4652
  const programmaticRef = react.default.useRef(false);
4477
- const bootRef = react.default.useRef(false);
4653
+ const restoredScopeRef = react.default.useRef(null);
4478
4654
  const pendingFocusRef = react.default.useRef(null);
4479
4655
  const navRef = react.default.useRef(null);
4480
- if (!navRef.current) navRef.current = createNavHistory();
4656
+ if (!navRef.current) navRef.current = navHistoryFor(scope);
4481
4657
  const navPendingRef = react.default.useRef(null);
4482
4658
  const navCursorTimerRef = react.default.useRef(null);
4483
4659
  const navBackRef = react.default.useRef(null);
@@ -4494,6 +4670,7 @@ window.__ModuleLoader__.load({
4494
4670
  const batchBusyRef = react.default.useRef(false);
4495
4671
  const menuHandlersRef = react.default.useRef(null);
4496
4672
  const doSaveRef = react.default.useRef(null);
4673
+ const saveViewStateRef = react.default.useRef(null);
4497
4674
  const diffRendererRef = react.default.useRef(null);
4498
4675
  const layoutRef = react.default.useRef(layout);
4499
4676
  layoutRef.current = layout;
@@ -4533,7 +4710,7 @@ window.__ModuleLoader__.load({
4533
4710
  if (select) setActive(path);
4534
4711
  };
4535
4712
  /**
4536
- * 保存当前活动文件的视图状态(光标/滚动/折叠)到会话缓存。
4713
+ * 保存当前活动文件的视图状态(光标/滚动/折叠)到工作区作用域缓存。
4537
4714
  * @author ddj 2026年08月28号
4538
4715
  * @param path 要保存的文件路径(缺省 = 当前 active)
4539
4716
  */
@@ -4545,9 +4722,10 @@ window.__ModuleLoader__.load({
4545
4722
  const state = ed.saveViewState();
4546
4723
  if (!state) return;
4547
4724
  viewStatesRef.current = upsertViewState(viewStatesRef.current, target, state);
4548
- viewStatesSave(sessionId, viewStatesRef.current);
4725
+ viewStatesSave(scope, viewStatesRef.current);
4549
4726
  } catch (e) {}
4550
4727
  };
4728
+ saveViewStateRef.current = saveViewState;
4551
4729
  /**
4552
4730
  * 恢复指定文件的视图状态(内容就绪、setModel 后调用;恢复后即消费删除)。
4553
4731
  * @author ddj 2026年08月28号
@@ -5038,11 +5216,12 @@ window.__ModuleLoader__.load({
5038
5216
  };
5039
5217
  }, [sessionId, layout]);
5040
5218
  react.default.useEffect(() => {
5041
- if (bootRef.current || !sessionId) return;
5042
- bootRef.current = true;
5043
- viewStatesRef.current = viewStatesLoad(sessionId);
5219
+ if (!sessionId || restoredScopeRef.current === scope) return;
5220
+ restoredScopeRef.current = scope;
5221
+ modelsRef.current = modelsForScope(scope);
5222
+ viewStatesRef.current = viewStatesLoad(scope);
5044
5223
  try {
5045
- const raw = localStorage.getItem(CACHE_KEY.editor + String(sessionId));
5224
+ const raw = localStorage.getItem(CACHE_KEY.editor + String(scope));
5046
5225
  if (raw) {
5047
5226
  const saved = JSON.parse(raw);
5048
5227
  if (Array.isArray(saved.tabs) && saved.tabs.length) {
@@ -5051,20 +5230,20 @@ window.__ModuleLoader__.load({
5051
5230
  }
5052
5231
  }
5053
5232
  } catch (e) {}
5054
- }, [sessionId]);
5233
+ }, [sessionId, scope]);
5055
5234
  react.default.useEffect(() => {
5056
- navRef.current = createNavHistory();
5235
+ navRef.current = navHistoryFor(scope);
5057
5236
  navPendingRef.current = null;
5058
5237
  if (navCursorTimerRef.current) {
5059
5238
  clearTimeout(navCursorTimerRef.current);
5060
5239
  navCursorTimerRef.current = null;
5061
5240
  }
5062
5241
  setNavTick((v) => v + 1);
5063
- }, [sessionId]);
5242
+ }, [scope]);
5064
5243
  react.default.useEffect(() => {
5065
5244
  if (!sessionId) return;
5066
5245
  try {
5067
- localStorage.setItem(CACHE_KEY.editor + String(sessionId), JSON.stringify({
5246
+ localStorage.setItem(CACHE_KEY.editor + String(scope), JSON.stringify({
5068
5247
  tabs: tabs.map((t) => t.path),
5069
5248
  active
5070
5249
  }));
@@ -5072,9 +5251,10 @@ window.__ModuleLoader__.load({
5072
5251
  }, [
5073
5252
  tabs,
5074
5253
  active,
5075
- sessionId
5254
+ sessionId,
5255
+ scope
5076
5256
  ]);
5077
- const sidebarKey = CACHE_KEY.sidebar + (layout === "side" ? "side." : "") + String(sessionId);
5257
+ const sidebarKey = CACHE_KEY.sidebar + (layout === "side" ? "side." : "") + String(scope);
5078
5258
  react.default.useEffect(() => {
5079
5259
  if (!sessionId) return;
5080
5260
  try {
@@ -5259,8 +5439,9 @@ window.__ModuleLoader__.load({
5259
5439
  const cache = modelsRef.current;
5260
5440
  let model = cache.get(path);
5261
5441
  if (!model) {
5262
- model = window.monaco.editor.createModel(text ?? "", langOf(path), window.monaco.Uri.parse("edrv:///" + encodeURI(path)));
5263
- cache.set(path, model);
5442
+ const uri = window.monaco.Uri.parse("edrv:///" + encodeURI(path));
5443
+ model = window.monaco.editor.getModel(uri) || window.monaco.editor.createModel(text ?? "", langOf(path), uri);
5444
+ rememberModel(cache, path, model);
5264
5445
  } else if (text !== void 0 && model.getValue() !== text) {
5265
5446
  programmaticRef.current = true;
5266
5447
  model.setValue(text);
@@ -5382,14 +5563,13 @@ window.__ModuleLoader__.load({
5382
5563
  ]);
5383
5564
  react.default.useEffect(() => () => {
5384
5565
  flushSave();
5385
- saveViewState(active);
5566
+ flushNavCursor();
5567
+ saveViewStateRef.current?.();
5386
5568
  diffRendererRef.current?.dispose?.();
5387
5569
  if (editorRef.current) {
5388
5570
  editorRef.current.dispose();
5389
5571
  editorRef.current = null;
5390
5572
  }
5391
- for (const m of modelsRef.current.values()) m.dispose();
5392
- modelsRef.current.clear();
5393
5573
  for (const ctl of pdfCtlRef.current.values()) ctl.destroy();
5394
5574
  pdfCtlRef.current.clear();
5395
5575
  const root = editorRef.current && editorRef.current.getDomNode ? editorRef.current.getDomNode() : null;
@@ -6317,7 +6497,8 @@ window.__ModuleLoader__.load({
6317
6497
  const sidebarPanels = props.sidebarPanels;
6318
6498
  const sidebarCtx = {
6319
6499
  sessionId,
6320
- cwd: props.sessions?.list?.getSnapshot?.()?.byId?.[sessionId]?.cwd ?? null,
6500
+ cwd: cwd ?? null,
6501
+ scope,
6321
6502
  openFile: (p) => openFile(p, false),
6322
6503
  openFileAt,
6323
6504
  activePath: active,
@@ -6343,7 +6524,7 @@ window.__ModuleLoader__.load({
6343
6524
  const copyInstallCmd = () => {
6344
6525
  if (navigator.clipboard?.writeText) navigator.clipboard.writeText(SIDEBAR_INSTALL_CMD).catch(() => {});
6345
6526
  };
6346
- const sideHintEl = layout === "tab" && sideHint && !hintDismissed ? react.default.createElement("div", { className: "edrv-side-hint" }, react.default.createElement("span", { className: "edrv-side-hint-text" }, "安装 dsh-better-sidebar 后可启用侧边栏编辑(对话+编辑同屏):"), react.default.createElement("code", { className: "edrv-side-hint-cmd" }, SIDEBAR_INSTALL_CMD), react.default.createElement("button", {
6527
+ const sideHintEl = layout === "tab" && sideHint && !hintDismissed ? react.default.createElement("div", { className: "edrv-side-hint" }, react.default.createElement("span", { className: "edrv-side-hint-text" }, "DSH 0.1.5+ 自带官方右侧侧边栏(升级即用);旧版可安装 dsh-better-sidebar 启用侧边栏编辑(对话+编辑同屏):"), react.default.createElement("code", { className: "edrv-side-hint-cmd" }, SIDEBAR_INSTALL_CMD), react.default.createElement("button", {
6347
6528
  className: "edrv-pill edrv-pill-ghost",
6348
6529
  title: "复制安装命令",
6349
6530
  onClick: copyInstallCmd
@@ -6883,14 +7064,307 @@ window.__ModuleLoader__.load({
6883
7064
  };
6884
7065
  }
6885
7066
  //#endregion
7067
+ //#region src/client/officialSidebar.ts
7068
+ /**
7069
+ * dsh-vscode-mode client — 官方右侧 Sidebar 桥:探测 DSH 0.1.5-alpha.1+ 的
7070
+ * ctx.sidebarRightTabs / ctx.sidebarRight 服务,两段式注册「文件编辑」Tab
7071
+ * (类型定义进注册表,正文进 keyed slot sidebar.right.pane.tab)。
7072
+ * 可选依赖模式:服务缺失时调用方回退 better-sidebar / 中央页签;本模块不注册任何东西。
7073
+ * 维护约定:官方侧边栏(DSH 0.1.5+)是侧边栏编辑形态的唯一维护面;
7074
+ * dsh-better-sidebar 桥(sidebarBridge.ts)已归档,仅作旧版 DSH 回退,不再新增能力。
7075
+ * 纯逻辑(探测/导航参数解析)可单测,注册装配仅存在于 install 路径。
7076
+ * 作者 ddj 2026年09月09号
7077
+ */
7078
+ /** 官方 Tab 类型注册表服务名(ctx.get 用;不进 inject——旧版 DSH 无此包,inject 会停等)。 */
7079
+ const OFFICIAL_TABS_SERVICE = "sidebarRightTabs";
7080
+ /** 官方导航控制器服务名(ctx.get 用)。 */
7081
+ const OFFICIAL_SERVICE = "sidebarRight";
7082
+ /** 官方 keyed slot 名(Tab 正文挂点,由官方侧边栏的 rightbar seat 声明)。 */
7083
+ const OFFICIAL_SLOT_NAME = "sidebar.right.pane.tab";
7084
+ /** 本插件 Tab 类型定义 id(官方约定用包名;正文 slot 的 key 与之相同)。 */
7085
+ const OFFICIAL_TAB_ID = "dsh-vscode-mode";
7086
+ /** 本插件 Tab 的类型判别符(ctx.sidebarRight.openTab 的 kind)。 */
7087
+ const OFFICIAL_TAB_KIND = "edrvEditor";
7088
+ /** Tab 标题(与 better-sidebar 形态一致)。 */
7089
+ const OFFICIAL_TAB_TITLE = "文件编辑";
7090
+ /** file 地址认领的 patterns(extension 档接管官方 textpreview 的 fallback 认领,注销自动回落)。 */
7091
+ const OFFICIAL_FILE_PATTERN = "dsh-resource://file/**";
7092
+ /** file 资源类型的定义 id(正文 slot 的 key)。 */
7093
+ const OFFICIAL_FILE_TAB_ID = "dsh-vscode-mode/file";
7094
+ /** file 资源类型的 kind(与页类型 edrvEditor 区分:同 kind 只允许一份 extension)。 */
7095
+ const OFFICIAL_FILE_KIND = "edrvEditorFile";
7096
+ /**
7097
+ * 结构化探测官方右侧 Sidebar 服务(可选依赖:缺失/降级返回 undefined)。
7098
+ * 服务仅存在于 DSH 0.1.5-alpha.1+,探测命中 ≡ 版本判定成立。
7099
+ * @author ddj 2026年09月09号
7100
+ * @param ctx 客户端服务上下文
7101
+ * @returns 注册表与控制器对,或 undefined
7102
+ */
7103
+ function detectOfficial(ctx) {
7104
+ try {
7105
+ const tabs = ctx.get(OFFICIAL_TABS_SERVICE);
7106
+ const service = ctx.get(OFFICIAL_SERVICE);
7107
+ if (!tabs || typeof tabs.register !== "function") return void 0;
7108
+ if (!service || typeof service.openTab !== "function") return void 0;
7109
+ return {
7110
+ tabs,
7111
+ service
7112
+ };
7113
+ } catch {
7114
+ return;
7115
+ }
7116
+ }
7117
+ /**
7118
+ * 从官方导航参数安全解析打开请求(缺字段/坏类型一律降级为空请求,不抛错)。
7119
+ * @author ddj 2026年09月09号
7120
+ * @param params openTab 传入的 params(正文经 navigation.params 读回)
7121
+ * @returns 待打开路径与是否聚焦首个差异
7122
+ */
7123
+ function resolveNavOpen(params) {
7124
+ const value = params ?? {};
7125
+ return {
7126
+ path: typeof value?.openPath === "string" && value.openPath ? value.openPath : null,
7127
+ focusDiff: value?.focusDiff === true
7128
+ };
7129
+ }
7130
+ /** 逐段 component 解码(失败返回 null,对齐官方 parseFileAddress 的容错语义)。 */
7131
+ function decodeSegment(raw) {
7132
+ if (raw === void 0) return null;
7133
+ try {
7134
+ return decodeURIComponent(raw);
7135
+ } catch {
7136
+ return null;
7137
+ }
7138
+ }
7139
+ /**
7140
+ * 解析官方 file 资源地址(镜像 dsh-util-workspace-path 的 parseFileAddress 语法,不 import 官方包)。
7141
+ * session 域:`dsh-resource://file/session/<sessionId>/<逐段编码的相对路径>`;
7142
+ * absolute 域:`dsh-resource://file/absolute/<去前导 / 的绝对路径>`(POSIX 补回前导 /,
7143
+ * Windows 盘符 `C:/` 原样,UNC 保留空首段 `//server/share`)。
7144
+ * @author ddj 2026年09月09号
7145
+ * @param address 候选地址
7146
+ * @returns 文件路径(session 域附带 sessionId);非 file 地址/畸形/解码失败返回 null
7147
+ */
7148
+ function parseOfficialFileAddress(address) {
7149
+ if (typeof address !== "string" || !address.startsWith("dsh-resource://file/")) return null;
7150
+ const [scope, ...tail] = address.slice(20).split("/");
7151
+ const kind = decodeSegment(scope);
7152
+ if (kind === "session") {
7153
+ const sessionId = decodeSegment(tail[0]);
7154
+ const segments = tail.slice(1).map(decodeSegment);
7155
+ if (!sessionId || segments.length === 0 || segments.some((s) => s === null)) return null;
7156
+ const path = segments.join("/");
7157
+ return path ? {
7158
+ path,
7159
+ sessionId
7160
+ } : null;
7161
+ }
7162
+ if (kind === "absolute") {
7163
+ const unc = tail[0] === "" && tail.length > 1;
7164
+ const segments = (unc ? tail.slice(1) : tail).map(decodeSegment);
7165
+ if (segments.length === 0 || segments[0] === "" || segments.some((s) => s === null)) return null;
7166
+ const list = segments;
7167
+ if (unc) return { path: "//" + list.join("/") };
7168
+ if (/^[A-Za-z]:$/.test(list[0])) return { path: list.join("/") };
7169
+ return { path: "/" + list.join("/") };
7170
+ }
7171
+ return null;
7172
+ }
7173
+ /**
7174
+ * file 地址的 Tab chip 标题:解码后的 basename(解码失败回退原始串切片)。
7175
+ * @author ddj 2026年09月09号
7176
+ * @param address file 资源地址
7177
+ * @returns 标题文本
7178
+ */
7179
+ function officialFileTitle(address) {
7180
+ const source = parseOfficialFileAddress(address)?.path ?? (typeof address === "string" ? address : "");
7181
+ const normalized = source.replace(/\\/g, "/");
7182
+ return normalized.slice(normalized.lastIndexOf("/") + 1) || source;
7183
+ }
7184
+ /** component 编码一段(对齐官方 encodeSegment:盘符冒号保持字面)。 */
7185
+ function encodeSegment(segment) {
7186
+ return encodeURIComponent(segment).replace(/%3A/gi, ":");
7187
+ }
7188
+ /**
7189
+ * 构造官方 file 资源地址(镜像 fileAddressFor 的分派:绝对路径→absolute 域,相对→session 域)。
7190
+ * @author ddj 2026年09月09号
7191
+ * @param path 绝对或工作区相对路径(两种分隔符均可)
7192
+ * @param sessionId 会话 id(相对路径必需;绝对路径可省)
7193
+ * @returns `dsh-resource://file/…` 地址
7194
+ * @throws 路径为空,或相对路径缺 sessionId
7195
+ */
7196
+ function buildFileAddress(path, sessionId) {
7197
+ const posix = (typeof path === "string" ? path : "").replace(/\\/g, "/");
7198
+ if (!posix.trim()) throw new Error("buildFileAddress:路径为空");
7199
+ if (posix.startsWith("/") || posix.startsWith("//") || /^[A-Za-z]:[/\\]/.test(posix)) {
7200
+ const unc = posix.startsWith("//");
7201
+ const body = posix.replace(/^\/+/, "");
7202
+ return "dsh-resource://file/absolute/" + (unc ? "/" : "") + body.split("/").map(encodeSegment).join("/");
7203
+ }
7204
+ if (!sessionId) throw new Error("buildFileAddress:相对路径需要会话 id");
7205
+ const relative = posix.replace(/^(?:\.\/)+/, "").replace(/^\/+/, "");
7206
+ return "dsh-resource://file/session/" + encodeSegment(sessionId) + "/" + relative.split("/").map(encodeSegment).join("/");
7207
+ }
7208
+ /**
7209
+ * 注册官方 Tab 正文(keyed slot,key=定义 id;slots.inject 等待 rightbar seat 声明)。
7210
+ * @author ddj 2026年09月09号
7211
+ * @param slots slots 服务
7212
+ * @param key 正文注册键(= 类型定义 id)
7213
+ * @param renderTab 正文组件(包装后的 OfficialSideTab)
7214
+ * @returns 注销函数;注册抛错返回 null
7215
+ */
7216
+ function registerBodySlot(slots, key, renderTab) {
7217
+ try {
7218
+ const disposer = slots.inject(OFFICIAL_SLOT_NAME, () => slots.register({
7219
+ name: OFFICIAL_SLOT_NAME,
7220
+ key
7221
+ }, renderTab));
7222
+ return typeof disposer === "function" ? disposer : null;
7223
+ } catch (error) {
7224
+ log.warn("官方侧边栏正文 slot 注册失败(" + String(error) + ")");
7225
+ return null;
7226
+ }
7227
+ }
7228
+ /**
7229
+ * 注册官方右侧 Sidebar「文件编辑」Tab 并接管打开路由(两段式 + 路由)。
7230
+ * 调用方须把返回值挂进 ctx.effect;类型注册抛错时返回 null,由调用方回退旧形态。
7231
+ * @author ddj 2026年09月09号
7232
+ * @param options 装配参数(探测命中的一对服务 + slots + 正文组件 + guide 文案)
7233
+ * @returns 卸载器(反注册类型与正文 + 撤销路由);类型注册失败返回 null
7234
+ */
7235
+ function installOfficial(options) {
7236
+ const { tabs, service, slots, renderTab, guide } = options;
7237
+ const open = (path, focusDiff) => {
7238
+ try {
7239
+ service.openTab(OFFICIAL_TAB_KIND, { params: {
7240
+ openPath: path || void 0,
7241
+ focusDiff
7242
+ } });
7243
+ } catch (error) {
7244
+ log.warn("官方侧边栏打开失败(" + String(error) + ")");
7245
+ return false;
7246
+ }
7247
+ return true;
7248
+ };
7249
+ let typeDisposer = null;
7250
+ try {
7251
+ typeDisposer = tabs.register({
7252
+ id: OFFICIAL_TAB_ID,
7253
+ kind: OFFICIAL_TAB_KIND,
7254
+ priority: "extension",
7255
+ title: () => OFFICIAL_TAB_TITLE,
7256
+ guide: [{
7257
+ order: 20,
7258
+ title: guide.title,
7259
+ description: guide.description
7260
+ }]
7261
+ });
7262
+ } catch (error) {
7263
+ log.warn("官方侧边栏 Tab 类型注册失败(" + String(error) + "),回退旧形态");
7264
+ return null;
7265
+ }
7266
+ setEnsureSideEditor(open);
7267
+ const bodyDisposer = registerBodySlot(slots, OFFICIAL_TAB_ID, renderTab);
7268
+ return () => {
7269
+ if (typeof typeDisposer === "function") typeDisposer();
7270
+ if (typeof bodyDisposer === "function") bodyDisposer();
7271
+ setEnsureSideEditor(null);
7272
+ };
7273
+ }
7274
+ /**
7275
+ * 注册官方 file 资源地址认领(extension 档接管 `dsh-resource://file/**`,
7276
+ * 聊天文件链接/文件树打开改落本插件编辑器;注销后官方 textpreview 自动恢复)。
7277
+ * 认领与否由「文件链接使用工具」设置驱动(见 fileOpeners.shouldClaimFiles)。
7278
+ * @author ddj 2026年09月09号
7279
+ * @param options 装配参数(探测命中的注册表 + slots + 正文组件)
7280
+ * @returns 卸载器(反注册类型与正文);类型注册抛错返回 null(回落官方查看器)
7281
+ */
7282
+ function registerOfficialFileClaim(options) {
7283
+ const { tabs, slots, renderTab } = options;
7284
+ let typeDisposer = null;
7285
+ try {
7286
+ typeDisposer = tabs.register({
7287
+ id: OFFICIAL_FILE_TAB_ID,
7288
+ kind: OFFICIAL_FILE_KIND,
7289
+ patterns: [OFFICIAL_FILE_PATTERN],
7290
+ priority: "extension",
7291
+ canOpen: (address) => parseOfficialFileAddress(address) !== null,
7292
+ title: (address) => officialFileTitle(address)
7293
+ });
7294
+ } catch (error) {
7295
+ log.warn("官方 file 地址认领注册失败(" + String(error) + "),链接走官方查看器");
7296
+ return null;
7297
+ }
7298
+ const bodyDisposer = registerBodySlot(slots, OFFICIAL_FILE_TAB_ID, renderTab);
7299
+ return () => {
7300
+ if (typeof typeDisposer === "function") typeDisposer();
7301
+ if (typeof bodyDisposer === "function") bodyDisposer();
7302
+ };
7303
+ }
7304
+ /** 编辑 Tab 是否处于激活态(正文挂载 = 激活;模块级,跨会话存活)。 */
7305
+ let editorTabActive = false;
7306
+ /** 正文挂载纪元(每次挂载自增;延迟判定期间有新挂载则放弃清除)。 */
7307
+ let mountEpoch = 0;
7308
+ /**
7309
+ * 标记编辑 Tab 激活并推进挂载纪元(正文挂载时调用)。
7310
+ * @author ddj 2026年09月09号
7311
+ */
7312
+ function markEditorMounted() {
7313
+ mountEpoch += 1;
7314
+ markEditorActive$1(true);
7315
+ }
7316
+ /**
7317
+ * 直接设置激活标记(正文卸载延迟判定用;测试 also 用作状态编排入口)。
7318
+ * @author ddj 2026年09月09号
7319
+ * @param active 是否激活
7320
+ */
7321
+ function markEditorActive$1(active) {
7322
+ editorTabActive = active;
7323
+ }
7324
+ /**
7325
+ * 读编辑 Tab 激活标记(会话切换时决定是否自动恢复编辑栏)。
7326
+ * @author ddj 2026年09月09号
7327
+ * @returns 上次标记的激活状态
7328
+ */
7329
+ function isEditorTabActive() {
7330
+ return editorTabActive;
7331
+ }
7332
+ /**
7333
+ * 读当前挂载纪元(卸载延迟判定用:纪元已推进 = 有新正文接管,不清标记)。
7334
+ * @author ddj 2026年09月09号
7335
+ * @returns 当前纪元
7336
+ */
7337
+ function editorMountEpoch() {
7338
+ return mountEpoch;
7339
+ }
7340
+ /**
7341
+ * 在挂载会话恢复编辑 Tab:已激活跳过(官方幂等);seat 未就绪抛错时有界重试。
7342
+ * openTab 官方语义「打开即展开侧栏」,一次调用同时完成展开 + 激活。
7343
+ * @author ddj 2026年09月09号
7344
+ * @param service 官方 sidebarRight 服务(缺失/未装配直接返回)
7345
+ * @param schedule 延时调度(ctx.timeout;重试节拍)
7346
+ * @param attempts 剩余尝试次数(默认 10 次 × 150ms ≈ 1.5s 窗口)
7347
+ */
7348
+ function restoreEditorTab(service, schedule, attempts = 10) {
7349
+ if (!service || typeof service.openTab !== "function" || !editorTabActive) return;
7350
+ try {
7351
+ if (service.active?.()?.kind === "edrvEditor") return;
7352
+ service.openTab(OFFICIAL_TAB_KIND, {});
7353
+ } catch {
7354
+ if (attempts > 1) schedule(() => restoreEditorTab(service, schedule, attempts - 1), 150);
7355
+ }
7356
+ }
7357
+ //#endregion
6886
7358
  //#region src/client/fileOpeners.ts
6887
7359
  /**
6888
7360
  * dsh-vscode-mode client — 文件打开器注册表与自动选择。
6889
- * @author ddj 2026年08月24号
7361
+ * @author ddj 2026年08月24号 / 2026年09月09号
6890
7362
  */
6891
7363
  const AUTO_OPEN_TOOL = "auto";
6892
7364
  const VSCODE_OPEN_TOOL = "dsh-vscode-mode";
6893
7365
  const SIDEBAR_OPEN_TOOL = SIDEBAR_PLUGIN;
7366
+ /** 官方右侧侧边栏打开器 id(「文件链接使用工具」下拉的官方选项;DSH 0.1.5+)。 */
7367
+ const OFFICIAL_OPEN_TOOL = "dsh-official-sidebar";
6894
7368
  /**
6895
7369
  * 写入外部打开器并触发注册表通知。
6896
7370
  * @author ddj 2026年08月24号
@@ -7016,6 +7490,36 @@ window.__ModuleLoader__.load({
7016
7490
  const normalized = path.replace(/\\/g, "/");
7017
7491
  return normalized.slice(normalized.lastIndexOf("/") + 1) || path;
7018
7492
  }
7493
+ /**
7494
+ * 文件链接是否由本插件认领官方 file 地址:自动/VSCodeMode 档接管(链接进本插件编辑器),
7495
+ * 官方侧边栏/system/better-sidebar 档不认领(官方查看器或旧版路由生效)。
7496
+ * @author ddj 2026年09月09号
7497
+ * @param selected 「文件链接使用工具」当前值
7498
+ * @returns 是否认领
7499
+ */
7500
+ function shouldClaimFiles(selected) {
7501
+ return selected === "auto" || selected === "dsh-vscode-mode";
7502
+ }
7503
+ /**
7504
+ * 创建「官方侧边栏」文件打开器(DSH 0.1.5+,经 ctx.sidebarRight.openResource 打开官方查看器)。
7505
+ * 仅官方服务探测命中时注册;下拉选项随注册动态出现(McpSettings 动态渲染)。
7506
+ * @author ddj 2026年09月09号
7507
+ * @param service 官方导航控制器
7508
+ * @returns 文件打开器
7509
+ */
7510
+ function officialSidebarOpener(service) {
7511
+ return {
7512
+ id: OFFICIAL_OPEN_TOOL,
7513
+ label: "官方侧边栏",
7514
+ description: "在 DSH 官方右侧侧边栏查看器中打开(0.1.5+)",
7515
+ priority: 90,
7516
+ isAvailable: () => typeof service.openResource === "function",
7517
+ open: (path, context) => {
7518
+ if (typeof service.openResource !== "function") throw new Error("官方侧边栏 openResource 能力不可用");
7519
+ service.openResource(buildFileAddress(path, context.sessionId));
7520
+ }
7521
+ };
7522
+ }
7019
7523
  //#endregion
7020
7524
  //#region src/client/settingsContext.ts
7021
7525
  /**
@@ -9578,6 +10082,83 @@ window.__ModuleLoader__.load({
9578
10082
  });
9579
10083
  }
9580
10084
  //#endregion
10085
+ //#region src/client/ui/OfficialSideTab.ts
10086
+ /**
10087
+ * dsh-vscode-mode client — OfficialSideTab:DSH 官方右侧 Sidebar「文件编辑」Tab 正文包装。
10088
+ * 双用途:页类型 Tab(edrvEditor)经 navigation.params(openPath/focusDiff)打开;
10089
+ * file 资源类型 Tab(edrvEditorFile,认领 dsh-resource://file/**)经 navigation.address
10090
+ * 解码出真实路径并透传 params.line 行号定位。派发既有 edrv:open-editor 事件复用
10091
+ * EditorView 打开流;按会话 key 保证切会话干净重建。
10092
+ * Tab 正文切换后重挂载时按最新导航恢复打开(对齐 better-sidebar 形态的种子行为)。
10093
+ * 作者 ddj 2026年09月09号
10094
+ */
10095
+ /**
10096
+ * 官方侧边栏 Tab 正文组件(keyed slot sidebar.right.pane.tab 装配)。
10097
+ * @param props slot 框架注入(useTabInfo/sessionId)+ 装配依赖(schedule 等)
10098
+ * @returns EditorView 侧栏形态
10099
+ */
10100
+ function OfficialSideTab(props) {
10101
+ const sessionId = props?.sessionId;
10102
+ const useTabInfo = props?.useTabInfo;
10103
+ let navigation;
10104
+ if (typeof useTabInfo === "function") try {
10105
+ navigation = useTabInfo()?.tab?.navigation;
10106
+ } catch {
10107
+ navigation = void 0;
10108
+ }
10109
+ const revision = typeof navigation?.revision === "number" ? navigation.revision : -1;
10110
+ const revisionRef = react.default.useRef(-1);
10111
+ const sessionIdRef = react.default.useRef(sessionId);
10112
+ sessionIdRef.current = sessionId;
10113
+ react.default.useEffect(() => {
10114
+ setSideEditorMounted(true);
10115
+ markEditorMounted();
10116
+ return () => {
10117
+ setSideEditorMounted(false);
10118
+ const epoch = editorMountEpoch();
10119
+ const self = sessionIdRef.current;
10120
+ const schedule = props?.schedule;
10121
+ const decide = () => {
10122
+ if (editorMountEpoch() !== epoch) return;
10123
+ const current = props?.sessions?.list?.getSnapshot?.()?.current;
10124
+ markEditorActive(current === self);
10125
+ };
10126
+ if (typeof schedule === "function") schedule(decide, 0);
10127
+ else decide();
10128
+ };
10129
+ }, []);
10130
+ react.default.useEffect(() => {
10131
+ if (revision === revisionRef.current) return;
10132
+ revisionRef.current = revision;
10133
+ if (revision <= 0) return;
10134
+ const fileHit = parseOfficialFileAddress(navigation?.address);
10135
+ if (fileHit) {
10136
+ const line = navigation?.params?.line;
10137
+ window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: {
10138
+ path: fileHit.path,
10139
+ line: typeof line === "number" ? line : void 0
10140
+ } }));
10141
+ return;
10142
+ }
10143
+ const request = resolveNavOpen(navigation?.params);
10144
+ window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: {
10145
+ path: request.path,
10146
+ focusDiff: request.focusDiff
10147
+ } }));
10148
+ });
10149
+ return react.default.createElement(EditorView, {
10150
+ key: sessionId,
10151
+ sessionId,
10152
+ layout: "side",
10153
+ schedule: props?.schedule,
10154
+ addToConversation: props?.addToConversation,
10155
+ sidebarPanels: props?.sidebarPanels,
10156
+ outlineSources: props?.outlineSources,
10157
+ fileMenuItems: props?.fileMenuItems,
10158
+ sessions: props?.sessions
10159
+ });
10160
+ }
10161
+ //#endregion
9581
10162
  //#region src/client/sidebar/registry.ts
9582
10163
  /** 校验面板定义并写入注册表(缺 id/render 抛 TypeError)。 */
9583
10164
  function panelRegister(entries, notify, panel) {
@@ -9771,7 +10352,7 @@ window.__ModuleLoader__.load({
9771
10352
  * 键前缀统一走 paths.ts PathConst(CACHE_KEY.expanded)。
9772
10353
  * 作者 ddj 2026-08-28
9773
10354
  */
9774
- /** localStorage 键前缀(按会话隔离)。 */
10355
+ /** localStorage 键前缀(按工作区作用域隔离,无 cwd 会话回退会话键)。 */
9775
10356
  const KEY_PREFIX$1 = CACHE_KEY.expanded;
9776
10357
  /**
9777
10358
  * 归一化展开路径:去空白、反斜杠→斜杠、去首尾斜杠,`..` 段拒绝。
@@ -9843,27 +10424,27 @@ window.__ModuleLoader__.load({
9843
10424
  }
9844
10425
  }
9845
10426
  /**
9846
- * 读取某会话的展开状态缓存(localStorage 不可用 → null)。
10427
+ * 读取某作用域的展开状态缓存(localStorage 不可用 → null)。
9847
10428
  * @author ddj 2026年08月28号
9848
- * @param sessionId 会话 id
10429
+ * @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
9849
10430
  * @returns 缓存数据或 null
9850
10431
  */
9851
- function explorerLoad(sessionId) {
10432
+ function explorerLoad(scope) {
9852
10433
  try {
9853
- return parseExplorer(window.localStorage.getItem(KEY_PREFIX$1 + String(sessionId)));
10434
+ return parseExplorer(window.localStorage.getItem(KEY_PREFIX$1 + String(scope)));
9854
10435
  } catch (error) {
9855
10436
  return null;
9856
10437
  }
9857
10438
  }
9858
10439
  /**
9859
- * 写入某会话的展开状态缓存(损坏/配额满 → 忽略)。
10440
+ * 写入某作用域的展开状态缓存(损坏/配额满 → 忽略)。
9860
10441
  * @author ddj 2026年08月28号
9861
- * @param sessionId 会话 id
10442
+ * @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
9862
10443
  * @param data 缓存数据
9863
10444
  */
9864
- function explorerSave(sessionId, data) {
10445
+ function explorerSave(scope, data) {
9865
10446
  try {
9866
- window.localStorage.setItem(KEY_PREFIX$1 + String(sessionId), serializeExplorer(data));
10447
+ window.localStorage.setItem(KEY_PREFIX$1 + String(scope), serializeExplorer(data));
9867
10448
  } catch (error) {}
9868
10449
  }
9869
10450
  //#endregion
@@ -9928,9 +10509,9 @@ window.__ModuleLoader__.load({
9928
10509
  return null;
9929
10510
  }
9930
10511
  }
9931
- /** sessionId → 缓存镜像(同步读,避免每次渲染 parse)。 */
10512
+ /** scope → 缓存镜像(同步读,避免每次渲染 parse)。 */
9932
10513
  const mirrors = /* @__PURE__ */ new Map();
9933
- /** sessionId → 落盘防抖计时器。 */
10514
+ /** scope → 落盘防抖计时器。 */
9934
10515
  const saveTimers = /* @__PURE__ */ new Map();
9935
10516
  /** 单调时钟:连续 put 的 ts 严格递增(同毫秒不并列,逐出顺序可预期)。 */
9936
10517
  let clock = 0;
@@ -9940,82 +10521,82 @@ window.__ModuleLoader__.load({
9940
10521
  return clock;
9941
10522
  }
9942
10523
  /** localStorage 读取(不可用 → null)。 */
9943
- function readRaw(sessionId) {
10524
+ function readRaw(scope) {
9944
10525
  try {
9945
- return window.localStorage.getItem(KEY_PREFIX + sessionId);
10526
+ return window.localStorage.getItem(KEY_PREFIX + scope);
9946
10527
  } catch (error) {
9947
10528
  return null;
9948
10529
  }
9949
10530
  }
9950
10531
  /** localStorage 写入(配额/隐私模式失败静默)。 */
9951
- function writeRaw(sessionId, text) {
10532
+ function writeRaw(scope, text) {
9952
10533
  try {
9953
- window.localStorage.setItem(KEY_PREFIX + sessionId, text);
10534
+ window.localStorage.setItem(KEY_PREFIX + scope, text);
9954
10535
  } catch (error) {}
9955
10536
  }
9956
- /** 取(或建)会话缓存镜像。 */
9957
- function mirrorOf(sessionId) {
9958
- const existing = mirrors.get(sessionId);
10537
+ /** 取(或建)作用域缓存镜像。 */
10538
+ function mirrorOf(scope) {
10539
+ const existing = mirrors.get(scope);
9959
10540
  if (existing) return existing;
9960
- const data = entriesParse(readRaw(sessionId)) ?? {
10541
+ const data = entriesParse(readRaw(scope)) ?? {
9961
10542
  v: 2,
9962
10543
  root: null,
9963
10544
  dirs: {}
9964
10545
  };
9965
- mirrors.set(sessionId, data);
10546
+ mirrors.set(scope, data);
9966
10547
  return data;
9967
10548
  }
9968
10549
  /** 调度防抖落盘(重置已有计时器)。 */
9969
- function scheduleSave(sessionId) {
9970
- const prev = saveTimers.get(sessionId);
10550
+ function scheduleSave(scope) {
10551
+ const prev = saveTimers.get(scope);
9971
10552
  if (prev) clearTimeout(prev);
9972
- saveTimers.set(sessionId, setTimeout(() => {
9973
- saveTimers.delete(sessionId);
9974
- const data = mirrors.get(sessionId);
9975
- if (data) writeRaw(sessionId, entriesSerialize(entriesTrim(data)));
10553
+ saveTimers.set(scope, setTimeout(() => {
10554
+ saveTimers.delete(scope);
10555
+ const data = mirrors.get(scope);
10556
+ if (data) writeRaw(scope, entriesSerialize(entriesTrim(data)));
9976
10557
  }, 300));
9977
10558
  }
9978
10559
  /**
9979
10560
  * 读某目录缓存条目(无 → null;展示不受新鲜度限制)。
9980
10561
  * @author ddj 2026年08月31号
9981
- * @param sessionId 会话 id
10562
+ * @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
9982
10563
  * @param rel 目录相对路径('' = 根)
9983
10564
  * @returns 缓存条目或 null
9984
10565
  */
9985
- function entriesCacheGet(sessionId, rel) {
9986
- if (!sessionId) return null;
9987
- const dir = mirrorOf(sessionId).dirs[rel];
10566
+ function entriesCacheGet(scope, rel) {
10567
+ if (!scope) return null;
10568
+ const dir = mirrorOf(scope).dirs[rel];
9988
10569
  if (!dir || !dir.entries.length) return null;
9989
10570
  return dir.entries;
9990
10571
  }
9991
10572
  /**
9992
10573
  * 某目录缓存是否新鲜(预取去重用)。
9993
10574
  * @author ddj 2026年08月31号
9994
- * @param sessionId 会话 id
10575
+ * @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
9995
10576
  * @param rel 目录相对路径
9996
10577
  * @returns 新鲜返回 true
9997
10578
  */
9998
- function entriesCacheIsFresh(sessionId, rel) {
9999
- if (!sessionId) return false;
10000
- const dir = mirrorOf(sessionId).dirs[rel];
10579
+ function entriesCacheIsFresh(scope, rel) {
10580
+ if (!scope) return false;
10581
+ const dir = mirrorOf(scope).dirs[rel];
10001
10582
  return Boolean(dir && Date.now() - dir.ts < 3e4);
10002
10583
  }
10003
10584
  /**
10004
10585
  * 写入某目录缓存条目(内存镜像 + 防抖落盘;单目录条目截断)。
10005
10586
  * @author ddj 2026年08月31号
10006
- * @param sessionId 会话 id
10587
+ * @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
10007
10588
  * @param rel 目录相对路径
10008
10589
  * @param entries 目录条目
10009
10590
  */
10010
- function entriesCachePut(sessionId, rel, entries) {
10011
- if (!sessionId) return;
10012
- const data = mirrorOf(sessionId);
10591
+ function entriesCachePut(scope, rel, entries) {
10592
+ if (!scope) return;
10593
+ const data = mirrorOf(scope);
10013
10594
  data.dirs[rel] = {
10014
10595
  ts: nextTs(),
10015
10596
  entries: entries.slice(0, 500)
10016
10597
  };
10017
10598
  data.dirs = entriesTrim(data).dirs;
10018
- scheduleSave(sessionId);
10599
+ scheduleSave(scope);
10019
10600
  }
10020
10601
  //#endregion
10021
10602
  //#region src/client/sidebar/panels/FileExplorer.ts
@@ -10047,6 +10628,7 @@ window.__ModuleLoader__.load({
10047
10628
  function FileExplorer(props) {
10048
10629
  const ctx = props?.ctx;
10049
10630
  const sessionId = ctx?.sessionId;
10631
+ const scope = ctx?.scope ?? workspaceScopeOf(null, sessionId);
10050
10632
  const openFile = ctx?.openFile;
10051
10633
  const activePath = ctx?.activePath ?? null;
10052
10634
  const pendingByPath = ctx?.pendingByPath ?? {};
@@ -10067,14 +10649,14 @@ window.__ModuleLoader__.load({
10067
10649
  const loadDirRef = react.default.useRef(null);
10068
10650
  const refreshRef = react.default.useRef(null);
10069
10651
  /** 渲染取数:内存态 → 本地条目缓存 → null(显示加载态)。 */
10070
- const entriesOf = (rel) => dirsRef.current[rel] ?? entriesCacheGet(sessionId, rel) ?? null;
10652
+ const entriesOf = (rel) => dirsRef.current[rel] ?? entriesCacheGet(scope, rel) ?? null;
10071
10653
  /** 预取子目录:≤4 个、排除重型目录、缓存新鲜跳过、已加载跳过、不级联。 */
10072
10654
  const prefetchDirs = (rel, entries) => {
10073
10655
  const want = [];
10074
10656
  for (const e of entries) {
10075
10657
  if (e.type !== "directory") continue;
10076
10658
  if (PREFETCH_EXCLUDED.has(e.name)) continue;
10077
- if (entriesCacheIsFresh(sessionId, e.path)) continue;
10659
+ if (entriesCacheIsFresh(scope, e.path)) continue;
10078
10660
  if (dirsRef.current[e.path] !== void 0) continue;
10079
10661
  want.push(e.path);
10080
10662
  if (want.length >= PREFETCH_MAX) break;
@@ -10107,7 +10689,7 @@ window.__ModuleLoader__.load({
10107
10689
  if (res && res.ok && Array.isArray(res.entries)) {
10108
10690
  setDirs((prev) => Object.assign({}, prev, { [rel]: res.entries }));
10109
10691
  if (res.root) setRoot((prev) => prev || res.root);
10110
- entriesCachePut(sessionId, rel, res.entries);
10692
+ entriesCachePut(scope, rel, res.entries);
10111
10693
  if (doPrefetch) prefetchDirs(rel, res.entries);
10112
10694
  } else if (!entriesOf(rel)) setErrors((prev) => Object.assign({}, prev, { [rel]: res?.error ? String(res.error) : "读取目录失败" }));
10113
10695
  }).catch((e) => {
@@ -10160,7 +10742,7 @@ window.__ModuleLoader__.load({
10160
10742
  setErrors({});
10161
10743
  setMenu(null);
10162
10744
  setRoot(null);
10163
- const cached = sessionId ? explorerLoad(sessionId) : null;
10745
+ const cached = scope ? explorerLoad(scope) : null;
10164
10746
  const restored = cached?.expanded ?? [];
10165
10747
  if (cached?.root) setRoot(cached.root);
10166
10748
  if (restored.length) {
@@ -10170,17 +10752,17 @@ window.__ModuleLoader__.load({
10170
10752
  }
10171
10753
  loadDir("", { prefetch: true });
10172
10754
  for (const rel of restored) if (rel) loadDir(rel, { prefetch: true });
10173
- }, [sessionId]);
10755
+ }, [scope]);
10174
10756
  react.default.useEffect(() => {
10175
10757
  if (saveTimerRef.current) {
10176
10758
  clearTimeout(saveTimerRef.current);
10177
10759
  saveTimerRef.current = null;
10178
10760
  }
10179
- if (!sessionId) return;
10761
+ if (!scope) return;
10180
10762
  saveTimerRef.current = setTimeout(() => {
10181
10763
  saveTimerRef.current = null;
10182
10764
  const expandedList = Object.keys(expanded).filter((k) => expanded[k] === true);
10183
- explorerSave(sessionId, {
10765
+ explorerSave(scope, {
10184
10766
  root,
10185
10767
  expanded: expandedList
10186
10768
  });
@@ -10194,7 +10776,7 @@ window.__ModuleLoader__.load({
10194
10776
  }, [
10195
10777
  expanded,
10196
10778
  root,
10197
- sessionId
10779
+ scope
10198
10780
  ]);
10199
10781
  react.default.useEffect(() => {
10200
10782
  const onRefresh = () => refreshRef.current?.();
@@ -10202,14 +10784,14 @@ window.__ModuleLoader__.load({
10202
10784
  return () => window.removeEventListener("edrv:refresh", onRefresh);
10203
10785
  }, []);
10204
10786
  react.default.useEffect(() => {
10205
- if (!sessionId) return;
10787
+ if (!scope) return;
10206
10788
  const timer = setInterval(() => {
10207
10789
  const open = Object.keys(expandedRef.current).filter((k) => expandedRef.current[k] === true);
10208
10790
  if (!open.length) return;
10209
10791
  for (const rel of open) loadDirRef.current?.(rel, {});
10210
10792
  }, FOLLOW_INTERVAL_MS);
10211
10793
  return () => clearInterval(timer);
10212
- }, [sessionId]);
10794
+ }, [scope]);
10213
10795
  const rootName = root ? String(root).split(/[\\/]/).pop() || root : "";
10214
10796
  const rowEl = (e, depth, isDir, isOpen) => {
10215
10797
  const pending = isDir ? 0 : pendingByPath[e.path] ?? 0;
@@ -10348,6 +10930,7 @@ window.__ModuleLoader__.load({
10348
10930
  function SearchPanel(props) {
10349
10931
  const ctx = props?.ctx;
10350
10932
  const sessionId = ctx?.sessionId;
10933
+ const scope = ctx?.scope ?? workspaceScopeOf(null, sessionId);
10351
10934
  const openFileAt = ctx?.openFileAt ?? (() => {});
10352
10935
  const activePath = ctx?.activePath ?? null;
10353
10936
  const [query, setQuery] = react.default.useState("");
@@ -10387,9 +10970,9 @@ window.__ModuleLoader__.load({
10387
10970
  exclude: excludeOn ? splitGlobs(excludeText) : []
10388
10971
  };
10389
10972
  react.default.useEffect(() => {
10390
- if (!sessionId) return;
10973
+ if (!scope) return;
10391
10974
  try {
10392
- const saved = JSON.parse(localStorage.getItem(CACHE_KEY.search + String(sessionId)) || "null");
10975
+ const saved = JSON.parse(localStorage.getItem(CACHE_KEY.search + String(scope)) || "null");
10393
10976
  if (saved && typeof saved === "object") {
10394
10977
  if (typeof saved.query === "string") setQuery(saved.query);
10395
10978
  if (typeof saved.matchCase === "boolean") setMatchCase(saved.matchCase);
@@ -10402,11 +10985,11 @@ window.__ModuleLoader__.load({
10402
10985
  if (typeof saved.sectionOpen === "boolean") setSectionOpen(saved.sectionOpen);
10403
10986
  }
10404
10987
  } catch (e) {}
10405
- }, [sessionId]);
10988
+ }, [scope]);
10406
10989
  react.default.useEffect(() => {
10407
- if (!sessionId) return;
10990
+ if (!scope) return;
10408
10991
  try {
10409
- localStorage.setItem(CACHE_KEY.search + String(sessionId), JSON.stringify({
10992
+ localStorage.setItem(CACHE_KEY.search + String(scope), JSON.stringify({
10410
10993
  query,
10411
10994
  matchCase,
10412
10995
  wholeWord,
@@ -10419,7 +11002,7 @@ window.__ModuleLoader__.load({
10419
11002
  }));
10420
11003
  } catch (e) {}
10421
11004
  }, [
10422
- sessionId,
11005
+ scope,
10423
11006
  query,
10424
11007
  matchCase,
10425
11008
  wholeWord,
@@ -12232,8 +12815,8 @@ window.__ModuleLoader__.load({
12232
12815
  //#region src/client/index.ts
12233
12816
  /**
12234
12817
  * dsh-vscode-mode client — 浏览器半入口:slot 注册 + 装配。
12235
- * 挂点:betterSidebar「文件编辑」Tab(侧边栏形态,对话+编辑同屏;未装 dsh-better-sidebar 时
12236
- * 回退 conversation.view 中央页签)+ conversation.input.dock 差异条 + header 差异角标。
12818
+ * 挂点:官方右侧 Sidebar「文件编辑」Tab(DSH 0.1.5+,对话+编辑同屏)> betterSidebar Tab
12819
+ * (归档,仅旧版 DSH 回退)> conversation.view 中央页签 + conversation.input.dock 差异条 + header 差异角标。
12237
12820
  * 与 Host 通信:同源 fetch('/edrv/rpc')(shared/rpc 契约)。
12238
12821
  *
12239
12822
  * ⚠️ 跨版本 slot 装配(2026-08-21):新版 DSH 的 slots 系统要求 slot 必须由父 entry
@@ -12290,6 +12873,23 @@ window.__ModuleLoader__.load({
12290
12873
  setSession(list.getSnapshot()?.current);
12291
12874
  }), "vscode-mode: lsp session sync");
12292
12875
  }
12876
+ if (monacoList && typeof monacoList.subscribe === "function") {
12877
+ const list = monacoList;
12878
+ ctx.effect(() => {
12879
+ let lastCurrent = list.getSnapshot?.()?.current;
12880
+ const check = () => {
12881
+ const current = list.getSnapshot?.()?.current;
12882
+ if (!current || current === lastCurrent) return;
12883
+ lastCurrent = current;
12884
+ if (!isEditorTabActive() || officialService === void 0) return;
12885
+ try {
12886
+ officialService.service.openTabIn?.(current, OFFICIAL_TAB_KIND, {});
12887
+ } catch {}
12888
+ restoreEditorTab(officialService.service, schedule);
12889
+ };
12890
+ return list.subscribe(() => schedule(check, 0));
12891
+ }, "vscode-mode: editor tab restore");
12892
+ }
12293
12893
  const originalOpenPath = workspaces?.openPath;
12294
12894
  const binder = pickSettingsBinder(ctx);
12295
12895
  const settings = binder.scope;
@@ -12309,6 +12909,10 @@ window.__ModuleLoader__.load({
12309
12909
  };
12310
12910
  /** 可选探测 betterSidebar 服务(不进 inject:缺失会让插件停靠等待,杀死回退路径)。 */
12311
12911
  let sideService = detectSidebarService(ctx);
12912
+ /** 可选探测官方右侧 Sidebar(DSH 0.1.5-alpha.1+;探测命中 ≡ 版本判定,不进 inject 同上)。 */
12913
+ let officialService = detectOfficial(ctx);
12914
+ /** 官方 file 地址认领的当前注销器(null=未认领;由 syncFileClaim 依设置值切换)。 */
12915
+ let claimDisposer = null;
12312
12916
  /** 「添加到对话」动作集:编辑区/Tab 右键菜单注入文件引用与代码块(conversation 服务缺失时各动作安全降级)。 */
12313
12917
  const addToConversation = createAddToConversation(ctx);
12314
12918
  /** 客户端侧兼容摘要(与 host 报告合并展示;惰性求值保证 HMR 后仍新鲜)。 */
@@ -12334,9 +12938,19 @@ window.__ModuleLoader__.load({
12334
12938
  note: registry.get("dsh-better-sidebar") !== void 0 ? "已注册(优先级 80)" : "未检测到侧边栏打开能力"
12335
12939
  },
12336
12940
  {
12337
- name: "侧边栏编辑区(dsh-better-sidebar)",
12941
+ name: "侧边栏编辑区(官方 Sidebar)",
12942
+ active: officialService !== void 0,
12943
+ note: officialService !== void 0 ? "编辑区=官方右侧 Sidebar Tab(对话+编辑同屏,DSH 0.1.5+)" : "官方侧边栏服务未探测到(DSH < 0.1.5-alpha.1 时属预期)"
12944
+ },
12945
+ {
12946
+ name: "侧边栏编辑区(dsh-better-sidebar,归档)",
12338
12947
  active: sideService !== void 0,
12339
- note: sideService !== void 0 ? "编辑区=侧边栏 Tab(对话+编辑同屏)" : "未检测到;安装 dsh-better-sidebar 后刷新启用侧边栏形态(" + SIDEBAR_INSTALL_CMD + ")"
12948
+ note: sideService !== void 0 ? "编辑区=侧边栏 Tab(旧版 DSH 回退形态)" : "未检测到;DSH ≥ 0.1.5 优先官方侧边栏,旧版可安装(" + SIDEBAR_INSTALL_CMD + ")"
12949
+ },
12950
+ {
12951
+ name: "文件链接官方认领(dsh-resource://file)",
12952
+ active: claimDisposer !== null,
12953
+ note: claimDisposer !== null ? "聊天文件链接由本插件编辑器接管(官方侧边栏内打开)" : "链接走官方查看器或旧版路由(未认领)"
12340
12954
  }
12341
12955
  ];
12342
12956
  ctx.provide("fileOpeners", registry);
@@ -12364,12 +12978,39 @@ window.__ModuleLoader__.load({
12364
12978
  const sidebar = scanSidebar(ctx);
12365
12979
  return sidebar ? registry.register(sidebar) : void 0;
12366
12980
  }, "vscode-mode: sidebar file opener");
12981
+ const officialRenderTab = (props) => react.default.createElement(OfficialSideTab, Object.assign({}, props, {
12982
+ schedule,
12983
+ addToConversation,
12984
+ sidebarPanels,
12985
+ outlineSources,
12986
+ fileMenuItems,
12987
+ sessions
12988
+ }));
12989
+ /** 官方 file 地址认领同步:自动/VSCodeMode 档认领(链接进本插件编辑器),其余交官方查看器。 */
12990
+ const syncFileClaim = () => {
12991
+ const official = officialService;
12992
+ const want = official !== void 0 && shouldClaimFiles(selected);
12993
+ if (want && claimDisposer === null && official) {
12994
+ const disposer = registerOfficialFileClaim({
12995
+ tabs: official.tabs,
12996
+ slots: ctx.slots,
12997
+ renderTab: officialRenderTab
12998
+ });
12999
+ if (disposer !== null) claimDisposer = ctx.effect(() => disposer, "vscode-mode: official file claim");
13000
+ return;
13001
+ }
13002
+ if (!want && claimDisposer !== null) {
13003
+ claimDisposer();
13004
+ claimDisposer = null;
13005
+ }
13006
+ };
12367
13007
  ctx.effect(() => {
12368
13008
  if (!settings) return void 0;
12369
13009
  const sync = () => {
12370
13010
  const snapshot = settings.getSnapshot();
12371
13011
  if (snapshot.status === "loading") return;
12372
13012
  selected = autoValue(snapshot.value?.fileOpenTool);
13013
+ syncFileClaim();
12373
13014
  window.dispatchEvent(new CustomEvent("edrv:file-open-tool-change", { detail: { value: selected } }));
12374
13015
  };
12375
13016
  sync();
@@ -12442,11 +13083,50 @@ window.__ModuleLoader__.load({
12442
13083
  sessions
12443
13084
  })));
12444
13085
  };
13086
+ let officialDisposer = null;
12445
13087
  let sideDisposer = null;
12446
13088
  let legacyDisposer = null;
13089
+ const installOfficialForm = () => {
13090
+ const official = officialService;
13091
+ if (officialDisposer !== null || !official) return;
13092
+ if (sideDisposer !== null) {
13093
+ sideDisposer();
13094
+ sideDisposer = null;
13095
+ }
13096
+ if (legacyDisposer !== null) {
13097
+ legacyDisposer();
13098
+ legacyDisposer = null;
13099
+ }
13100
+ const outcome = installOfficial({
13101
+ tabs: official.tabs,
13102
+ service: official.service,
13103
+ slots: ctx.slots,
13104
+ renderTab: officialRenderTab,
13105
+ guide: {
13106
+ title: () => OFFICIAL_TAB_TITLE,
13107
+ description: () => "Monaco 文件编辑器:对话与编辑同屏(差异审查/跳转/大纲)"
13108
+ }
13109
+ });
13110
+ if (outcome === null) {
13111
+ officialService = void 0;
13112
+ applySideForm();
13113
+ return;
13114
+ }
13115
+ officialDisposer = ctx.effect(() => outcome, "vscode-mode: official sidebar editor tab");
13116
+ if (typeof official.service.openResource === "function") ctx.effect(() => registry.register(officialSidebarOpener(official.service)), "vscode-mode: official sidebar opener");
13117
+ syncFileClaim();
13118
+ };
12447
13119
  const applySideForm = () => {
13120
+ if (officialService) {
13121
+ installOfficialForm();
13122
+ return;
13123
+ }
12448
13124
  if (sideService) {
12449
13125
  if (sideDisposer !== null) return;
13126
+ if (officialDisposer !== null) {
13127
+ officialDisposer();
13128
+ officialDisposer = null;
13129
+ }
12450
13130
  if (legacyDisposer !== null) {
12451
13131
  legacyDisposer();
12452
13132
  legacyDisposer = null;
@@ -12472,28 +13152,41 @@ window.__ModuleLoader__.load({
12472
13152
  },
12473
13153
  registerLegacyFallback: registerLegacyTab
12474
13154
  }), "vscode-mode: sidebar editor tab");
12475
- } else {
12476
- if (legacyDisposer !== null) return;
12477
- if (sideDisposer !== null) {
12478
- sideDisposer();
12479
- sideDisposer = null;
12480
- }
12481
- setEnsureSideEditor(null);
12482
- legacyDisposer = registerLegacyTab();
13155
+ return;
12483
13156
  }
13157
+ if (legacyDisposer !== null) return;
13158
+ if (officialDisposer !== null) {
13159
+ officialDisposer();
13160
+ officialDisposer = null;
13161
+ }
13162
+ if (sideDisposer !== null) {
13163
+ sideDisposer();
13164
+ sideDisposer = null;
13165
+ }
13166
+ setEnsureSideEditor(null);
13167
+ legacyDisposer = registerLegacyTab();
12484
13168
  };
12485
13169
  applySideForm();
12486
13170
  let sideRetries = 0;
12487
13171
  const retrySideService = () => {
12488
- if (sideService !== void 0 || sideRetries >= 15) return;
13172
+ if (officialService !== void 0 || sideRetries >= 15) return;
12489
13173
  sideRetries += 1;
12490
13174
  schedule(() => {
12491
- if (sideService !== void 0) return;
12492
- sideService = detectSidebarService(ctx);
12493
- if (sideService !== void 0) {
12494
- log.info("检测到 dsh-better-sidebar,切换侧边栏编辑形态");
13175
+ if (officialService !== void 0) return;
13176
+ officialService = detectOfficial(ctx);
13177
+ if (officialService !== void 0) {
13178
+ log.info("检测到官方右侧 Sidebar(DSH 0.1.5+),切换官方侧边栏编辑形态");
12495
13179
  applySideForm();
12496
- } else retrySideService();
13180
+ return;
13181
+ }
13182
+ if (sideService === void 0) {
13183
+ sideService = detectSidebarService(ctx);
13184
+ if (sideService !== void 0) {
13185
+ log.info("检测到 dsh-better-sidebar,切换侧边栏编辑形态(官方未探测到,后续仍优先官方)");
13186
+ applySideForm();
13187
+ }
13188
+ }
13189
+ retrySideService();
12497
13190
  }, 2e3);
12498
13191
  };
12499
13192
  retrySideService();