dsh-vscode-mode 0.1.52 → 0.1.54

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.
Files changed (41) hide show
  1. package/README.md +71 -0
  2. package/assets/shell/dsh-open.cs +182 -0
  3. package/assets/shell/dsh-open.ps1 +52 -0
  4. package/assets/shell/dsh-open.sh +70 -0
  5. package/assets/shell/dsh-whale.ico +0 -0
  6. package/lib/client.js +1038 -13
  7. package/lib/client.js.map +1 -1
  8. package/lib/index.js +1130 -7
  9. package/lib/index.js.map +1 -1
  10. package/package.json +2 -1
  11. package/src/client/chooseDialog.ts +70 -0
  12. package/src/client/compat.ts +31 -0
  13. package/src/client/externalOpen.ts +130 -0
  14. package/src/client/imagePreview.ts +45 -0
  15. package/src/client/index.ts +50 -8
  16. package/src/client/openFlow.ts +330 -0
  17. package/src/client/remoteOpenRouter.ts +78 -0
  18. package/src/client/styles/editor.css +6 -0
  19. package/src/client/ui/EditorView.ts +116 -1
  20. package/src/client/ui/IntegrationSettings.ts +243 -0
  21. package/src/client/ui/McpSettings.ts +2 -0
  22. package/src/compat.ts +1 -1
  23. package/src/dshVersion.ts +1 -0
  24. package/src/externalHandoff.ts +70 -0
  25. package/src/fileOpenSettings.ts +10 -2
  26. package/src/index.ts +3 -0
  27. package/src/integrate.ts +576 -0
  28. package/src/rpc.ts +97 -3
  29. package/src/shared/externalOpen.ts +62 -0
  30. package/src/shared/integration.ts +73 -0
  31. package/src/shared/rpc.ts +58 -5
  32. package/src/unityBridge.ts +234 -0
  33. package/unity/com.dsh.editor/Editor/DshCodeEditor.cs +267 -0
  34. package/unity/com.dsh.editor/Editor/DshCodeEditor.cs.meta +11 -0
  35. package/unity/com.dsh.editor/Editor/com.dsh.editor.asmdef +15 -0
  36. package/unity/com.dsh.editor/Editor/com.dsh.editor.asmdef.meta +7 -0
  37. package/unity/com.dsh.editor/Editor.meta +8 -0
  38. package/unity/com.dsh.editor/README.md +33 -0
  39. package/unity/com.dsh.editor/README.md.meta +7 -0
  40. package/unity/com.dsh.editor/package.json +7 -0
  41. package/unity/com.dsh.editor/package.json.meta +7 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-vscode-mode",
3
- "version": "0.1.52",
3
+ "version": "0.1.54",
4
4
  "description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 dsh-better-sidebar 侧边栏与对话同屏)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车",
5
5
  "keywords": [
6
6
  "dsh",
@@ -36,6 +36,7 @@
36
36
  "lib",
37
37
  "assets",
38
38
  "src",
39
+ "unity",
39
40
  "cordis.patch.yml"
40
41
  ],
41
42
  "license": "BSD-3-Clause",
@@ -0,0 +1,70 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * dsh-vscode-mode client — 工作区选择弹窗(深链打开规则 1)。
4
+ * 复用与「添加 MCP」一致的 vsm-mcp-modal / vsm-mcp-dialog 模态(页面居中卡片 + 遮罩 + 主题
5
+ * token),React portal 挂 body;Esc / 遮罩点击 / 取消 → null(中止流程不改任何状态)。
6
+ * 作者 ddj 2026-09-07
7
+ */
8
+ import React from 'react'
9
+ import { createRoot } from 'react-dom/client'
10
+
11
+ /** 用户选择:使用最近的工作区 / 新建工作区 / null=中止。 */
12
+ export type WorkspaceChoice = 'recent' | 'create' | null
13
+
14
+ /** 弹窗组件属性。 */
15
+ interface ChoiceModalProps {
16
+ /** 匹配到的工作区标题。 */
17
+ title: string
18
+ /** 触发打开的文件夹路径。 */
19
+ folder: string
20
+ /** 选择回调(首次调用即卸载)。 */
21
+ done: (choice: WorkspaceChoice) => void
22
+ }
23
+
24
+ /** 选择弹窗(vsm-mcp-modal 遮罩 + vsm-mcp-dialog 卡片,Esc/遮罩点击取消)。 */
25
+ function ChoiceModal({ title, folder, done }: ChoiceModalProps) {
26
+ React.useEffect(() => {
27
+ const onKey = (event) => {
28
+ if (event.key === 'Escape') done(null)
29
+ }
30
+ window.addEventListener('keydown', onKey)
31
+ return () => window.removeEventListener('keydown', onKey)
32
+ }, [])
33
+ return React.createElement('div', {
34
+ className: 'vsm-mcp-modal',
35
+ onMouseDown: (event) => {
36
+ if (event.target === event.currentTarget) done(null)
37
+ },
38
+ },
39
+ React.createElement('div', { className: 'vsm-mcp-dialog' },
40
+ React.createElement('h3', null, '在 DSH 中打开'),
41
+ React.createElement('label', null, '文件夹 ' + folder + ' 位于已注册的工作区「' + title + '」内,选择打开方式:'),
42
+ React.createElement('div', { className: 'vsm-mcp-dialog-actions' },
43
+ React.createElement('button', { onClick: () => done(null) }, '取消'),
44
+ React.createElement('button', { onClick: () => done('create') }, '新建工作区'),
45
+ React.createElement('button', { className: 'vsm-primary', onClick: () => done('recent') }, '使用最近的工作区'),
46
+ ),
47
+ ),
48
+ )
49
+ }
50
+
51
+ /**
52
+ * 弹出工作区选择弹窗(深链打开规则 1)。
53
+ * @author ddj 2026年09月07号
54
+ * @param title 匹配到的工作区标题
55
+ * @param folder 文件夹路径
56
+ * @returns 用户选择;null = 取消/中止
57
+ */
58
+ export function chooseWorkspace(title: string, folder: string): Promise<WorkspaceChoice> {
59
+ return new Promise((resolve) => {
60
+ const host = document.createElement('div')
61
+ document.body.appendChild(host)
62
+ const root = createRoot(host)
63
+ const done = (choice: WorkspaceChoice) => {
64
+ root.unmount()
65
+ host.remove()
66
+ resolve(choice)
67
+ }
68
+ root.render(React.createElement(ChoiceModal, { title, folder, done }))
69
+ })
70
+ }
@@ -92,3 +92,34 @@ export function patchMethod<T extends object, K extends keyof T>(
92
92
  if (owner[key] === patched) owner[key] = original
93
93
  }
94
94
  }
95
+
96
+ /** patchAccessor 的包装实现形状(original 为补丁前的实时实现)。 */
97
+ export type PatchWrapper = (original: (...args: never[]) => unknown, ...args: never[]) => unknown
98
+
99
+ /**
100
+ * accessor 感知的链式补丁:DSH 0.1.3+ 的 remote 命名空间方法(如
101
+ * remote.session.openWorkspacePath)是 getter-only accessor(Object.defineProperty
102
+ * 仅定义 get,直接赋值在严格模式下抛 TypeError),须整体替换属性描述符。
103
+ * 新 getter 每次访问现场调用原 getter 取最新一次性函数作 original,天然跟随
104
+ * DSH 内部 methods 表变化;data 属性降级走 patchMethod;属性缺失返回 null。
105
+ * @author ddj 2026年09月08号
106
+ * @param owner 目标对象
107
+ * @param key 属性名
108
+ * @param wrapper 包装实现(original 为补丁前的实时实现)
109
+ * @returns 恢复函数(幂等、带归属校验);属性缺失时返回 null
110
+ */
111
+ export function patchAccessor(owner: object, key: string, wrapper: PatchWrapper): (() => void) | null {
112
+ const descriptor = Object.getOwnPropertyDescriptor(owner, key)
113
+ if (!descriptor) return null
114
+ if (typeof descriptor.get !== 'function') return patchMethod(owner, key as never, wrapper as never)
115
+ const rawGet = descriptor.get as (this: object) => unknown
116
+ const patchedGet = function (): unknown {
117
+ const original = (...args: never[]) => (rawGet.call(owner) as (...args: never[]) => unknown)(...args)
118
+ return (...args: never[]) => wrapper(original, ...args)
119
+ }
120
+ Object.defineProperty(owner, key, { configurable: true, enumerable: descriptor.enumerable, get: patchedGet })
121
+ return () => {
122
+ const current = Object.getOwnPropertyDescriptor(owner, key)
123
+ if (current?.get === patchedGet) Object.defineProperty(owner, key, descriptor)
124
+ }
125
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * dsh-vscode-mode client — 外部深链落地(Windows 右键菜单 / Unity 外部编辑器)。
3
+ * 启动时解析 location.search 的 edrvOpen 深链:跨源 referrer 守卫防外部网页诱导;
4
+ * 处理完剥离 URL 参数防刷新重开;执行委托 openFlow 打开规则引擎(工作区/对话智能路由)。
5
+ * 另挂 3s 移交轮询(兼活跃心跳):launcher 投递的打开请求由已打开页面就地执行,避免重复开新页。
6
+ * 作者 ddj 2026-09-07 / 2026-09-08
7
+ */
8
+ import { EDRV_PARAM_KEYS, parseOpenParams } from '../shared/externalOpen.js'
9
+ import { openDeepLink } from './openFlow.js'
10
+ import { rpc } from './rpc.js'
11
+
12
+ /** toast 自隐时长。 */
13
+ const TOAST_MS = 5000
14
+ /** 深链移交轮询周期(兼活跃心跳)。 */
15
+ const HANDOFF_POLL_MS = 3000
16
+
17
+ /** 深链装配选项(全部可注入便于测试;缺省读浏览器全局)。 */
18
+ export interface ExtOpenOptions {
19
+ /** 查询串(缺省 location.search)。 */
20
+ search?: string
21
+ /** referrer(缺省 document.referrer)。 */
22
+ referrer?: string
23
+ /** 当前源(缺省 location.origin)。 */
24
+ origin?: string
25
+ }
26
+
27
+ /**
28
+ * 装配外部深链:深链参数缺失时仅保持移交轮询(零额外开销)。
29
+ * @author ddj 2026年09月07号
30
+ * @param ctx 客户端根上下文(sessions/workspaces 服务)
31
+ * @param options 可注入选项
32
+ */
33
+ export function setupExtOpen(ctx: unknown, options: ExtOpenOptions = {}): void {
34
+ startPresencePoll(ctx)
35
+ const search = options.search ?? (typeof location !== 'undefined' ? location.search : '')
36
+ const params = parseOpenParams(search)
37
+ if (!params) return
38
+ const referrer = options.referrer ?? (typeof document !== 'undefined' ? document.referrer : '')
39
+ const origin = options.origin ?? (typeof location !== 'undefined' ? location.origin : '')
40
+ if (!referrerAllowed(referrer, origin)) {
41
+ console.warn('[dsh-vscode-mode] 已忽略跨源深链请求(referrer=' + referrer + ')')
42
+ return
43
+ }
44
+ stripCurrentUrl()
45
+ void openDeepLink(ctx, params).catch((error) => {
46
+ console.warn('[dsh-vscode-mode] 深链打开失败:' + String(error))
47
+ toastDom('深链打开失败:' + String((error as Error)?.message ?? error))
48
+ })
49
+ }
50
+
51
+ /**
52
+ * 深链移交轮询:每 3s 向 host 领取待打开请求(兼活跃心跳,launcher 据此决定是否开新页)。
53
+ * 页面级单例(window 标记防 HMR 重复);领取到即执行打开规则。
54
+ * @author ddj 2026年09月08号
55
+ * @param ctx 客户端根上下文
56
+ */
57
+ function startPresencePoll(ctx: unknown): void {
58
+ if (typeof window === 'undefined') return
59
+ const marker = window as { __edrvExtPoll?: boolean }
60
+ if (marker.__edrvExtPoll) return
61
+ marker.__edrvExtPoll = true
62
+ let executing = false
63
+ window.setInterval(() => {
64
+ if (executing) return
65
+ executing = true
66
+ void rpc('edrv.external.pending', {})
67
+ .then(async (result) => {
68
+ if (!result.ok || !result.open) return
69
+ await openDeepLink(ctx, { paths: result.open.paths, line: result.open.line, column: result.open.column })
70
+ })
71
+ .catch(() => { /* 旧版 host 无此方法/离线:忽略 */ })
72
+ .finally(() => { executing = false })
73
+ }, HANDOFF_POLL_MS)
74
+ }
75
+
76
+ /**
77
+ * 跨源守卫:referrer 为空(launcher/Unity 直开)或同源 → 允许。
78
+ * @author ddj 2026年09月07号
79
+ * @param referrer 文档 referrer
80
+ * @param origin 当前页面源
81
+ * @returns 是否允许处理深链
82
+ */
83
+ export function referrerAllowed(referrer: string, origin: string): boolean {
84
+ if (!referrer) return true
85
+ try {
86
+ return new URL(referrer).origin === origin
87
+ } catch {
88
+ return false
89
+ }
90
+ }
91
+
92
+ /**
93
+ * 剥离 URL 中的深链参数(保留其余参数与 hash,防刷新重开)。
94
+ * @author ddj 2026年09月07号
95
+ * @param href 完整 URL
96
+ * @returns 清理后的 URL(pathname+search+hash)
97
+ */
98
+ export function stripOpenHref(href: string): string {
99
+ const url = new URL(href)
100
+ for (const key of EDRV_PARAM_KEYS) url.searchParams.delete(key)
101
+ return url.pathname + url.search + url.hash
102
+ }
103
+
104
+ /** 清理当前页面 URL(best-effort,失败不阻塞打开)。 */
105
+ function stripCurrentUrl(): void {
106
+ try {
107
+ if (typeof location === 'undefined' || typeof history === 'undefined') return
108
+ const next = stripOpenHref(location.href)
109
+ if (next !== location.pathname + location.search + location.hash) history.replaceState(null, '', next)
110
+ } catch { /* 清理失败忽略 */ }
111
+ }
112
+
113
+ /**
114
+ * 轻量 toast(编辑器未挂载也能提示;5s 自隐;openFlow 通知与错误呈现共用)。
115
+ * @author ddj 2026年09月07号
116
+ * @param text 提示文本
117
+ */
118
+ export function toastDom(text: string): void {
119
+ if (typeof document === 'undefined') return
120
+ const toast = document.createElement('div')
121
+ toast.textContent = text
122
+ toast.setAttribute('style', [
123
+ 'position:fixed', 'left:50%', 'bottom:28px', 'transform:translateX(-50%)',
124
+ 'z-index:99999', 'padding:8px 14px', 'border-radius:8px', 'max-width:80vw',
125
+ 'background:#2a2a2a', 'color:#ddd', 'border:1px solid #444', 'font-size:12px',
126
+ 'box-shadow:0 4px 16px rgba(0,0,0,.35)',
127
+ ].join(';'))
128
+ document.body?.appendChild(toast)
129
+ setTimeout(() => toast.remove(), TOAST_MS)
130
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * dsh-vscode-mode client — 图片文件判定与 data URL 组装(编辑区图片预览面板用)。
3
+ * 纯函数、无 DOM 依赖,便于单测;MIME 表收敛在 shared/rpc.ts(host base64 响应同源)。
4
+ * @author ddj 2026年09月08号
5
+ */
6
+ import { IMAGE_MIME } from '../shared/rpc.js'
7
+
8
+ /** 可预览的图片扩展名集合(小写、无点)。 */
9
+ const IMAGE_EXT_SET: Set<string> = new Set(Object.keys(IMAGE_MIME))
10
+
11
+ /**
12
+ * 判断路径是否为可预览的图片文件(取 basename 扩展名,大小写不敏感)。
13
+ * @author ddj 2026年09月08号
14
+ * @param path 文件路径(`/` 或 `\` 分隔均可)
15
+ * @returns 是否图片文件
16
+ */
17
+ export function isImagePath(path: string): boolean {
18
+ const base = String(path || '').split(/[\\/]/).pop() || ''
19
+ const dot = base.lastIndexOf('.')
20
+ if (dot <= 0) return false
21
+ return IMAGE_EXT_SET.has(base.slice(dot + 1).toLowerCase())
22
+ }
23
+
24
+ /**
25
+ * 判断路径是否为 SVG 文件(唯一提供「以文本打开」切换的图片格式)。
26
+ * @author ddj 2026年09月08号
27
+ * @param path 文件路径(`/` 或 `\` 分隔均可)
28
+ * @returns 是否 SVG
29
+ */
30
+ export function isSvgPath(path: string): boolean {
31
+ const base = String(path || '').split(/[\\/]/).pop() || ''
32
+ const dot = base.lastIndexOf('.')
33
+ return dot > 0 && base.slice(dot + 1).toLowerCase() === 'svg'
34
+ }
35
+
36
+ /**
37
+ * base64 文本 → 可直接赋给 `<img src>` 的 data URL。
38
+ * @author ddj 2026年09月08号
39
+ * @param base64 base64 文本(不含 data: 前缀)
40
+ * @param mime MIME 类型(host edrv.read base64 响应携带)
41
+ * @returns data URL 字符串
42
+ */
43
+ export function dataUrlOf(base64: string, mime: string): string {
44
+ return 'data:' + (mime || 'application/octet-stream') + ';base64,' + base64
45
+ }
@@ -25,6 +25,8 @@ import { loadMonaco } from './monaco/loader.js'
25
25
  import { createFileOpenerRegistry, scanSidebar, type FileOpenContext } from './fileOpeners.js'
26
26
  import type { FileOpenerRegistry } from './fileOpeners.js'
27
27
  import { installOpenPathRouter, vscodeOpener, autoValue } from './openPathRouter.js'
28
+ import { patchRemoteOpen, probeRemoteOpen } from './remoteOpenRouter.js'
29
+ import { setupExtOpen } from './externalOpen.js'
28
30
  import { SettingsContext } from './settingsContext.js'
29
31
  import { SIDEBAR_PLUGIN, pickSettingsBinder, registerSlotSafely } from './compat.js'
30
32
  import { detectSidebarService, installSideEditor, setEnsureSideEditor, SIDEBAR_INSTALL_CMD } from './sidebarBridge.js'
@@ -62,6 +64,9 @@ export function apply(ctx: any): void {
62
64
  const workspaces = ctx.get('workspaces')
63
65
  const sessions = ctx.get('sessions')
64
66
 
67
+ // 外部深链落地:Windows 右键菜单 / Unity 外部编辑器 → 浏览器 URL 参数 → 打开规则路由
68
+ setupExtOpen(ctx)
69
+
65
70
  // Monaco 加载时机:不再 DSH 启动即预热,改为进入会话界面(sessions.list.current 出现)后再后台加载,
66
71
  // 用户点开「文件编辑」页签即用;空闲时仍由 EditorView 挂载兜底加载。
67
72
  // 模块级 promise 去重,重复触发只首次真正加载;requestIdleCallback 让出会话首屏带宽,缺省回退延时调度;
@@ -94,6 +99,17 @@ export function apply(ctx: any): void {
94
99
  const binder = pickSettingsBinder(ctx)
95
100
  const settings = binder.scope
96
101
  let selected = autoValue('auto')
102
+ /** 0.1.3+ 会话文件链接路由(remote.session.openWorkspacePath)是否已安装(compatSummary 展示用)。 */
103
+ let remoteOpenInstalled = false
104
+ /** 两条文件链接路由共用的路由日志(openPathRouter / remoteOpenRouter)。 */
105
+ const routeLogger = (message: string): void => console.warn('[dsh-vscode-mode] ' + message)
106
+ /** 两条路由共用的 FileOpenContext:当前会话 id 与工作区 cwd。 */
107
+ const openContext = (): FileOpenContext => {
108
+ const current = sessions?.list?.getSnapshot?.()
109
+ const sessionId = current?.current
110
+ const summary = sessionId ? current?.byId?.[sessionId] : undefined
111
+ return { sessionId, cwd: summary?.cwd } as FileOpenContext
112
+ }
97
113
  /** 可选探测 betterSidebar 服务(不进 inject:缺失会让插件停靠等待,杀死回退路径)。 */
98
114
  let sideService = detectSidebarService(ctx)
99
115
  /** 「添加到对话」动作集:编辑区/Tab 右键菜单注入文件引用与代码块(conversation 服务缺失时各动作安全降级)。 */
@@ -102,7 +118,8 @@ export function apply(ctx: any): void {
102
118
  /** 客户端侧兼容摘要(与 host 报告合并展示;惰性求值保证 HMR 后仍新鲜)。 */
103
119
  const compatSummary = (): CompatAdapter[] => [
104
120
  { name: '设置桥', active: settings !== undefined, note: settings !== undefined ? '使用 ' + binder.service + ' 桥' : '未绑定设置服务(fileOpenTool 持久化不可用)' },
105
- { name: '文件打开路由(workspaces.openPath)', active: Boolean(workspaces?.openPath), note: 'vscode 打开器优先,失败回退系统打开' },
121
+ { name: '文件打开路由(workspaces.openPath)', active: Boolean(workspaces?.openPath), note: 'vscode 打开器优先,失败回退系统打开(0.1.2 及更早 DSH 的对话文件链接主路径)' },
122
+ { name: '会话文件链接路由(remote.session.openWorkspacePath)', active: remoteOpenInstalled, note: '0.1.3+ 对话文件引用/产物打开优先走插件打开器,失败回退系统打开' },
106
123
  { name: '侧边栏打开器(' + SIDEBAR_PLUGIN + ')', active: registry.get(SIDEBAR_PLUGIN) !== undefined, note: registry.get(SIDEBAR_PLUGIN) !== undefined ? '已注册(优先级 80)' : '未检测到侧边栏打开能力' },
107
124
  { name: '侧边栏编辑区(' + SIDEBAR_PLUGIN + ')', active: sideService !== undefined, note: sideService !== undefined ? '编辑区=侧边栏 Tab(对话+编辑同屏)' : '未检测到;安装 dsh-better-sidebar 后刷新启用侧边栏形态(' + SIDEBAR_INSTALL_CMD + ')' },
108
125
  ]
@@ -169,15 +186,40 @@ export function apply(ctx: any): void {
169
186
  workspaces,
170
187
  registry,
171
188
  selected: () => selected,
172
- context: () => {
173
- const current = sessions?.list?.getSnapshot?.()
174
- const sessionId = current?.current
175
- const summary = sessionId ? current?.byId?.[sessionId] : undefined
176
- return { sessionId, cwd: summary?.cwd } as FileOpenContext
177
- },
178
- logger: (message: string) => console.warn('[dsh-vscode-mode] ' + message),
189
+ context: openContext,
190
+ logger: routeLogger,
179
191
  }), 'vscode-mode: file link routing')
180
192
  }
193
+ // 0.1.3+ 会话文件链接主路径:dsh-client-ui-chat 的 openFile 直接调
194
+ // remote.session.openWorkspacePath(session/openWorkspacePath RPC),绕开
195
+ // workspaces.openPath;该 namespace 由 api-remotes 在 loader.await 后挂载,
196
+ // 晚于本插件 apply,且旧版 DSH 可能整段不存在——不加 inject(避免旧版停等
197
+ // 永不激活),改为定时探测有界重试(与侧栏服务探测同款节奏)。
198
+ let remoteRetries = 0
199
+ const retryRemoteOpen = (): void => {
200
+ if (remoteOpenInstalled || remoteRetries >= 15) {
201
+ if (!remoteOpenInstalled) console.warn('[dsh-vscode-mode] 未探测到 remote.session.openWorkspacePath,0.1.3+ 会话文件链接路由未安装')
202
+ return
203
+ }
204
+ remoteRetries += 1
205
+ schedule(() => {
206
+ if (remoteOpenInstalled) return
207
+ const service = probeRemoteOpen(ctx)
208
+ if (!service) {
209
+ retryRemoteOpen()
210
+ return
211
+ }
212
+ const disposer = patchRemoteOpen(service, { registry, selected: () => selected, context: openContext, logger: routeLogger })
213
+ if (!disposer) {
214
+ console.warn('[dsh-vscode-mode] remote.session 存在但 openWorkspacePath 不可补丁,会话文件链接路由未安装')
215
+ return
216
+ }
217
+ remoteOpenInstalled = true
218
+ ctx.effect(() => disposer, 'vscode-mode: remote file link routing')
219
+ console.info('[dsh-vscode-mode] 已安装 0.1.3+ 会话文件链接路由(remote.session.openWorkspacePath)')
220
+ }, 2000)
221
+ }
222
+ retryRemoteOpen()
181
223
 
182
224
  // 中央「文件编辑」页签(旧形态回退):类 VSCode 编辑器;侧边栏形态可用时编辑器住 betterSidebar Tab,
183
225
  // 本页签不注册(避免双实例:Monaco×2 + diff dock 每会话单源抢占)。