dsh-bce 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +181 -2
- package/README.zh.md +182 -0
- package/cordis.patch.yml +5 -0
- package/icon.png +0 -0
- package/lib/client.js +544 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +256 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +81 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 linqiu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,182 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="docs/banner.png" alt="DeepSeek Harness and Better Context Engine" width="480" />
|
|
4
|
+
|
|
5
|
+
# dsh-bce
|
|
6
|
+
|
|
7
|
+
**Context-aware prompt enhancement for the DeepSeek Harness composer, powered by your Better Context Engine MCP.**
|
|
8
|
+
|
|
9
|
+
[](package.json) [](#license) [](#requirements) [](https://github.com/deepseek-ai/deepseek-harness)
|
|
10
|
+
|
|
11
|
+
English · [简体中文](README.zh.md)
|
|
12
|
+
|
|
13
|
+
[Demo](#demo) · [Features](#features) · [How it works](#how-it-works) · [Installation](#installation) · [Usage](#usage) · [FAQ](#faq)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
`dsh-bce` adds an enhance icon next to the composer's attach button. One click incrementally indexes the current workspace through the Better Context Engine (BCE) MCP server already configured in your profile, then rewrites the draft into a context-rich prompt.
|
|
20
|
+
|
|
21
|
+
> [!NOTE]
|
|
22
|
+
> The plugin stores no BCE base URL or token. Credentials stay on the MCP entry of the active profile and never reach the browser.
|
|
23
|
+
|
|
24
|
+
## Demo
|
|
25
|
+
|
|
26
|
+
<div align="center">
|
|
27
|
+
<img src="docs/demo.gif" alt="Click the enhance icon, wait for indexing, and the draft is replaced by the enhanced prompt" width="100%" />
|
|
28
|
+
<br />
|
|
29
|
+
<sub>Played at 1.5× speed · <a href="docs/demo.mp4">Download MP4</a></sub>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **One-click enhancement** – the icon sits on `conversation.input.left` (order `-40`), right next to the attach button.
|
|
35
|
+
- **Workspace-aware** – runs an incremental `search_context` before `enhance_prompt`, so the rewrite is grounded in your code.
|
|
36
|
+
- **Clear feedback** – disabled on an empty draft; while running it shows **Enhancing prompt…**, an animated edge ring, and locks further clicks.
|
|
37
|
+
- **Reversible** – **Restore original** brings back the pre-enhancement draft.
|
|
38
|
+
- **Zero extra config** – reuses the profile's bce-tool MCP; no plugin-side BCE settings form.
|
|
39
|
+
- **One bundle** – web and Desktop share the same client build.
|
|
40
|
+
|
|
41
|
+
## Overview
|
|
42
|
+
|
|
43
|
+
| Item | Value |
|
|
44
|
+
| :--- | :--- |
|
|
45
|
+
| Host | [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`web` / Desktop) |
|
|
46
|
+
| Engine | [Better Context Engine](https://github.com/linqiu919/better-context-engine) |
|
|
47
|
+
| MCP server | [bce-tool](https://github.com/linqiu919/bce-tool-rs) (`search_context`, `enhance_prompt`) |
|
|
48
|
+
| Client platform | `dsh.client.platform: web` (shared by web and Desktop) |
|
|
49
|
+
| Enhance timeout | 90 s (`ENHANCE_TIMEOUT_MS`) |
|
|
50
|
+
| Index timeout | MCP `toolCallTimeoutMs` |
|
|
51
|
+
|
|
52
|
+
## How it works
|
|
53
|
+
|
|
54
|
+
```mermaid
|
|
55
|
+
sequenceDiagram
|
|
56
|
+
participant UI as Composer (enhance icon)
|
|
57
|
+
participant Host as dsh-bce Host RPC
|
|
58
|
+
participant MCP as bce-tool MCP
|
|
59
|
+
participant BCE as Better Context Engine
|
|
60
|
+
UI->>Host: dsh-bce.enhance(draft, workspace)
|
|
61
|
+
Host->>MCP: search_context
|
|
62
|
+
MCP->>BCE: incremental index + retrieval
|
|
63
|
+
Host->>MCP: enhance_prompt
|
|
64
|
+
MCP->>BCE: rewrite prompt
|
|
65
|
+
Host-->>UI: enhanced prompt
|
|
66
|
+
UI->>UI: setDraft (original kept for Restore)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
1. The client reads the composer draft and resolves the workspace path.
|
|
70
|
+
2. Host RPC `dsh-bce.enhance` locates `mcp__*__search_context` and `mcp__*__enhance_prompt`; servers whose name contains `bce` are preferred.
|
|
71
|
+
3. `search_context` performs incremental indexing and retrieval.
|
|
72
|
+
4. `enhance_prompt` rewrites the prompt, aborted after 90 s.
|
|
73
|
+
5. The enhanced text replaces the draft. **Restore original** writes back the previous prompt.
|
|
74
|
+
|
|
75
|
+
> [!IMPORTANT]
|
|
76
|
+
> bce-tool must be started with `--no-webbrowser-enhance-prompt`. Without it, `enhance_prompt` waits on a local review page and the request times out.
|
|
77
|
+
|
|
78
|
+
## Requirements
|
|
79
|
+
|
|
80
|
+
| Component | Constraint |
|
|
81
|
+
| :--- | :--- |
|
|
82
|
+
| Node.js | `^22.19` or `>=24` |
|
|
83
|
+
| DeepSeek Harness | web CLI or official Desktop |
|
|
84
|
+
| BCE | Reachable HTTP API and a valid token |
|
|
85
|
+
| bce-tool MCP | Same profile as the plugin, with `--no-webbrowser-enhance-prompt` |
|
|
86
|
+
| Workspace | Git repository, unless the MCP is started with `--allow-non-git` |
|
|
87
|
+
|
|
88
|
+
Web and Desktop profiles do not share MCP servers or plugin installs. Configure each one separately.
|
|
89
|
+
|
|
90
|
+
## Installation
|
|
91
|
+
|
|
92
|
+
### 1. Register the bce-tool MCP
|
|
93
|
+
|
|
94
|
+
Add the following to the target profile's `cordis.patch.yml`:
|
|
95
|
+
|
|
96
|
+
```yaml
|
|
97
|
+
- insert:
|
|
98
|
+
- id: mcp-bce
|
|
99
|
+
name: '@deepseek-ai/dsh-mcp-client'
|
|
100
|
+
config:
|
|
101
|
+
serverName: bce
|
|
102
|
+
transport: stdio
|
|
103
|
+
command: npx
|
|
104
|
+
args:
|
|
105
|
+
- -y
|
|
106
|
+
- bce-tool
|
|
107
|
+
- --base-url
|
|
108
|
+
- 'https://your-bce-host'
|
|
109
|
+
- --token
|
|
110
|
+
- '***'
|
|
111
|
+
- --allow-non-git
|
|
112
|
+
- --no-webbrowser-enhance-prompt
|
|
113
|
+
env:
|
|
114
|
+
PATH: /usr/local/bin:/usr/bin
|
|
115
|
+
toolCallTimeoutMs: 120000
|
|
116
|
+
failOnStartupError: false
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| Field | Notes |
|
|
120
|
+
| :--- | :--- |
|
|
121
|
+
| `serverName` | Any name. Discovery matches tool-name suffixes; names containing `bce` are preferred. |
|
|
122
|
+
| `command` | On Desktop / Electron, use an absolute `npx` path — the GUI `PATH` usually omits nvm. |
|
|
123
|
+
| `toolCallTimeoutMs` | Indexing budget, independent of the plugin's 90 s enhance abort. |
|
|
124
|
+
|
|
125
|
+
### 2. Install the plugin
|
|
126
|
+
|
|
127
|
+
Install the prebuilt package from npm. No local build is required.
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
# web
|
|
131
|
+
dsh plugin --profile web add dsh-bce
|
|
132
|
+
|
|
133
|
+
# official Desktop: launch once, quit completely, then run
|
|
134
|
+
dsh plugin --profile desktop add dsh-bce
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
If the packaged Desktop app boots the `web` profile under its own data directory, install into that profile instead.
|
|
138
|
+
|
|
139
|
+
To update later, run the same command with `dsh-bce@latest`.
|
|
140
|
+
|
|
141
|
+
### 3. Restart
|
|
142
|
+
|
|
143
|
+
Quit DeepSeek Harness completely and start it again.
|
|
144
|
+
|
|
145
|
+
> [!TIP]
|
|
146
|
+
> Host and client plugins load at process start. A soft reload of the window is not enough.
|
|
147
|
+
|
|
148
|
+
### Build from source (optional)
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
git clone https://github.com/linqiu919/dsh-bce.git
|
|
152
|
+
cd dsh-bce
|
|
153
|
+
pnpm install && pnpm build
|
|
154
|
+
dsh plugin --profile web add "$(pwd)"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Usage
|
|
158
|
+
|
|
159
|
+
1. Open a workspace.
|
|
160
|
+
2. Type a non-empty prompt in the composer.
|
|
161
|
+
3. Click the enhance icon. The status reads **Enhancing prompt…**
|
|
162
|
+
4. When it finishes, the draft is replaced. Click **Restore original** to go back.
|
|
163
|
+
|
|
164
|
+
## FAQ
|
|
165
|
+
|
|
166
|
+
| Symptom | Cause | Resolution |
|
|
167
|
+
| :--- | :--- | :--- |
|
|
168
|
+
| BCE MCP was not found | `enhance_prompt` is not loaded in this profile | Add the MCP entry above with `--no-webbrowser-enhance-prompt`, then restart. Configure web and Desktop separately. |
|
|
169
|
+
| Open a workspace first | The session has no workspace root | Select a workspace and retry. |
|
|
170
|
+
| Enhancement is slow | Latency is tied directly to the enhancement model configured in the BCE engine: `enhance_prompt` is generated by that model, so duration depends on its inference speed, output length, and upstream load | Switch to a faster enhancement model in the BCE engine, or check the model service load. The plugin only calls the tool and writes back the result; it does not affect generation time. |
|
|
171
|
+
| Enhancement timed out (90 s) | Review page blocking, or a slow BCE enhancement model | Make sure `--no-webbrowser-enhance-prompt` is set; if it still times out, switch to a faster enhancement model in the BCE engine. Indexing is governed by `toolCallTimeoutMs`, not the 90 s cap. |
|
|
172
|
+
| Not a Git repository | bce-tool default | Add `--allow-non-git`. |
|
|
173
|
+
| Project too large to index | bce-tool size limit | Narrow the tree with `.gitignore` / `.bceignore`. |
|
|
174
|
+
| No response after an update | Stale process | Quit completely and reopen. |
|
|
175
|
+
| Cmd/Ctrl+Z does not revert | `setDraft` is outside the composer undo stack | Use **Restore original**. |
|
|
176
|
+
| Markdown is not rendered in the composer | The composer is a plain-text draft with file-reference chips | Keep enhancer output as plain text. |
|
|
177
|
+
|
|
178
|
+
**Why not call BCE from the browser?** The BCE service has no CORS layer, and the token must stay on the host. The enhance icon calls the host RPC, which executes the configured MCP tools.
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="docs/banner.png" alt="DeepSeek Harness 与 Better Context Engine" width="480" />
|
|
4
|
+
|
|
5
|
+
# dsh-bce
|
|
6
|
+
|
|
7
|
+
**为 DeepSeek Harness Composer 提供基于工作区上下文的提示增强,复用你已配置的 Better Context Engine MCP。**
|
|
8
|
+
|
|
9
|
+
[](package.json) [](#许可) [](#环境要求) [](https://github.com/deepseek-ai/deepseek-harness)
|
|
10
|
+
|
|
11
|
+
[English](README.md) · 简体中文
|
|
12
|
+
|
|
13
|
+
[演示](#演示) · [功能](#功能) · [工作原理](#工作原理) · [安装](#安装) · [使用](#使用) · [常见问题](#常见问题)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
`dsh-bce` 在 Composer 附件按钮旁增加一个增强图标。点击后,插件通过当前 profile 已配置的 Better Context Engine(BCE)MCP 对工作区做增量索引,再把草稿重写为带有上下文的提示词。
|
|
20
|
+
|
|
21
|
+
> [!NOTE]
|
|
22
|
+
> 插件不保存 BCE 服务地址或 Token。凭证仅存在于当前 profile 的 MCP 配置中,不会进入浏览器。
|
|
23
|
+
|
|
24
|
+
## 演示
|
|
25
|
+
|
|
26
|
+
<div align="center">
|
|
27
|
+
<img src="docs/demo.gif" alt="点击增强图标,等待索引完成后,草稿被替换为增强后的提示词" width="100%" />
|
|
28
|
+
<br />
|
|
29
|
+
<sub>1.5 倍速播放 · <a href="docs/demo.mp4">下载 MP4</a></sub>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
## 功能
|
|
33
|
+
|
|
34
|
+
- **一键增强**:图标位于插槽 `conversation.input.left`(order `-40`),紧挨附件按钮。
|
|
35
|
+
- **感知工作区**:先增量 `search_context`,再 `enhance_prompt`,增强结果基于你的代码。
|
|
36
|
+
- **状态清晰**:草稿为空时禁用;进行中显示 **提示增强中**、边缘流动光环,并锁定重复点击。
|
|
37
|
+
- **可还原**:增强成功后可点 **还原** 恢复原稿。
|
|
38
|
+
- **零额外配置**:复用 profile 中的 bce-tool MCP,不提供插件内 BCE 配置页。
|
|
39
|
+
- **单一产物**:web 与 Desktop 共用同一客户端构建。
|
|
40
|
+
|
|
41
|
+
## 概览
|
|
42
|
+
|
|
43
|
+
| 项目 | 说明 |
|
|
44
|
+
| :--- | :--- |
|
|
45
|
+
| 宿主 | [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`web` / Desktop) |
|
|
46
|
+
| 引擎 | [Better Context Engine](https://github.com/linqiu919/better-context-engine) |
|
|
47
|
+
| MCP 服务 | [bce-tool](https://github.com/linqiu919/bce-tool-rs)(`search_context`、`enhance_prompt`) |
|
|
48
|
+
| 客户端平台 | `dsh.client.platform: web`(web 与 Desktop 共用) |
|
|
49
|
+
| 增强超时 | 90 秒(`ENHANCE_TIMEOUT_MS`) |
|
|
50
|
+
| 索引超时 | MCP `toolCallTimeoutMs` |
|
|
51
|
+
|
|
52
|
+
## 工作原理
|
|
53
|
+
|
|
54
|
+
```mermaid
|
|
55
|
+
sequenceDiagram
|
|
56
|
+
participant UI as Composer(增强图标)
|
|
57
|
+
participant Host as dsh-bce Host RPC
|
|
58
|
+
participant MCP as bce-tool MCP
|
|
59
|
+
participant BCE as Better Context Engine
|
|
60
|
+
UI->>Host: dsh-bce.enhance(草稿、工作区)
|
|
61
|
+
Host->>MCP: search_context
|
|
62
|
+
MCP->>BCE: 增量索引与检索
|
|
63
|
+
Host->>MCP: enhance_prompt
|
|
64
|
+
MCP->>BCE: 重写提示词
|
|
65
|
+
Host-->>UI: 增强结果
|
|
66
|
+
UI->>UI: setDraft(保留原稿供还原)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
1. 客户端读取 Composer 草稿并解析工作区路径。
|
|
70
|
+
2. Host RPC `dsh-bce.enhance` 发现 `mcp__*__search_context` 与 `mcp__*__enhance_prompt`,优先使用名称含 `bce` 的服务。
|
|
71
|
+
3. `search_context` 执行增量索引与检索。
|
|
72
|
+
4. `enhance_prompt` 重写提示词,超过 90 秒自动中止。
|
|
73
|
+
5. 增强文本替换草稿,**还原** 可写回增强前的原文。
|
|
74
|
+
|
|
75
|
+
> [!IMPORTANT]
|
|
76
|
+
> bce-tool 必须带 `--no-webbrowser-enhance-prompt` 启动,否则 `enhance_prompt` 会阻塞在本地审查页,最终超时。
|
|
77
|
+
|
|
78
|
+
## 环境要求
|
|
79
|
+
|
|
80
|
+
| 组件 | 约束 |
|
|
81
|
+
| :--- | :--- |
|
|
82
|
+
| Node.js | `^22.19` 或 `>=24` |
|
|
83
|
+
| DeepSeek Harness | web CLI 或官方 Desktop |
|
|
84
|
+
| BCE | 可访问的 HTTP API 与有效 Token |
|
|
85
|
+
| bce-tool MCP | 与插件位于同一 profile,并带 `--no-webbrowser-enhance-prompt` |
|
|
86
|
+
| 工作区 | Git 仓库;非 Git 目录需 MCP 启用 `--allow-non-git` |
|
|
87
|
+
|
|
88
|
+
web 与 Desktop 的 MCP 和插件安装相互隔离,需要分别配置。
|
|
89
|
+
|
|
90
|
+
## 安装
|
|
91
|
+
|
|
92
|
+
### 1. 注册 bce-tool MCP
|
|
93
|
+
|
|
94
|
+
将以下内容加入目标 profile 的 `cordis.patch.yml`:
|
|
95
|
+
|
|
96
|
+
```yaml
|
|
97
|
+
- insert:
|
|
98
|
+
- id: mcp-bce
|
|
99
|
+
name: '@deepseek-ai/dsh-mcp-client'
|
|
100
|
+
config:
|
|
101
|
+
serverName: bce
|
|
102
|
+
transport: stdio
|
|
103
|
+
command: npx
|
|
104
|
+
args:
|
|
105
|
+
- -y
|
|
106
|
+
- bce-tool
|
|
107
|
+
- --base-url
|
|
108
|
+
- 'https://your-bce-host'
|
|
109
|
+
- --token
|
|
110
|
+
- '***'
|
|
111
|
+
- --allow-non-git
|
|
112
|
+
- --no-webbrowser-enhance-prompt
|
|
113
|
+
env:
|
|
114
|
+
PATH: /usr/local/bin:/usr/bin
|
|
115
|
+
toolCallTimeoutMs: 120000
|
|
116
|
+
failOnStartupError: false
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| 字段 | 说明 |
|
|
120
|
+
| :--- | :--- |
|
|
121
|
+
| `serverName` | 可自定义。按工具名后缀发现,名称含 `bce` 时优先。 |
|
|
122
|
+
| `command` | Desktop / Electron 建议使用 `npx` 绝对路径,图形界面的 `PATH` 通常不含 nvm。 |
|
|
123
|
+
| `toolCallTimeoutMs` | 索引超时,与插件 90 秒的增强中止相互独立。 |
|
|
124
|
+
|
|
125
|
+
### 2. 安装插件
|
|
126
|
+
|
|
127
|
+
直接从 npm 安装已构建好的包,无需本地构建。
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
# web
|
|
131
|
+
dsh plugin --profile web add dsh-bce
|
|
132
|
+
|
|
133
|
+
# 官方 Desktop:先启动一次,完全退出后再执行
|
|
134
|
+
dsh plugin --profile desktop add dsh-bce
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
若打包版 Desktop 实际启动的是其数据目录下的 `web` profile,请对该 profile 执行安装。
|
|
138
|
+
|
|
139
|
+
后续更新时,将包名换成 `dsh-bce@latest` 再执行一次即可。
|
|
140
|
+
|
|
141
|
+
### 3. 重启
|
|
142
|
+
|
|
143
|
+
完全退出 DeepSeek Harness 后重新启动。
|
|
144
|
+
|
|
145
|
+
> [!TIP]
|
|
146
|
+
> 宿主与客户端插件在进程启动时加载,仅刷新窗口不会生效。
|
|
147
|
+
|
|
148
|
+
### 从源码构建(可选)
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
git clone https://github.com/linqiu919/dsh-bce.git
|
|
152
|
+
cd dsh-bce
|
|
153
|
+
pnpm install && pnpm build
|
|
154
|
+
dsh plugin --profile web add "$(pwd)"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 使用
|
|
158
|
+
|
|
159
|
+
1. 打开工作区。
|
|
160
|
+
2. 在 Composer 中输入非空提示词。
|
|
161
|
+
3. 点击增强图标,状态显示 **提示增强中**。
|
|
162
|
+
4. 完成后草稿被替换;点击 **还原** 可恢复原文。
|
|
163
|
+
|
|
164
|
+
## 常见问题
|
|
165
|
+
|
|
166
|
+
| 现象 | 原因 | 处理 |
|
|
167
|
+
| :--- | :--- | :--- |
|
|
168
|
+
| 未检测到 BCE MCP | 当前 profile 未加载 `enhance_prompt` | 按上文添加 MCP(含 `--no-webbrowser-enhance-prompt`)后重启。web 与 Desktop 需分别配置。 |
|
|
169
|
+
| 请先打开一个工作区 | 会话未绑定工作区根目录 | 选择工作区后重试。 |
|
|
170
|
+
| 增强耗时较长 | 增强延迟与 BCE 引擎配置的增强模型直接相关:`enhance_prompt` 由该模型生成,耗时取决于模型推理速度、输出长度与上游负载 | 在 BCE 引擎中改用响应更快的增强模型,或检查模型服务的负载。插件本身只负责调用和写回,不影响生成耗时。 |
|
|
171
|
+
| 增强超时(90 秒) | 审查页阻塞,或 BCE 增强模型响应过慢 | 确认已加 `--no-webbrowser-enhance-prompt`;仍超时则在 BCE 引擎中更换更快的增强模型。索引耗时由 `toolCallTimeoutMs` 控制,不受 90 秒限制。 |
|
|
172
|
+
| 不是 Git 仓库 | bce-tool 默认行为 | 添加 `--allow-non-git`。 |
|
|
173
|
+
| 项目过大,无法索引 | bce-tool 体积限制 | 使用 `.gitignore` / `.bceignore` 缩小范围。 |
|
|
174
|
+
| 更新后点击无响应 | 进程未重新加载 | 完全退出后重启。 |
|
|
175
|
+
| Cmd/Ctrl+Z 无法撤销 | `setDraft` 不进入 Composer 撤销栈 | 使用 **还原**。 |
|
|
176
|
+
| 输入框不渲染 Markdown | Composer 是纯文本草稿加文件引用 chip | 增强结果使用纯文本。 |
|
|
177
|
+
|
|
178
|
+
**为什么不从浏览器直连 BCE?** BCE 服务端没有 CORS,且 Token 不能进入浏览器。增强图标通过 Host RPC 调用已配置的 MCP 工具。
|
|
179
|
+
|
|
180
|
+
## 许可
|
|
181
|
+
|
|
182
|
+
MIT
|
package/cordis.patch.yml
ADDED
package/icon.png
ADDED
|
Binary file
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-bce",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
// \0rolldown/runtime.js
|
|
8
|
+
var __create = Object.create;
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
16
|
+
key = keys[i];
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
18
|
+
get: ((k) => from[k]).bind(null, key),
|
|
19
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
25
|
+
value: mod,
|
|
26
|
+
enumerable: true
|
|
27
|
+
}) : target, mod));
|
|
28
|
+
let react = require("react");
|
|
29
|
+
react = __toESM(react, 1);
|
|
30
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
31
|
+
// src/client/constants.ts
|
|
32
|
+
const NAMESPACE = "dsh-bce";
|
|
33
|
+
const LOCALE_NAMESPACE = "settings.dshBce";
|
|
34
|
+
// src/client/locales.ts
|
|
35
|
+
const en = {
|
|
36
|
+
"wand.label": "Enhance prompt with BCE",
|
|
37
|
+
"wand.enhancing": "Enhancing prompt…",
|
|
38
|
+
"wand.empty": "Enter a prompt first.",
|
|
39
|
+
"wand.noWorkspace": "Open a workspace first.",
|
|
40
|
+
"wand.mcpMissing": "BCE MCP was not found. Add bce-tool to this profile with --no-webbrowser-enhance-prompt.",
|
|
41
|
+
"wand.notGit": "This folder is not a Git repository. bce-tool only indexes Git projects.",
|
|
42
|
+
"wand.tooLarge": "This project is too large to index. Trim it with .gitignore or .bceignore.",
|
|
43
|
+
"wand.timeout": "Enhancement timed out (90s). Add --no-webbrowser-enhance-prompt to the bce-tool MCP args.",
|
|
44
|
+
"wand.indexFailed": "Indexing failed. Check the bce-tool MCP logs.",
|
|
45
|
+
"wand.upstream": "Enhancement failed. Check MCP tool permissions and bce-tool logs.",
|
|
46
|
+
"wand.noIndex": "Enhanced without a fresh index (search_context was not found).",
|
|
47
|
+
"wand.done": "Prompt enhanced.",
|
|
48
|
+
"wand.noRpc": "Enhancement is not available. Confirm dsh-bce is installed in this profile and restart.",
|
|
49
|
+
"wand.restore": "Restore original"
|
|
50
|
+
};
|
|
51
|
+
const zh = {
|
|
52
|
+
"wand.label": "用 BCE 增强提示",
|
|
53
|
+
"wand.enhancing": "提示增强中",
|
|
54
|
+
"wand.empty": "请先输入提示。",
|
|
55
|
+
"wand.noWorkspace": "请先打开一个工作区。",
|
|
56
|
+
"wand.mcpMissing": "未检测到 BCE MCP。请在当前 profile 配置 bce-tool,并加上 --no-webbrowser-enhance-prompt。",
|
|
57
|
+
"wand.notGit": "当前目录不是 Git 仓库。bce-tool 默认只索引 Git 项目。",
|
|
58
|
+
"wand.tooLarge": "项目过大,无法索引。请用 .gitignore 或 .bceignore 排除大目录后再试。",
|
|
59
|
+
"wand.timeout": "增强超时(90 秒)。请确认 MCP 启动参数包含 --no-webbrowser-enhance-prompt。",
|
|
60
|
+
"wand.indexFailed": "索引失败。请查看 bce-tool MCP 日志。",
|
|
61
|
+
"wand.upstream": "增强失败。请检查 MCP 工具权限和 bce-tool 日志。",
|
|
62
|
+
"wand.noIndex": "已增强,但未做索引(未找到 search_context)。",
|
|
63
|
+
"wand.done": "提示已增强。",
|
|
64
|
+
"wand.noRpc": "增强服务未就绪。请确认当前 profile 已安装 dsh-bce 并重启。",
|
|
65
|
+
"wand.restore": "还原"
|
|
66
|
+
};
|
|
67
|
+
// src/client/styles.ts
|
|
68
|
+
const CSS = `
|
|
69
|
+
.dshbce-wrap {
|
|
70
|
+
display: inline-flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
gap: 6px;
|
|
73
|
+
height: 26px;
|
|
74
|
+
max-width: 280px;
|
|
75
|
+
}
|
|
76
|
+
.dshbce-control {
|
|
77
|
+
position: relative;
|
|
78
|
+
width: 26px;
|
|
79
|
+
height: 26px;
|
|
80
|
+
flex: none;
|
|
81
|
+
}
|
|
82
|
+
.dshbce-btn {
|
|
83
|
+
position: relative;
|
|
84
|
+
z-index: 1;
|
|
85
|
+
display: inline-flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
justify-content: center;
|
|
88
|
+
flex: none;
|
|
89
|
+
width: 26px;
|
|
90
|
+
height: 26px;
|
|
91
|
+
padding: 0;
|
|
92
|
+
border: none;
|
|
93
|
+
border-radius: 50%;
|
|
94
|
+
background: var(--dsw-alias-fill-secondary, var(--dsw-alias-bg-tertiary, rgba(15, 23, 42, 0.05)));
|
|
95
|
+
color: var(--dsw-alias-label-secondary);
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
transition: background-color 0.12s cubic-bezier(0.16, 1, 0.3, 1), color 0.12s cubic-bezier(0.16, 1, 0.3, 1);
|
|
98
|
+
}
|
|
99
|
+
.dshbce-btn:hover {
|
|
100
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(15, 23, 42, 0.08));
|
|
101
|
+
color: var(--dsw-alias-label-primary);
|
|
102
|
+
}
|
|
103
|
+
.dshbce-btn:hover:active {
|
|
104
|
+
transform: scale(0.96);
|
|
105
|
+
}
|
|
106
|
+
.dshbce-btn:disabled:not([data-busy='true']) {
|
|
107
|
+
opacity: 0.45;
|
|
108
|
+
cursor: not-allowed;
|
|
109
|
+
color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));
|
|
110
|
+
}
|
|
111
|
+
.dshbce-btn:disabled:not([data-busy='true']):hover {
|
|
112
|
+
background: var(--dsw-alias-fill-secondary, var(--dsw-alias-bg-tertiary, rgba(15, 23, 42, 0.05)));
|
|
113
|
+
}
|
|
114
|
+
.dshbce-icon {
|
|
115
|
+
width: 13px;
|
|
116
|
+
height: 13px;
|
|
117
|
+
display: block;
|
|
118
|
+
}
|
|
119
|
+
.dshbce-ring {
|
|
120
|
+
position: absolute;
|
|
121
|
+
inset: 0;
|
|
122
|
+
border-radius: 50%;
|
|
123
|
+
pointer-events: none;
|
|
124
|
+
opacity: 0;
|
|
125
|
+
}
|
|
126
|
+
.dshbce-wrap[data-busy='true'] .dshbce-control {
|
|
127
|
+
pointer-events: none;
|
|
128
|
+
}
|
|
129
|
+
.dshbce-wrap[data-busy='true'] .dshbce-ring {
|
|
130
|
+
opacity: 0.72;
|
|
131
|
+
animation: dshbce-from 1.6s linear infinite;
|
|
132
|
+
background: conic-gradient(
|
|
133
|
+
from var(--dshbce-from, 0deg) at 50% 50%,
|
|
134
|
+
transparent 0deg,
|
|
135
|
+
rgba(56, 189, 248, 0) 210deg,
|
|
136
|
+
rgba(56, 189, 248, 0.35) 255deg,
|
|
137
|
+
rgba(99, 102, 241, 0.55) 300deg,
|
|
138
|
+
rgba(168, 85, 247, 0.4) 335deg,
|
|
139
|
+
transparent 360deg
|
|
140
|
+
);
|
|
141
|
+
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 1.5px), #000 calc(100% - 1px));
|
|
142
|
+
mask: radial-gradient(farthest-side, transparent calc(100% - 1.5px), #000 calc(100% - 1px));
|
|
143
|
+
}
|
|
144
|
+
.dshbce-btn[data-busy='true'] {
|
|
145
|
+
opacity: 1;
|
|
146
|
+
cursor: progress;
|
|
147
|
+
pointer-events: none;
|
|
148
|
+
color: #4f46e5;
|
|
149
|
+
background: var(--dsw-alias-fill-secondary, var(--dsw-alias-bg-tertiary, rgba(15, 23, 42, 0.05)));
|
|
150
|
+
}
|
|
151
|
+
.dshbce-btn[data-busy='true']:hover {
|
|
152
|
+
color: #4f46e5;
|
|
153
|
+
background: var(--dsw-alias-fill-secondary, var(--dsw-alias-bg-tertiary, rgba(15, 23, 42, 0.05)));
|
|
154
|
+
}
|
|
155
|
+
@property --dshbce-from {
|
|
156
|
+
syntax: '<angle>';
|
|
157
|
+
inherits: false;
|
|
158
|
+
initial-value: 0deg;
|
|
159
|
+
}
|
|
160
|
+
@keyframes dshbce-from {
|
|
161
|
+
to { --dshbce-from: 360deg; }
|
|
162
|
+
}
|
|
163
|
+
.dshbce-status {
|
|
164
|
+
min-width: 0;
|
|
165
|
+
max-width: 148px;
|
|
166
|
+
font-size: 11px;
|
|
167
|
+
line-height: 1.2;
|
|
168
|
+
white-space: nowrap;
|
|
169
|
+
overflow: hidden;
|
|
170
|
+
text-overflow: ellipsis;
|
|
171
|
+
color: var(--dsw-alias-label-tertiary, #8b8b8b);
|
|
172
|
+
pointer-events: none;
|
|
173
|
+
}
|
|
174
|
+
.dshbce-status[data-level='busy'] {
|
|
175
|
+
color: #6366f1;
|
|
176
|
+
}
|
|
177
|
+
.dshbce-status[data-level='error'] {
|
|
178
|
+
color: var(--dsw-alias-state-error-primary, #e5484d);
|
|
179
|
+
}
|
|
180
|
+
.dshbce-status[data-level='ok'] {
|
|
181
|
+
color: var(--dsw-alias-state-success-primary, #30a46c);
|
|
182
|
+
}
|
|
183
|
+
@media (prefers-reduced-motion: reduce) {
|
|
184
|
+
.dshbce-wrap[data-busy='true'] .dshbce-ring {
|
|
185
|
+
animation: none;
|
|
186
|
+
opacity: 0.45;
|
|
187
|
+
}
|
|
188
|
+
.dshbce-btn,
|
|
189
|
+
.dshbce-restore {
|
|
190
|
+
transition: none;
|
|
191
|
+
animation: none;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
.dshbce-restore {
|
|
195
|
+
height: 26px;
|
|
196
|
+
padding: 0 8px;
|
|
197
|
+
border: none;
|
|
198
|
+
border-radius: 8px;
|
|
199
|
+
background: transparent;
|
|
200
|
+
color: var(--dsw-alias-label-secondary);
|
|
201
|
+
font-size: 11px;
|
|
202
|
+
line-height: 26px;
|
|
203
|
+
cursor: pointer;
|
|
204
|
+
}
|
|
205
|
+
.dshbce-restore:hover {
|
|
206
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(15, 23, 42, 0.08));
|
|
207
|
+
color: var(--dsw-alias-label-primary);
|
|
208
|
+
}
|
|
209
|
+
.dshbce-live {
|
|
210
|
+
position: absolute;
|
|
211
|
+
width: 1px;
|
|
212
|
+
height: 1px;
|
|
213
|
+
padding: 0;
|
|
214
|
+
margin: -1px;
|
|
215
|
+
overflow: hidden;
|
|
216
|
+
clip: rect(0, 0, 0, 0);
|
|
217
|
+
white-space: nowrap;
|
|
218
|
+
border: 0;
|
|
219
|
+
}
|
|
220
|
+
`;
|
|
221
|
+
function injectStyles() {
|
|
222
|
+
const tag = document.createElement("style");
|
|
223
|
+
tag.dataset.plugin = NAMESPACE;
|
|
224
|
+
tag.textContent = CSS;
|
|
225
|
+
document.head.appendChild(tag);
|
|
226
|
+
return () => {
|
|
227
|
+
tag.remove();
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
// src/protocol.ts
|
|
231
|
+
/** Shared wire contract between the Host RPC and the browser wand. */
|
|
232
|
+
const RPC_CHANNEL = "/api";
|
|
233
|
+
const RPC_ENDPOINT = "dsh-bce.enhance";
|
|
234
|
+
// src/client/restore-store.ts
|
|
235
|
+
let snap;
|
|
236
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
237
|
+
function getRestore() {
|
|
238
|
+
return snap;
|
|
239
|
+
}
|
|
240
|
+
function subscribeRestore(listener) {
|
|
241
|
+
listeners.add(listener);
|
|
242
|
+
return () => {
|
|
243
|
+
listeners.delete(listener);
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function setRestore(next) {
|
|
247
|
+
snap = next;
|
|
248
|
+
for (const listener of listeners) listener();
|
|
249
|
+
}
|
|
250
|
+
// src/client/wand-button.tsx
|
|
251
|
+
function nonEmpty(value) {
|
|
252
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
253
|
+
}
|
|
254
|
+
function cwdOf(value) {
|
|
255
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
256
|
+
const rec = value;
|
|
257
|
+
return nonEmpty(rec.cwd) ?? cwdOf(rec.header);
|
|
258
|
+
}
|
|
259
|
+
function snapshotOf(holder) {
|
|
260
|
+
try {
|
|
261
|
+
return holder?.list?.getSnapshot?.();
|
|
262
|
+
} catch {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function hookValue(props, name) {
|
|
267
|
+
const hook = props[name];
|
|
268
|
+
if (typeof hook !== "function") return void 0;
|
|
269
|
+
try {
|
|
270
|
+
return hook((state) => state);
|
|
271
|
+
} catch {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function resolveWorkspace(props, extras) {
|
|
276
|
+
const session = hookValue(props, "useSession") ?? props.session;
|
|
277
|
+
const sessions = hookValue(props, "useSessions") ?? snapshotOf(extras.sessions);
|
|
278
|
+
const workspaces = hookValue(props, "useWorkspaces") ?? snapshotOf(extras.workspaces);
|
|
279
|
+
const sessionId = nonEmpty(props.sessionId) ?? nonEmpty(sessions?.current);
|
|
280
|
+
const fromSession = cwdOf(session);
|
|
281
|
+
if (fromSession !== void 0) return {
|
|
282
|
+
path: fromSession,
|
|
283
|
+
sessionId
|
|
284
|
+
};
|
|
285
|
+
if (sessionId !== void 0) {
|
|
286
|
+
const rowCwd = cwdOf(sessions?.byId?.[sessionId]);
|
|
287
|
+
if (rowCwd !== void 0) return {
|
|
288
|
+
path: rowCwd,
|
|
289
|
+
sessionId
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const items = workspaces?.items ?? [];
|
|
293
|
+
if (sessionId !== void 0) {
|
|
294
|
+
const ownedPath = nonEmpty(items.find((item) => item.sessionIds?.includes(sessionId))?.path);
|
|
295
|
+
if (ownedPath !== void 0) return {
|
|
296
|
+
path: ownedPath,
|
|
297
|
+
sessionId
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const recentId = nonEmpty(workspaces?.recentWorkspaceId);
|
|
301
|
+
if (recentId !== void 0) {
|
|
302
|
+
const recentPath = nonEmpty(items.find((item) => item.workspaceId === recentId || item.id === recentId)?.path);
|
|
303
|
+
if (recentPath !== void 0) return {
|
|
304
|
+
path: recentPath,
|
|
305
|
+
sessionId
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
path: void 0,
|
|
310
|
+
sessionId
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function messageFor(code, t) {
|
|
314
|
+
switch (code) {
|
|
315
|
+
case "mcp_missing": return t("wand.mcpMissing");
|
|
316
|
+
case "no_workspace": return t("wand.noWorkspace");
|
|
317
|
+
case "empty_prompt": return t("wand.empty");
|
|
318
|
+
case "not_git": return t("wand.notGit");
|
|
319
|
+
case "too_large": return t("wand.tooLarge");
|
|
320
|
+
case "timeout": return t("wand.timeout");
|
|
321
|
+
case "index_failed": return t("wand.indexFailed");
|
|
322
|
+
case "no_rpc": return t("wand.noRpc");
|
|
323
|
+
default: return t("wand.upstream");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function readRpc(ctx) {
|
|
327
|
+
return ctx.get("connection")?.rpc;
|
|
328
|
+
}
|
|
329
|
+
function parseEnhanceResult(value) {
|
|
330
|
+
let current = value;
|
|
331
|
+
if (typeof current === "object" && current !== null && "result" in current) current = current.result;
|
|
332
|
+
if (typeof current !== "object" || current === null) return void 0;
|
|
333
|
+
const rec = current;
|
|
334
|
+
if (rec.ok === true) {
|
|
335
|
+
const payload = rec.value;
|
|
336
|
+
if (typeof payload === "object" && payload !== null && typeof payload.text === "string") return {
|
|
337
|
+
ok: true,
|
|
338
|
+
value: payload
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
if (rec.ok === false && typeof rec.error === "object" && rec.error !== null) {
|
|
342
|
+
const error = rec.error;
|
|
343
|
+
const nested = error.details?.code;
|
|
344
|
+
const code = typeof error.code === "string" && error.code !== "internal" ? error.code : typeof nested === "string" ? nested : "upstream";
|
|
345
|
+
return {
|
|
346
|
+
ok: false,
|
|
347
|
+
error: {
|
|
348
|
+
code,
|
|
349
|
+
message: typeof error.message === "string" ? error.message : code,
|
|
350
|
+
details: {}
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function WandIcon() {
|
|
356
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
357
|
+
className: "dshbce-icon",
|
|
358
|
+
viewBox: "0 0 24 24",
|
|
359
|
+
fill: "none",
|
|
360
|
+
stroke: "currentColor",
|
|
361
|
+
strokeWidth: "2",
|
|
362
|
+
strokeLinecap: "round",
|
|
363
|
+
strokeLinejoin: "round",
|
|
364
|
+
"aria-hidden": "true",
|
|
365
|
+
focusable: "false",
|
|
366
|
+
children: [
|
|
367
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72" }),
|
|
368
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m14 7 3 3" }),
|
|
369
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5 6v4" }),
|
|
370
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M19 14v4" }),
|
|
371
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M10 2v2" }),
|
|
372
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M7 8H3" }),
|
|
373
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M21 16h-4" }),
|
|
374
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M11 3H9" })
|
|
375
|
+
]
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function WandButton(props) {
|
|
379
|
+
const { t, ctx, inputActions, sessionsStore, workspacesStore } = props;
|
|
380
|
+
const [busy, setBusy] = react.default.useState(false);
|
|
381
|
+
const [status, setStatus] = react.default.useState("");
|
|
382
|
+
const [level, setLevel] = react.default.useState("idle");
|
|
383
|
+
const lock = react.default.useRef(false);
|
|
384
|
+
const draftFromHook = props.useInput?.((state) => state.draft);
|
|
385
|
+
const draft = typeof draftFromHook === "string" ? draftFromHook : props.input?.draft ?? "";
|
|
386
|
+
const extras = {
|
|
387
|
+
sessions: sessionsStore,
|
|
388
|
+
workspaces: workspacesStore
|
|
389
|
+
};
|
|
390
|
+
const sessionId = nonEmpty(props.sessionId);
|
|
391
|
+
const empty = draft.trim().length === 0;
|
|
392
|
+
const disabled = busy || empty;
|
|
393
|
+
const title = empty ? t("wand.empty") : status.length > 0 ? status : t("wand.label");
|
|
394
|
+
const announce = (nextLevel, text) => {
|
|
395
|
+
setStatus(text);
|
|
396
|
+
setLevel(nextLevel);
|
|
397
|
+
};
|
|
398
|
+
react.default.useEffect(() => {
|
|
399
|
+
if (level !== "ok") return;
|
|
400
|
+
const timer = window.setTimeout(() => {
|
|
401
|
+
setStatus("");
|
|
402
|
+
setLevel("idle");
|
|
403
|
+
}, 3500);
|
|
404
|
+
return () => {
|
|
405
|
+
window.clearTimeout(timer);
|
|
406
|
+
};
|
|
407
|
+
}, [level]);
|
|
408
|
+
const onClick = async () => {
|
|
409
|
+
if (lock.current || empty) return;
|
|
410
|
+
lock.current = true;
|
|
411
|
+
setBusy(true);
|
|
412
|
+
announce("busy", t("wand.enhancing"));
|
|
413
|
+
const prompt = draft.trim();
|
|
414
|
+
const workspace = resolveWorkspace(props, extras);
|
|
415
|
+
try {
|
|
416
|
+
if (workspace.path === void 0 && workspace.sessionId === void 0) {
|
|
417
|
+
announce("error", t("wand.noWorkspace"));
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const rpc = readRpc(ctx);
|
|
421
|
+
if (rpc === void 0) {
|
|
422
|
+
announce("error", t("wand.noRpc"));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const parsed = parseEnhanceResult(await rpc.call(RPC_CHANNEL, RPC_ENDPOINT, {
|
|
426
|
+
prompt,
|
|
427
|
+
workspacePath: workspace.path,
|
|
428
|
+
sessionId: workspace.sessionId ?? sessionId
|
|
429
|
+
}));
|
|
430
|
+
if (parsed?.ok === true) {
|
|
431
|
+
inputActions?.setDraft(parsed.value.text);
|
|
432
|
+
const previous = getRestore();
|
|
433
|
+
const original = (previous === void 0 || previous.sessionId === void 0 || previous.sessionId === sessionId) && previous !== void 0 && prompt === previous.enhanced ? previous.original : prompt;
|
|
434
|
+
setRestore({
|
|
435
|
+
sessionId,
|
|
436
|
+
original,
|
|
437
|
+
enhanced: parsed.value.text
|
|
438
|
+
});
|
|
439
|
+
announce("ok", parsed.value.warning === "no_index" ? t("wand.noIndex") : t("wand.done"));
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
announce("error", messageFor(parsed?.error.code ?? "upstream", t));
|
|
443
|
+
} catch {
|
|
444
|
+
announce("error", t("wand.upstream"));
|
|
445
|
+
} finally {
|
|
446
|
+
lock.current = false;
|
|
447
|
+
setBusy(false);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
const restore = react.default.useSyncExternalStore(subscribeRestore, getRestore, getRestore);
|
|
451
|
+
const canRestore = restore !== void 0 && (restore.sessionId === void 0 || restore.sessionId === sessionId) && draft !== restore.original;
|
|
452
|
+
const onRestore = () => {
|
|
453
|
+
if (restore === void 0 || busy) return;
|
|
454
|
+
inputActions?.setDraft(restore.original);
|
|
455
|
+
setRestore(void 0);
|
|
456
|
+
announce("idle", "");
|
|
457
|
+
};
|
|
458
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
459
|
+
className: "dshbce-wrap",
|
|
460
|
+
"data-empty": empty ? "true" : "false",
|
|
461
|
+
"data-busy": busy ? "true" : "false",
|
|
462
|
+
title,
|
|
463
|
+
children: [
|
|
464
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
465
|
+
className: "dshbce-control",
|
|
466
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
467
|
+
className: "dshbce-ring",
|
|
468
|
+
"aria-hidden": "true"
|
|
469
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
470
|
+
type: "button",
|
|
471
|
+
className: "dshbce-btn",
|
|
472
|
+
"data-busy": busy ? "true" : "false",
|
|
473
|
+
disabled,
|
|
474
|
+
title,
|
|
475
|
+
"aria-label": title,
|
|
476
|
+
"aria-busy": busy,
|
|
477
|
+
onMouseDown: (event) => {
|
|
478
|
+
event.preventDefault();
|
|
479
|
+
},
|
|
480
|
+
onClick: () => {
|
|
481
|
+
onClick();
|
|
482
|
+
},
|
|
483
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WandIcon, {})
|
|
484
|
+
})]
|
|
485
|
+
}),
|
|
486
|
+
status.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
487
|
+
className: "dshbce-status",
|
|
488
|
+
"data-level": level,
|
|
489
|
+
children: status
|
|
490
|
+
}) : null,
|
|
491
|
+
canRestore && !busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
492
|
+
type: "button",
|
|
493
|
+
className: "dshbce-restore",
|
|
494
|
+
title: t("wand.restore"),
|
|
495
|
+
onMouseDown: (event) => {
|
|
496
|
+
event.preventDefault();
|
|
497
|
+
},
|
|
498
|
+
onClick: onRestore,
|
|
499
|
+
children: t("wand.restore")
|
|
500
|
+
}) : null,
|
|
501
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
502
|
+
className: "dshbce-live",
|
|
503
|
+
"aria-live": "polite",
|
|
504
|
+
children: status
|
|
505
|
+
})
|
|
506
|
+
]
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
function registerWandButton(ctx) {
|
|
510
|
+
const sessionsStore = ctx.get("sessions");
|
|
511
|
+
const workspacesStore = ctx.get("workspaces");
|
|
512
|
+
ctx.slots.inject("conversation.input.left", () => ctx.slots.register({
|
|
513
|
+
name: "conversation.input.left",
|
|
514
|
+
id: NAMESPACE,
|
|
515
|
+
order: -40,
|
|
516
|
+
locale: LOCALE_NAMESPACE
|
|
517
|
+
}, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WandButton, {
|
|
518
|
+
...props,
|
|
519
|
+
ctx,
|
|
520
|
+
sessionsStore,
|
|
521
|
+
workspacesStore
|
|
522
|
+
})));
|
|
523
|
+
}
|
|
524
|
+
// src/client/index.ts
|
|
525
|
+
const inject = [
|
|
526
|
+
"slots",
|
|
527
|
+
"locale",
|
|
528
|
+
"connection"
|
|
529
|
+
];
|
|
530
|
+
function apply(ctx) {
|
|
531
|
+
ctx.effect(() => ctx.locale.register(LOCALE_NAMESPACE, {
|
|
532
|
+
zh,
|
|
533
|
+
en
|
|
534
|
+
}), "dsh-bce: dictionaries");
|
|
535
|
+
ctx.effect(() => injectStyles());
|
|
536
|
+
registerWandButton(ctx);
|
|
537
|
+
}
|
|
538
|
+
exports.apply = apply;
|
|
539
|
+
exports.inject = inject;
|
|
540
|
+
return module.exports;
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
//# sourceMappingURL=client.js.map
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
/** Must match package.json name and cordis.patch.yml id. */
|
|
3
|
+
declare const name = "dsh-bce";
|
|
4
|
+
/** Wait until the tool registry exists; connection is optional (headless). */
|
|
5
|
+
declare const inject: string[];
|
|
6
|
+
declare function apply(ctx: Context): void;
|
|
7
|
+
export { apply, inject, name };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// src/protocol.ts
|
|
2
|
+
const RPC_ENDPOINT = "dsh-bce.enhance";
|
|
3
|
+
/** Abort budget for the enhance_prompt MCP call only (indexing uses the MCP client's own timeout). */
|
|
4
|
+
const ENHANCE_TIMEOUT_MS = 9e4;
|
|
5
|
+
// src/mcp-enhance.ts
|
|
6
|
+
const ENHANCE_SUFFIX = "__enhance_prompt";
|
|
7
|
+
const SEARCH_SUFFIX = "__search_context";
|
|
8
|
+
const MCP_PREFIX = "mcp__";
|
|
9
|
+
function fail(code, message) {
|
|
10
|
+
return {
|
|
11
|
+
ok: false,
|
|
12
|
+
error: {
|
|
13
|
+
code,
|
|
14
|
+
message,
|
|
15
|
+
details: {}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function ok(value) {
|
|
20
|
+
return {
|
|
21
|
+
ok: true,
|
|
22
|
+
value
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function toolNames(ctx) {
|
|
26
|
+
const names = /* @__PURE__ */ new Set();
|
|
27
|
+
const runtime = ctx.tools;
|
|
28
|
+
try {
|
|
29
|
+
for (const schema of runtime.schemas?.() ?? []) if (typeof schema?.name === "string" && schema.name.length > 0) names.add(schema.name);
|
|
30
|
+
} catch {}
|
|
31
|
+
try {
|
|
32
|
+
for (const definition of runtime.definitions?.() ?? []) if (typeof definition?.name === "string" && definition.name.length > 0) names.add(definition.name);
|
|
33
|
+
} catch {}
|
|
34
|
+
return [...names];
|
|
35
|
+
}
|
|
36
|
+
function serverOf(name, suffix) {
|
|
37
|
+
const index = name.indexOf(suffix);
|
|
38
|
+
if (index <= 0) return void 0;
|
|
39
|
+
if (name.startsWith(MCP_PREFIX)) return name.slice(5, index);
|
|
40
|
+
return name.slice(0, index).replace(/_+$/g, "") || void 0;
|
|
41
|
+
}
|
|
42
|
+
function preferBce(servers) {
|
|
43
|
+
if (servers.length === 0) return void 0;
|
|
44
|
+
return servers.filter((server) => server.toLowerCase().includes("bce"))[0] ?? servers[0];
|
|
45
|
+
}
|
|
46
|
+
function discoverBceTools(ctx) {
|
|
47
|
+
const names = toolNames(ctx);
|
|
48
|
+
const enhanceByServer = /* @__PURE__ */ new Map();
|
|
49
|
+
const searchByServer = /* @__PURE__ */ new Map();
|
|
50
|
+
for (const name of names) {
|
|
51
|
+
const enhanceServer = serverOf(name, ENHANCE_SUFFIX);
|
|
52
|
+
if (enhanceServer !== void 0) enhanceByServer.set(enhanceServer, name);
|
|
53
|
+
const searchServer = serverOf(name, SEARCH_SUFFIX);
|
|
54
|
+
if (searchServer !== void 0) searchByServer.set(searchServer, name);
|
|
55
|
+
}
|
|
56
|
+
const server = preferBce([...enhanceByServer.keys()]);
|
|
57
|
+
if (server === void 0) return void 0;
|
|
58
|
+
const enhance = enhanceByServer.get(server);
|
|
59
|
+
if (enhance === void 0) return void 0;
|
|
60
|
+
const search = searchByServer.get(server);
|
|
61
|
+
return {
|
|
62
|
+
enhance,
|
|
63
|
+
...search === void 0 ? {} : { search }
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function contentText(result) {
|
|
67
|
+
return result.content.map((block) => {
|
|
68
|
+
if (block && typeof block === "object" && "type" in block && block.type === "text" && "text" in block) return typeof block.text === "string" ? block.text : "";
|
|
69
|
+
return "";
|
|
70
|
+
}).filter((part) => part.length > 0).join("\n").trim();
|
|
71
|
+
}
|
|
72
|
+
function asRecord(value) {
|
|
73
|
+
if (value !== null && typeof value === "object") return value;
|
|
74
|
+
}
|
|
75
|
+
function nonEmpty(value) {
|
|
76
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
77
|
+
}
|
|
78
|
+
function workspacePathFromRegistry(ctx, sessionId) {
|
|
79
|
+
const registry = ctx.get("workspaceRegistry");
|
|
80
|
+
const listed = typeof registry?.list === "function" ? registry.list() : void 0;
|
|
81
|
+
const rows = Array.isArray(listed) ? listed : [];
|
|
82
|
+
for (const row of rows) {
|
|
83
|
+
const rec = asRecord(row);
|
|
84
|
+
if (rec === void 0) continue;
|
|
85
|
+
const ids = rec.sessionIds;
|
|
86
|
+
if (!Array.isArray(ids) || !ids.includes(sessionId)) continue;
|
|
87
|
+
const path = nonEmpty(rec.path);
|
|
88
|
+
if (path !== void 0) return path;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function resolveWorkspacePath(ctx, record) {
|
|
92
|
+
const direct = nonEmpty(record.workspacePath);
|
|
93
|
+
if (direct !== void 0) return direct;
|
|
94
|
+
const sessionId = nonEmpty(record.sessionId);
|
|
95
|
+
if (sessionId === void 0) return void 0;
|
|
96
|
+
return workspacePathFromRegistry(ctx, sessionId);
|
|
97
|
+
}
|
|
98
|
+
function classifyToolText(text, isError) {
|
|
99
|
+
const lower = text.toLowerCase();
|
|
100
|
+
if (lower.includes("not managed by git") || lower.includes("not a git repository")) return "not_git";
|
|
101
|
+
if (lower.includes("too large to index") || lower.includes("exceeds the") && lower.includes("mb")) return "too_large";
|
|
102
|
+
if (text.startsWith("Error:") || isError) return "index_failed";
|
|
103
|
+
if (lower.includes("tool unavailable")) return "upstream";
|
|
104
|
+
}
|
|
105
|
+
function isAbortError(error) {
|
|
106
|
+
if (error instanceof Error && error.name === "AbortError") return true;
|
|
107
|
+
if (typeof error === "object" && error !== null && "name" in error && error.name === "AbortError") return true;
|
|
108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
109
|
+
return /abort|timeout/i.test(message);
|
|
110
|
+
}
|
|
111
|
+
async function callTool(ctx, name, args, signal) {
|
|
112
|
+
return ctx.tools.execute({
|
|
113
|
+
callId: `dsh-bce-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
114
|
+
name,
|
|
115
|
+
arguments: args,
|
|
116
|
+
signal
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async function enhancePrompt(ctx, payload, signal) {
|
|
120
|
+
if (typeof payload !== "object" || payload === null) return fail("empty_prompt", "payload must be an object");
|
|
121
|
+
const record = payload;
|
|
122
|
+
const prompt = typeof record.prompt === "string" ? record.prompt.trim() : "";
|
|
123
|
+
if (prompt.length === 0) return fail("empty_prompt", "prompt is empty");
|
|
124
|
+
const workspacePath = resolveWorkspacePath(ctx, record) ?? "";
|
|
125
|
+
if (workspacePath.length === 0) return fail("no_workspace", "workspace path is missing");
|
|
126
|
+
const tools = discoverBceTools(ctx);
|
|
127
|
+
if (tools === void 0) return fail("mcp_missing", "BCE MCP enhance_prompt was not found. Configure bce-tool in this profile with --no-webbrowser-enhance-prompt.");
|
|
128
|
+
let warning;
|
|
129
|
+
if (tools.search !== void 0) {
|
|
130
|
+
let searchResult;
|
|
131
|
+
try {
|
|
132
|
+
searchResult = await callTool(ctx, tools.search, {
|
|
133
|
+
project_root_path: workspacePath,
|
|
134
|
+
query: prompt
|
|
135
|
+
}, signal);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (isAbortError(error)) return fail("timeout", "Indexing timed out. Raise toolCallTimeoutMs on the bce-tool MCP entry.");
|
|
138
|
+
return fail("index_failed", error instanceof Error ? error.message : String(error));
|
|
139
|
+
}
|
|
140
|
+
const searchText = contentText(searchResult);
|
|
141
|
+
const searchCode = classifyToolText(searchText, searchResult.isError);
|
|
142
|
+
if (searchCode === "not_git" || searchCode === "too_large") return fail(searchCode, searchText || searchCode);
|
|
143
|
+
if (searchCode !== void 0) return fail("index_failed", searchText || "search_context failed");
|
|
144
|
+
} else warning = "no_index";
|
|
145
|
+
const enhanceAbort = new AbortController();
|
|
146
|
+
const onParentAbort = () => enhanceAbort.abort();
|
|
147
|
+
if (signal.aborted) enhanceAbort.abort();
|
|
148
|
+
else signal.addEventListener("abort", onParentAbort, { once: true });
|
|
149
|
+
const timer = setTimeout(() => enhanceAbort.abort(), ENHANCE_TIMEOUT_MS);
|
|
150
|
+
let enhanceResult;
|
|
151
|
+
try {
|
|
152
|
+
enhanceResult = await callTool(ctx, tools.enhance, {
|
|
153
|
+
project_root_path: workspacePath,
|
|
154
|
+
prompt,
|
|
155
|
+
conversation_history: ""
|
|
156
|
+
}, enhanceAbort.signal);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (isAbortError(error) || enhanceAbort.signal.aborted) return fail("timeout", `Enhancement timed out (${ENHANCE_TIMEOUT_MS / 1e3}s). Add --no-webbrowser-enhance-prompt to the bce-tool MCP args if the review page is blocking.`);
|
|
159
|
+
return fail("upstream", error instanceof Error ? error.message : String(error));
|
|
160
|
+
} finally {
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
signal.removeEventListener("abort", onParentAbort);
|
|
163
|
+
}
|
|
164
|
+
const text = contentText(enhanceResult);
|
|
165
|
+
const enhanceCode = classifyToolText(text, enhanceResult.isError);
|
|
166
|
+
if (enhanceCode === "not_git" || enhanceCode === "too_large" || enhanceCode === "timeout") return fail(enhanceCode, text || enhanceCode);
|
|
167
|
+
if (enhanceCode !== void 0) return fail("upstream", text || "enhance_prompt failed");
|
|
168
|
+
if (text.length === 0) return fail("upstream", "enhance_prompt returned empty text");
|
|
169
|
+
return ok(warning === void 0 ? { text } : {
|
|
170
|
+
text,
|
|
171
|
+
warning
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
// src/rpc.ts
|
|
175
|
+
function readEnvelope(body) {
|
|
176
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
177
|
+
const record = body;
|
|
178
|
+
if (record.type !== "client-request" || typeof record.rpcId !== "string" || typeof record.method !== "string") return;
|
|
179
|
+
return {
|
|
180
|
+
type: "client-request",
|
|
181
|
+
rpcId: record.rpcId,
|
|
182
|
+
method: record.method,
|
|
183
|
+
payload: record.payload
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function serverResponse(rpcId, result) {
|
|
187
|
+
return Response.json({
|
|
188
|
+
type: "server-response",
|
|
189
|
+
rpcId,
|
|
190
|
+
result
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function fetchRouteFor(handler) {
|
|
194
|
+
return {
|
|
195
|
+
path: `/api/${RPC_ENDPOINT}`,
|
|
196
|
+
methods: ["POST"],
|
|
197
|
+
requestBody: "buffered",
|
|
198
|
+
fetch: async (request) => {
|
|
199
|
+
if (request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") return new Response("content type must be application/json", { status: 415 });
|
|
200
|
+
let body;
|
|
201
|
+
try {
|
|
202
|
+
body = await request.json();
|
|
203
|
+
} catch {
|
|
204
|
+
return new Response("body is not JSON", { status: 400 });
|
|
205
|
+
}
|
|
206
|
+
const envelope = readEnvelope(body);
|
|
207
|
+
if (envelope === void 0) {
|
|
208
|
+
const rawId = body?.rpcId;
|
|
209
|
+
return serverResponse(typeof rawId === "string" ? rawId : "invalid-request", {
|
|
210
|
+
ok: false,
|
|
211
|
+
error: {
|
|
212
|
+
code: "upstream",
|
|
213
|
+
message: "invalid client-request message",
|
|
214
|
+
details: {}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (envelope.method !== "dsh-bce.enhance") return serverResponse(envelope.rpcId, {
|
|
219
|
+
ok: false,
|
|
220
|
+
error: {
|
|
221
|
+
code: "upstream",
|
|
222
|
+
message: `method ${JSON.stringify(envelope.method)} does not match ${JSON.stringify(RPC_ENDPOINT)}`,
|
|
223
|
+
details: {}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
return serverResponse(envelope.rpcId, await handler(envelope.payload, request.signal));
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Register Host RPC for the wand button.
|
|
232
|
+
* Prefers exact Fetch routes (DSH 0.1.5+); falls back to `/api` intercept (0.1.1).
|
|
233
|
+
* Headless profiles without `connection` skip registration.
|
|
234
|
+
*/
|
|
235
|
+
function registerEnhanceRpc(ctx) {
|
|
236
|
+
ctx.inject(["connection"], (wired) => {
|
|
237
|
+
const connection = wired.get("connection");
|
|
238
|
+
const handler = (payload, signal) => enhancePrompt(ctx, payload, signal);
|
|
239
|
+
const register = connection.fetch?.register;
|
|
240
|
+
if (typeof register === "function") {
|
|
241
|
+
wired.effect(() => register(fetchRouteFor(handler)), "dsh-bce: /api/dsh-bce.enhance fetch route");
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const intercept = connection.rpc?.intercept;
|
|
245
|
+
if (typeof intercept === "function") wired.effect(() => intercept("/api", (endpoint) => endpoint === RPC_ENDPOINT, async (_endpoint, payload, signal) => handler(payload, signal), { authority: "loopback" }), "dsh-bce: /api intercept dsh-bce.enhance");
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
// src/index.ts
|
|
249
|
+
/** Must match package.json name and cordis.patch.yml id. */
|
|
250
|
+
const name = "dsh-bce";
|
|
251
|
+
/** Wait until the tool registry exists; connection is optional (headless). */
|
|
252
|
+
const inject = ["tools"];
|
|
253
|
+
function apply(ctx) {
|
|
254
|
+
registerEnhanceRpc(ctx);
|
|
255
|
+
}
|
|
256
|
+
export { apply, inject, name };
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,84 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-bce",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DeepSeek Harness plugin that incrementally indexes the current workspace via the configured BCE MCP and enhances composer prompts.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"deepseek-harness",
|
|
7
|
+
"dsh",
|
|
8
|
+
"bce",
|
|
9
|
+
"prompt-enhance"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "linqiu",
|
|
13
|
+
"homepage": "https://github.com/linqiu919/dsh-bce#readme",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/linqiu919/dsh-bce.git"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/linqiu919/dsh-bce/issues"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"registry": "https://registry.npmjs.org/",
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"icon": "./icon.png",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "lib/index.js",
|
|
28
|
+
"types": "lib/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./lib/index.d.ts",
|
|
32
|
+
"default": "./lib/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./client": "./lib/client.js",
|
|
35
|
+
"./locale/*.json": "./locale/*.json",
|
|
36
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"lib/index.js",
|
|
41
|
+
"lib/index.d.ts",
|
|
42
|
+
"lib/client.js",
|
|
43
|
+
"locale/*.json",
|
|
44
|
+
"icon.png",
|
|
45
|
+
"cordis.patch.yml"
|
|
46
|
+
],
|
|
47
|
+
"dsh": {
|
|
48
|
+
"bundle": {
|
|
49
|
+
"patch": "./cordis.patch.yml"
|
|
50
|
+
},
|
|
51
|
+
"client": {
|
|
52
|
+
"platform": "web",
|
|
53
|
+
"inject": [
|
|
54
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
55
|
+
"@deepseek-ai/dsh-client-connection"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"@deepseek-ai/cordis": ">=4.0.1",
|
|
64
|
+
"@deepseek-ai/dsh-tools": ">=0.1.1-rc.2"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@deepseek-ai/cordis": "~4.0.1",
|
|
68
|
+
"@deepseek-ai/dsh-client-connection": "0.1.1-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.1-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-tools": "0.1.1-rc.2",
|
|
73
|
+
"@types/node": "^22.20.0",
|
|
74
|
+
"@types/react": "^18.3.0",
|
|
75
|
+
"react": "^18.3.1",
|
|
76
|
+
"tsdown": "^0.22.2",
|
|
77
|
+
"typescript": "^5.9.3"
|
|
78
|
+
},
|
|
79
|
+
"scripts": {
|
|
80
|
+
"build": "tsdown",
|
|
81
|
+
"typecheck": "tsc --noEmit",
|
|
82
|
+
"clean": "rm -rf lib"
|
|
83
|
+
}
|
|
6
84
|
}
|