dsh-vscode-mode 0.12.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 +129 -103
- 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 +12465 -11526
- package/lib/client.js.map +1 -1
- package/lib/index.js +3889 -2336
- 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/addToConversation.ts +4 -2
- package/src/client/ai/inlineProvider.ts +1 -8
- package/src/client/commandBridge.ts +6 -2
- package/src/client/externalOpen.ts +40 -19
- package/src/client/index.ts +10 -4
- package/src/client/keybindings.ts +18 -1
- 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 +13 -4
- package/src/client/sidebarBridge.ts +7 -0
- package/src/client/ui/DiffBadge.ts +2 -1
- package/src/client/ui/EditorView.ts +237 -55
- package/src/client/ui/McpSettings.ts +6 -3
- package/src/client/ui/OfficialSideTab.ts +5 -2
- package/src/client/ui/QuickOpen.ts +10 -9
- package/src/client/ui/ShortcutSettings.ts +34 -54
- package/src/client/ui/commandCatalog.ts +5 -4
- 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 +1 -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/shared/appUrl.ts +19 -0
- package/src/shared/externalOpen.ts +71 -0
- package/src/shared/integration.ts +2 -0
- package/src/shared/keybindings.ts +8 -5
- package/src/shared/mcp.ts +4 -1
- package/src/shared/rpc.ts +7 -4
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vscode-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 DSH 0.1.5+ 官方右侧 Sidebar 与对话同屏)+ 命令栏(Ctrl+Shift+P)与可扩展指令系统 + VS Code 兼容代码片段(.code-snippets 全局/项目,IntelliSense 展开)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ SVN 集成(侧栏变更面板/日志弹窗/并排差异)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车",
|
|
5
5
|
"icon": "assets/icon.svg",
|
|
6
6
|
"keywords": [
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"@vscode/ripgrep": "^1.18.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"@deepseek-ai/dsh-client-ui-slots": ">=0.0.1-rc.1 <0.1.0 || >=0.1.0-rc.1 <0.2.0-0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0 || >=0.1.3-0 <0.2.0-0 || >=0.1.4-0 <0.2.0-0 || >=0.1.5-0 <0.2.0-0 || >=0.1.6-0 <0.2.0-0",
|
|
52
|
-
"@deepseek-ai/dsh-settings": ">=0.1.0-rc.1 <0.2.0-0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0 || >=0.1.3-0 <0.2.0-0 || >=0.1.4-0 <0.2.0-0 || >=0.1.5-0 <0.2.0-0 || >=0.1.6-0 <0.2.0-0",
|
|
53
|
-
"@deepseek-ai/dsh-client-ui-settings": ">=0.1.0-rc.1 <0.2.0-0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0 || >=0.1.3-0 <0.2.0-0 || >=0.1.4-0 <0.2.0-0 || >=0.1.5-0 <0.2.0-0 || >=0.1.6-0 <0.2.0-0",
|
|
54
|
-
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.0-rc.1 <0.2.0-0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0 || >=0.1.3-0 <0.2.0-0 || >=0.1.4-0 <0.2.0-0 || >=0.1.5-0 <0.2.0-0 || >=0.1.6-0 <0.2.0-0",
|
|
55
|
-
"@deepseek-ai/dsh-llm": ">=0.0.1-rc.1 <0.1.0 || >=0.1.0-rc.1 <0.2.0-0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0 || >=0.1.3-0 <0.2.0-0 || >=0.1.4-0 <0.2.0-0 || >=0.1.5-0 <0.2.0-0 || >=0.1.6-0 <0.2.0-0",
|
|
56
|
-
"@deepseek-ai/dsh-tools": ">=0.0.1-rc.1 <0.1.0 || >=0.1.0-rc.1 <0.2.0-0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0 || >=0.1.3-0 <0.2.0-0 || >=0.1.4-0 <0.2.0-0 || >=0.1.5-0 <0.2.0-0 || >=0.1.6-0 <0.2.0-0",
|
|
51
|
+
"@deepseek-ai/dsh-client-ui-slots": ">=0.0.1-rc.1 <0.1.0 || >=0.1.0-rc.1 <0.2.0-0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0 || >=0.1.3-0 <0.2.0-0 || >=0.1.4-0 <0.2.0-0 || >=0.1.5-0 <0.2.0-0 || >=0.1.6-0 <0.2.0-0 || >=0.1.7-0 <0.2.0-0",
|
|
52
|
+
"@deepseek-ai/dsh-settings": ">=0.1.0-rc.1 <0.2.0-0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0 || >=0.1.3-0 <0.2.0-0 || >=0.1.4-0 <0.2.0-0 || >=0.1.5-0 <0.2.0-0 || >=0.1.6-0 <0.2.0-0 || >=0.1.7-0 <0.2.0-0",
|
|
53
|
+
"@deepseek-ai/dsh-client-ui-settings": ">=0.1.0-rc.1 <0.2.0-0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0 || >=0.1.3-0 <0.2.0-0 || >=0.1.4-0 <0.2.0-0 || >=0.1.5-0 <0.2.0-0 || >=0.1.6-0 <0.2.0-0 || >=0.1.7-0 <0.2.0-0",
|
|
54
|
+
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.0-rc.1 <0.2.0-0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0 || >=0.1.3-0 <0.2.0-0 || >=0.1.4-0 <0.2.0-0 || >=0.1.5-0 <0.2.0-0 || >=0.1.6-0 <0.2.0-0 || >=0.1.7-0 <0.2.0-0",
|
|
55
|
+
"@deepseek-ai/dsh-llm": ">=0.0.1-rc.1 <0.1.0 || >=0.1.0-rc.1 <0.2.0-0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0 || >=0.1.3-0 <0.2.0-0 || >=0.1.4-0 <0.2.0-0 || >=0.1.5-0 <0.2.0-0 || >=0.1.6-0 <0.2.0-0 || >=0.1.7-0 <0.2.0-0",
|
|
56
|
+
"@deepseek-ai/dsh-tools": ">=0.0.1-rc.1 <0.1.0 || >=0.1.0-rc.1 <0.2.0-0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0 || >=0.1.3-0 <0.2.0-0 || >=0.1.4-0 <0.2.0-0 || >=0.1.5-0 <0.2.0-0 || >=0.1.6-0 <0.2.0-0 || >=0.1.7-0 <0.2.0-0",
|
|
57
57
|
"cordis": ">=4.0.0-rc <5",
|
|
58
58
|
"react": "^18.2.0",
|
|
59
59
|
"react-dom": "^18.2.0",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"react": "^18.3.1",
|
|
68
68
|
"react-dom": "^18.3.1",
|
|
69
69
|
"schemastery": "3.18.0",
|
|
70
|
+
"semver": "^7.8.5",
|
|
70
71
|
"tsdown": "^0.22.14",
|
|
71
72
|
"typescript": "^5.9.0",
|
|
72
73
|
"vitest": "^3.2.4"
|
|
@@ -75,7 +76,7 @@
|
|
|
75
76
|
"build": "tsdown",
|
|
76
77
|
"build:client": "tsdown",
|
|
77
78
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
78
|
-
"test": "vitest run",
|
|
79
|
+
"test": "vitest run && node --test tests/nativeOpen.test.mjs",
|
|
79
80
|
"prebuild": "node -e \"fs.rmSync('lib',{recursive:true,force:true})\"",
|
|
80
81
|
"prepare": "npm run prebuild && npm run build",
|
|
81
82
|
"pack": "npm run prebuild && npm run build && npm pack",
|
|
@@ -1,46 +1,37 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dsh-vscodemode-mcp
|
|
3
|
-
description: 配置、新增、启用或排查 dsh-vscode-mode
|
|
4
|
-
whenToUse:
|
|
3
|
+
description: 配置、新增、启用或排查 dsh-vscode-mode 的 MCP 服务:全局「我的 MCP」、项目 .mcp.json、agent 连接实例、工具/资源/指令隔离、mcp.* RPC、命名冲突与连接状态。
|
|
4
|
+
whenToUse: 用户要求新增、修改、启停或排查 VSCodeMode MCP 配置,或判断项目工具、资源、服务器指令为何不可见或被拒绝时。
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# dsh-vscodemode-mcp —
|
|
7
|
+
# dsh-vscodemode-mcp — MCP 配置与使用
|
|
8
8
|
|
|
9
|
-
本技能覆盖
|
|
10
|
-
它管理的是 `@deepseek-ai/dsh-mcp-client` 的 loader 条目,连接由 DSH Host 维护,**不在浏览器侧连接 MCP**。
|
|
9
|
+
本技能覆盖 dsh-vscode-mode 的 MCP 管理面。连接由 DSH Host 内的官方 `@deepseek-ai/dsh-mcp-client` 插件维护;浏览器只管理配置。
|
|
11
10
|
|
|
12
|
-
## 1.
|
|
11
|
+
## 1. 先确定作用域
|
|
13
12
|
|
|
14
|
-
| | 全局「我的 MCP」 | 项目 MCP |
|
|
13
|
+
| 项目 | 全局「我的 MCP」 | 项目 MCP |
|
|
15
14
|
|---|---|---|
|
|
16
|
-
|
|
|
17
|
-
| 生效范围 |
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
15
|
+
| 持久配置 | profile loader 树 | 已注册 workspace 根目录的 `.mcp.json` |
|
|
16
|
+
| 生效范围 | profile 内所有工作区 | cwd 匹配该 workspace 的 agent |
|
|
17
|
+
| 运行方式 | 原有全局 loader entry | `agent.ctx.plugin(officialMcp, config)` |
|
|
18
|
+
| 连接实例 | 每个启用的全局 entry 一份 | 每个活动 agent、每个启用服务一份 |
|
|
19
|
+
| 没有活动 agent | 仍可运行 | 仅保存配置,`configured`、`instanceCount: 0` |
|
|
20
|
+
| 团队共享 | 否 | `.mcp.json` 可以随仓库共享 |
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
- 用户说「全局都能用」「所有项目都要」→ 全局。
|
|
24
|
-
- 用户说「这个项目需要」「跟仓库一起提交」→ 项目级。
|
|
25
|
-
- 不确定且该 MCP 只在某个仓库有意义 → 默认项目级。
|
|
22
|
+
用户说「全局都能用」选全局;说「这个项目需要」「跟仓库一起提交」选项目级。工作区匹配使用最长父路径;Windows 路径忽略大小写,POSIX 路径区分大小写。
|
|
26
23
|
|
|
27
|
-
## 2. UI
|
|
24
|
+
## 2. UI 与连接语义
|
|
28
25
|
|
|
29
|
-
设置 →
|
|
26
|
+
设置 → VSCodeMode → MCP 管理,包含「我的 MCP」「项目 MCP」「MCP 市场」(占位)。项目服务在所选项目内管理。
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
- **项目 MCP** — 先选项目(带路径与 MCP 计数),再管理该项目的服务。
|
|
33
|
-
- **MCP 市场** — 占位,暂未接入。
|
|
28
|
+
保存项目服务会写入 `.mcp.json` 并更新匹配的活动 agent;没有活动 agent 时保存为待挂载配置。启用不会凭空创建会话。每个新 agent 的串行 `agent/created` 监听会读取最新配置并等待其官方插件启动;插件装配时也会接入已经存在的 agent。
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
表单字段:名称 / 传输方式 / (stdio)命令·参数·工作目录 / (streamable-http)URL·请求头。
|
|
37
|
-
按钮文案为「保存并连接」——**保存后会立刻尝试连接**。
|
|
30
|
+
禁用、删除、配置变更、刷新重连及 agent/插件卸载都会释放相应 fiber 和连接。仅刷新未变化的项目列表不会重复连接。主机代码更新需要重启 DSH 才能加载新实现。
|
|
38
31
|
|
|
39
|
-
## 3.
|
|
32
|
+
## 3. 项目配置
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
对齐 Claude Code / Cursor 格式,存于项目根:
|
|
34
|
+
`.mcp.json` 示例:
|
|
44
35
|
|
|
45
36
|
```json
|
|
46
37
|
{
|
|
@@ -52,26 +43,26 @@ whenToUse: 用户要求新增/修改/启停/排查 dsh-vscode-mode 的 MCP 服
|
|
|
52
43
|
"remote-tools": {
|
|
53
44
|
"url": "http://localhost:3000/mcp",
|
|
54
45
|
"headers": { "Authorization": "Bearer <token>" },
|
|
55
|
-
"toolCallTimeoutMs": 60000
|
|
46
|
+
"toolCallTimeoutMs": 60000,
|
|
47
|
+
"reconnect": { "enabled": true }
|
|
56
48
|
}
|
|
57
49
|
}
|
|
58
50
|
}
|
|
59
51
|
```
|
|
60
52
|
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
### 3.2 全局(等价配置形)
|
|
53
|
+
- 有字符串 `url` 采用 `streamable-http`;其余采用 `stdio`。文件不用写 `transport`。
|
|
54
|
+
- stdio:`command` 必填;可填 `args`、`cwd`、`env`。未指定 `cwd` 时,以项目根作为进程工作目录。
|
|
55
|
+
- HTTP:`url` 必须以 `http://` 或 `https://` 开头,可填 `headers`。
|
|
56
|
+
- 高级参数:`toolCallTimeoutMs`、`failOnStartupError`、`maxInstructionBytes`、`reconnect`。
|
|
57
|
+
- `disabled: true` 保持配置但不创建连接;保存表单会恢复启用。重启和 reconcile 都尊重 `disabled`。
|
|
58
|
+
- 保存保留文档顶层、其他服务器及当前服务器的未知字段和未编辑高级参数;切换传输方式会移除旧传输专属字段。
|
|
59
|
+
- `env` / `headers` 在返回值中使用 `••••••` 脱敏。原键的未修改掩码保存时恢复真实值;新键只有掩码而没有原值时会拒绝保存,需填写凭据。
|
|
60
|
+
- 只有 `ENOENT` 表示配置不存在。权限、IO、JSON 或结构错误会报告 `fileError`,保留最后有效配置与活动连接,不覆盖损坏文件。
|
|
70
61
|
|
|
71
|
-
|
|
62
|
+
全局配置继续使用官方 loader entry:
|
|
72
63
|
|
|
73
64
|
```yaml
|
|
74
|
-
- id:
|
|
65
|
+
- id: mcp-example
|
|
75
66
|
name: '@deepseek-ai/dsh-mcp-client'
|
|
76
67
|
config:
|
|
77
68
|
serverName: codegraph
|
|
@@ -80,102 +71,73 @@ whenToUse: 用户要求新增/修改/启停/排查 dsh-vscode-mode 的 MCP 服
|
|
|
80
71
|
args: ['serve', '--mcp']
|
|
81
72
|
```
|
|
82
73
|
|
|
83
|
-
|
|
74
|
+
优先使用设置页或 RPC,保证冲突检查和运行状态同步。
|
|
84
75
|
|
|
85
|
-
|
|
76
|
+
## 4. RPC
|
|
86
77
|
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
名称: codegraph
|
|
90
|
-
传输方式: stdio
|
|
91
|
-
命令: codegraph
|
|
92
|
-
参数: serve --mcp --path D:/Work/MyProject
|
|
93
|
-
```
|
|
78
|
+
`POST /edrv/rpc`,请求 `{ "method": "...", "args": { ... } }`,返回 `{ ok: true, ... }` 或 `{ ok: false, error }`。
|
|
94
79
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
80
|
+
| 方法 | 入参 | 成功返回字段 |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `mcp.list` | `{}` | `servers` |
|
|
83
|
+
| `mcp.save` | `{ config }` | `server` |
|
|
84
|
+
| `mcp.remove` | `{ id }` | 无 |
|
|
85
|
+
| `mcp.toggle` | `{ id, enabled }` | `server` |
|
|
86
|
+
| `mcp.refresh` | `{ id }` | `server` |
|
|
87
|
+
| `mcp.projects` | `{}` | `projects` |
|
|
88
|
+
| `mcp.projectSave` | `{ workspacePath, serverName, config }` | `project` |
|
|
89
|
+
| `mcp.projectRemove` | `{ workspacePath, serverName }` | `project` |
|
|
90
|
+
| `mcp.projectToggle` | `{ workspacePath, serverName, enabled }` | `project` |
|
|
91
|
+
| `mcp.projectRefresh` | `{ workspacePath, serverName }` | `project` |
|
|
102
92
|
|
|
103
|
-
|
|
93
|
+
`MpcServer` 包括 `id`、`serverName`、`enabled`、`transport`、脱敏 `config`、`status`、`instanceCount`、`toolCount`、`tools[]`、可选 `error`。项目视图另有 `workspacePath`、`title`、`source: 'project'`,以及可选 `missingDir`、`fileError`。
|
|
104
94
|
|
|
105
|
-
|
|
95
|
+
## 5. 命名与迁移
|
|
106
96
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
`
|
|
121
|
-
`
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
## 6. 作用域隔离(项目 MCP 的边界)
|
|
134
|
-
|
|
135
|
-
项目 MCP 的连接由 Host 维护,但**每个 agent 只继承当前工作区的项目工具**:
|
|
136
|
-
|
|
137
|
-
- 机制:`tools.restrict({ deny })` 控制模型可见性 + `tools.guard()` 在执行层兜底拒绝。
|
|
138
|
-
- 当前对话工作区 ≠ 条目所属工作区时,调用被拒,返回:
|
|
139
|
-
`已拒绝:当前对话工作区不能使用其他项目的 MCP`
|
|
140
|
-
- 会话没有落在任何已注册工作区时:
|
|
141
|
-
`项目 MCP 需要在已注册工作区的对话中使用`
|
|
142
|
-
- **全局 MCP 不受此限制**,在任何会话都可用。
|
|
143
|
-
- 工作区匹配按**最长父路径**:在子目录会话里也能用到父级工作区的项目 MCP。
|
|
144
|
-
|
|
145
|
-
排查「工具有但没有出现在模型面前」时,先看当前会话 cwd 是否落在目标 workspace 内。
|
|
146
|
-
|
|
147
|
-
## 7. 故障排查
|
|
148
|
-
|
|
149
|
-
| 症状 | 原因与处理 |
|
|
97
|
+
- `serverName` 使用 `/^[A-Za-z0-9_-]{1,32}$/`;允许下划线,与技能名规则不同。
|
|
98
|
+
- VSCodeMode 管理的全局和所有项目配置之间名称唯一,包括未启用和没有活动 agent 的配置。官方 MCP 还会拒绝同一个 agent scope 中的重复名称。
|
|
99
|
+
- 工具名是 `mcp__<serverName>__<原始工具名>`。共享资源工具通过 `arguments.server` 选择服务器。
|
|
100
|
+
- 项目稳定 id 仍是 `vsm-mcp.<workspaceHash>.<serverName>`,用于 UI/RPC 身份;0.13 起不再代表全局 loader entry。
|
|
101
|
+
- 启动迁移会先删除旧的 `vsm-mcp.*` 和 `vsm-mcp:*` 全局项目条目及其连接,再在 agent scope 挂载。不要手动将项目条目改成其他 id。
|
|
102
|
+
- 项目写操作只接受已注册 workspace,目标固定为该根目录的 `.mcp.json`。
|
|
103
|
+
|
|
104
|
+
## 6. 工具、资源和指令隔离
|
|
105
|
+
|
|
106
|
+
官方 0.1.7-rc.2 的 MCP Config 没有关闭资源/指令的开关;官方注册随 `scopeOf(ctx)` 归属。因此项目连接必须直接在 `agent.ctx` 挂载,不能全局连接后只隐藏工具。
|
|
107
|
+
|
|
108
|
+
子 agent 会继承父 scope;跨 cwd 子会话还有三层处理:
|
|
109
|
+
|
|
110
|
+
- `tools.restrict({ deny })` 隐藏继承的外项目工具;执行 guard 同时检查所有项目工具命名空间。
|
|
111
|
+
- `list_mcp_resources`、`list_mcp_resource_templates`、`read_mcp_resource` 在执行前检查 `arguments.server` 归属,不能通过共享工具访问父工作区资源。
|
|
112
|
+
- 子 scope 为外项目 `mcp:<serverName>` 注册空指令段;通过公开 `system-prompt/assemble` waterfall 按实际查看 scope 过滤官方资源服务器列表,保留全局和独立 agent preset 的纯资源服务。不会改动 scope parent 链。官方 0.1.7-rc.2 列表格式已验证;外项目存在且列表格式无法识别时,安全隐藏该提示列表,执行 guard 仍独立生效。
|
|
113
|
+
|
|
114
|
+
全局 MCP 保持可用。工作区外的 agent 不能访问任何项目 MCP。拒绝文案:
|
|
115
|
+
|
|
116
|
+
- `已拒绝:当前对话工作区不能使用其他项目的 MCP`
|
|
117
|
+
- `项目 MCP 需要在已注册工作区的对话中使用`
|
|
118
|
+
|
|
119
|
+
## 7. 状态与排错
|
|
120
|
+
|
|
121
|
+
| 状态/现象 | 含义与处理 |
|
|
150
122
|
|---|---|
|
|
151
|
-
|
|
|
152
|
-
|
|
|
153
|
-
|
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
157
|
-
| `
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
## 8.
|
|
164
|
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
## 9. 关键源码位置
|
|
173
|
-
|
|
174
|
-
需要在插件仓库里核对行为时(按需读取,不要一次全开):
|
|
175
|
-
|
|
176
|
-
- `src/mcp.ts` — 全局 MCP 的 list/save/remove/toggle/refresh,`validateConfig`,`publicConfig` 脱敏,`toolsOf` 工具前缀。
|
|
177
|
-
- `src/mcpProject.ts` — 项目 `.mcp.json` 读写、`configFromDef`、reconcile、条目 id 生成。
|
|
178
|
-
- `src/mcpIsolation.ts` — 工作区隔离(restrict + guard)与两条拒绝文案。
|
|
179
|
-
- `src/shared/mcp.ts` — 共享契约(`MpcConfig` / `MpcServer` / `MpcProject`)。
|
|
180
|
-
- `src/shared/rpc.ts` — `mcp.*` 方法的入参与返回形状。
|
|
181
|
-
- `src/client/ui/McpSettings.ts` — 设置页 UI(三个子页签与表单字段映射)。
|
|
123
|
+
| `configured` | 配置已保存,没有已挂载实例;打开匹配工作区会话后挂载 |
|
|
124
|
+
| `disabled` | 配置已停用,不建立项目连接 |
|
|
125
|
+
| `connecting` | 官方插件还在等待启动完成;检查命令、cwd、URL 和握手 |
|
|
126
|
+
| `unverified` | fiber 已激活,但官方没有公开连接状态证据;不能宣称已连接 |
|
|
127
|
+
| `error` | 插件启动或挂载失败;读 `error` 与 Host 日志 |
|
|
128
|
+
| `instanceCount: N` | N 个挂载的官方插件 fiber,**不等于 N 个确认存活的网络连接** |
|
|
129
|
+
| `toolCount: 0` | 可能是合法的纯资源服务器,也可能启动失败或工具同步失败;结合资源调用和日志判断 |
|
|
130
|
+
| 文件读取/解析错误 | 修复源文件后刷新;最后有效实例保持运行 |
|
|
131
|
+
| 工作区不匹配 | 检查 agent cwd 和最长父路径匹配结果 |
|
|
132
|
+
|
|
133
|
+
`failOnStartupError` 默认 false:初次连接失败时 fiber 仍可能 ACTIVE。工具数和 ACTIVE 都不能单独证明连接成功;只有实际成功调用等公开证据才能确认。当前管理面保守使用 `unverified`。
|
|
134
|
+
|
|
135
|
+
## 8. 维护位置与验证
|
|
136
|
+
|
|
137
|
+
- `src/mcpRuntime.ts`:agent → fibers 台账、官方插件加载、串行创建、迁移与卸载。
|
|
138
|
+
- `src/mcpProject.ts`:源配置读取、保留字段、命名检查及 reconcile。
|
|
139
|
+
- `src/mcpIsolation.ts`:继承工具限制、资源执行 guard、指令和资源列表覆盖。
|
|
140
|
+
- `src/mcp.ts`:全局 loader CRUD、脱敏与诚实状态。
|
|
141
|
+
- `src/shared/mcp.ts`:共享配置和状态契约。
|
|
142
|
+
|
|
143
|
+
集成时先 `installIsolation(ctx)`,再 `await installMcpRuntime(ctx)`。UI 需支持 `configured`、`unverified` 和实例数。最小回归只运行 MCP 相关测试;可设置 `DSH_MCP_HOST_ROOT` 为安装的 harness 根来运行真实官方模块与本地 HTTP MCP 测试,未设置时该集成用例跳过。
|
package/src/childEnv.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** 外部进程环境:参照官方 dsh-subprocess.scrubbedParentEnv。 */
|
|
2
|
+
const SENSITIVE_ENV = /KEY|PASSWORD|SECRET|TOKEN/i
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 清理父进程隐式凭据及 DSH 身份,再合并用户显式配置;保留 Electron Node 模式。
|
|
6
|
+
* @public
|
|
7
|
+
* @author ddj 2026年09月28号
|
|
8
|
+
* @param explicit 用户显式配置的环境变量,可有意传递凭据
|
|
9
|
+
* @param parent 父环境,默认当前进程;测试可注入
|
|
10
|
+
* @returns 新环境对象,不修改任何输入
|
|
11
|
+
*/
|
|
12
|
+
export function childEnv(explicit: NodeJS.ProcessEnv = {}, parent: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
|
13
|
+
const env: NodeJS.ProcessEnv = {}
|
|
14
|
+
for (const [key, value] of Object.entries(parent)) {
|
|
15
|
+
if (value === undefined || SENSITIVE_ENV.test(key) || key.toUpperCase().startsWith('DSH_')) continue
|
|
16
|
+
env[key] = value
|
|
17
|
+
}
|
|
18
|
+
return { ...env, ...explicit }
|
|
19
|
+
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* 作者 ddj 2026-08-25
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { appUrl } from '../shared/appUrl.js'
|
|
10
|
+
|
|
9
11
|
/** 文件引用可选的行区间(选中内容用)。 */
|
|
10
12
|
export interface RefRange {
|
|
11
13
|
startLine: number
|
|
@@ -320,7 +322,7 @@ function rpcOk(res: unknown): { ok: boolean; value?: unknown } {
|
|
|
320
322
|
* @returns 新会话 id(失败空串)
|
|
321
323
|
*/
|
|
322
324
|
async function apiSessionCreate(cwd: string | undefined): Promise<string> {
|
|
323
|
-
const res = await fetch(SESSION_CREATE_PATH, {
|
|
325
|
+
const res = await fetch(appUrl(SESSION_CREATE_PATH), {
|
|
324
326
|
method: 'POST',
|
|
325
327
|
headers: { 'content-type': 'application/json' },
|
|
326
328
|
body: JSON.stringify({
|
|
@@ -347,7 +349,7 @@ async function apiSessionCreate(cwd: string | undefined): Promise<string> {
|
|
|
347
349
|
export async function apiSessionPrompt(sessionId: string, text: string): Promise<boolean> {
|
|
348
350
|
try {
|
|
349
351
|
const payload = { sessionId, mode: 'queue', content: [{ type: 'text', text }] }
|
|
350
|
-
const res = await fetch(SESSION_PROMPT_PATH, {
|
|
352
|
+
const res = await fetch(appUrl(SESSION_PROMPT_PATH), {
|
|
351
353
|
method: 'POST',
|
|
352
354
|
headers: { 'content-type': 'application/json' },
|
|
353
355
|
body: JSON.stringify({
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* 作者 ddj
|
|
10
10
|
*/
|
|
11
11
|
import { rpc } from '../rpc.js'
|
|
12
|
-
import { RPC_PATH } from '../../shared/rpc.js'
|
|
13
12
|
import {
|
|
14
13
|
AI_PREFIX_MAX, AI_SUFFIX_MAX, inlineWorth, trimInlineWindow,
|
|
15
14
|
} from '../../shared/ai.js'
|
|
@@ -145,13 +144,7 @@ export function registerAiInline(monaco) {
|
|
|
145
144
|
inFlight = controller
|
|
146
145
|
emitAiStatus('busy')
|
|
147
146
|
try {
|
|
148
|
-
const
|
|
149
|
-
method: 'POST',
|
|
150
|
-
headers: { 'content-type': 'application/json' },
|
|
151
|
-
body: JSON.stringify({ method: 'edrv.ai.inline', args: { path, prefix: trimmed.prefix, suffix: trimmed.suffix } }),
|
|
152
|
-
signal: controller.signal,
|
|
153
|
-
})
|
|
154
|
-
const data = await res.json()
|
|
147
|
+
const data = await rpc('edrv.ai.inline', { path, prefix: trimmed.prefix, suffix: trimmed.suffix }, controller.signal)
|
|
155
148
|
if (mySeq !== seq || !data?.ok || typeof data.text !== 'string' || !data.text) {
|
|
156
149
|
cache.set(key, { items: [], at: Date.now() })
|
|
157
150
|
if (data?.note) emitAiStatus('error', { note: String(data.note) })
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* React 状态,因此指令系统可在编辑器挂载前装配、卸载后仍安全。
|
|
8
8
|
* 作者 ddj 2026年09月10号 / 2026年10月
|
|
9
9
|
*/
|
|
10
|
+
import { setPanelRoute } from './panelCommands.js'
|
|
11
|
+
import type { PanelRoute } from './panelCommands.js'
|
|
10
12
|
import { BRIDGE_COMMANDS, EDITOR_COMMANDS, showCommandsDef } from './ui/commandCatalog.js'
|
|
11
13
|
import { createCommandRegistry } from './commandRegistry.js'
|
|
12
14
|
import type { CommandRegistry } from './commandRegistry.js'
|
|
@@ -25,12 +27,14 @@ export interface CommandBridge {
|
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* 创建指令执行桥:注册内置指令 + 命令栏自身,注入命令栏执行器。
|
|
28
|
-
* @author ddj 2026年09月
|
|
30
|
+
* @author ddj 2026年09月28号
|
|
31
|
+
* @param panelRoute 当前会话读取器与已有侧栏打开能力(首次打开命令使用)
|
|
29
32
|
* @returns 指令执行桥句柄
|
|
30
33
|
*/
|
|
31
|
-
export function createCommandBridge(): CommandBridge {
|
|
34
|
+
export function createCommandBridge(panelRoute?: PanelRoute): CommandBridge {
|
|
32
35
|
const registry = createCommandRegistry()
|
|
33
36
|
const disposers: Array<() => void> = []
|
|
37
|
+
if (panelRoute) disposers.push(setPanelRoute(panelRoute))
|
|
34
38
|
const palette = showCommandsDef(() => openCommandPalette('command'))
|
|
35
39
|
for (const command of [...EDITOR_COMMANDS, ...BRIDGE_COMMANDS]) {
|
|
36
40
|
disposers.push(registry.register(command))
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { EDRV_PARAM_KEYS, parseOpenParams } from '../shared/externalOpen.js'
|
|
9
9
|
import { openDeepLink } from './openFlow.js'
|
|
10
|
+
import { disposeOpenWait } from './openReceipt.js'
|
|
10
11
|
import { rpc } from './rpc.js'
|
|
11
12
|
import { log } from './log.js'
|
|
12
13
|
|
|
@@ -31,22 +32,23 @@ export interface ExtOpenOptions {
|
|
|
31
32
|
* @param ctx 客户端根上下文(sessions/workspaces 服务)
|
|
32
33
|
* @param options 可注入选项
|
|
33
34
|
*/
|
|
34
|
-
export function setupExtOpen(ctx: unknown, options: ExtOpenOptions = {}): void {
|
|
35
|
-
startPresencePoll(ctx)
|
|
35
|
+
export function setupExtOpen(ctx: unknown, options: ExtOpenOptions = {}): () => void {
|
|
36
|
+
const dispose = startPresencePoll(ctx)
|
|
36
37
|
const search = options.search ?? (typeof location !== 'undefined' ? location.search : '')
|
|
37
38
|
const params = parseOpenParams(search)
|
|
38
|
-
if (!params) return
|
|
39
|
+
if (!params) return dispose
|
|
39
40
|
const referrer = options.referrer ?? (typeof document !== 'undefined' ? document.referrer : '')
|
|
40
41
|
const origin = options.origin ?? (typeof location !== 'undefined' ? location.origin : '')
|
|
41
42
|
if (!referrerAllowed(referrer, origin)) {
|
|
42
43
|
log.warn('已忽略跨源深链请求(referrer=' + referrer + ')')
|
|
43
|
-
return
|
|
44
|
+
return dispose
|
|
44
45
|
}
|
|
45
46
|
stripCurrentUrl()
|
|
46
47
|
void openDeepLink(ctx, params).catch((error) => {
|
|
47
48
|
log.warn('深链打开失败:' + String(error))
|
|
48
49
|
toastDom('深链打开失败:' + String((error as Error)?.message ?? error))
|
|
49
50
|
})
|
|
51
|
+
return dispose
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/**
|
|
@@ -55,23 +57,42 @@ export function setupExtOpen(ctx: unknown, options: ExtOpenOptions = {}): void {
|
|
|
55
57
|
* @author ddj 2026年09月08号
|
|
56
58
|
* @param ctx 客户端根上下文
|
|
57
59
|
*/
|
|
58
|
-
function startPresencePoll(ctx: unknown): void {
|
|
59
|
-
if (typeof window === 'undefined') return
|
|
60
|
-
const marker = window as { __edrvExtPoll?:
|
|
61
|
-
if (marker.__edrvExtPoll)
|
|
62
|
-
|
|
60
|
+
function startPresencePoll(ctx: unknown): () => void {
|
|
61
|
+
if (typeof window === 'undefined') return () => {}
|
|
62
|
+
const marker = window as unknown as { __edrvExtPoll?: () => void }
|
|
63
|
+
if (typeof marker.__edrvExtPoll === 'function') marker.__edrvExtPoll()
|
|
64
|
+
const clientId = crypto.randomUUID()
|
|
65
|
+
let disposed = false
|
|
66
|
+
let polling = false
|
|
63
67
|
let executing = false
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
/** @private @author ddj 2026年09月28号 Open all requested paths before acknowledging this exact lease. */
|
|
69
|
+
async function execute(open: { paths: string[]; line?: number; column?: number; token: string; leaseId: string }): Promise<void> {
|
|
66
70
|
executing = true
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
try {
|
|
72
|
+
const success = await openDeepLink(ctx, open)
|
|
73
|
+
if (!disposed) await rpc('edrv.external.ack', { token: open.token, leaseId: open.leaseId, clientId, success, error: success ? undefined : '编辑器打开失败或取消' })
|
|
74
|
+
} finally { executing = false }
|
|
75
|
+
}
|
|
76
|
+
/** @private @author ddj 2026年09月28号 Poll while opening to renew the lease without accepting a second request. */
|
|
77
|
+
async function tick(): Promise<void> {
|
|
78
|
+
if (disposed || polling) return
|
|
79
|
+
polling = true
|
|
80
|
+
try {
|
|
81
|
+
const result = await rpc('edrv.external.pending', { clientId, busy: executing })
|
|
82
|
+
if (!disposed && result.ok && result.open && !executing) void execute(result.open).catch(() => {})
|
|
83
|
+
} finally { polling = false }
|
|
84
|
+
}
|
|
85
|
+
const timer = window.setInterval(() => { void tick().catch(() => {}) }, HANDOFF_POLL_MS)
|
|
86
|
+
/** @private @author ddj 2026年09月28号 Stop heartbeats and fail outstanding editor waits on plugin disposal. */
|
|
87
|
+
function dispose(): void {
|
|
88
|
+
disposed = true
|
|
89
|
+
window.clearInterval(timer)
|
|
90
|
+
disposeOpenWait()
|
|
91
|
+
if (marker.__edrvExtPoll === dispose) delete marker.__edrvExtPoll
|
|
92
|
+
}
|
|
93
|
+
marker.__edrvExtPoll = dispose
|
|
94
|
+
void tick().catch(() => {})
|
|
95
|
+
return dispose
|
|
75
96
|
}
|
|
76
97
|
|
|
77
98
|
/**
|
package/src/client/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ import { openEditorView } from './events.js'
|
|
|
32
32
|
import { setupExtOpen } from './externalOpen.js'
|
|
33
33
|
import { SettingsContext } from './settingsContext.js'
|
|
34
34
|
import { SIDEBAR_PLUGIN, registerSlotSafely, settingsBridge } from './compat.js'
|
|
35
|
-
import { detectSidebarService, installSideEditor, setEnsureSideEditor, SIDEBAR_INSTALL_CMD } from './sidebarBridge.js'
|
|
35
|
+
import { detectSidebarService, hasSideRoute, installSideEditor, routeSideEditor, setEnsureSideEditor, SIDEBAR_INSTALL_CMD } from './sidebarBridge.js'
|
|
36
36
|
import { detectOfficial, installOfficial, registerOfficialFileClaim, OFFICIAL_TAB_KIND, OFFICIAL_TAB_TITLE, isEditorTabActive, restoreEditorTab, buildFileAddress } from './officialSidebar.js'
|
|
37
37
|
import { setNativeCsv, setNativeOpenHandler, resetNativeOpen } from './nativeOpenStore.js'
|
|
38
38
|
import { SideEditorTab } from './ui/SideEditorTab.js'
|
|
@@ -88,7 +88,13 @@ let commandsBridge: CommandBridge | null = null
|
|
|
88
88
|
*/
|
|
89
89
|
function setupCommands(ctx: any): CommandBridge {
|
|
90
90
|
if (commandsBridge) return commandsBridge
|
|
91
|
-
|
|
91
|
+
/** @author ddj 2026年09月28号 @returns Current session/workspace for command ownership. */
|
|
92
|
+
const readScope = () => readSessionScope(ctx)
|
|
93
|
+
/** @author ddj 2026年09月28号 @returns Whether an installed sidebar can mount the editor. */
|
|
94
|
+
const canOpen = () => hasSideRoute()
|
|
95
|
+
/** @author ddj 2026年09月28号 @returns Whether the current editor route accepted the request. */
|
|
96
|
+
const open = () => routeSideEditor(null, false)
|
|
97
|
+
const bridge = createCommandBridge({ readScope, canOpen, open })
|
|
92
98
|
commandsBridge = bridge
|
|
93
99
|
ctx.provide(REGISTRY_GLOBAL, bridge.registry)
|
|
94
100
|
// 无条件镜像到 window(DSH 不创建 window.dsh,旧条件式赋值是死代码 → 命令栏空表)
|
|
@@ -143,7 +149,7 @@ export function apply(ctx: any): void {
|
|
|
143
149
|
const sessions = ctx.get('sessions')
|
|
144
150
|
|
|
145
151
|
// 外部深链落地:Windows 右键菜单 / Unity 外部编辑器 → 浏览器 URL 参数 → 打开规则路由
|
|
146
|
-
setupExtOpen(ctx)
|
|
152
|
+
ctx.effect(() => setupExtOpen(ctx), 'vscode-mode: external open')
|
|
147
153
|
|
|
148
154
|
// Monaco 加载时机:不再 DSH 启动即预热,改为进入会话界面(当前会话出现)后再后台加载,
|
|
149
155
|
// 用户点开「文件编辑」页签即用;空闲时仍由 EditorView 挂载兜底加载。
|
|
@@ -258,7 +264,7 @@ export function apply(ctx: any): void {
|
|
|
258
264
|
return sidebar ? registry.register(sidebar) : undefined
|
|
259
265
|
}, 'vscode-mode: sidebar file opener')
|
|
260
266
|
// 官方侧边栏正文组件装配(页类型正文;兼作 file 认领转发失败时的兜底正文)
|
|
261
|
-
const officialRenderTab = (props: Record<string, unknown>) => React.createElement(OfficialSideTab, Object.assign({}, props, { schedule, addToConversation, sidebarPanels, outlineSources, fileMenuItems, fileOpeners: registry, sessions }))
|
|
267
|
+
const officialRenderTab = (props: Record<string, unknown>) => React.createElement(OfficialSideTab, Object.assign({}, props, { schedule, addToConversation, sidebarPanels, outlineSources, fileMenuItems, fileOpeners: registry, sessions, readSessionScope: () => readSessionScope(ctx) }))
|
|
262
268
|
/** 官方 file 地址认领同步:自动/VSCodeMode 档认领(转发进单一编辑器页签),其余交官方查看器。 */
|
|
263
269
|
const syncFileClaim = (): void => {
|
|
264
270
|
const official = officialService
|
|
@@ -32,6 +32,22 @@ const MODIFIERS: Record<string, 'ctrl' | 'shift' | 'alt' | 'meta'> = {
|
|
|
32
32
|
|
|
33
33
|
/** 弦表(官方 catalog 派生:id → 显示键位弦)。官方目录就绪前为空(tooltip 走各自的兜底文案)。 */
|
|
34
34
|
let current: Record<string, string> = {}
|
|
35
|
+
let currentAria: Record<string, string | undefined> = {}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Read official accessibility metadata without deriving it from visual keycaps.
|
|
39
|
+
* @author ddj 2026年09月28号
|
|
40
|
+
* @param id Command identifier.
|
|
41
|
+
* @returns Official single-key ARIA shortcut, absent for two-key chords.
|
|
42
|
+
*/
|
|
43
|
+
export function ariaOf(id: string): string | undefined { return currentAria[id] }
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Replace accessibility metadata from the official catalog.
|
|
47
|
+
* @author ddj 2026年09月28号
|
|
48
|
+
* @param aria Complete catalog ARIA map, applied before display notification.
|
|
49
|
+
*/
|
|
50
|
+
export function applyKeyAria(aria: Record<string, string | undefined>): void { currentAria = { ...aria } }
|
|
35
51
|
const listeners = new Set<() => void>()
|
|
36
52
|
|
|
37
53
|
/**
|
|
@@ -162,7 +178,8 @@ export function formatChord(binding: Binding): string {
|
|
|
162
178
|
* @param bindings 解析键位或键位数组(null/空 = 未绑定,永不命中)
|
|
163
179
|
* @returns 是否命中
|
|
164
180
|
*/
|
|
165
|
-
export function matchEvent(e: { ctrlKey?: boolean; metaKey?: boolean; shiftKey?: boolean; altKey?: boolean; key?: string }, bindings: Binding | Binding[] | null): boolean {
|
|
181
|
+
export function matchEvent(e: { ctrlKey?: boolean; metaKey?: boolean; shiftKey?: boolean; altKey?: boolean; key?: string; isComposing?: boolean; keyCode?: number; defaultPrevented?: boolean; getModifierState?: (key: string) => boolean }, bindings: Binding | Binding[] | null): boolean {
|
|
182
|
+
if (e.defaultPrevented || e.isComposing || e.keyCode === 229 || e.key === 'Dead' || e.getModifierState?.('AltGraph')) return false
|
|
166
183
|
const list = Array.isArray(bindings) ? bindings : bindings ? [bindings] : []
|
|
167
184
|
for (const binding of list) {
|
|
168
185
|
if (Boolean(e.ctrlKey || e.metaKey) !== (binding.ctrl || binding.meta)) continue
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { applyOfficial, registerThemes } from './theme.js'
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import { appUrl } from '../../shared/appUrl.js'
|
|
10
|
+
|
|
11
|
+
export const MONACO_BASE = appUrl('/edrv/vendor/monaco/vs')
|
|
10
12
|
let monacoPromise = null
|
|
11
13
|
let monacoStage = { phase: 'idle', progress: 0, message: '准备加载 Monaco…' }
|
|
12
14
|
const stageListeners = new Set()
|