dsh-vscode-mode 0.12.0 → 0.13.1

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 (71) hide show
  1. package/README.md +148 -112
  2. package/assets/shell/dsh-open.cs +65 -149
  3. package/assets/shell/dsh-open.mjs +241 -0
  4. package/assets/shell/dsh-open.ps1 +28 -47
  5. package/assets/shell/dsh-open.sh +25 -66
  6. package/lib/client.js +30185 -29711
  7. package/lib/client.js.map +1 -1
  8. package/lib/index.js +3889 -2336
  9. package/lib/index.js.map +1 -1
  10. package/package.json +9 -8
  11. package/skills/dsh-vscodemode-mcp/SKILL.md +94 -132
  12. package/src/childEnv.ts +19 -0
  13. package/src/client/addToConversation.ts +4 -2
  14. package/src/client/ai/inlineProvider.ts +1 -8
  15. package/src/client/commandBridge.ts +6 -2
  16. package/src/client/externalOpen.ts +40 -19
  17. package/src/client/index.ts +17 -15
  18. package/src/client/keybindings.ts +18 -1
  19. package/src/client/monaco/loader.ts +3 -1
  20. package/src/client/openFlow.ts +36 -28
  21. package/src/client/openReceipt.ts +64 -0
  22. package/src/client/panelCommands.ts +101 -0
  23. package/src/client/pdf/pdfLoader.ts +4 -2
  24. package/src/client/receiptState.ts +120 -0
  25. package/src/client/rpc.ts +30 -4
  26. package/src/client/shortcutsOfficial.ts +39 -4
  27. package/src/client/sidebarBridge.ts +7 -0
  28. package/src/client/styles/mcp.css +1 -19
  29. package/src/client/ui/DiffBadge.ts +2 -1
  30. package/src/client/ui/EditorView.ts +237 -55
  31. package/src/client/ui/McpSettings.ts +32 -7
  32. package/src/client/ui/OfficialSideTab.ts +5 -2
  33. package/src/client/ui/QuickOpen.ts +10 -9
  34. package/src/client/ui/commandCatalog.ts +5 -4
  35. package/src/client/useOpenReceipt.ts +128 -0
  36. package/src/compat.ts +5 -3
  37. package/src/dap/manager.ts +46 -13
  38. package/src/devForm.ts +252 -92
  39. package/src/externalHandoff.ts +93 -52
  40. package/src/fileOpenSettings.ts +1 -24
  41. package/src/hostImport.ts +19 -0
  42. package/src/httpGuard.ts +101 -0
  43. package/src/index.ts +106 -97
  44. package/src/integrate.ts +56 -50
  45. package/src/lsp/transport.ts +84 -127
  46. package/src/mcp.ts +58 -9
  47. package/src/mcpIsolation.ts +211 -102
  48. package/src/mcpProject.ts +220 -133
  49. package/src/mcpRuntime.ts +343 -0
  50. package/src/openBridge.ts +69 -0
  51. package/src/openInbox.ts +207 -0
  52. package/src/paths.ts +8 -5
  53. package/src/processTree.ts +63 -0
  54. package/src/reveal.ts +5 -3
  55. package/src/routes.ts +156 -154
  56. package/src/rpc.ts +14 -13
  57. package/src/shared/appUrl.ts +19 -0
  58. package/src/shared/externalOpen.ts +71 -0
  59. package/src/shared/integration.ts +2 -0
  60. package/src/shared/keybindings.ts +8 -5
  61. package/src/shared/mcp.ts +4 -1
  62. package/src/shared/rpc.ts +7 -4
  63. package/src/shared/svn.ts +6 -8
  64. package/src/svnPlanTool.ts +51 -0
  65. package/src/unityBridge.ts +26 -3
  66. package/unity/com.dsh.editor/Editor/DshCodeEditor.cs +99 -203
  67. package/unity/com.dsh.editor/Editor/DshOpenWorker.cs +166 -0
  68. package/unity/com.dsh.editor/Editor/DshOpenWorker.cs.meta +2 -0
  69. package/unity/com.dsh.editor/README.md +16 -25
  70. package/unity/com.dsh.editor/package.json +2 -2
  71. package/src/client/ui/ShortcutSettings.ts +0 -294
@@ -9,7 +9,8 @@
9
9
  */
10
10
  import React from 'react'
11
11
  import { rpc } from '../rpc.js'
12
- import { chordOf, useKeybindingsVersion } from '../keybindings.js'
12
+ import { bindPanelCommand } from '../panelCommands.js'
13
+ import { ariaOf, chordOf, useKeybindingsVersion } from '../keybindings.js'
13
14
  import { stepIndex } from './searchNav.js'
14
15
 
15
16
  /**
@@ -66,19 +67,18 @@ export function QuickOpen(props) {
66
67
  }
67
68
  // Esc 关闭(仅收起浮窗;历史 capture 行为保留,不参与命令派发、不吞键)
68
69
  const onEscape = (e) => {
69
- if (e.key !== 'Escape') return
70
+ if (e.isComposing || e.keyCode === 229 || e.getModifierState?.('AltGraph') || e.key !== 'Escape') return
70
71
  setOpen(false)
71
72
  inputRef.current?.blur?.()
72
73
  }
73
- // 快速打开键位(Ctrl+P)已归官方 shortcuts 机制派发:官方 resolve → 注册表 run →
74
- // 派发 `edrv.command.quickOpen` 事件到此处(历史窗口 capture 键位自检移除,防双开)。
74
+ // 官方与旧版键位统一走会话级命令接收器;挂载时只消费本会话的待打开请求。
75
75
  window.addEventListener('keydown', onEscape, true)
76
- window.addEventListener('edrv.command.quickOpen', openBox)
76
+ const offCommand = bindPanelCommand('quickOpen', { sessionId }, openBox)
77
77
  return () => {
78
78
  window.removeEventListener('keydown', onEscape, true)
79
- window.removeEventListener('edrv.command.quickOpen', openBox)
79
+ offCommand()
80
80
  }
81
- }, [])
81
+ }, [sessionId])
82
82
 
83
83
  // 高亮项滚入浮窗可视区(候选集或高亮变化时;nearest 避免整页跳动)
84
84
  React.useEffect(() => {
@@ -99,7 +99,7 @@ export function QuickOpen(props) {
99
99
  * @param e 输入框键盘事件
100
100
  */
101
101
  const onInputKey = (e) => {
102
- if (e.nativeEvent?.isComposing) return
102
+ if (e.nativeEvent?.isComposing || e.nativeEvent?.keyCode === 229 || e.getModifierState?.('AltGraph')) return
103
103
  if (e.key === 'Escape') { setOpen(false); return }
104
104
  if (!showPop || !list.length) return
105
105
  if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
@@ -135,7 +135,8 @@ export function QuickOpen(props) {
135
135
 
136
136
  return React.createElement('div', { className: 'edrv-search-wrap' },
137
137
  React.createElement('input', {
138
- ref: inputRef, className: 'edrv-search', placeholder: '搜索文件 (' + (chordOf('edrv.quickOpen') ?? 'Ctrl+P') + ')',
138
+ ref: inputRef, className: 'edrv-search', placeholder: '搜索文件' + (chordOf('edrv.quickOpen') ? ' (' + chordOf('edrv.quickOpen') + ')' : ''),
139
+ 'aria-keyshortcuts': ariaOf('edrv.quickOpen'),
139
140
  value: q,
140
141
  role: 'combobox',
141
142
  'aria-expanded': showPop && list.length > 0,
@@ -6,6 +6,7 @@
6
6
  * 与既有 `edrv:` 刷新/主题事件分属不同命名空间,互不干扰)。
7
7
  * 作者 ddj 2026年09月10号
8
8
  */
9
+ import { panelAvailable, runPanelCommand } from '../panelCommands.js'
9
10
  import { hasEditorModel, hasOpenTabs } from '../editorModelState.js'
10
11
  import { activeEditorPath } from '../activePathStore.js'
11
12
  import { isMarkdownPath } from '../markdownPreview.js'
@@ -201,8 +202,8 @@ export const EDITOR_COMMANDS: readonly CommandDef[] = [
201
202
  },
202
203
  {
203
204
  id: 'edrv.quickOpen', label: '快速打开文件', category: '文件', order: 20,
204
- available: alwaysAvailable,
205
- run: () => emit('quickOpen'),
205
+ available: () => panelAvailable('quickOpen'),
206
+ run: () => { runPanelCommand('quickOpen') },
206
207
  },
207
208
  {
208
209
  id: 'edrv.toggleSidebar', label: '切换侧边栏', category: '视图', order: 10,
@@ -211,8 +212,8 @@ export const EDITOR_COMMANDS: readonly CommandDef[] = [
211
212
  },
212
213
  {
213
214
  id: 'edrv.searchInFiles', label: '在工作区中搜索', category: '视图', order: 20,
214
- available: alwaysAvailable,
215
- run: () => emit('searchInFiles'),
215
+ available: () => panelAvailable('searchInFiles'),
216
+ run: () => { runPanelCommand('searchInFiles') },
216
217
  },
217
218
  {
218
219
  id: 'edrv.toggleMarkdownPreview', label: '切换 Markdown 预览', category: '视图', order: 25,
@@ -0,0 +1,128 @@
1
+ /** Correlated editor readiness for external file opens. @author ddj 2026年09月28号 */
2
+ import React from 'react'
3
+ import { finishOpen, hasOpen, peekOpen } from './openReceipt.js'
4
+ import type { EditorRequest } from './openReceipt.js'
5
+
6
+ export interface ReceiptHost {
7
+ sessionId: string
8
+ open(request: EditorRequest): void | Promise<void>
9
+ ready(request: EditorRequest): boolean
10
+ error(request: EditorRequest): string | null
11
+ }
12
+
13
+ /** One mounted editor owns at most one claimed external request. */
14
+ export class OpenMonitor {
15
+ private pending: EditorRequest | undefined
16
+ private frame: number | undefined
17
+ private stopped = false
18
+ private opening = false
19
+ /** @private @author ddj 2026年09月28号 Claim a request arriving after mount. */
20
+ private listener = (): void => { this.claim() }
21
+
22
+ /** @author ddj 2026年09月28号 @param read Read current component state without a stale closure. */
23
+ constructor(private readonly read: () => ReceiptHost) {}
24
+
25
+ /** @public @author ddj 2026年09月28号 Attach the listener and claim a pre-mount request. */
26
+ start(): void {
27
+ window.addEventListener('edrv:open-request', this.listener)
28
+ this.claim()
29
+ }
30
+
31
+ /** @public @author ddj 2026年09月28号 Fail claimed work and cancel the frame on unmount. */
32
+ stop(): void {
33
+ this.stopped = true
34
+ window.removeEventListener('edrv:open-request', this.listener)
35
+ if (this.frame !== undefined) cancelAnimationFrame(this.frame)
36
+ this.frame = undefined
37
+ if (this.pending) finishOpen(this.pending.requestId, false, '编辑器已关闭')
38
+ this.pending = undefined
39
+ }
40
+
41
+ /** @private @author ddj 2026年09月28号 Schedule at most one readiness/queue check. */
42
+ private arm(): void {
43
+ if (!this.stopped && this.frame === undefined) this.frame = requestAnimationFrame(this.check)
44
+ }
45
+
46
+ /**
47
+ * Settle only the request this instance still owns; queued work runs next frame.
48
+ * @private @author ddj 2026年09月28号
49
+ * @param request Claimed request, captured before asynchronous work.
50
+ * @param success Whether actual readiness was confirmed.
51
+ * @param error Failure description.
52
+ */
53
+ private finish(request: EditorRequest, success: boolean, error?: string): void {
54
+ if (this.stopped || this.pending !== request) return
55
+ this.pending = undefined
56
+ this.opening = false
57
+ if (hasOpen(request.requestId)) finishOpen(request.requestId, success, error)
58
+ this.arm()
59
+ }
60
+
61
+ /**
62
+ * Observe startup without letting a late completion affect a new claim.
63
+ * @private @author ddj 2026年09月28号
64
+ * @param request Startup's request identity.
65
+ * @param operation Optional asynchronous editor startup.
66
+ */
67
+ private watchOpen(request: EditorRequest, operation: void | Promise<void>): void {
68
+ if (!operation) return
69
+ this.opening = true
70
+ /** @author ddj 2026年09月28号 Release readiness only for this live claim. */
71
+ const ready = (): void => {
72
+ if (!this.stopped && this.pending === request) this.opening = false
73
+ }
74
+ /** @author ddj 2026年09月28号 @param error Correlated startup rejection. */
75
+ const fail = (error: unknown): void => this.finish(request, false, String(error))
76
+ void Promise.resolve(operation).then(ready, fail)
77
+ }
78
+
79
+ /** @private @author ddj 2026年09月28号 Claim this session; exceptions fail the exact request. */
80
+ private claim(): void {
81
+ if (this.stopped || (this.pending && hasOpen(this.pending.requestId))) return
82
+ this.pending = undefined
83
+ this.opening = false
84
+ try {
85
+ const host = this.read()
86
+ const request = peekOpen(host.sessionId)
87
+ if (!request) return
88
+ this.pending = request
89
+ this.watchOpen(request, host.open(request))
90
+ this.arm()
91
+ } catch (error) {
92
+ if (this.pending) this.finish(this.pending, false, String(error))
93
+ }
94
+ }
95
+
96
+ /** @private @author ddj 2026年09月28号 Check readiness; expired claims and exceptions cannot ACK later work. */
97
+ private check = (): void => {
98
+ this.frame = undefined
99
+ if (this.stopped) return
100
+ const request = this.pending
101
+ if (!request || !hasOpen(request.requestId)) { this.pending = undefined; this.claim(); return }
102
+ try {
103
+ const host = this.read()
104
+ if (request.sessionId !== host.sessionId) { this.finish(request, false, '会话已切换'); return }
105
+ if (this.opening) { this.arm(); return }
106
+ const error = host.error(request)
107
+ if (error || host.ready(request)) { this.finish(request, !error, error ?? undefined); return }
108
+ this.arm()
109
+ } catch (error) { this.finish(request, false, String(error)) }
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Bind a component while reading its latest model state.
115
+ * @author ddj 2026年09月28号
116
+ * @param host Mounted editor actions and readiness checks.
117
+ */
118
+ export function useOpenReceipt(host: ReceiptHost): void {
119
+ const latest = React.useRef(host)
120
+ latest.current = host
121
+ /** @author ddj 2026年09月28号 @returns Listener and animation-frame cleanup. */
122
+ function mount(): () => void {
123
+ const monitor = new OpenMonitor(() => latest.current)
124
+ monitor.start()
125
+ return () => monitor.stop()
126
+ }
127
+ React.useEffect(mount, [host.sessionId])
128
+ }
package/src/compat.ts CHANGED
@@ -132,8 +132,10 @@ export function detectGuards(ctx: Ctx): CompatAdapter[] {
132
132
  ]
133
133
  }
134
134
 
135
- /** 已实测覆盖的最高 DSH 版本(适配矩阵上界,超过则提示,见 buildReport)。 */
136
- const TESTED_DSH_MAX = '0.1.7-alpha.2'
135
+ /** 已实测覆盖的最高 DSH 版本(适配矩阵上界,超过则提示,见 buildReport)。
136
+ * 0.1.7-rc.2 已在官方已签名打包 Desktop 上实机装配(认证路由、同源 RPC、项目 MCP
137
+ * 与私有打开队列均生效);更高版本仍按能力探测运行并提示。 */
138
+ const TESTED_DSH_MAX = '0.1.7-rc.2'
137
139
 
138
140
  /** Config volatile 标记起效的版本线(此前设置走 section 安装,与 volatile 无关)。 */
139
141
  const CONFIG_VOLATILE_MIN = '0.1.7-alpha.1'
@@ -210,7 +212,7 @@ export async function buildReport(
210
212
  guards,
211
213
  adapters,
212
214
  warnings,
213
- devForm: readDevForm(),
215
+ devForm: readDevForm(ctx),
214
216
  dshVersion,
215
217
  }
216
218
  }
@@ -18,6 +18,8 @@
18
18
  * 作者 ddj 2026年09月29号 / 2026年09月21号
19
19
  */
20
20
  import { spawn, type ChildProcess } from 'node:child_process'
21
+ import { childEnv } from '../childEnv.js'
22
+ import { stopTree } from '../processTree.js'
21
23
  import { join } from 'node:path'
22
24
  import { createFrameParser, encodeMessage } from '../lsp/jsonrpc.js'
23
25
  import { sourceBaseOf, sourceKeyOf, sourcePathOf, normalizeSourcePath } from './sourcePath.js'
@@ -288,9 +290,18 @@ export class DapSession {
288
290
 
289
291
  // ---------------------------------------------------------------- 内部实现
290
292
 
291
- /** spawn 适配器进程并接好帧解析。 */
293
+ /**
294
+ * 启动独立适配器进程组并接入协议,清理隐式宿主凭据。
295
+ * @private
296
+ * @author ddj 2026年09月28号
297
+ * @param spec 适配器规格
298
+ * @throws spawn 同步失败
299
+ */
292
300
  private spawnAdapter(spec: DapAdapterSpec): void {
293
- const child = spawn(spec.command, spec.args, { stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true })
301
+ const child = spawn(spec.command, spec.args, {
302
+ stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
303
+ detached: process.platform !== 'win32', env: this.adapterEnv(),
304
+ })
294
305
  this.child = child
295
306
  const parser = createFrameParser()
296
307
  child.stdout?.on('data', (chunk: Buffer) => {
@@ -300,11 +311,16 @@ export class DapSession {
300
311
  log.debug('[dap-adapter] ' + chunk.toString('utf8').trim())
301
312
  this.stderrTail = (this.stderrTail + chunk.toString('utf8')).slice(-STDERR_TAIL_CAP)
302
313
  })
303
- child.on('error', (error) => this.fail('适配器进程错误:' + String(error)))
314
+ child.on('error', (error) => {
315
+ if (this.child === child) this.fail('适配器进程错误:' + String(error))
316
+ })
317
+ child.stdin?.on('error', (error) => {
318
+ if (this.child === child) this.fail('适配器 stdin 错误:' + String(error))
319
+ })
304
320
  child.on('exit', (code) => {
305
- if (this.child === child && this.phase !== 'idle' && this.phase !== 'terminated') {
306
- this.fail('适配器退出(code=' + code + ')' + this.stderrSuffix())
307
- }
321
+ if (this.child !== child) return
322
+ if (this.phase === 'idle' || this.phase === 'terminated') this.killChild()
323
+ else this.fail('适配器退出(code=' + code + ')' + this.stderrSuffix())
308
324
  })
309
325
  }
310
326
 
@@ -529,18 +545,35 @@ export class DapSession {
529
545
  this.setPhase('terminated')
530
546
  }
531
547
 
532
- /** 杀适配器进程树(Windows taskkill /T /F,同 LSP transport 口径)。 */
548
+ /**
549
+ * 清理父环境后合并 launch 配置显式 env,null 表示移除该项。
550
+ * @private
551
+ * @author ddj 2026年09月28号
552
+ * @returns 适配器的环境副本
553
+ */
554
+ private adapterEnv(): NodeJS.ProcessEnv {
555
+ const explicit: NodeJS.ProcessEnv = {}
556
+ const raw = this.config?.raw?.env
557
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
558
+ for (const [key, value] of Object.entries(raw)) {
559
+ if (typeof value === 'string' || value === null) explicit[key] = value ?? undefined
560
+ }
561
+ }
562
+ return childEnv(explicit)
563
+ }
564
+
565
+ /**
566
+ * 解除会话拥有权并终止适配器进程树;不先杀根进程。
567
+ * @private
568
+ * @author ddj 2026年09月28号
569
+ * @description 同时清理所有待处理请求与计时器。
570
+ */
533
571
  private killChild(): void {
534
572
  const child = this.child
535
573
  this.child = null
536
574
  for (const entry of this.pending.values()) { clearTimeout(entry.timer); entry.resolve(null) }
537
575
  this.pending.clear()
538
- if (!child) return
539
- try { child.kill() } catch { /* 忽略 */ }
540
- if (process.platform === 'win32' && child.pid) {
541
- try { spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' }) } catch { /* 忽略 */ }
542
- }
543
- setTimeout(() => { try { child.kill('SIGKILL') } catch { /* 忽略 */ } }, 1500).unref?.()
576
+ if (child) stopTree(child, (line) => log.warn('[dap] ' + line))
544
577
  }
545
578
 
546
579
  /** 相位切换(running→running 等幂等跳过)。 */