dsh-vscode-mode 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -111
- package/assets/shell/dsh-open.cs +65 -149
- package/assets/shell/dsh-open.mjs +241 -0
- package/assets/shell/dsh-open.ps1 +28 -47
- package/assets/shell/dsh-open.sh +25 -66
- package/lib/client.js +13362 -11806
- package/lib/client.js.map +1 -1
- package/lib/index.js +4110 -2507
- package/lib/index.js.map +1 -1
- package/package.json +9 -8
- package/skills/dsh-vscodemode-mcp/SKILL.md +94 -132
- package/src/childEnv.ts +19 -0
- package/src/client/activePathStore.ts +28 -0
- package/src/client/addToConversation.ts +4 -2
- package/src/client/ai/inlineProvider.ts +1 -8
- package/src/client/commandBridge.ts +14 -42
- package/src/client/externalOpen.ts +40 -19
- package/src/client/index.ts +101 -18
- package/src/client/keybindings.ts +36 -73
- package/src/client/monaco/loader.ts +3 -1
- package/src/client/openFlow.ts +36 -28
- package/src/client/openReceipt.ts +64 -0
- package/src/client/panelCommands.ts +101 -0
- package/src/client/pdf/pdfLoader.ts +4 -2
- package/src/client/receiptState.ts +120 -0
- package/src/client/rpc.ts +30 -4
- package/src/client/shortcutRecorder.ts +161 -0
- package/src/client/shortcutsOfficial.ts +460 -0
- package/src/client/sidebar/panels/RulesPanel.ts +15 -11
- package/src/client/sidebarBridge.ts +7 -0
- package/src/client/ui/DiffBadge.ts +2 -1
- package/src/client/ui/EditorView.ts +246 -137
- package/src/client/ui/McpSettings.ts +10 -8
- package/src/client/ui/OfficialSideTab.ts +5 -2
- package/src/client/ui/QuickOpen.ts +16 -16
- package/src/client/ui/ShortcutSettings.ts +274 -0
- package/src/client/ui/commandCatalog.ts +39 -43
- package/src/client/useOpenReceipt.ts +128 -0
- package/src/compat.ts +5 -3
- package/src/dap/manager.ts +46 -13
- package/src/devForm.ts +252 -92
- package/src/externalHandoff.ts +93 -52
- package/src/fileOpenSettings.ts +4 -24
- package/src/hostImport.ts +19 -0
- package/src/httpGuard.ts +101 -0
- package/src/index.ts +106 -97
- package/src/integrate.ts +56 -50
- package/src/lsp/transport.ts +84 -127
- package/src/mcp.ts +58 -9
- package/src/mcpIsolation.ts +211 -102
- package/src/mcpProject.ts +220 -133
- package/src/mcpRuntime.ts +343 -0
- package/src/openBridge.ts +69 -0
- package/src/openInbox.ts +207 -0
- package/src/paths.ts +8 -5
- package/src/processTree.ts +63 -0
- package/src/reveal.ts +5 -3
- package/src/routes.ts +156 -154
- package/src/rpc.ts +14 -13
- package/src/rules.ts +100 -44
- package/src/shared/appUrl.ts +19 -0
- package/src/shared/externalOpen.ts +71 -0
- package/src/shared/integration.ts +2 -0
- package/src/shared/keybindings.ts +256 -0
- package/src/shared/mcp.ts +4 -1
- package/src/shared/rpc.ts +7 -4
- package/src/shared/rules.ts +9 -1
- package/src/shared/svn.ts +6 -8
- package/src/svnPlanTool.ts +51 -0
- package/src/unityBridge.ts +26 -3
- package/unity/com.dsh.editor/Editor/DshCodeEditor.cs +99 -203
- package/unity/com.dsh.editor/Editor/DshOpenWorker.cs +166 -0
- package/unity/com.dsh.editor/Editor/DshOpenWorker.cs.meta +2 -0
- package/unity/com.dsh.editor/README.md +16 -25
- package/unity/com.dsh.editor/package.json +2 -2
- package/src/client/ui/KeybindingsPanel.ts +0 -190
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* `|` 连接多个候选(任一命中即触发),如 `Alt+ArrowLeft|Ctrl+Alt+-`。
|
|
6
6
|
* 约束:本模块被 host 与 client 两半共用,**禁止 import client 侧模块**(React/浏览器 API)。
|
|
7
7
|
* 与 client/ui/commandCatalog 的一致性由 tests/commands.test.ts 断言兜底。
|
|
8
|
+
* 官方机制(2026-10):SHORTCUT_PROFILES 描述各命令在官方 ctx.shortcuts 的 profile 默认键位
|
|
9
|
+
* (由 client 注册官方服务时使用);chordToBinding/bindingToChord 负责插件弦与官方绑定互转。
|
|
8
10
|
* 作者 ddj 2026年08月26号 / 2026年09月10号
|
|
9
11
|
*/
|
|
10
12
|
|
|
@@ -66,3 +68,257 @@ export function normalizeKeybindings(raw: unknown): Record<string, string> {
|
|
|
66
68
|
}
|
|
67
69
|
return out
|
|
68
70
|
}
|
|
71
|
+
|
|
72
|
+
//#region 官方 shortcuts 机制映射层(纯数据/纯函数,禁 import client 侧与官方包)
|
|
73
|
+
|
|
74
|
+
/** 官方 shortcuts 服务的修饰符标记('primary' = 桌面端按平台展开为 Ctrl/Cmd,web 同语义)。 */
|
|
75
|
+
export type OfficialModifier = 'primary' | 'control' | 'alt' | 'shift' | 'meta'
|
|
76
|
+
|
|
77
|
+
/** 官方绑定形状(物理 code + 修饰符;与 @deepseek-ai/dsh-client-shortcuts 绑定契约同构,鸭子类型)。 */
|
|
78
|
+
export interface OfficialBinding {
|
|
79
|
+
code: string
|
|
80
|
+
modifiers: OfficialModifier[]
|
|
81
|
+
secondCode?: string
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** 官方 profile 键(desktop|web × macos|windows|linux)。 */
|
|
85
|
+
export type ShortcutProfileKey =
|
|
86
|
+
| 'desktop:macos' | 'desktop:windows' | 'desktop:linux'
|
|
87
|
+
| 'web:macos' | 'web:windows' | 'web:linux'
|
|
88
|
+
|
|
89
|
+
/** 命令 id → 各 profile 官方默认键位(缺 profile = 该端不绑定)。 */
|
|
90
|
+
export type ShortcutProfiles = Partial<Record<ShortcutProfileKey, OfficialBinding>>
|
|
91
|
+
|
|
92
|
+
/** 显示主键 → 官方物理 code 对照(官方 code 白名单:Key[A-Z]|Digit[0-9]|F1-24|具名键)。 */
|
|
93
|
+
const DISPLAY_KEY_TO_CODE: Record<string, string> = {
|
|
94
|
+
'-': 'Minus', '=': 'Equal', '[': 'BracketLeft', ']': 'BracketRight',
|
|
95
|
+
';': 'Semicolon', "'": 'Quote', ',': 'Comma', '.': 'Period',
|
|
96
|
+
'/': 'Slash', '\\': 'Backslash', '`': 'Backquote',
|
|
97
|
+
Enter: 'Enter', Tab: 'Tab', Backspace: 'Backspace', Delete: 'Delete',
|
|
98
|
+
Escape: 'Escape', Space: 'Space',
|
|
99
|
+
ArrowUp: 'ArrowUp', ArrowDown: 'ArrowDown', ArrowLeft: 'ArrowLeft', ArrowRight: 'ArrowRight',
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** 官方具名 code(非 Key/Digit/F 前缀且可回显的部分)。 */
|
|
103
|
+
const NAMED_CODES = ['Enter', 'Tab', 'Backspace', 'Delete', 'Escape', 'Space',
|
|
104
|
+
'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'] as const
|
|
105
|
+
|
|
106
|
+
/** 官方 code → 显示主键(符号键反向对照)。 */
|
|
107
|
+
const CODE_TO_DISPLAY_KEY: Record<string, string> = {
|
|
108
|
+
Minus: '-', Equal: '=', BracketLeft: '[', BracketRight: ']',
|
|
109
|
+
Semicolon: ';', Quote: "'", Comma: ',', Period: '.',
|
|
110
|
+
Slash: '/', Backslash: '\\', Backquote: '`',
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** 显示主键 → 官方 code(PageUp/PageDown/Home/End/Insert 等官方不支持 → null)。 */
|
|
114
|
+
function keyToCode(key: string): string | null {
|
|
115
|
+
if (Object.hasOwn(DISPLAY_KEY_TO_CODE, key)) return DISPLAY_KEY_TO_CODE[key]
|
|
116
|
+
if (key.length === 1 && key >= 'a' && key <= 'z') return 'Key' + key.toUpperCase()
|
|
117
|
+
if (key.length === 1 && key >= 'A' && key <= 'Z') return 'Key' + key
|
|
118
|
+
if (key.length === 1 && key >= '0' && key <= '9') return 'Digit' + key
|
|
119
|
+
if (/^F([1-9]|1[0-9]|2[0-4])$/u.test(key)) return key
|
|
120
|
+
return null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 官方 code → 显示主键(不可回显 → null)。 */
|
|
124
|
+
function codeToKey(code: string): string | null {
|
|
125
|
+
if (Object.hasOwn(CODE_TO_DISPLAY_KEY, code)) return CODE_TO_DISPLAY_KEY[code]
|
|
126
|
+
if (/^Key[A-Z]$/u.test(code)) return code.slice(3)
|
|
127
|
+
if (/^Digit[0-9]$/u.test(code)) return code.slice(5)
|
|
128
|
+
if (/^F([1-9]|1[0-9]|2[0-4])$/u.test(code)) return code
|
|
129
|
+
if ((NAMED_CODES as readonly string[]).includes(code)) return code
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** 插件弦的修饰符 token → 官方修饰符(Ctrl 与 Cmd 同为 primary,与 matchEvent 的互认语义一致)。 */
|
|
134
|
+
const CHORD_MODIFIER_TOKENS: Record<string, OfficialModifier> = {
|
|
135
|
+
ctrl: 'primary', control: 'primary', cmd: 'primary', meta: 'primary',
|
|
136
|
+
shift: 'shift', alt: 'alt',
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 插件键位弦(单候选)→ 官方绑定。
|
|
141
|
+
* 不可表示的主键(PageUp/PageDown/Home/End/Insert 等)返回 null,调用方跳过。
|
|
142
|
+
* @author ddj 2026年10月
|
|
143
|
+
* @param chord 键位弦(如 `Ctrl+Alt+-` / `F9` / `Alt+ArrowLeft`)
|
|
144
|
+
* @returns 官方绑定或 null
|
|
145
|
+
*/
|
|
146
|
+
export function chordToBinding(chord: string): OfficialBinding | null {
|
|
147
|
+
const parts = String(chord ?? '').split('+').map((part) => part.trim()).filter(Boolean)
|
|
148
|
+
if (!parts.length) return null
|
|
149
|
+
const modifiers = new Set<OfficialModifier>()
|
|
150
|
+
let key = ''
|
|
151
|
+
for (const part of parts) {
|
|
152
|
+
const modifier = CHORD_MODIFIER_TOKENS[part.toLowerCase()]
|
|
153
|
+
if (modifier) {
|
|
154
|
+
modifiers.add(modifier)
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
if (key) return null
|
|
158
|
+
key = part
|
|
159
|
+
}
|
|
160
|
+
if (!key) return null
|
|
161
|
+
if (key === ' ') key = 'Space'
|
|
162
|
+
else if (key.length === 1 && key >= 'a' && key <= 'z') key = key.toUpperCase()
|
|
163
|
+
const code = keyToCode(key)
|
|
164
|
+
return code === null ? null : { code, modifiers: [...modifiers] }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 官方绑定 → 可读键位弦,保留规范化后的 Control/Meta 物理区别。
|
|
169
|
+
* 官方目录展示优先使用官方 keys;此函数仅作为缺少展示字段时的回落。
|
|
170
|
+
* @author ddj 2026年09月28号
|
|
171
|
+
* @param binding 官方绑定
|
|
172
|
+
* @returns 插件弦(不可表示 → null)
|
|
173
|
+
*/
|
|
174
|
+
export function bindingToChord(binding: OfficialBinding): string | null {
|
|
175
|
+
if (!binding || typeof binding !== 'object') return null
|
|
176
|
+
const key = codeToKey(binding.code)
|
|
177
|
+
if (key === null) return null
|
|
178
|
+
let suffix = ''
|
|
179
|
+
if (binding.secondCode !== undefined) {
|
|
180
|
+
const secondKey = codeToKey(binding.secondCode)
|
|
181
|
+
if (secondKey === null) return null
|
|
182
|
+
suffix = '+' + secondKey
|
|
183
|
+
}
|
|
184
|
+
const parts: string[] = []
|
|
185
|
+
if (binding.modifiers.includes('primary') || binding.modifiers.includes('control')) parts.push('Ctrl')
|
|
186
|
+
if (binding.modifiers.includes('meta')) parts.push('Meta')
|
|
187
|
+
if (binding.modifiers.includes('shift')) parts.push('Shift')
|
|
188
|
+
if (binding.modifiers.includes('alt')) parts.push('Alt')
|
|
189
|
+
return [...parts, key + suffix].join('+')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** 官方绑定字面量构造(表格可读性辅助)。 */
|
|
193
|
+
function bind(code: string, modifiers: OfficialModifier[]): OfficialBinding {
|
|
194
|
+
return { code, modifiers }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 命令 id → 官方 profile 默认键位(与 KEYBINDING_DEFAULTS 一一对应;client 注册官方服务的数据源)。
|
|
199
|
+
* 取值约束(对齐官方 bindingIssue/isWebBindingAllowed 校验,错报会在注册时抛错):
|
|
200
|
+
* - desktop:macos/windows:官方不做保留校验,用 VS Code 同款 chord;
|
|
201
|
+
* - desktop:linux / web:linux:Arrow*、裸 F 键、全 Shift、Ctrl+V/C/X/Z/Y/Q/H/A、Meta 均为保留键 → 不声明;
|
|
202
|
+
* - web:macos/windows:单修饰符组合不在白名单 → 用 primary+alt / primary+shift 变体(官方 ui-layout 同款);
|
|
203
|
+
* - Ctrl+PageDown/PageUp 候选废弃(官方 code 白名单无 PageUp/PageDown);
|
|
204
|
+
* - edrv.toggleSidebar 留空:Ctrl+B 与官方 sidebar.left.toggle(Mod+B)冲突,注册会抛错且冲突键互相阻断。
|
|
205
|
+
* @author ddj 2026年10月
|
|
206
|
+
*/
|
|
207
|
+
export const SHORTCUT_PROFILES: Record<string, ShortcutProfiles> = {
|
|
208
|
+
'edrv.toggleSidebar': {},
|
|
209
|
+
'edrv.save': {
|
|
210
|
+
'desktop:macos': bind('KeyS', ['primary']),
|
|
211
|
+
'desktop:windows': bind('KeyS', ['primary']),
|
|
212
|
+
'desktop:linux': bind('KeyS', ['primary']),
|
|
213
|
+
},
|
|
214
|
+
// 官方 workspace.files 已占用桌面 primary+P 和 Web primary+alt+P;插件 QuickOpen 使用非冲突组合。
|
|
215
|
+
'edrv.quickOpen': {
|
|
216
|
+
'desktop:macos': bind('KeyP', ['primary', 'alt']),
|
|
217
|
+
'desktop:windows': bind('KeyP', ['primary', 'alt']),
|
|
218
|
+
'desktop:linux': bind('KeyP', ['primary', 'alt']),
|
|
219
|
+
'web:macos': bind('KeyP', ['primary', 'alt', 'shift']),
|
|
220
|
+
'web:windows': bind('KeyP', ['primary', 'alt', 'shift']),
|
|
221
|
+
},
|
|
222
|
+
// 官方 session.fork 占用两种 Web primary+shift+F;加 Alt 避开联合目录冲突。
|
|
223
|
+
'edrv.searchInFiles': {
|
|
224
|
+
'desktop:macos': bind('KeyF', ['primary', 'shift']),
|
|
225
|
+
'desktop:windows': bind('KeyF', ['primary', 'shift']),
|
|
226
|
+
'desktop:linux': bind('KeyF', ['primary', 'shift']),
|
|
227
|
+
'web:macos': bind('KeyF', ['primary', 'alt', 'shift']),
|
|
228
|
+
'web:windows': bind('KeyF', ['primary', 'alt', 'shift']),
|
|
229
|
+
},
|
|
230
|
+
// Ctrl+Shift+V(primary+KeyV)在 linux 为保留键(官方 bindingIssue 不豁免 shift),desktop:linux 不声明
|
|
231
|
+
'edrv.toggleMarkdownPreview': {
|
|
232
|
+
'desktop:macos': bind('KeyV', ['primary', 'shift']),
|
|
233
|
+
'desktop:windows': bind('KeyV', ['primary', 'shift']),
|
|
234
|
+
'web:macos': bind('KeyV', ['primary', 'shift']),
|
|
235
|
+
'web:windows': bind('KeyV', ['primary', 'shift']),
|
|
236
|
+
},
|
|
237
|
+
// 导航历史:主候选 Alt+Arrow 在 linux/web 为保留或白名单外;web 取第二候选(Ctrl+Alt+- / Ctrl+Shift+-)
|
|
238
|
+
'edrv.navigateBack': {
|
|
239
|
+
'desktop:macos': bind('ArrowLeft', ['alt']),
|
|
240
|
+
'desktop:windows': bind('ArrowLeft', ['alt']),
|
|
241
|
+
'web:macos': bind('Minus', ['primary', 'alt']),
|
|
242
|
+
'web:windows': bind('Minus', ['primary', 'alt']),
|
|
243
|
+
},
|
|
244
|
+
'edrv.navigateForward': {
|
|
245
|
+
'desktop:macos': bind('ArrowRight', ['alt']),
|
|
246
|
+
'desktop:windows': bind('ArrowRight', ['alt']),
|
|
247
|
+
'web:macos': bind('Minus', ['primary', 'shift']),
|
|
248
|
+
'web:windows': bind('Minus', ['primary', 'shift']),
|
|
249
|
+
},
|
|
250
|
+
// 页签循环:Ctrl+PageDown/PageUp 候选官方不可表示,仅保留 Ctrl+Alt+Arrow 主候选
|
|
251
|
+
'edrv.nextTab': {
|
|
252
|
+
'desktop:macos': bind('ArrowRight', ['primary', 'alt']),
|
|
253
|
+
'desktop:windows': bind('ArrowRight', ['primary', 'alt']),
|
|
254
|
+
'web:macos': bind('ArrowRight', ['primary', 'alt']),
|
|
255
|
+
'web:windows': bind('ArrowRight', ['primary', 'alt']),
|
|
256
|
+
},
|
|
257
|
+
'edrv.prevTab': {
|
|
258
|
+
'desktop:macos': bind('ArrowLeft', ['primary', 'alt']),
|
|
259
|
+
'desktop:windows': bind('ArrowLeft', ['primary', 'alt']),
|
|
260
|
+
'web:macos': bind('ArrowLeft', ['primary', 'alt']),
|
|
261
|
+
'web:windows': bind('ArrowLeft', ['primary', 'alt']),
|
|
262
|
+
},
|
|
263
|
+
'edrv.goToLine': {
|
|
264
|
+
'desktop:macos': bind('KeyG', ['primary']),
|
|
265
|
+
'desktop:windows': bind('KeyG', ['primary']),
|
|
266
|
+
'desktop:linux': bind('KeyG', ['primary']),
|
|
267
|
+
},
|
|
268
|
+
'edrv.showCommands': {
|
|
269
|
+
'desktop:macos': bind('KeyP', ['primary', 'shift']),
|
|
270
|
+
'desktop:windows': bind('KeyP', ['primary', 'shift']),
|
|
271
|
+
'desktop:linux': bind('KeyP', ['primary', 'shift']),
|
|
272
|
+
'web:macos': bind('KeyP', ['primary', 'shift']),
|
|
273
|
+
'web:windows': bind('KeyP', ['primary', 'shift']),
|
|
274
|
+
},
|
|
275
|
+
'edrv.nextEditorRow': {
|
|
276
|
+
'desktop:macos': bind('ArrowDown', ['primary', 'alt']),
|
|
277
|
+
'desktop:windows': bind('ArrowDown', ['primary', 'alt']),
|
|
278
|
+
'web:macos': bind('ArrowDown', ['primary', 'alt']),
|
|
279
|
+
'web:windows': bind('ArrowDown', ['primary', 'alt']),
|
|
280
|
+
},
|
|
281
|
+
'edrv.prevEditorRow': {
|
|
282
|
+
'desktop:macos': bind('ArrowUp', ['primary', 'alt']),
|
|
283
|
+
'desktop:windows': bind('ArrowUp', ['primary', 'alt']),
|
|
284
|
+
'web:macos': bind('ArrowUp', ['primary', 'alt']),
|
|
285
|
+
'web:windows': bind('ArrowUp', ['primary', 'alt']),
|
|
286
|
+
},
|
|
287
|
+
'edrv.addSelectionRef': {
|
|
288
|
+
'desktop:macos': bind('KeyU', ['primary']),
|
|
289
|
+
'desktop:windows': bind('KeyU', ['primary']),
|
|
290
|
+
'desktop:linux': bind('KeyU', ['primary']),
|
|
291
|
+
},
|
|
292
|
+
'edrv.closeTab': {
|
|
293
|
+
'desktop:macos': bind('F4', ['primary']),
|
|
294
|
+
'desktop:windows': bind('F4', ['primary']),
|
|
295
|
+
'desktop:linux': bind('F4', ['primary']),
|
|
296
|
+
},
|
|
297
|
+
// 调试 F 键:linux 判 modifier-required(裸 F/全 Shift),仅 desktop:macos/windows
|
|
298
|
+
'edrv.debugToggleBreakpoint': {
|
|
299
|
+
'desktop:macos': bind('F9', []),
|
|
300
|
+
'desktop:windows': bind('F9', []),
|
|
301
|
+
},
|
|
302
|
+
'edrv.debugStartContinue': {
|
|
303
|
+
'desktop:macos': bind('F5', []),
|
|
304
|
+
'desktop:windows': bind('F5', []),
|
|
305
|
+
},
|
|
306
|
+
'edrv.debugStepOver': {
|
|
307
|
+
'desktop:macos': bind('F10', []),
|
|
308
|
+
'desktop:windows': bind('F10', []),
|
|
309
|
+
},
|
|
310
|
+
'edrv.debugStepInto': {
|
|
311
|
+
'desktop:macos': bind('F11', []),
|
|
312
|
+
'desktop:windows': bind('F11', []),
|
|
313
|
+
},
|
|
314
|
+
'edrv.debugStepOut': {
|
|
315
|
+
'desktop:macos': bind('F11', ['shift']),
|
|
316
|
+
'desktop:windows': bind('F11', ['shift']),
|
|
317
|
+
},
|
|
318
|
+
'edrv.debugStop': {
|
|
319
|
+
'desktop:macos': bind('F5', ['shift']),
|
|
320
|
+
'desktop:windows': bind('F5', ['shift']),
|
|
321
|
+
},
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
//#endregion
|
package/src/shared/mcp.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** dsh-vscode-mode MCP 管理共享数据契约。作者 ddj 2026年08月22号 */
|
|
2
2
|
|
|
3
3
|
export type MpcTransport = 'stdio' | 'streamable-http'
|
|
4
|
-
export type MpcStatus = 'connected' | 'connecting' | 'error' | 'disabled'
|
|
4
|
+
export type MpcStatus = 'connected' | 'connecting' | 'error' | 'disabled' | 'configured' | 'unverified'
|
|
5
5
|
|
|
6
6
|
export interface MpcTool {
|
|
7
7
|
name: string
|
|
@@ -18,6 +18,7 @@ export interface MpcConfig {
|
|
|
18
18
|
url?: string
|
|
19
19
|
headers?: Record<string, string>
|
|
20
20
|
toolCallTimeoutMs?: number
|
|
21
|
+
maxInstructionBytes?: number
|
|
21
22
|
failOnStartupError?: boolean
|
|
22
23
|
reconnect?: Record<string, unknown>
|
|
23
24
|
}
|
|
@@ -29,6 +30,8 @@ export interface MpcServer {
|
|
|
29
30
|
transport: MpcTransport
|
|
30
31
|
config: MpcConfig
|
|
31
32
|
status: MpcStatus
|
|
33
|
+
/** Mounted official plugin fibers, not verified live connections. */
|
|
34
|
+
instanceCount?: number
|
|
32
35
|
toolCount: number
|
|
33
36
|
tools: MpcTool[]
|
|
34
37
|
error?: string
|
package/src/shared/rpc.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type { AiConfigPatch, AiConfigView, AiDirectoryView, AiInlineRequest, AiI
|
|
|
22
22
|
import type { SvnAction, SvnAiPlan, SvnChangeEntry, SvnConflictArtifact, SvnDiffRevResult, SvnIgnoreItem, SvnLogEntry, SvnRemoteOutdatedEntry, SvnStatusPayload, SvnSumEntry, SvnUpdatePoll, SvnUpdateResult, SvnUpdateState } from './svn.js'
|
|
23
23
|
import type { DapAction, DapAdapterInfo, DapBreakpointAck, DapBreakpointInput, DapConfigSnippet, DapConfigSource, DapDebugConfig, DapFrameView, DapPhase, DapPollResult, DapProcessInfo, DapScopeView, DapVariableView } from './dap.js'
|
|
24
24
|
import { DAP_ACTIONS } from './dap.js'
|
|
25
|
+
import { appUrl } from './appUrl.js'
|
|
25
26
|
import type { LoggerLevel } from './logger.js'
|
|
26
27
|
|
|
27
28
|
/** webServer 精确路由。 */
|
|
@@ -133,7 +134,7 @@ export async function readBinaryPreview(
|
|
|
133
134
|
*/
|
|
134
135
|
async function tryBinaryRead(args: RpcRequestMap['edrv.readBinary']): Promise<PreviewRead | null> {
|
|
135
136
|
try {
|
|
136
|
-
const res = await fetch(RPC_PATH, {
|
|
137
|
+
const res = await fetch(appUrl(RPC_PATH), {
|
|
137
138
|
method: 'POST',
|
|
138
139
|
headers: { 'content-type': 'application/json' },
|
|
139
140
|
body: JSON.stringify({ method: 'edrv.readBinary', args }),
|
|
@@ -382,7 +383,8 @@ export interface RpcRequestMap {
|
|
|
382
383
|
'edrv.unity.install': { path: string }
|
|
383
384
|
'edrv.externalStat': { path: string }
|
|
384
385
|
'edrv.external.handoff': { paths: string[]; line?: number; column?: number }
|
|
385
|
-
'edrv.external.pending': {}
|
|
386
|
+
'edrv.external.pending': { clientId: string; busy?: boolean }
|
|
387
|
+
'edrv.external.ack': { token: string; clientId: string; leaseId: string; success: boolean; error?: string }
|
|
386
388
|
'edrv.external.pendingState': { token: string; take?: boolean }
|
|
387
389
|
'vscode.devFormGet': {}
|
|
388
390
|
'vscode.devFormSet': { enabled: boolean; path?: string }
|
|
@@ -535,8 +537,9 @@ export interface RpcOkMap {
|
|
|
535
537
|
'edrv.unity.install': { project: UnityProjectEntry }
|
|
536
538
|
'edrv.externalStat': { kind: 'file' | 'directory' | 'missing' }
|
|
537
539
|
'edrv.external.handoff': { clients: number; token: string }
|
|
538
|
-
'edrv.external.pending': { open: { paths: string[]; line?: number; column?: number } | null }
|
|
539
|
-
'edrv.external.
|
|
540
|
+
'edrv.external.pending': { open: { paths: string[]; line?: number; column?: number; token: string; leaseId: string } | null }
|
|
541
|
+
'edrv.external.ack': { accepted: boolean }
|
|
542
|
+
'edrv.external.pendingState': { delivered: boolean; completed?: boolean; error?: string }
|
|
540
543
|
'vscode.devFormGet': { devForm: DevFormInfo }
|
|
541
544
|
'vscode.devFormSet': { devForm: DevFormInfo; restart: boolean }
|
|
542
545
|
'compat': { report: CompatReport }
|
package/src/shared/rules.ts
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
* 作者 ddj 2026年09月03号
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/** 用户规则来源:当前 DSH_HOME 或系统用户目录的旧版 .dsh。 */
|
|
8
|
+
export type UserRuleOrigin = 'current' | 'legacy'
|
|
9
|
+
|
|
10
|
+
/** 规则作用域:用户规则(DSH_HOME/rules + ~/.dsh/rules)或项目规则(<工作区>/.dsh/rules)。 */
|
|
8
11
|
export type RuleScope = 'user' | 'project'
|
|
9
12
|
|
|
10
13
|
/** 规则生效类型(由 frontmatter 推导):总是 / 自动(globs 命中时)/ 手动(仅索引)。 */
|
|
@@ -13,6 +16,8 @@ export type RuleType = 'always' | 'auto' | 'manual'
|
|
|
13
16
|
/** 一条 .mdc 规则的元信息(列表行展示 + 注入语义所需的最小集)。 */
|
|
14
17
|
export interface RuleInfo {
|
|
15
18
|
scope: RuleScope
|
|
19
|
+
/** 用户规则的实际来源;项目规则不设置。 */
|
|
20
|
+
origin?: UserRuleOrigin
|
|
16
21
|
/** 文件名(含 .mdc 后缀,不含路径)。 */
|
|
17
22
|
file: string
|
|
18
23
|
/** 绝对路径(host 解析,供展示与按需读取)。 */
|
|
@@ -48,6 +53,8 @@ export interface RuleSaveInput {
|
|
|
48
53
|
scope: RuleScope
|
|
49
54
|
/** project 必填:目标工作区绝对路径(须为 DSH 已注册 workspace)。 */
|
|
50
55
|
workspacePath?: string
|
|
56
|
+
/** 缺省 current;仅用户规则可指定 legacy。 */
|
|
57
|
+
origin?: UserRuleOrigin
|
|
51
58
|
file: string
|
|
52
59
|
content: string
|
|
53
60
|
}
|
|
@@ -56,5 +63,6 @@ export interface RuleSaveInput {
|
|
|
56
63
|
export interface RuleRefInput {
|
|
57
64
|
scope: RuleScope
|
|
58
65
|
workspacePath?: string
|
|
66
|
+
origin?: UserRuleOrigin
|
|
59
67
|
file: string
|
|
60
68
|
}
|
package/src/shared/svn.ts
CHANGED
|
@@ -799,18 +799,15 @@ export function ignoreItemsOf(paths: readonly string[]): SvnIgnoreItem[] {
|
|
|
799
799
|
return [...byDir.entries()].map(([dir, names]) => ({ dir, names }))
|
|
800
800
|
}
|
|
801
801
|
|
|
802
|
-
/** 深度分析投递口地址(host RPC 本地免鉴权;agent 用 shell 直接 POST)。 */
|
|
803
|
-
const AGENT_SUBMIT_URL = 'http://127.0.0.1:3080/edrv/rpc'
|
|
804
|
-
|
|
805
802
|
/**
|
|
806
803
|
* 会话 agent 深度分析任务文本(混合通道 02-deep-session-prompt 修正版)。
|
|
807
804
|
* 在**独立新会话**草稿箱填入后由用户发送;agent 自跑只读 `svn status` 取清单
|
|
808
805
|
* (prompt 定长瘦身,不随变更数膨胀),借 codegraph/读文件/sub agent 深度分析,
|
|
809
|
-
*
|
|
806
|
+
* 产出方案经当前会话的 svn_plan_submit 工具投递回面板收件箱,确认与执行仍全部
|
|
810
807
|
* 走面板(agent 禁写、只读放行)。
|
|
811
|
-
* @author ddj 2026年09月
|
|
808
|
+
* @author ddj 2026年09月28号
|
|
812
809
|
* @param wcRoot 工作副本根(取数指引内嵌)
|
|
813
|
-
* @param sessionId
|
|
810
|
+
* @param sessionId 分析会话 id(仅作说明,不参与工具授权)
|
|
814
811
|
* @returns 任务文本(定长 ~1KB:取数指引/分析要求/投递命令/硬约束)
|
|
815
812
|
*/
|
|
816
813
|
export function buildAgentPrompt(wcRoot: string, sessionId: string): string {
|
|
@@ -838,8 +835,9 @@ export function buildAgentPrompt(wcRoot: string, sessionId: string): string {
|
|
|
838
835
|
'3. 组名用简短英文、不以 - 开头;',
|
|
839
836
|
'4. 只使用 `svn status` 实际返回的路径,绝不虚构或改写路径。',
|
|
840
837
|
'',
|
|
841
|
-
'
|
|
842
|
-
'
|
|
838
|
+
'分析完成后,调用 svn_plan_submit 工具,把方案 JSON 作为 plan 参数投递回面板。',
|
|
839
|
+
'工具只接收 { "plan": <PLAN_JSON> },自动使用当前执行会话;不要传入其他 sessionId。',
|
|
840
|
+
'当前分析会话:' + sessionId + '。工具仅保存待审方案,不执行方案中的文件操作。',
|
|
843
841
|
'',
|
|
844
842
|
'投递成功会返回 { ok, accepted, dropped };随后我会在面板弹窗中确认执行,你无需再做任何操作。',
|
|
845
843
|
].join('\n')
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Session-bound SVN analysis result delivery. @author ddj 2026年09月28号 */
|
|
2
|
+
import { loadHostModule } from './hostImport.js'
|
|
3
|
+
import type { Ctx } from './store.js'
|
|
4
|
+
import type { RpcHandler, RpcResult } from './shared/rpc.js'
|
|
5
|
+
|
|
6
|
+
interface PlanArgs { plan: unknown }
|
|
7
|
+
interface PlanExec { agent?: { session?: { id?: string } }; signal?: AbortSignal }
|
|
8
|
+
type Submit = RpcHandler<'svn.aiPlanSubmit'>
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build a result-only tool that derives ownership from the executing session.
|
|
12
|
+
* It cannot revert, commit, ignore, or select another session.
|
|
13
|
+
* @author ddj 2026年09月28号
|
|
14
|
+
* @param submit Existing validated plan inbox handler.
|
|
15
|
+
* @returns Definition options for the running Host's defineTool API.
|
|
16
|
+
*/
|
|
17
|
+
export function planToolOptions(submit: Submit) {
|
|
18
|
+
/** @author ddj 2026年09月28号 @param args Model plan. @param exec Owning execution context. @returns Validated inbox result. */
|
|
19
|
+
async function execute(args: PlanArgs, exec: PlanExec): Promise<RpcResult<'svn.aiPlanSubmit'>> {
|
|
20
|
+
const sessionId = exec.agent?.session?.id
|
|
21
|
+
if (!sessionId) return { ok: false, error: 'SVN 方案投递需要所属会话' }
|
|
22
|
+
if (exec.signal?.aborted) return { ok: false, error: 'SVN 方案投递已取消' }
|
|
23
|
+
return submit({ sessionId, plan: args.plan })
|
|
24
|
+
}
|
|
25
|
+
/** @author ddj 2026年09月28号 @param _args Request. @param result Inbox result. @returns Model-visible result. */
|
|
26
|
+
function render(_args: PlanArgs, result: RpcResult<'svn.aiPlanSubmit'>) {
|
|
27
|
+
return [{ type: 'text', text: JSON.stringify(result) }]
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
name: 'svn_plan_submit',
|
|
31
|
+
description: 'Submit an SVN change-analysis plan to the current workspace review inbox. This only records the proposal; the user must confirm changes in VSCodeMode. It does not modify or commit files.',
|
|
32
|
+
parameters: { plan: { type: 'json', required: true, description: 'Analysis proposal with groups, reverts and ignores arrays, using paths from the current SVN working-copy status.' } },
|
|
33
|
+
output: { schema: { type: 'json' }, render },
|
|
34
|
+
isConcurrencySafe: false,
|
|
35
|
+
execute,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Register the SVN submission tool using the running Host API and lifecycle owner.
|
|
41
|
+
* @author ddj 2026年09月28号
|
|
42
|
+
* @param ctx Host plugin context owning the registration.
|
|
43
|
+
* @param submit Validated SVN inbox handler.
|
|
44
|
+
*/
|
|
45
|
+
export async function installPlanTool(ctx: Ctx, submit: Submit): Promise<void> {
|
|
46
|
+
const module = await loadHostModule('@deepseek-ai/dsh-tools') as { defineTool: (options: unknown) => unknown }
|
|
47
|
+
const definition = module.defineTool(planToolOptions(submit))
|
|
48
|
+
/** @author ddj 2026年09月28号 @returns Registration disposer owned by this plugin. */
|
|
49
|
+
function install(): () => void { return ctx.get('tools').register(definition) }
|
|
50
|
+
ctx.effect(install, 'vscode-mode:svn-plan-submit')
|
|
51
|
+
}
|
package/src/unityBridge.ts
CHANGED
|
@@ -7,10 +7,14 @@
|
|
|
7
7
|
* package.json name 不是本包 → 拒绝覆盖。纯路径构造与项目特征判定可单测。
|
|
8
8
|
* 作者 ddj 2026-09-07
|
|
9
9
|
*/
|
|
10
|
-
import { cp, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'
|
|
10
|
+
import { cp, mkdir, readFile, readdir, rm, writeFile, rename } from 'node:fs/promises'
|
|
11
11
|
import { basename, dirname, join, sep } from 'node:path'
|
|
12
12
|
import { fileURLToPath } from 'node:url'
|
|
13
13
|
import { dshHome, PLUGIN_ID } from './paths.js'
|
|
14
|
+
import { randomUUID } from 'node:crypto'
|
|
15
|
+
import { ensureOpenBridge } from './openBridge.js'
|
|
16
|
+
import { checkOpenPath } from './openInbox.js'
|
|
17
|
+
import type { Ctx } from './store.js'
|
|
14
18
|
import { UNITY_PACKAGE_NAME } from './shared/integration.js'
|
|
15
19
|
import type { UnityListPayload, UnityProjectEntry } from './shared/integration.js'
|
|
16
20
|
|
|
@@ -21,6 +25,8 @@ const PROJECTS_FILE = 'unity-projects.json'
|
|
|
21
25
|
/** 包源目录相对插件包根的位置。 */
|
|
22
26
|
const UNITY_DIR_NAME = 'unity'
|
|
23
27
|
|
|
28
|
+
export interface UnityBridgeOpts { ctx: Ctx; baseUrl: string }
|
|
29
|
+
|
|
24
30
|
/**
|
|
25
31
|
* 包源目录(随插件包分发的 unity/com.dsh.editor 绝对路径)。
|
|
26
32
|
* @author ddj 2026年09月07号
|
|
@@ -192,15 +198,17 @@ export async function unityRemove(rawPath: string, home = dshHome()): Promise<vo
|
|
|
192
198
|
* @param moduleUrl 模块 URL(测试注入)
|
|
193
199
|
* @returns 安装后项目状态
|
|
194
200
|
*/
|
|
195
|
-
export async function unityInstall(rawPath: string, moduleUrl: string = import.meta.url): Promise<UnityProjectEntry> {
|
|
201
|
+
export async function unityInstall(rawPath: string, moduleUrl: string = import.meta.url, bridge?: UnityBridgeOpts): Promise<UnityProjectEntry> {
|
|
196
202
|
const root = normalizeUnityRoot(rawPath)
|
|
197
203
|
const source = unitySourceDir(moduleUrl)
|
|
198
204
|
const target = unityTargetOf(root)
|
|
199
205
|
await validateInstallTarget(root, source, target)
|
|
206
|
+
const bridgeConfigPath = bridge ? await ensureOpenBridge(bridge.ctx, bridge.baseUrl) : undefined
|
|
200
207
|
await rm(target, { recursive: true, force: true })
|
|
201
208
|
await mkdir(join(target, '..'), { recursive: true })
|
|
202
209
|
await cp(source, target, { recursive: true })
|
|
203
|
-
|
|
210
|
+
if (bridgeConfigPath) await writeUnityHint(root, bridgeConfigPath)
|
|
211
|
+
return { ...(await unityEntryOf(root, moduleUrl)), bridgeConfigPath }
|
|
204
212
|
}
|
|
205
213
|
|
|
206
214
|
/**
|
|
@@ -232,3 +240,18 @@ async function validateInstallTarget(root: string, source: string, target: strin
|
|
|
232
240
|
} catch { /* 解析失败按未知包处理 */ }
|
|
233
241
|
if (name !== UNITY_PACKAGE_NAME) throw new Error('目标目录已存在且不是 DSH 包,拒绝覆盖:' + target)
|
|
234
242
|
}
|
|
243
|
+
|
|
244
|
+
/** @private @author ddj 2026年09月28号
|
|
245
|
+
* Bind a project's machine-local configuration to its selected profile bridge.
|
|
246
|
+
* @param root Validated Unity project root.
|
|
247
|
+
* @param configPath Absolute profile bridge INI path.
|
|
248
|
+
*/
|
|
249
|
+
async function writeUnityHint(root: string, configPath: string): Promise<void> {
|
|
250
|
+
const dir = join(root, 'UserSettings')
|
|
251
|
+
await mkdir(dir, { recursive: true })
|
|
252
|
+
await checkOpenPath(dir)
|
|
253
|
+
const target = join(dir, 'dsh-editor.ini')
|
|
254
|
+
const temporary = target + '.' + randomUUID() + '.tmp'
|
|
255
|
+
await writeFile(temporary, '[dsh]\nconfig=' + configPath + '\n', { flag: 'wx', mode: 0o600 })
|
|
256
|
+
try { await rename(temporary, target) } finally { await rm(temporary, { force: true }) }
|
|
257
|
+
}
|