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
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
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — 活动编辑器文件路径单点(模块级,不依赖 React)。
|
|
3
|
+
* 命令目录(ui/commandCatalog)是模块级常量、拿不到 EditorView 实例;其可用性判定
|
|
4
|
+
* 需要「当前活动文件是什么」(如 Markdown 预览切换仅 .md 活动文件吞键),故由
|
|
5
|
+
* EditorView 把 active 状态镜像到本 store,目录按需读取。
|
|
6
|
+
* 作者 ddj 2026年10月
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** 当前活动文件路径(null = 无活动文件/编辑器未挂载)。 */
|
|
10
|
+
let activePath: string | null = null
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 镜像活动文件路径(EditorView 渲染同步点调用;卸载时回写 null)。
|
|
14
|
+
* @author ddj 2026年10月
|
|
15
|
+
* @param path 活动文件路径或 null
|
|
16
|
+
*/
|
|
17
|
+
export function setActiveEditorPath(path: string | null): void {
|
|
18
|
+
activePath = path
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 读取当前活动文件路径。
|
|
23
|
+
* @author ddj 2026年10月
|
|
24
|
+
* @returns 活动文件路径或 null
|
|
25
|
+
*/
|
|
26
|
+
export function activeEditorPath(): string | null {
|
|
27
|
+
return activePath
|
|
28
|
+
}
|
|
@@ -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) })
|
|
@@ -1,50 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-vscode-mode client — 指令执行桥(装配层)。
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* 职责两件:① 把内置指令目录注册进注册表(命令栏候选源);② 注入命令栏执行器。
|
|
4
|
+
* 键位派发全部归官方 shortcuts 机制(shortcutsOfficial.registerOfficialShortcuts 注册官方命令,
|
|
5
|
+
* resolve 走注册表 run)——本桥不再挂窗口键位监听,避免与官方派发双执行。
|
|
5
6
|
* 编辑器动作本身由 EditorView / QuickOpen 监听 `edrv.command.<action>` 窗口事件完成——桥不持有
|
|
6
7
|
* React 状态,因此指令系统可在编辑器挂载前装配、卸载后仍安全。
|
|
7
|
-
* 作者 ddj 2026年09月10号
|
|
8
|
+
* 作者 ddj 2026年09月10号 / 2026年10月
|
|
8
9
|
*/
|
|
9
|
-
import {
|
|
10
|
+
import { setPanelRoute } from './panelCommands.js'
|
|
11
|
+
import type { PanelRoute } from './panelCommands.js'
|
|
12
|
+
import { BRIDGE_COMMANDS, EDITOR_COMMANDS, showCommandsDef } from './ui/commandCatalog.js'
|
|
10
13
|
import { createCommandRegistry } from './commandRegistry.js'
|
|
11
14
|
import type { CommandRegistry } from './commandRegistry.js'
|
|
12
|
-
import { bindingsOf, matchEvent } from './keybindings.js'
|
|
13
15
|
import { log } from './log.js'
|
|
14
16
|
import { openCommandPalette, setPaletteRunner, setRegistryRef } from './commandPaletteStore.js'
|
|
15
17
|
|
|
16
18
|
const bridgeLog = log.child('commands')
|
|
17
19
|
|
|
18
|
-
/** 桥接装配选项(事件目标可注入,便于单测)。 */
|
|
19
|
-
export interface CommandBridgeOptions {
|
|
20
|
-
/** 键盘事件目标(缺省 window;无 window 的纯 Node 环境自动跳过监听)。 */
|
|
21
|
-
target?: Pick<EventTarget, 'addEventListener' | 'removeEventListener'>
|
|
22
|
-
}
|
|
23
|
-
|
|
24
20
|
/** 指令执行桥句柄。 */
|
|
25
21
|
export interface CommandBridge {
|
|
26
22
|
/** 指令注册表(第三方可继续 register)。 */
|
|
27
23
|
registry: CommandRegistry
|
|
28
|
-
/**
|
|
24
|
+
/** 卸载全部内置指令(幂等)。 */
|
|
29
25
|
dispose(): void
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
/** 解析事件目标(缺省 window;无 window 返回 null)。 */
|
|
33
|
-
function resolveTarget(options: CommandBridgeOptions): CommandBridgeOptions['target'] | null {
|
|
34
|
-
if (options.target) return options.target
|
|
35
|
-
if (typeof window === 'undefined') return null
|
|
36
|
-
return window
|
|
37
|
-
}
|
|
38
|
-
|
|
39
28
|
/**
|
|
40
|
-
* 创建指令执行桥:注册内置指令 +
|
|
41
|
-
* @author ddj 2026年09月
|
|
42
|
-
* @param
|
|
29
|
+
* 创建指令执行桥:注册内置指令 + 命令栏自身,注入命令栏执行器。
|
|
30
|
+
* @author ddj 2026年09月28号
|
|
31
|
+
* @param panelRoute 当前会话读取器与已有侧栏打开能力(首次打开命令使用)
|
|
43
32
|
* @returns 指令执行桥句柄
|
|
44
33
|
*/
|
|
45
|
-
export function createCommandBridge(
|
|
34
|
+
export function createCommandBridge(panelRoute?: PanelRoute): CommandBridge {
|
|
46
35
|
const registry = createCommandRegistry()
|
|
47
36
|
const disposers: Array<() => void> = []
|
|
37
|
+
if (panelRoute) disposers.push(setPanelRoute(panelRoute))
|
|
48
38
|
const palette = showCommandsDef(() => openCommandPalette('command'))
|
|
49
39
|
for (const command of [...EDITOR_COMMANDS, ...BRIDGE_COMMANDS]) {
|
|
50
40
|
disposers.push(registry.register(command))
|
|
@@ -54,24 +44,7 @@ export function createCommandBridge(options: CommandBridgeOptions = {}): Command
|
|
|
54
44
|
// 命令栏候选来源:模块引用直传(旧实现依赖 window.dsh,DSH 无该命名空间 → 恒空表)
|
|
55
45
|
setRegistryRef(registry)
|
|
56
46
|
|
|
57
|
-
// 全局键位派发集合:桥接类指令 + 命令栏自身(EditorView/QuickOpen 原生监听的 8 条不在内,避免双执行)
|
|
58
|
-
const dispatched = dispatchedCommands(palette)
|
|
59
|
-
const onKey = (event: KeyboardEvent): void => {
|
|
60
|
-
for (const command of dispatched) {
|
|
61
|
-
if (!matchEvent(event, bindingsOf(command.id))) continue
|
|
62
|
-
// 可用性前置:命令不可用时既不执行也不吞键(否则无编辑器时 Ctrl+U 会被吃掉)
|
|
63
|
-
if (!registry.isAvailable(command.id)) continue
|
|
64
|
-
event.preventDefault()
|
|
65
|
-
event.stopPropagation()
|
|
66
|
-
registry.run(command.id)
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
const target = resolveTarget(options)
|
|
71
|
-
if (target) target.addEventListener('keydown', onKey as EventListener, true)
|
|
72
|
-
|
|
73
47
|
const dispose = (): void => {
|
|
74
|
-
if (target) target.removeEventListener('keydown', onKey as EventListener, true)
|
|
75
48
|
while (disposers.length) {
|
|
76
49
|
const release = disposers.pop()
|
|
77
50
|
if (release) release()
|
|
@@ -79,7 +52,6 @@ export function createCommandBridge(options: CommandBridgeOptions = {}): Command
|
|
|
79
52
|
setPaletteRunner(null)
|
|
80
53
|
setRegistryRef(null)
|
|
81
54
|
}
|
|
82
|
-
bridgeLog.info('指令注册表已装配(' + registry.list().length + '
|
|
83
|
-
+ dispatched.length + ' 条)')
|
|
55
|
+
bridgeLog.info('指令注册表已装配(' + registry.list().length + ' 条指令;键位派发已归官方 shortcuts 机制)')
|
|
84
56
|
return { registry, dispose }
|
|
85
57
|
}
|
|
@@ -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
|
/**
|