opencode-idea 0.1.1 → 0.1.3
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 +77 -45
- package/package.json +2 -2
- package/skills/idea-run-config-manager/assets/AGENTS.template.md +2 -1
- package/src/ide-guidance.js +21 -98
- package/src/ide-guidance.template.md +85 -80
- package/src/mcp/idea-tool-defaults.js +4 -2
- package/src/mcp/idea-tool-output.js +118 -0
- package/src/mcp/idea.js +1 -1
- package/src/plugin.js +2 -4
- package/src/mcp/enhance.js +0 -151
package/README.md
CHANGED
|
@@ -9,10 +9,10 @@ OpenCode 插件,面向 JetBrains 项目 (IntelliJ IDEA / PyCharm / WebStorm 等)
|
|
|
9
9
|
2. **优先使用 IDE 工具** —— IDE 可用时,向模型注入 **逐项目**的引导:在哪个项目里工作,就用哪个项目前缀的工具;IDE 不可用时
|
|
10
10
|
回退原生工具 (正常回退,无需声明)。
|
|
11
11
|
|
|
12
|
-
> **需要项目环境的命令走 Run Configuration**:跑 `java` / `python` / `npm` 这类依赖项目配好的 JDK / Node / Python 版本的
|
|
13
|
-
>
|
|
14
|
-
>
|
|
15
|
-
>
|
|
12
|
+
> **需要项目环境的命令走 IDE 终端 / Run Configuration**:跑 `java` / `python` / `npm` 这类依赖项目配好的 JDK / Node / Python 版本的
|
|
13
|
+
> **一次性命令**,用配套插件提供的 `ide_project_sdk_terminal`(它带项目 SDK 环境);**反复发生的流程**仍固化成 Run Configuration。
|
|
14
|
+
> **IDE 内置的 `ide_execute_terminal_command` 继续隐藏** —— 实测它不继承项目环境,已被 `project_sdk_terminal` 取代。
|
|
15
|
+
> 详见「Run Configuration 全套」与「终端全套」。
|
|
16
16
|
|
|
17
17
|
非 JetBrains 项目完全不受影响。 **手动接入**:插件启动不探测、不连接任何 IDE。三个命令管全部:
|
|
18
18
|
|
|
@@ -142,15 +142,18 @@ IDE MCP 可用时,插件通过 **系统提示注入**告诉模型 **哪些事该
|
|
|
142
142
|
(只做减法:从 schema 里删掉一个参数),不修改任何工具 (原生或 IDEA) 的定义、描述或可用性:
|
|
143
143
|
|
|
144
144
|
1. **系统提示注入**:通过 `ctx.session.hook("context", …)` 往每次请求的 system 里追加一段引导。
|
|
145
|
-
**文本本身在 `src/ide-guidance.template.md`** —— 它是一份要给人 review
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
**文本本身在 `src/ide-guidance.template.md`** —— 它是一份要给人 review 的提示词文档,纯 Markdown,只有每条工具后面的
|
|
146
|
+
命令名(`{{cmd_xxx}}`)由代码填;`src/template.js` 是渲染器(支持 `{{var}}`,`{{#if}}` 仍在实现里但引导模板不再使用;
|
|
147
|
+
**不做 HTML 转义** —— 输出是 Markdown,`<jar>!/<entry>` 和反引号必须原样过)。
|
|
148
148
|
含工具名(`<项目前缀>_<name>`;root 是 `ide_*`,子项目是 `<目录名>_*`)与关键参数名。
|
|
149
149
|
|
|
150
|
+
> **配套插件 `idea-mcp-enhance` 是硬前提**:引导默认它一定装了,所以配套插件的工具 (运行配置启停 / 部署 / 终端)
|
|
151
|
+
> **不做"有没有装 / 有没有这个工具"的判断**,直接写进引导。
|
|
152
|
+
|
|
150
153
|
**路由总纲** —— 整个引导的第一条,按「路径在不在项目里」分路:
|
|
151
154
|
|
|
152
155
|
- **项目内** —— **OpenCode 自带的工具为主**,它们更快。**它们做不了、或者做得很差的,才用 IDE 工具**:
|
|
153
|
-
|
|
156
|
+
符号检索、依赖实现、重构、格式化、校验、运行配置、项目终端、跨文件补丁。
|
|
154
157
|
**两边都不顺手时,你自己判断用 `shell` 里的命令** —— 那是**第三选择,不是第一选择**。
|
|
155
158
|
- **项目外** —— **只有 OpenCode 自带的工具**,IDE 工具不参与。这不是保守,是硬事实:**请求的路径必须落在某个
|
|
156
159
|
已打开项目的目录树内,IDE 才会去解析它**;项目外的路径(`/tmp`、别的仓库、用户主目录)两个 IDE 都会直接报错。
|
|
@@ -165,7 +168,8 @@ IDE MCP 可用时,插件通过 **系统提示注入**告诉模型 **哪些事该
|
|
|
165
168
|
| 批量改文件 | `ide_apply_patch` |
|
|
166
169
|
| 校验 | `ide_lint_files` / `ide_get_file_problems` / `ide_build_project` |
|
|
167
170
|
| 运行配置 | `ide_get_run_configurations` / `ide_execute_run_configuration`(+ 配套插件的启停工具) |
|
|
168
|
-
|
|
|
171
|
+
| 终端 | `ide_project_sdk_terminal` / `ide_terminal_io` / `ide_list_terminals` / `ide_execute_in_remote_host`(远程执行还要求 Ultimate + Deployment SFTP) |
|
|
172
|
+
| 部署 | `ide_list_server_deployments` / `ide_upload_to_deployment` / `ide_download_from_deployment` / `ide_get_deployment_task_status`(要求 Ultimate) |
|
|
169
173
|
|
|
170
174
|
**读、找文本、找文件、改单个文件一律用 OpenCode 自带的工具**(`read` / `grep` / `glob` / `write` / `edit`):
|
|
171
175
|
这几件事走 IDE 只是多一跳,所以映射表里**不出现** `ide_search_text` / `ide_search_regex` / `ide_search_file`。
|
|
@@ -263,7 +267,7 @@ IDE MCP 可用时,插件通过 **系统提示注入**告诉模型 **哪些事该
|
|
|
263
267
|
## Run Configuration 全套 (配套插件 `idea-mcp-enhance`)
|
|
264
268
|
|
|
265
269
|
IDE 内置的 MCP Server 只能 **启动已有**的 run configuration (`execute_run_configuration`), **不能新建、不能停止、不能重启、
|
|
266
|
-
也查不到运行状态**。配套的 IDEA 插件 [`idea-mcp-enhance`](https://github.com/syllr/idea-mcp-enhance)(IDEA 2026.2
|
|
270
|
+
也查不到运行状态**。配套的 IDEA 插件 [`idea-mcp-enhance`](https://github.com/syllr/idea-mcp-enhance)(IDEA 2026.2+,必须安装)
|
|
267
271
|
补齐这一族 —— 「定义态 (查 / 建 / 改 / 删)」加「运行态 (看 / 停 / 重启)」:
|
|
268
272
|
|
|
269
273
|
| 工具 (OpenCode 里叫 `ide_*`) | 作用 |
|
|
@@ -276,34 +280,31 @@ IDE 内置的 MCP Server 只能 **启动已有**的 run configuration (`execute_
|
|
|
276
280
|
| `ide_stop_run_configuration` | 停掉该配置的**全部**实例并等它们退出;没在跑也返回成功(幂等) |
|
|
277
281
|
| `ide_rerun_run_configuration` | 等同 IDE 的 **Rerun** 按钮:在跑就重启、没在跑就启动,读 `started` 判断是否真起来了 |
|
|
278
282
|
|
|
279
|
-
**需要项目环境的命令走 run configuration
|
|
280
|
-
(IDE 里配好的 JDK / Node / Python 等)。是 →
|
|
281
|
-
run configuration
|
|
282
|
-
|
|
283
|
+
**需要项目环境的命令走 IDE 终端 / run configuration** —— 判据是这条命令 **依赖项目的运行时版本**
|
|
284
|
+
(IDE 里配好的 JDK / Node / Python 等)。是 → **一次性命令**走配套插件的 `ide_project_sdk_terminal`;**会反复发生的流程 / 常驻
|
|
285
|
+
服务**固化成 run configuration;不是 (看文件、git、临时脚本)→ 原生 shell。配置必须 **先在 IDEA 里存在**(由用户建),
|
|
286
|
+
没有就请用户建一个再跑;要不要建、建什么,按 `idea-run-config-manager` skill 的判据走。
|
|
283
287
|
|
|
284
|
-
>
|
|
288
|
+
> **为什么内置 IDE 终端仍然隐藏**:`ide_execute_terminal_command` 之前被当成"自动继承 IDE 项目环境"的通道而保留暴露,但实测
|
|
285
289
|
> **它拿不到项目配好的 JDK / Node / Python 版本**,和原生 shell 没有区别。既然没有优势,就把它从模型可见的工具表里
|
|
286
|
-
> 隐藏掉 (`HIDDEN_IDE_TOOL_SUFFIXES`)
|
|
290
|
+
> 隐藏掉 (`HIDDEN_IDE_TOOL_SUFFIXES`)。配套插件的 `project_sdk_terminal` 走的是另一条路:**主动把项目 SDK / Maven /
|
|
291
|
+
> Node / Python 的路径注入终端环境**,所以它才是"需要项目环境"的那条路由。
|
|
287
292
|
|
|
288
293
|
配置名有自己的文法:**`[os@]base[@env]`** —— os 段 (`win` = Windows,缺省 = macOS / Linux)、base 段 (配置本体)、env 段 (环境名,
|
|
289
294
|
缺省 = 默认环境 —— 起一批时逐条解析:有 `@<env>` 用它,没有就用默认)。`win@base` 与 `base@env` 都是同一个 base 的 **变体**,档案里只登记 base。解析 **交给工具**:
|
|
290
295
|
`ide_inspect_run_configuration` 的 `naming` 字段把名字拆成 platform / base / env,规则是从两头往中间收 (末段是环境;首段只有
|
|
291
296
|
恰好是 `win` 才算平台,不是就并进 base),所以 `a@b@c` 会解析成 base=`a@b`、env=`c`,不会丢段。
|
|
292
297
|
|
|
293
|
-
opencode-idea
|
|
298
|
+
opencode-idea 对这一组工具做两件事:
|
|
294
299
|
|
|
295
300
|
1. **透传**:IDE MCP server 以 `codemode: false` 注册,工具目录整体转发,所以这七个工具自动以 `ide_*` 出现在模型的
|
|
296
|
-
工具表里 ——
|
|
297
|
-
2.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
插件少给一两个工具时,引导也不会提到它们。同理, **每条命令名只列真的提供它的项目**(逐项目核对,见「条件注入」)——
|
|
304
|
-
一个项目装了配套插件、另一个没装是可能的。
|
|
305
|
-
|
|
306
|
-
> 没装 `idea-mcp-enhance` 的 IDE 一切照旧:工具不存在,引导也不提,模型继续按原来的方式工作。
|
|
301
|
+
工具表里 —— 插件侧不需要单独声明,也不需要判断它们在不在;
|
|
302
|
+
2. **无条件引导**:这七个工具**默认都在**(配套插件是硬前提),「运行配置」节直接逐个点名。其中一条值得单独说:
|
|
303
|
+
**启动 / 重启常驻进程就用 `ide_rerun_run_configuration`,不要用 `pkill` / `kill` / shell** —— 这正是它要解决的问题:
|
|
304
|
+
外部杀掉的进程 IDE 并不知道,Run 窗口仍显示运行中,下次启动就会撞端口。
|
|
305
|
+
|
|
306
|
+
> **前提**:配套插件 `idea-mcp-enhance` 必须装在每个接入的 IDE 上。没装时引导仍会点名这些工具、调用会失败 —— 这是
|
|
307
|
+
> 刻意选择的硬前提,不做兼容判断。
|
|
307
308
|
|
|
308
309
|
## Run Configuration:文件由工具写,档案由 skill 写
|
|
309
310
|
|
|
@@ -407,39 +408,69 @@ opencode-idea 对这一组工具做三件事:
|
|
|
407
408
|
## Deployment 全套 (配套插件 `idea-mcp-enhance`)
|
|
408
409
|
|
|
409
410
|
IDE 内置的 MCP Server **完全不碰部署** —— 既不报 IDE 里配了哪些 Deployment 服务器,也不能把文件传上去 / 拉下来。
|
|
410
|
-
配套插件 [`idea-mcp-enhance`](https://github.com/syllr/idea-mcp-enhance)(IDEA 2026.2
|
|
411
|
+
配套插件 [`idea-mcp-enhance`](https://github.com/syllr/idea-mcp-enhance)(IDEA 2026.2+,必须安装)补齐这四个:
|
|
411
412
|
|
|
412
413
|
| 工具 (OpenCode 里叫 `ide_*`) | 作用 |
|
|
413
414
|
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
414
|
-
| `
|
|
415
|
-
| `ide_upload_to_deployment` |
|
|
416
|
-
| `ide_download_from_deployment` |
|
|
415
|
+
| `ide_list_server_deployments` | 本项目可用的**服务器**,以及每台服务器上的 **deployment(路径映射)**。Server 是连接目标(`servers[].name` → 远程终端的 `serverName`),deployment 是挂在它上面的一条映射(`servers[].deployments[].name` → 上传 / 下载的 `deploymentName`,全局唯一)。server 可以 0 个 deployment,那样只能远程执行。另带 `level` / `accessType` / `host` / `port` / `rootFolder` / `hasCredentials` / `validationIssue` / `isDefault`、项目级 `options` 与 `warnings[]`。**只读 IDE 配置,不联网** |
|
|
416
|
+
| `ide_upload_to_deployment` | 把**一条 deployment**(本地映射目录)传到服务器(等同 IDE 的 Upload to …)。`deploymentName` 传 `servers[].deployments[].name`,**不是 `serverName`**。`mode` 默认 `mirror` —— **会删掉服务器上多出来的文件**;`upload` 则保留远端多余文件。**破坏性** |
|
|
417
|
+
| `ide_download_from_deployment` | 把**一条 deployment** 从服务器拉回本地(等同 IDE 的 Download from …),**以服务器为准**:两边都有的文件被远端覆盖(相同则不写)。同样传 `deploymentName`。**没有"保留我的版本"这个选项** |
|
|
417
418
|
| `ide_get_deployment_task_status` | 用 `handle` 查一次传输的状态与计数;**永不启动任何传输** |
|
|
418
419
|
|
|
419
|
-
opencode-idea
|
|
420
|
+
opencode-idea 对它做的是**同一套两件事**(透传 / 无条件引导),不再按工具快照裁剪文本:
|
|
420
421
|
|
|
421
|
-
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
整节**锚在一个工具上**(模板里 `{{#if canListDeploymentServers}}` 直接包住标题):这四个由同一道前提注册、同进同出,
|
|
425
|
-
拿其中一个当判据就够,不需要再造一个"四个里任一个"的聚合标志;
|
|
426
|
-
- **工具名清单**在 `src/mcp/enhance.js` 的 `DEPLOYMENT_SUFFIXES` + `deploymentCapabilities`,与该仓库的
|
|
427
|
-
`DeploymentSchemas.kt` 保持同步。
|
|
422
|
+
- **前提**:部署工具除配套插件外,还要求 `com.jetbrains.plugins.webDeployment`(**仅 Ultimate**)。引导默认它在,
|
|
423
|
+
所以「服务器文件同步」节始终出现;Community 上调用这些工具会失败 —— 这属于配套环境没装全,不做兼容判断;
|
|
424
|
+
- **契约**:`DeploymentSchemas.kt` 就是这组工具的对外形状(参数名、返回字段),引导按它写,不再单独维护工具名清单。
|
|
428
425
|
|
|
429
426
|
这节存在的理由是 **一条路由规则**:往服务器传 / 取文件要走这些工具, **不要自己拼 `ssh` / `rsync` / `scp`** ——
|
|
430
427
|
IDE 的连接、路径映射、排除规则都在它后面,自己拼的那份与 IDE 看到、管理的不是同一份。文案里同时点明两件容易读错的事:
|
|
431
428
|
`mirror` 是 **默认值**且 **会删远端文件**(而 MCP 路径上没有确认步骤),下载 **以远端为准**、没有"保留我的版本"。
|
|
432
429
|
|
|
430
|
+
**同步只是上半段**:每条 deployment 都属于某台 Server,传完之后在那台 Server 上启动刚传上去的二进制、重启服务、看日志,
|
|
431
|
+
走 `execute_in_remote_host`(`serverName` 用服务器名,不是 deployment 名)。
|
|
432
|
+
|
|
433
433
|
还有一个语义必须写明,否则模型会把正常的中间态当失败去重试:上传 / 下载都在 IDE 后台跑,单次调用只等到 `waitMs`
|
|
434
|
-
(默认 10 秒)为止,返回 `state: "in_progress"` 是 **正常**的 —— 拿 `handle`
|
|
435
|
-
|
|
434
|
+
(默认 10 秒,上限 12 秒)为止,返回 `state: "in_progress"` 是 **正常**的(它可能以 **error 结果**回来)—— 拿 `handle`
|
|
435
|
+
调 `get_deployment_task_status` 接着看,**不要用重复调上传 / 下载来代替**(那样会再起一次传输)。`completed` 之后
|
|
436
|
+
`counters` 才可信,而且数的是**操作次数**,不是文件数(删掉整个目录也只算 1)。
|
|
436
437
|
|
|
437
438
|
> **这一组没有 skill,和 run configuration 不同。** run configuration 要 skill 是因为"某种类型怎么写"是一批得逐类型实测的
|
|
438
|
-
> 模板 (`assets/types/`);部署没有这层知识 —— 四个工具的 schema 已经说清 `serverName` / `mode`
|
|
439
|
-
> 是模型当场看 `
|
|
439
|
+
> 模板 (`assets/types/`);部署没有这层知识 —— 四个工具的 schema 已经说清 `serverName` / `deploymentName` / `mode` 怎么填,
|
|
440
|
+
> 而"该不该传、传给谁"是模型当场看 `list_server_deployments` 的判断。再加一个 skill 只是把同一段话抄第二遍。
|
|
441
|
+
|
|
442
|
+
## 终端全套 (配套插件 `idea-mcp-enhance`)
|
|
443
|
+
|
|
444
|
+
IDE 内置的 `execute_terminal_command` **不继承项目环境**(实测拿不到项目配好的 JDK / Node / Python),所以它被隐藏。配套插件
|
|
445
|
+
补上的是一个**真正的 IDE 终端**,外加远程 SSH 终端和一套"接着用同一个 tab"的读写工具:
|
|
446
|
+
|
|
447
|
+
| 工具 (OpenCode 里叫 `ide_*`) | 作用 |
|
|
448
|
+
| --- | --- |
|
|
449
|
+
| `ide_project_sdk_terminal` | 在本项目 SDK 终端里跑**一条**命令,返回输出 + `tab`。每次调用 **新开一个 tab**;`timeoutMs` 默认 10000(`0` = 发出去就返回,上限 12000)。终端环境由插件注入:项目 JDK(`PATH`)、Maven home、Node runtime / 包管理器、Go 的 `GOROOT` / `GOPATH` / `GOBIN`、Python SDK / venv(`PATH` + `VIRTUAL_ENV`),用户传的 `env` 最后覆盖 |
|
|
450
|
+
| `ide_list_terminals` | 列出 `mcp_project_sdk` / `mcp_remote` / `user` 三类 tab(状态、当前目录、最后一条命令、`lastUsedAt`)。**只有 `tab` 非空且 `addressable: true` 的能交给 `terminal_io`**;用户手开的 tab 只能看 |
|
|
451
|
+
| `ide_terminal_io` | 对已有 tab 读 / 写。`read`:默认读上次之后的增量,`tail: N` 读最近 N 个命令段(本地带每段 `exitCode`),`full: true` 读整屏,`timeoutMs > 0` 等输出稳定;`write`:原样写进当前命令(自带换行) |
|
|
452
|
+
| `ide_execute_in_remote_host` | 通过本项目的一台 Deployment **SFTP** 服务器开真实 SSH 终端。`loginShell` 默认 `true`(套 `zsh -lic` / `bash -lc`),`strictHostKey` 默认 `false`(accept-new) |
|
|
453
|
+
|
|
454
|
+
路由:引导里写的是 **"这条命令要不要项目运行时"** 一刀切 —— 要(IDE 里配好的 JDK / Node / Python / Maven / Go)走
|
|
455
|
+
`ide_project_sdk_terminal`,不要(`git` / `ls` / `curl` / `docker` / `ps`)仍走原生 `shell`。**常驻进程**(dev server / watch)
|
|
456
|
+
有 run configuration 就用它;没有就在终端里以 `timeoutMs: 0` 起,之后用 `terminal_io` 跟日志、回答提示。
|
|
457
|
+
|
|
458
|
+
三条容易读错的语义,引导里都点名了:
|
|
459
|
+
|
|
460
|
+
- **每次调用都新开 tab** —— 同一件事的后续(看日志、回答提示、再敲一条)必须 `list_terminals` → `terminal_io`,不能反复
|
|
461
|
+
`project_sdk_terminal` 把 tab 堆起来;
|
|
462
|
+
- **远程没有"命令结束"信号** —— `exitCode` 永远 `null`,`running` 也可能 `null`;别等退出码,按输出判断或继续 `terminal_io` 读;
|
|
463
|
+
- **tab 只在这次 IDE 会话里有效** —— 关掉就失效(报 `terminal_closed` / 找不到),这时才重开一个;没有"关闭 tab"的工具,
|
|
464
|
+
用完不必主动关,用户可以直接在 IDE 里接手。
|
|
465
|
+
|
|
466
|
+
opencode-idea 对它做的仍是**同一套两件事**(透传 / 无条件引导):本地三个工具(Terminal 插件在就有)和
|
|
467
|
+
`execute_in_remote_host`(还要 `remote-run`,仅 Ultimate)都**默认齐全**,引导直接点名,不再按工具快照裁剪。
|
|
468
|
+
契约以该仓库的 `TerminalSchemas.kt` 为准。
|
|
440
469
|
|
|
441
470
|
## 前置条件
|
|
442
471
|
|
|
472
|
+
- **必须安装配套插件 [`idea-mcp-enhance`](https://github.com/syllr/idea-mcp-enhance)(IDEA 2026.2+)**。引导默认它的工具都在
|
|
473
|
+
(运行配置启停 / 部署 / 终端),**不做"装没装 / 有没有这个工具"的判断**;部署与远程终端还要求 Ultimate。
|
|
443
474
|
- JetBrains IDE 已开启 MCP 服务 (IDE 设置里的 MCP Server / AI Assistant MCP),版本需 **2026.2+**(提供 Streamable HTTP 端点
|
|
444
475
|
`/stream`);若关闭,`/open-in-ide` 会立即提示如何重新开启。
|
|
445
476
|
- IDE 侧需开启 **Brave Mode**(否则需要确认的 MCP 工具调用会停下来等用户确认);MCP 未就绪时通知会同时提醒开启它。
|
|
@@ -573,7 +604,8 @@ export { default } from "/Users/yutao/IdeaProjects/opencode-idea/src/index.js";
|
|
|
573
604
|
(0.0.6 就是这样丢了 `/open-in-ide`)。完整字段是 `id` / `name` / `description?` / `autoinvoke?` / `path` / `content`。
|
|
574
605
|
- 一批用不到的 IDE MCP 工具被硬编码隐藏(`src/plugin.js` 的 `HIDDEN_IDE_TOOL_SUFFIXES`,**共 48 个后缀**):
|
|
575
606
|
VCS(`git_status` / `get_repositories`)、Router 派发工具(`execute_tool`)、Debugger(全部 `xdebug_*`)、Dev Kit MCP、
|
|
576
|
-
Inspection KTS MCP、解释器环境 MCP、**IDE 内置终端**(`execute_terminal_command
|
|
607
|
+
Inspection KTS MCP、解释器环境 MCP、**IDE 内置终端**(`execute_terminal_command`:实测不继承项目环境,已由配套插件的
|
|
608
|
+
`project_sdk_terminal` 取代)、
|
|
577
609
|
**整个 Database 组**(数据源由用户在 IDE 里配置)、**有原生对应的文件搜索**(`search_text` / `search_regex` / `search_file`)、
|
|
578
610
|
以及**建文件**(`create_new_file`)。配套插件自带的那个写文件工具同样隐藏。**`apply_patch` 不在隐藏列表里**。
|
|
579
611
|
存的是**后缀**,清洗时按当前每个项目前缀**各删一遍** —— 只处理 `ide_` 会让子项目那几份原样送到模型面前。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-idea",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "OpenCode plugin that connects to JetBrains IDE MCP servers and prefers IDE tools when the current project is open in the IDE",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"src/ide-guidance.template.md",
|
|
13
13
|
"src/ide-projects.js",
|
|
14
14
|
"src/mcp/idea.js",
|
|
15
|
-
"src/mcp/enhance.js",
|
|
16
15
|
"src/mcp/idea-tool-defaults.js",
|
|
16
|
+
"src/mcp/idea-tool-output.js",
|
|
17
17
|
"src/project.js",
|
|
18
18
|
"src/ide-launcher.js",
|
|
19
19
|
"src/ide-guidance.js",
|
|
@@ -56,7 +56,8 @@ base,变体不另起条目**:`{配置名}` 不带任何 `@` 段 —— 名字文
|
|
|
56
56
|
- run:ide_execute_run_configuration
|
|
57
57
|
- after run:
|
|
58
58
|
- 产物 `middleware/seatunnel/connectors/connector-fileparse-<ST 版本>.jar`(78M,已 gitignore)
|
|
59
|
-
- 接着同步到部署机:`ide_upload_to_deployment`,`
|
|
59
|
+
- 接着同步到部署机:`ide_upload_to_deployment`,`deploymentName` 传 `list_server_deployments` 里这条映射的名字
|
|
60
|
+
(本地映射目录的最后一段,如 `.../mbpmiddleware` → `mbpmiddleware`)、`mode` 传 `upload`(默认 `mirror`
|
|
60
61
|
会删远端多余文件,别用);`in_progress` 就按 handle 轮 `ide_get_deployment_task_status`
|
|
61
62
|
- 同步完在部署机上 `docker compose restart seatunnel`
|
|
62
63
|
- desc:
|
package/src/ide-guidance.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// IDE MCP routing guidance injected into the model's system prompt.
|
|
2
2
|
//
|
|
3
3
|
// The TEXT lives in `ide-guidance.template.md`, not here. It is a prompt document
|
|
4
|
-
// a human has to review as a document — with the
|
|
5
|
-
// `{{
|
|
4
|
+
// a human has to review as a document — plain Markdown, with only the per-project
|
|
5
|
+
// command lists (`{{cmd_xxx}}`) generated — and building it by concatenating
|
|
6
6
|
// arrays made the result impossible to read without running the code. The
|
|
7
7
|
// template renders byte-for-byte to what the model receives; see `src/template.js`
|
|
8
8
|
// for why the renderer is 60 lines here rather than an engine dependency.
|
|
@@ -55,24 +55,18 @@
|
|
|
55
55
|
// 这样模型同时拿到"这条功能是什么"和"这个项目里它叫什么",不需要自己拼前缀 —— 少一层
|
|
56
56
|
// 推理,就少一类"拿错项目工具"的失败;而且文本不会随项目数成倍增长。
|
|
57
57
|
//
|
|
58
|
-
// The
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
58
|
+
// The guidance describes the companion plugin's tools (`idea-mcp-enhance`)
|
|
59
|
+
// unconditionally: that plugin is a hard prerequisite, not an optional extra, so
|
|
60
|
+
// there is nothing to detect here. A tool named in the guidance is expected to be
|
|
61
|
+
// in the request; if it is not, the installation is wrong, and hiding the text
|
|
62
|
+
// would only make that harder to notice.
|
|
63
|
+
//
|
|
64
|
+
// The template is therefore plain Markdown with no `{{#if …}}` blocks at all —
|
|
65
|
+
// only the per-project command lists (`{{cmd_xxx}}`) are generated.
|
|
65
66
|
|
|
66
67
|
import { readFileSync } from 'node:fs';
|
|
67
68
|
|
|
68
69
|
import { IDE_LABELS } from './ide-projects.js';
|
|
69
|
-
import {
|
|
70
|
-
DEPLOYMENT_SUFFIXES,
|
|
71
|
-
RUN_LIFECYCLE_SUFFIXES,
|
|
72
|
-
deploymentCapabilities,
|
|
73
|
-
runLifecycleCapabilities,
|
|
74
|
-
servesTool,
|
|
75
|
-
} from './mcp/enhance.js';
|
|
76
70
|
import { renderTemplate } from './template.js';
|
|
77
71
|
|
|
78
72
|
/** The prompt document, next to this module so it ships with it. */
|
|
@@ -81,17 +75,6 @@ const TEMPLATE_PATH = new URL('ide-guidance.template.md', import.meta.url);
|
|
|
81
75
|
/** Read once: the file is fixed for the life of the process, and this runs per request. */
|
|
82
76
|
const TEMPLATE = readFileSync(TEMPLATE_PATH, 'utf8');
|
|
83
77
|
|
|
84
|
-
/**
|
|
85
|
-
* 配套插件那两组工具的后缀全集 —— 判据只有一条:**这张表里的工具按项目分别存在**。
|
|
86
|
-
*
|
|
87
|
-
* 表外(内置 MCP Server 自带的那批)是每个项目都有的,列命令名时不需要看快照;表内的必须
|
|
88
|
-
* 逐项目核对(见 {@link commandVariables})。两组都收进来而不是只收部署那组:它们的
|
|
89
|
-
* 存在性本来就按项目、按 IDE 不同 —— 启停工具取决于装没装配套插件,部署工具还多一道
|
|
90
|
-
* "是不是 Ultimate"。把 run-lifecycle 留在表外,等于默认"一个项目装了、别的项目也装了",
|
|
91
|
-
* 而那只是个默认,不是事实。
|
|
92
|
-
*/
|
|
93
|
-
const ENHANCE_SUFFIXES = new Set([...RUN_LIFECYCLE_SUFFIXES, ...DEPLOYMENT_SUFFIXES]);
|
|
94
|
-
|
|
95
78
|
/**
|
|
96
79
|
* 「本会话有哪些项目、各自用哪个 MCP 工具前缀」的清单。
|
|
97
80
|
*
|
|
@@ -125,80 +108,26 @@ function routeTableFor(projects) {
|
|
|
125
108
|
* `` `根项目 ide_search_symbol` | `modelserver modelserver_search_symbol` ``。
|
|
126
109
|
* 所以模板增删一条工具不需要动这里的代码,也不会出现"模板里写了、代码忘了给值"的空行。
|
|
127
110
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* 部署那组还要求 Ultimate)。它常常同进同出,但"通常"不是"总是" —— 一旦不一致,列全就会在
|
|
131
|
-
* 缺工具的那个项目下点名一个模型调不到的命令,而那正是这整个模块要防的失败。
|
|
132
|
-
* 过滤不会产生空行:每条 bullet 的 `{{#if}}` 用的是**并集**判断(任一项目有即渲染),
|
|
133
|
-
* 并集为真 ⇒ 至少一个项目通过过滤。
|
|
111
|
+
* 每个项目都列:配套插件是硬前提,每个接入的 IDE 都装了它,所以不需要再按工具快照逐项目
|
|
112
|
+
* 过滤"这个项目有没有这个工具"。
|
|
134
113
|
*
|
|
135
114
|
* @param {ReadonlyArray<{ label: string, server: string, isRoot: boolean }>} projects
|
|
136
|
-
* @param {Record<string, unknown> | undefined} tools 本次请求的工具快照
|
|
137
115
|
*/
|
|
138
|
-
function commandVariables(projects
|
|
116
|
+
function commandVariables(projects) {
|
|
139
117
|
/** @type {Record<string, string>} */
|
|
140
118
|
const variables = {};
|
|
141
119
|
for (const match of TEMPLATE.matchAll(/\{\{\s*(cmd_[A-Za-z0-9_]+)\s*\}\}/g)) {
|
|
142
120
|
const name = match[1];
|
|
143
121
|
const suffix = name.slice('cmd_'.length);
|
|
122
|
+
// 每条都写清"哪个项目 → 哪个命令":根项目直接用它的角色名,子项目用目录名。
|
|
123
|
+
// 不写 `.`/目录名这种需要回看清单才能解读的代号 —— 模型读每条功能时不必回头查表。
|
|
144
124
|
variables[name] = projects
|
|
145
|
-
.filter((project) => !ENHANCE_SUFFIXES.has(suffix) || servesTool(tools, `${project.server}_`, suffix))
|
|
146
|
-
// 每条都写清"哪个项目 → 哪个命令":根项目直接用它的角色名,子项目用目录名。
|
|
147
|
-
// 不写 `.`/目录名这种需要回看清单才能解读的代号 —— 模型读每条功能时不必回头查表。
|
|
148
125
|
.map((project) => `${project.isRoot ? '根项目' : project.label} \`${project.server}_${suffix}\``)
|
|
149
126
|
.join(' | ');
|
|
150
127
|
}
|
|
151
128
|
return variables;
|
|
152
129
|
}
|
|
153
130
|
|
|
154
|
-
/**
|
|
155
|
-
* 本会话里这些 run-lifecycle 能力可不可用。
|
|
156
|
-
*
|
|
157
|
-
* 提示词只有一份,所以这里要回答的是"这套能力在本次会话里存不存在" —— 任一项目提供即算存在。
|
|
158
|
-
* 同一个人的几个 IDE 装不装配套插件通常是一致的;万一不一致,模板里那句"没装就交给用户"兜住。
|
|
159
|
-
*
|
|
160
|
-
* @param {ReadonlyArray<{ server: string }>} projects
|
|
161
|
-
* @param {Record<string, unknown> | undefined} tools
|
|
162
|
-
*/
|
|
163
|
-
function mergeRunLifecycle(projects, tools) {
|
|
164
|
-
const all = projects.map((project) => runLifecycleCapabilities(tools, `${project.server}_`));
|
|
165
|
-
return {
|
|
166
|
-
canCreateRunConfiguration: all.some((c) => c.canCreateRunConfiguration),
|
|
167
|
-
canInspectRunConfiguration: all.some((c) => c.canInspectRunConfiguration),
|
|
168
|
-
canUpdateRunConfiguration: all.some((c) => c.canUpdateRunConfiguration),
|
|
169
|
-
canDeleteRunConfiguration: all.some((c) => c.canDeleteRunConfiguration),
|
|
170
|
-
canListRunningConfigurations: all.some((c) => c.canListRunningConfigurations),
|
|
171
|
-
canStopRunConfiguration: all.some((c) => c.canStopRunConfiguration),
|
|
172
|
-
canRerunRunConfiguration: all.some((c) => c.canRerunRunConfiguration),
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* 部署工具同理(见 `mergeRunLifecycle`):逐项目判断,任一项目提供即算存在。
|
|
178
|
-
*
|
|
179
|
-
* **整节的锚点就是 `canListDeploymentServers`**(模板里 `{{#if canListDeploymentServers}}` 直接包住标题):
|
|
180
|
-
* 配套插件只要装了,IDE 的 MCP 工具表里就多出这一组(它们由同一道前提注册,同进同出),
|
|
181
|
-
* 所以拿其中一个当判据就够了 —— 不需要再造一个"四个里任一个"的聚合标志。
|
|
182
|
-
*
|
|
183
|
-
* 不锚在那些"必须有工具才算"的地方也不成问题:一个都没有时 `canListDeploymentServers` 为 false,
|
|
184
|
-
* 标题与正文一起消失;而这一节和前一组不同 —— run-lifecycle 那节是内置工具
|
|
185
|
-
* (`get_run_configurations` / `execute_run_configuration`)撑起来的**自己的**标题,缺配套插件时标题仍在。
|
|
186
|
-
* 顺带一提,这一组还额外要求 `com.jetbrains.plugins.webDeployment`(**仅 Ultimate 有**),所以
|
|
187
|
-
* "整节不出现"是常态而非边角。
|
|
188
|
-
*
|
|
189
|
-
* @param {ReadonlyArray<{ server: string }>} projects
|
|
190
|
-
* @param {Record<string, unknown> | undefined} tools
|
|
191
|
-
*/
|
|
192
|
-
function mergeDeployment(projects, tools) {
|
|
193
|
-
const all = projects.map((project) => deploymentCapabilities(tools, `${project.server}_`));
|
|
194
|
-
return {
|
|
195
|
-
canListDeploymentServers: all.some((c) => c.canListDeploymentServers),
|
|
196
|
-
canUploadToDeployment: all.some((c) => c.canUploadToDeployment),
|
|
197
|
-
canDownloadFromDeployment: all.some((c) => c.canDownloadFromDeployment),
|
|
198
|
-
canGetDeploymentTaskStatus: all.some((c) => c.canGetDeploymentTaskStatus),
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
131
|
/**
|
|
203
132
|
* 把本会话接入的全部项目渲染成**一份**引导文本。
|
|
204
133
|
*
|
|
@@ -206,17 +135,13 @@ function mergeDeployment(projects, tools) {
|
|
|
206
135
|
* 列出各项目的命令名(`cmd_*` 变量),而不是把整份工具清单按前缀抄 N 遍。
|
|
207
136
|
*
|
|
208
137
|
* @param {ReadonlyArray<{ path: string, server: string, app: string, label: string, isRoot: boolean }>} projects
|
|
209
|
-
* @param {{ tools?: Record<string, unknown> }} [options] `tools` 是本次请求的工具快照
|
|
210
|
-
* (`event.tools`),用来判断配套插件(run-lifecycle / deployment)的工具在不在。
|
|
211
138
|
* @returns {string}
|
|
212
139
|
*/
|
|
213
|
-
export function buildIdeGuidanceText(projects
|
|
140
|
+
export function buildIdeGuidanceText(projects) {
|
|
214
141
|
return renderTemplate(TEMPLATE, {
|
|
215
142
|
projectCount: projects.length,
|
|
216
143
|
routeTable: routeTableFor(projects),
|
|
217
|
-
...
|
|
218
|
-
...mergeDeployment(projects, options.tools),
|
|
219
|
-
...commandVariables(projects, options.tools),
|
|
144
|
+
...commandVariables(projects),
|
|
220
145
|
});
|
|
221
146
|
}
|
|
222
147
|
|
|
@@ -252,18 +177,16 @@ export const MCP_RECOVERY_GUIDANCE_RECONNECTING = [
|
|
|
252
177
|
* Push the guidance onto a session `system` array. Called for every primary
|
|
253
178
|
* model request, so it stays idempotent per request.
|
|
254
179
|
*
|
|
255
|
-
* The text depends on which projects are connected and
|
|
256
|
-
*
|
|
257
|
-
* than assuming one variant.
|
|
180
|
+
* The text depends only on which projects are connected, and the idempotence check
|
|
181
|
+
* compares the built text rather than assuming one variant.
|
|
258
182
|
*
|
|
259
183
|
* @param {unknown} system
|
|
260
184
|
* @param {ReadonlyArray<{ path: string, server: string, app: string, label: string, isRoot: boolean }>} projects
|
|
261
|
-
* @param {{ tools?: Record<string, unknown> }} [options]
|
|
262
185
|
*/
|
|
263
|
-
export function appendIdeGuidance(system, projects
|
|
186
|
+
export function appendIdeGuidance(system, projects) {
|
|
264
187
|
if (!Array.isArray(system)) return;
|
|
265
188
|
if (!Array.isArray(projects) || projects.length === 0) return;
|
|
266
|
-
const text = buildIdeGuidanceText(projects
|
|
189
|
+
const text = buildIdeGuidanceText(projects);
|
|
267
190
|
if (system.some((part) => part?.text === text)) return;
|
|
268
191
|
system.push({ type: 'text', text });
|
|
269
192
|
}
|
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
2. **IDE 工具补自带工具做不到的**:符号与定义用 `<前缀>_search_symbol` + `<前缀>_read_file`;改名用
|
|
8
8
|
`<前缀>_rename_refactoring`;单文件检查用 `<前缀>_lint_files` / `<前缀>_get_file_problems`;跨模块构建用
|
|
9
9
|
`<前缀>_build_project`。
|
|
10
|
-
3.
|
|
11
|
-
|
|
10
|
+
3. **要项目运行时环境的命令用项目终端**:{{cmd_project_sdk_terminal}};`git` / `ls` / `curl` / `docker` 这类与项目无关的
|
|
11
|
+
仍走 `shell`。细则见「命令执行」与「终端」。
|
|
12
|
+
4. **校验节奏**:改一处只在收尾跑 `lint_files` (要细节再 `get_file_problems`);改一大片或动了接口 / 依赖就用
|
|
13
|
+
`build_project`;全仓检查只在收尾。
|
|
12
14
|
5. **批量修改**:逐文件用 `edit`,或一次用 `<前缀>_apply_patch`。
|
|
13
15
|
|
|
14
|
-
本会话接入了 {{projectCount}} 个 JetBrains
|
|
16
|
+
本会话接入了 {{projectCount}} 个 JetBrains 项目。**每个 IDE 功能在所有项目下都各有一份工具**,命令名是
|
|
15
17
|
`<项目前缀>_<工具名>`;下文写 `<前缀>` 就是指这一截。
|
|
16
18
|
|
|
17
19
|
## 本会话接入的项目与各自的 MCP 工具
|
|
@@ -24,8 +26,8 @@
|
|
|
24
26
|
**规则:操作哪个目录下的文件/代码,就用那个目录对应的命令。** IDE 按项目解析 —— 用错前缀时,调用要么报错,
|
|
25
27
|
要么作用在另一个项目的代码上。
|
|
26
28
|
|
|
27
|
-
按需读文件:要了解某个子项目自己的约定,读它的 `<子项目目录>/AGENTS.md`
|
|
28
|
-
|
|
29
|
+
按需读文件:要了解某个子项目自己的约定,读它的 `<子项目目录>/AGENTS.md` —— 它不会自动进入本会话的上下文
|
|
30
|
+
(这里加载的是 root 的那一份)。
|
|
29
31
|
|
|
30
32
|
## 路由:先看路径在不在项目里
|
|
31
33
|
|
|
@@ -33,7 +35,7 @@
|
|
|
33
35
|
项目外的路径(`/tmp`、别的仓库、用户主目录)不在 IDE 的解析范围内。
|
|
34
36
|
|
|
35
37
|
- **项目内** —— **OpenCode 自带的工具为主**,它们更快。**它们做不了、或者做得很差的,才用 IDE 工具**:
|
|
36
|
-
|
|
38
|
+
符号检索、依赖实现、重构、格式化、校验、运行配置、项目终端、跨文件补丁。
|
|
37
39
|
**两边都不顺手时,你自己判断用 `shell` 里的命令** —— 那是第三选择,不是第一选择。
|
|
38
40
|
- **项目外** —— 用 OpenCode 自带的工具;IDE 工具只对项目内路径生效。
|
|
39
41
|
|
|
@@ -42,13 +44,13 @@
|
|
|
42
44
|
- **OpenCode 自带的工具直接调用** —— 工具列表里直接列出的那些(`read` / `grep` / `glob` / `edit` / `write` / `shell`),
|
|
43
45
|
参数按各自 schema 传。它们就在当前工具表里,直接调用即可。
|
|
44
46
|
- **读文件用 `read` 工具** —— 要读某一段就传 `offset`(从第几行起)+ `limit`(读几行),输出自带行号。
|
|
45
|
-
- **搜文本用 `grep` 工具** —— 它按 `.gitignore`
|
|
47
|
+
- **搜文本用 `grep` 工具** —— 它按 `.gitignore` 排除,依赖目录 / 构建产物不进结果,范围用 `path` 收窄。
|
|
46
48
|
要看命中行前后的大段上下文:先用它拿到**行号**,再用 `read` + `offset` / `limit` 读那一段。
|
|
47
49
|
- **工具输出不够用时,按这个顺序调整**:① 调参数重试 → ② 缩小路径范围重试 → ③ 换工具。
|
|
48
50
|
换用 `shell` 的判断依据是"这个工具确实没有这个能力"。
|
|
49
51
|
典型:`<前缀>_list_directory_tree` 的输出到 `maxDepth` 边界就停 —— 调大 `maxDepth` 再试一次。
|
|
50
52
|
- **改文件**:改一个符号用 `<前缀>_rename_refactoring`;改一段文字用 `edit` / `write` 工具;
|
|
51
|
-
同一段文字铺到多个文件用 `<前缀>_apply_patch
|
|
53
|
+
同一段文字铺到多个文件用 `<前缀>_apply_patch`。跑命令的路由见「命令执行」。
|
|
52
54
|
改代码始终落在具体的那几行上,其余内容保持原样。
|
|
53
55
|
- **改完代码自己先查一遍**:`lint_files` / `build_project`,怎么挑见「校验」。
|
|
54
56
|
- **文档类文件 (Markdown / 文档) 改完之后,用 `<前缀>_reformat_file` 过一遍** —— 表格对齐、换行宽度、缩进交给 IDE
|
|
@@ -57,17 +59,21 @@
|
|
|
57
59
|
|
|
58
60
|
## 命令执行 (所有项目通用)
|
|
59
61
|
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
- **按"这条命令要不要项目运行时"分流**:
|
|
63
|
+
- **要**(IDE 里配好的 JDK / Node / Python / Maven / Go,以及依赖它们的构建、脚本、生成、迁移)→
|
|
64
|
+
{{cmd_project_sdk_terminal}}。它开的是 IDE 里真正的终端,自动带上项目 SDK 与 Maven / Node / Python 的路径;
|
|
65
|
+
`workingDirectory` 默认项目根,`env` 里的值最后覆盖。
|
|
66
|
+
- **不要**(`git` / `ls` / `cat` / `curl` / `docker` / `ps`)→ `shell` 工具。
|
|
67
|
+
- **常驻进程**(dev server / watch / 长时间跑的服务)有 run configuration 就用 `rerun` 起它;
|
|
68
|
+
没有就用项目终端传 `timeoutMs: 0` 起在 tab 里,之后用 {{cmd_terminal_io}} 跟日志、回答提示。
|
|
69
|
+
- **一次性的命令就地跑完,反复发生的流程才固化成 run configuration** —— 配置是**项目资产**,不是"跑命令"
|
|
70
|
+
的手段。判据只有一条: **这件事以后会不会以同样的形态再发生?** 会 (构建 / 生成 / 迁移 / 起服务 / 部署这类)
|
|
71
|
+
就 **主动提**一次要不要固化成配置,并把配置名 / 类型 / 命令 / 工作目录 / 为什么值得留摊开;
|
|
72
|
+
不会 (查条数据、看个日志、试一条命令) 就地跑完算。
|
|
63
73
|
- **建 / 改 / 删运行配置,走 `idea-run-config-manager` skill**(服务化应用的配置归 `idea-standalone`):
|
|
64
74
|
它落手用的就是「运行配置」里的那组工具 —— 配置文件由工具写入和删除。
|
|
65
|
-
{{#if canCreateRunConfiguration}}
|
|
66
|
-
{{else}}
|
|
67
|
-
本会话没有新建工具,"建"这一步它只能讲清配置名、实际执行的命令、工作目录、为什么值得留, **请用户在 IDE 里创建**
|
|
68
|
-
{{/if}}
|
|
69
75
|
|
|
70
|
-
## 工具 (
|
|
76
|
+
## 工具 (每条先列各项目的命令名,再写用法)
|
|
71
77
|
|
|
72
78
|
### 符号 (只有 IDE 的语义索引能给)
|
|
73
79
|
|
|
@@ -79,11 +85,9 @@
|
|
|
79
85
|
(类 / 接口 / 方法 / 函数 / 字段 / 类型 / 常量这类 **有名字的定义**)的**声明位置**,不是"哪段文本提到过它"(那是
|
|
80
86
|
`grep` 工具的事);带上 `include_external` 才会连依赖库和标准库一起搜 —— 这是 `grep` / `glob` 工具够不着的地方。
|
|
81
87
|
- **查某个库的 API**:代码里 import 的包名就是线索,直接「包名 + 符号名」当 `q` —— Java 用全限定名
|
|
82
|
-
(
|
|
83
|
-
(
|
|
84
|
-
(
|
|
85
|
-
- **Node/TS 例外**:带 `-` 或 `@scope/` 的包名这么拼是 0 条(`element-plus`);改用 import 的符号名,并用 `paths` 圈到
|
|
86
|
-
那个 `node_modules`(`paths: ['frontend/user/**']` 就收敛到一处)。
|
|
88
|
+
(`<包名>.<类名>`)、Go 用 `pkg.Symbol`(`<包名>.<导出符号>`)、Python 给全模块路径
|
|
89
|
+
(`<模块>.<类>`)、Node/TS 用 import 的那层包名(`<包名>.<导出名>`)。**调研一个库有哪些 API**:拿包名当 `q`
|
|
90
|
+
(入口文件通常就是导出清单),再读它的入口。
|
|
87
91
|
- **搜不到 = 依赖没进来**:先把它加进依赖,或改用 web 搜索 / GitHub 看源码。
|
|
88
92
|
- **同名符号往往有多处定义**(标准库 / 依赖 / 测试各一份):按调用处的 import 与参数类型来选。
|
|
89
93
|
- **它返回的路径原样交给同一个项目的 `<前缀>_read_file`** —— 那个路径的基准是**它所在的项目**,不是会话工作目录;
|
|
@@ -95,11 +99,10 @@
|
|
|
95
99
|
|
|
96
100
|
### 项目与编辑器
|
|
97
101
|
|
|
98
|
-
- {{cmd_list_directory_tree}} **项目视角的目录树**(尊重内容根与排除项):`maxDepth`
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
**把 `maxDepth` 调大重试,或把 `directoryPath` 直接指到那个子目录再看一层。**
|
|
102
|
+
- {{cmd_list_directory_tree}} **项目视角的目录树**(尊重内容根与排除项):`maxDepth` 控深度,是**绝对上限但只展开
|
|
103
|
+
`maxDepth-1` 层**(省略时默认 5,即 4 层);要看全就传明显够大的值(如 50),没有额外代价。**输出到边界就停,不带截断
|
|
104
|
+
标记** —— 目录显示为空也可能是被切了;判据是树深度顶到传入值,或**包 / 源码目录显示为空**。调大 `maxDepth`,或把
|
|
105
|
+
`directoryPath` 指到那个子目录再看一层。
|
|
103
106
|
- {{cmd_get_project_modules}} **项目模块及类型**。
|
|
104
107
|
- {{cmd_get_project_dependencies}} **项目直接声明的依赖**。
|
|
105
108
|
- {{cmd_get_all_open_file_paths}} **用户在 IDE 里打开了哪些文件**(含当前激活的那个)。
|
|
@@ -120,74 +123,51 @@
|
|
|
120
123
|
- 返回里 `with N usages` 的 N 是**真引用数**;比预期少,说明有引用没被解析到(文件没进 IDE 索引),
|
|
121
124
|
这时**先确认索引到那个模块**,再处理剩下的引用。
|
|
122
125
|
- 改的本来就是文字(配置 key、模板占位符、文档、SQL)、**就是要改那句字符串**、或者文件 IDE 没索引(生成物、
|
|
123
|
-
|
|
126
|
+
第三方目录)时,用 `edit` / `write` / `apply_patch`。动手前先用 `grep` 工具确认命中范围。
|
|
124
127
|
- {{cmd_reformat_file}} **按 IDE 的代码风格重排**。
|
|
125
128
|
- {{cmd_apply_patch}} **一次改多个文件**:`input` 传补丁正文,Codex 格式(`*** Begin Patch` / `*** Update File: <路径>`
|
|
126
129
|
/ `@@` / `-` 旧行 / `+` 新行)与 unified git diff 都收。`edit` / `write` 工具都只管一个文件。
|
|
127
130
|
- 它**按文本**改,适合同一段文字铺到多个文件;**改符号走上面那条**。
|
|
128
|
-
-
|
|
129
|
-
|
|
130
|
-
- 多个文件是**逐个应用**的:返回里 `N out of M operations applied`,失败的那些读一下再改;
|
|
131
|
-
改完用 `grep` 工具确认落盘。
|
|
131
|
+
- 只碰补丁里写明的那几行。上下文对不上时该文件**原样不动**(`<路径>: Hunk context not found`);多个文件按返回的
|
|
132
|
+
`N out of M operations applied` 逐个补,改完用 `grep` 工具确认落盘。
|
|
132
133
|
- {{cmd_lint_files}} **批量检查**:`files` 传数组;只列有问题的文件。
|
|
133
134
|
- {{cmd_get_file_problems}} **单文件检查**:`filePath`;结果在 `errors`。
|
|
134
135
|
- {{cmd_build_project}} **构建校验**:`filesToRebuild` 可只构建指定文件;有问题返回在 `problems`,没返回即没问题。
|
|
135
136
|
|
|
136
137
|
**改完代码,自己先查一遍再收尾。** 查一次很便宜,而问题一旦漏到用户看见、或留到构建 / 测试炸了才暴露,回头纠正的
|
|
137
|
-
代价就高一个量级。 校验跟着改动范围走 —— 这一轮属于"改一大片"还是"改一处"
|
|
138
|
-
|
|
139
|
-
-
|
|
140
|
-
|
|
141
|
-
-
|
|
142
|
-
|
|
143
|
-
- **改了签名 / 接口 / import / 依赖**这类"别处会跟着坏"的地方 → 走 `build_project`;只动了文件内部的实现 →
|
|
144
|
-
lint 就够。
|
|
138
|
+
代价就高一个量级。 校验跟着改动范围走 —— 这一轮属于"改一大片"还是"改一处",按下面两条判断。
|
|
139
|
+
|
|
140
|
+
- **改一大片**(多文件、跨模块、"顺手把这几处一起对齐"),或**动了签名 / 接口 / import / 依赖**这类"别处会跟着坏"的
|
|
141
|
+
地方 → 全部改完再 `build_project` 一次,在完整状态上检查。
|
|
142
|
+
- **只改一处,或只在文件内部微调** → 告一段落时把这轮改过的文件跑一次 `lint_files`;要看单个文件的细节再上
|
|
143
|
+
`get_file_problems`。
|
|
145
144
|
- 查出来的问题 **改掉,或说清为什么不改**。
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
`
|
|
149
|
-
- 只有改依赖清单、生成 Prisma Client、跑迁移这类必须执行命令的动作,才额外调用 `pnpm`;一个任务通常不超过两次。
|
|
145
|
+
- 校验就用上面这些工具;索引没同步或环境不对时,第一次调用就会明说。
|
|
146
|
+
- **全仓检查是收尾动作,不是编辑循环**:同一任务只在最后跑一次;过程中只对改动文件跑 `lint_files` /
|
|
147
|
+
`get_file_problems`。
|
|
150
148
|
|
|
151
149
|
### 运行配置
|
|
152
150
|
|
|
153
151
|
- {{cmd_get_run_configurations}} **项目里有哪些运行配置**:传 `filePath` 可列该文件的运行入口。
|
|
154
152
|
配置由 IDE 自己存储(通常在项目目录之外),用这个工具读。
|
|
155
|
-
- {{cmd_execute_run_configuration}} **跑已有配置**:配置里的 `EXECUTE_IN_TERMINAL` 要是 `false`(共享出去的配置都这样)
|
|
156
|
-
|
|
157
|
-
{{#if canCreateRunConfiguration}}
|
|
153
|
+
- {{cmd_execute_run_configuration}} **跑已有配置**:配置里的 `EXECUTE_IN_TERMINAL` 要是 `false`(共享出去的配置都这样);
|
|
154
|
+
覆盖参数 (`programArguments` / `workingDirectory` / `envs`) 需配置支持 `supportsDynamicLaunchOverrides`,否则会直接报错。
|
|
158
155
|
- {{cmd_create_run_configuration}} **新建任意类型的项目级配置**:内容来源三选一 —— `cloneFrom`(复制一个现成的,最稳;
|
|
159
156
|
字段名不确定时只走它)、`configurationXml`(模板里的 XML 片段)、平台模板 + `options` / `attributes` / `envs` 补丁。
|
|
160
|
-
|
|
161
|
-
**要不要建、建什么、怎么建、档案怎么记,全按上面那个 skill 走。** 判据只有一条: **这件事以后会不会以同样的形态再发生?**
|
|
162
|
-
会 (改表后同步 DDL、跑迁移、生成代码、打包、起 dev server、往某个环境部署这类)就 **主动提**一次
|
|
163
|
-
"要不要固化成 run configuration",并把配置名 / 类型 / 命令 / 工作目录 / 为什么值得留摊开;不会 (查条数据、看个日志、
|
|
164
|
-
试一条命令)就用 `shell` 工具跑完算。
|
|
157
|
+
**建不建按上面那条判据**;怎么建、档案怎么记,全按 `idea-run-config-manager` skill 走。
|
|
165
158
|
**改完同步档案**:新增 / 删除 / 改名之后按 skill 更新 `AGENTS.md` 里的档案块 —— 档案和项目里的配置对不上,比没有档案更糟。
|
|
166
|
-
{{/if}}
|
|
167
|
-
{{#if canInspectRunConfiguration}}
|
|
168
159
|
- {{cmd_inspect_run_configuration}} **看一个配置的全貌**:定义 (options / envs / before-launch)、命名解析 (`win` /
|
|
169
160
|
mac-linux、base、env)、当前活实例、历史运行及各自的日志文件路径。 **改之前先用它拿当前值**;服务起没起来想看日志,先用
|
|
170
161
|
`list_running_configurations` 的 `logFile`,要翻"上一次为什么挂了"再走这里。
|
|
171
|
-
{{/if}}
|
|
172
|
-
{{#if canUpdateRunConfiguration}}
|
|
173
162
|
- {{cmd_update_run_configuration}} **改字段或改名**:`options` / `envs` / `attributes` 是补丁 (`null` = 删),`newName` 改名并
|
|
174
163
|
**连带更新别人对它的引用**、迁移历史日志。改之前先 `inspect` 拿 `configurationXml` 与当前值。
|
|
175
|
-
{{/if}}
|
|
176
|
-
{{#if canDeleteRunConfiguration}}
|
|
177
164
|
- {{cmd_delete_run_configuration}} **删配置**:在跑或被人引用时拒绝;`force = true` 才会连引用一起清掉。
|
|
178
|
-
{{/if}}
|
|
179
|
-
{{#if canListRunningConfigurations}}
|
|
180
165
|
- {{cmd_list_running_configurations}} **当前真正在跑的配置**(几个实例 / 各自 pid / 退出码,每个实例还带自己的日志文件路径
|
|
181
166
|
`logFile`):停 / 重启前后都先用它看状态。
|
|
182
|
-
{{/if}}
|
|
183
|
-
{{#if canStopRunConfiguration}}
|
|
184
167
|
- {{cmd_stop_run_configuration}} **停掉该配置的全部实例并等它们退出**:没在跑也返回成功。
|
|
185
|
-
{{/if}}
|
|
186
|
-
{{#if canRerunRunConfiguration}}
|
|
187
168
|
- {{cmd_rerun_run_configuration}} **启动 / 重启常驻进程**:在跑就重启、没在跑就启动 —— 改完代码要重启就直接用它,
|
|
188
169
|
有没有热重载都一样;读 `started` 判断新进程是否真的起来了。这次运行的输出在 `output`,完整日志文件路径在
|
|
189
170
|
`fullOutputPath`。启停都走这个工具,IDE 的 Run 窗口状态才会跟着更新。
|
|
190
|
-
{{/if}}
|
|
191
171
|
|
|
192
172
|
**配置名怎么读**:`[os@]base[@env]`,`@` 分割、最多三段 —— `os` = `win`(缺省 macOS / Linux)、`base` = 配置本体(必填)、
|
|
193
173
|
`env` = 环境名(缺省 = 默认环境)。`win@base`(换平台)与 `base@env`(换环境)都是 `base` 的 **变体**,跑哪一个:
|
|
@@ -197,27 +177,52 @@
|
|
|
197
177
|
变体、没有默认版的,就用它自己那个 —— "10 个服务"可以是"3 个 `@dev` + 7 个默认",不必逐条问。
|
|
198
178
|
- 一条配置同时有 `base` 与 `base@<env>`、又没说走哪个环境 → **问一次走哪个环境**,选定后本会话默认走这套。
|
|
199
179
|
- 一个环境怎么接上(启动参数 / 环境变量 / 配置文件)是项目自己的事:照现成的同类变体来,或问用户。
|
|
200
|
-
|
|
180
|
+
|
|
181
|
+
### 终端 (项目环境 · 交互 · 远程)
|
|
182
|
+
|
|
183
|
+
- {{cmd_project_sdk_terminal}} **在本项目 SDK 终端里跑一条命令,返回输出与 tab id**。`command` 必填;`timeoutMs` 默认
|
|
184
|
+
10000(`0` = 发出去就返回,最长 12000);`workingDirectory` 默认项目根;`env` 最后覆盖自动注入的环境;`input` 在命令后
|
|
185
|
+
写一次(自带换行);`maxChars` 只保留最新输出。 **每次调用都新开一个 tab** —— 同一件事的后续改用 `list_terminals` /
|
|
186
|
+
`terminal_io`,别再调它。
|
|
187
|
+
- {{cmd_list_terminals}} **本项目的终端 tab 清单**:`kind`(`mcp_project_sdk` / `mcp_remote` / `user`)、`status`、
|
|
188
|
+
`workingDirectory` / `currentDirectory`、`lastCommand`、`lastUsedAt`。 **只有 `tab` 非空且 `addressable: true` 的能交给
|
|
189
|
+
`terminal_io`**;用户自己开的 tab 只用于认位置,不可控制;`title` 不是 id。
|
|
190
|
+
- {{cmd_terminal_io}} **对已有 tab 读 / 写**:`target` 取 `local`(`project_sdk_terminal` 建的)或 `remote`。
|
|
191
|
+
- `action: "read"` —— 默认读**上次之后的新输出**;`tail: N` 读最近 N 个命令段(本地还带每段的 `exitCode`);`full: true`
|
|
192
|
+
读整屏;`timeoutMs > 0` 先等输出稳定再读(最长 12000)。
|
|
193
|
+
- `action: "write"` —— `text` **原样**送进当前命令(自带换行),不是新开一条命令。
|
|
194
|
+
- {{cmd_execute_in_remote_host}} **在远程主机的真实 SSH 终端里跑命令** —— 主机与凭据来自本项目的一台 Deployment
|
|
195
|
+
SFTP 服务器,名字传 `serverName`(那台**服务器**的名字,不是 deployment 名)。`loginShell` 默认 `true`(套
|
|
196
|
+
`zsh -lic` / `bash -lc`,PATH 与工具链才会加载);`strictHostKey` 默认 `false`(接受并记录新主机密钥)。
|
|
197
|
+
**远程命令一律走它,不要自己拼 `ssh` / `scp`** —— 凭据、代理、路径映射都在 IDE 这边。
|
|
198
|
+
远程没有"命令结束"信号:**`exitCode` 永远为 `null`,`running` 也可能为 `null`** —— 别等 `exitCode`,看输出,或接着用
|
|
199
|
+
`terminal_io` 读。
|
|
200
|
+
|
|
201
|
+
**tab 只在这次 IDE 会话里有效**:tab 一关(报 `terminal_closed` / 找不到 tab)才重新开一个;用完不必主动关 —— 用户能看见,
|
|
202
|
+
也能直接在 IDE 里接手。
|
|
201
203
|
|
|
202
204
|
### 服务器文件同步
|
|
203
205
|
|
|
204
206
|
往服务器传文件 / 从服务器取文件,走这一组 —— IDE 的连接、路径映射、排除规则都在它们后面,传的就是 IDE 在管的那一份。
|
|
205
207
|
|
|
206
|
-
- {{
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
- {{cmd_list_server_deployments}} **本项目能用的服务器,以及每台服务器上的 deployment**。先分清两层:
|
|
209
|
+
**Server 是连接目标,deployment 是挂在它上面的一条路径映射**;一台 Server 可以有 **0 个 deployment** —— 那样它仍能给
|
|
210
|
+
`execute_in_remote_host` 用,但**上传 / 下载没有目标**。
|
|
211
|
+
- `servers[].name` → **远程终端**的 `serverName`;`servers[].deployments[].name` → **上传 / 下载**的 `deploymentName`
|
|
212
|
+
(由本地路径最后一段派生,**全局唯一**)。
|
|
213
|
+
- 每台 Server 还带 `level`(项目级 / 应用级)、`accessType`、`host` / `port`、`rootFolder`、`hasCredentials`、
|
|
214
|
+
`validationIssue`、`isDefault`;返回里另有**整个项目共用**的 `options` 与 `warnings[]`(**只读 IDE 配置、不联网**)。
|
|
215
|
+
- **`hasCredentials: false` 时不要连接** —— IDE 会弹模态登录框,MCP 调用会卡死;动手前先看 `warnings[]`。
|
|
216
|
+
- {{cmd_upload_to_deployment}} **把一条 deployment(本地映射目录)传到服务器**(等同 IDE 的 Upload to …)。
|
|
217
|
+
`deploymentName` 传 `servers[].deployments[].name` —— **不是 `serverName`**。`mode` 默认 `mirror` ——
|
|
211
218
|
**会删掉服务器上多出来的文件**,使两边一致;要留住它们就传 `upload`。 **破坏性操作**。
|
|
212
|
-
{{
|
|
213
|
-
|
|
214
|
-
- {{cmd_download_from_deployment}} **把服务器上映射目录的内容拉回本地**(等同 IDE 的 Download from …), **以服务器为准**:
|
|
215
|
-
两边都有的文件会被远端覆盖 (内容相同则不写)。 **没有"保留我的版本"这个选项**。
|
|
216
|
-
{{/if}}
|
|
217
|
-
{{#if canGetDeploymentTaskStatus}}
|
|
219
|
+
- {{cmd_download_from_deployment}} **把一条 deployment 从服务器拉回本地**(等同 IDE 的 Download from …), **以服务器为准**:
|
|
220
|
+
两边都有的文件会被远端覆盖 (内容相同则不写)。同样传 `deploymentName`。 **没有"保留我的版本"这个选项**。
|
|
218
221
|
- {{cmd_get_deployment_task_status}} **查一次传输的状态**:`handle` 用上传 / 下载返回的那个 —— **它自己不启动任何传输**。
|
|
219
|
-
{{/if}}
|
|
220
222
|
|
|
221
|
-
上传 / 下载都在 IDE 后台跑,单次调用只等到 `waitMs`(默认 10 秒)
|
|
222
|
-
`handle`
|
|
223
|
-
|
|
223
|
+
上传 / 下载都在 IDE 后台跑,单次调用只等到 `waitMs`(默认 10 秒,上限 12 秒)为止。返回 `state: "in_progress"` 是 **正常**的,
|
|
224
|
+
不是失败 (它可能以 error 结果回来) —— 拿 `handle` 调 `get_deployment_task_status` 接着看; **不要用重复调上传 / 下载来代替**
|
|
225
|
+
(那样会再起一次传输)。到 `completed` 之后 `counters` 才可信,而且它数的是**操作次数**,不是文件数(删掉整个目录也只算 1)。
|
|
226
|
+
|
|
227
|
+
**同步只是上半段**:传完之后在那台 Server 上继续操作(启动刚传上去的二进制、重启服务、看日志)—— 用
|
|
228
|
+
`execute_in_remote_host`,`serverName` 取 `servers[].name`。
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// Arguments this plugin OWNS on the IDE's own MCP tools.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// Two files, two different subjects — kept apart on purpose:
|
|
4
4
|
//
|
|
5
5
|
// * `idea.js` the IDE's bundled MCP server: endpoint, transport, probe
|
|
6
6
|
// * `idea-tool-defaults` (this file) that server's TOOL CATALOG, and the
|
|
7
7
|
// arguments on it that this plugin controls
|
|
8
|
-
//
|
|
8
|
+
//
|
|
9
|
+
// The companion plugin's own tools are not managed here at all: they are a hard
|
|
10
|
+
// prerequisite, passed through untouched.
|
|
9
11
|
//
|
|
10
12
|
// ## Why an argument can be owned at all
|
|
11
13
|
//
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// IDE 工具的**输出**增强。
|
|
2
|
+
//
|
|
3
|
+
// 和 `idea-tool-defaults.js` 分开:那个管"调用前要传什么",这个管"回来以后怎么读"。
|
|
4
|
+
//
|
|
5
|
+
// `list_directory_tree` 的输出在到达 maxDepth 边界时会**静默停止** —— 没有 `…`、没有"已达深度上限",
|
|
6
|
+
// 一棵被截断的树和一个空目录在文本里长得一模一样。实测(2026-10):模型把 `maxDepth=6` 截断后的空包目录
|
|
7
|
+
// 读成"工具看不到",随后整条查询链退回 `shell` 里的 `find` / `grep` / `sed`。
|
|
8
|
+
//
|
|
9
|
+
// 参数侧(`RAISED_ARGUMENTS`)已经把下限抬到 50,这里再加一道**看得见**的提示:只要树的深度顶到了上限,
|
|
10
|
+
// 就在结果后面追加一句,让"被截断"和"真的空"不再同形。提示是追加的,不改原有内容。
|
|
11
|
+
|
|
12
|
+
/** ` ├── name` / ` │ └── name`:每一层固定占 4 个字符(空格或竖线)。 */
|
|
13
|
+
const TREE_ENTRY = /^([\s│]*)(?:├──|└──)\s/;
|
|
14
|
+
|
|
15
|
+
const LIST_DIRECTORY_TREE_SUFFIX = 'list_directory_tree';
|
|
16
|
+
|
|
17
|
+
/** 与 `RAISED_ARGUMENTS` 的下限一致:拿不到输入时按这个判断。 */
|
|
18
|
+
const DEFAULT_MAX_DEPTH = 50;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {unknown} input
|
|
22
|
+
* @returns {number}
|
|
23
|
+
*/
|
|
24
|
+
function readMaxDepth(input) {
|
|
25
|
+
const raw = input !== null && typeof input === 'object' ? input.maxDepth : undefined;
|
|
26
|
+
return typeof raw === 'number' && Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_MAX_DEPTH;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 从 MCP 的 text 里取出树本身;不是那个 JSON 就整段当树。
|
|
31
|
+
*
|
|
32
|
+
* @param {string} text
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
function treeText(text) {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(text);
|
|
38
|
+
if (parsed !== null && typeof parsed === 'object' && typeof parsed.tree === 'string') {
|
|
39
|
+
return parsed.tree;
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// 不是 JSON:按纯文本树处理
|
|
43
|
+
}
|
|
44
|
+
return text;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 最深的条目在第几层(根目录下的直接子项是第 1 层)。
|
|
49
|
+
*
|
|
50
|
+
* @param {string} text
|
|
51
|
+
* @returns {number}
|
|
52
|
+
*/
|
|
53
|
+
function deepestLevel(text) {
|
|
54
|
+
let deepest = 0;
|
|
55
|
+
for (const line of text.split('\n')) {
|
|
56
|
+
const match = TREE_ENTRY.exec(line);
|
|
57
|
+
if (!match) continue;
|
|
58
|
+
const level = Math.floor(match[1].length / 4);
|
|
59
|
+
if (level > deepest) deepest = level;
|
|
60
|
+
}
|
|
61
|
+
return deepest;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 把一段提示接到结果的文本后面,不改原有内容。
|
|
66
|
+
*
|
|
67
|
+
* `Tool.Result.content` 允许 `string` 或 `Content[]`,两种都要覆盖。
|
|
68
|
+
*
|
|
69
|
+
* @param {{ content?: unknown }} result
|
|
70
|
+
* @param {string} text
|
|
71
|
+
* @returns {unknown} 新结果;格式不认识时返回 `undefined`。
|
|
72
|
+
*/
|
|
73
|
+
function appendText(result, text) {
|
|
74
|
+
const content = result.content;
|
|
75
|
+
if (typeof content === 'string') {
|
|
76
|
+
return { ...result, content: `${content}\n\n${text}` };
|
|
77
|
+
}
|
|
78
|
+
if (Array.isArray(content)) {
|
|
79
|
+
return { ...result, content: [...content, { type: 'text', text }] };
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 如果这棵目录树顶到了 `maxDepth` 的边界,追加一句截断提示。
|
|
86
|
+
*
|
|
87
|
+
* @param {unknown} result `Tool.Result`(只读,不改原对象)
|
|
88
|
+
* @param {{ tool: string, input: unknown, prefixes: readonly string[] }} call
|
|
89
|
+
* @returns {unknown} 新的 result;不该动时返回**原引用**,调用方据此跳过改写。
|
|
90
|
+
*/
|
|
91
|
+
export function appendDirectoryTreeTruncationNotice(result, { tool, input, prefixes }) {
|
|
92
|
+
if (typeof tool !== 'string') return result;
|
|
93
|
+
if (!prefixes.some((prefix) => tool === `${prefix}${LIST_DIRECTORY_TREE_SUFFIX}`)) return result;
|
|
94
|
+
if (result === null || typeof result !== 'object') return result;
|
|
95
|
+
|
|
96
|
+
const maxDepth = readMaxDepth(input);
|
|
97
|
+
const content = result.content;
|
|
98
|
+
const texts =
|
|
99
|
+
typeof content === 'string'
|
|
100
|
+
? [content]
|
|
101
|
+
: Array.isArray(content)
|
|
102
|
+
? content
|
|
103
|
+
.filter((item) => item?.type === 'text' && typeof item.text === 'string')
|
|
104
|
+
.map((item) => item.text)
|
|
105
|
+
: [];
|
|
106
|
+
if (texts.length === 0) return result;
|
|
107
|
+
|
|
108
|
+
const deepest = Math.max(...texts.map((text) => deepestLevel(treeText(text))));
|
|
109
|
+
if (deepest < maxDepth - 1) return result;
|
|
110
|
+
|
|
111
|
+
const next = appendText(
|
|
112
|
+
result,
|
|
113
|
+
`注意:这棵树已经到达 maxDepth=${maxDepth} 的边界(最深渲染到第 ${deepest} 层),` +
|
|
114
|
+
`更深的条目可能没有显示。要看完整内容就把 maxDepth 调大(例如 ${maxDepth * 2})重试,` +
|
|
115
|
+
'或者把 directoryPath 直接指到目标子目录再看一层。',
|
|
116
|
+
);
|
|
117
|
+
return next ?? result;
|
|
118
|
+
}
|
package/src/mcp/idea.js
CHANGED
|
@@ -127,7 +127,7 @@ export async function callTool(port, projectPath, name, args = {}, options = {})
|
|
|
127
127
|
params: {
|
|
128
128
|
protocolVersion: PROTOCOL_VERSION,
|
|
129
129
|
capabilities: {},
|
|
130
|
-
clientInfo: { name: 'opencode-idea', version: '0.1.
|
|
130
|
+
clientInfo: { name: 'opencode-idea', version: '0.1.3' },
|
|
131
131
|
},
|
|
132
132
|
});
|
|
133
133
|
// The IDE's Streamable-HTTP endpoint binds follow-up requests to the session
|
package/src/plugin.js
CHANGED
|
@@ -1376,10 +1376,8 @@ export default {
|
|
|
1376
1376
|
// Guidance promises tools the model can call, so it only goes out when
|
|
1377
1377
|
// they are actually in this request.
|
|
1378
1378
|
if (!serving) return;
|
|
1379
|
-
//
|
|
1380
|
-
|
|
1381
|
-
// 所以一个项目装了 enhance、另一个没装时,各节的内容本来就不一样。
|
|
1382
|
-
appendIdeGuidance(event?.system, currentProjects(), {tools});
|
|
1379
|
+
// 逐项目注入:每条工具后面直接列各项目的命令名,模型不需要自己换算前缀。
|
|
1380
|
+
appendIdeGuidance(event?.system, currentProjects());
|
|
1383
1381
|
});
|
|
1384
1382
|
|
|
1385
1383
|
// 自愈入口(钩子型)。OpenCode 会 await `prompt`,所以在这里修好的连接会在
|
package/src/mcp/enhance.js
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
// MCP surface added by the companion IDEA plugin `idea-mcp-enhance`.
|
|
2
|
-
//
|
|
3
|
-
// Kept apart from `idea.js` on purpose. `idea.js` describes ONE thing: the MCP
|
|
4
|
-
// server IntelliJ itself bundles, which every supported IDE has. Everything in
|
|
5
|
-
// this file describes a SECOND, optional source of tools — a plugin the user may
|
|
6
|
-
// or may not have installed. Two different origins, two files:
|
|
7
|
-
//
|
|
8
|
-
// * native -> `idea.js` (always present; the IDE owns the catalog)
|
|
9
|
-
// * plugin -> this file (present only if the user installed the plugin)
|
|
10
|
-
//
|
|
11
|
-
// The companion plugin (https://github.com/syllr/idea-mcp-enhance) fills gaps the
|
|
12
|
-
// bundled server leaves in two places:
|
|
13
|
-
//
|
|
14
|
-
// * run configuration — the bundled server can only START an existing
|
|
15
|
-
// configuration; it cannot create one, stop one, restart one, or say what is
|
|
16
|
-
// currently running;
|
|
17
|
-
// * deployment — the bundled server has nothing at all for the IDE's
|
|
18
|
-
// Deployment settings or for moving files to/from a server.
|
|
19
|
-
//
|
|
20
|
-
// The two are kept as separate groups everywhere (here, in the capability
|
|
21
|
-
// functions, and in the guidance), because they are NOT the same precondition:
|
|
22
|
-
// deployment additionally needs `com.jetbrains.plugins.webDeployment`, which
|
|
23
|
-
// exists only in Ultimate. One boolean covering both would let a deployment tool
|
|
24
|
-
// answer "are the run-lifecycle tools here?".
|
|
25
|
-
//
|
|
26
|
-
// Consequence of the split, and the reason it matters: a stock IDE serves a
|
|
27
|
-
// SHORTER catalog than an enhanced one. Nothing here may be assumed — every use
|
|
28
|
-
// has to be gated on the tools really being in the request.
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* The enhance plugin's **run-lifecycle** tool names, as **suffixes**.
|
|
32
|
-
*
|
|
33
|
-
* OpenCode namespaces an MCP server's tools with the server name, and each
|
|
34
|
-
* project is registered under its own prefix (root = `ide`, a subproject = its
|
|
35
|
-
* directory name). So the wire name is `<prefix><suffix>` — for example
|
|
36
|
-
* `ide_rerun_run_configuration` or `modelserver_rerun_run_configuration`.
|
|
37
|
-
*
|
|
38
|
-
* 存后缀而不是完整名字:同一个工具在每个项目前缀下各有一份,写死任何一个前缀都会
|
|
39
|
-
* 让别的项目查不到它。
|
|
40
|
-
*
|
|
41
|
-
* Keep in sync with `RunLifecycleSchemas.kt` in the enhance repository — the
|
|
42
|
-
* suffixes are its contract, and there is no runtime way to discover "which
|
|
43
|
-
* plugin provided this tool": MCP carries no such marker.
|
|
44
|
-
*/
|
|
45
|
-
export const CREATE_RUN_CONFIGURATION_SUFFIX = 'create_run_configuration';
|
|
46
|
-
export const INSPECT_RUN_CONFIGURATION_SUFFIX = 'inspect_run_configuration';
|
|
47
|
-
export const UPDATE_RUN_CONFIGURATION_SUFFIX = 'update_run_configuration';
|
|
48
|
-
export const DELETE_RUN_CONFIGURATION_SUFFIX = 'delete_run_configuration';
|
|
49
|
-
export const LIST_RUNNING_CONFIGURATIONS_SUFFIX = 'list_running_configurations';
|
|
50
|
-
export const STOP_RUN_CONFIGURATION_SUFFIX = 'stop_run_configuration';
|
|
51
|
-
export const RERUN_RUN_CONFIGURATION_SUFFIX = 'rerun_run_configuration';
|
|
52
|
-
|
|
53
|
-
/** All of them, in the order the guidance lists them: create, see, stop, restart. */
|
|
54
|
-
export const RUN_LIFECYCLE_SUFFIXES = [
|
|
55
|
-
CREATE_RUN_CONFIGURATION_SUFFIX,
|
|
56
|
-
INSPECT_RUN_CONFIGURATION_SUFFIX,
|
|
57
|
-
UPDATE_RUN_CONFIGURATION_SUFFIX,
|
|
58
|
-
DELETE_RUN_CONFIGURATION_SUFFIX,
|
|
59
|
-
LIST_RUNNING_CONFIGURATIONS_SUFFIX,
|
|
60
|
-
STOP_RUN_CONFIGURATION_SUFFIX,
|
|
61
|
-
RERUN_RUN_CONFIGURATION_SUFFIX,
|
|
62
|
-
];
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* The enhance plugin's **deployment** tool names, as suffixes — same contract and
|
|
66
|
-
* same reason for suffixes as {@link RUN_LIFECYCLE_SUFFIXES}.
|
|
67
|
-
*
|
|
68
|
-
* Keep in sync with `DeploymentSchemas.kt` in the enhance repository.
|
|
69
|
-
*
|
|
70
|
-
* The four are gated together on one plugin (`com.jetbrains.plugins.webDeployment`,
|
|
71
|
-
* Ultimate only) rather than on version drift, so they usually appear or vanish as
|
|
72
|
-
* a set — the guidance still names them one by one, because "usually" is not
|
|
73
|
-
* "always" and a named tool the model cannot call is only a failed call.
|
|
74
|
-
*/
|
|
75
|
-
export const LIST_DEPLOYMENT_SERVERS_SUFFIX = 'list_deployment_servers';
|
|
76
|
-
export const UPLOAD_TO_DEPLOYMENT_SUFFIX = 'upload_to_deployment';
|
|
77
|
-
export const DOWNLOAD_FROM_DEPLOYMENT_SUFFIX = 'download_from_deployment';
|
|
78
|
-
export const GET_DEPLOYMENT_TASK_STATUS_SUFFIX = 'get_deployment_task_status';
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
/** All of them: look at the servers, upload, download, then poll a transfer. */
|
|
82
|
-
export const DEPLOYMENT_SUFFIXES = [
|
|
83
|
-
LIST_DEPLOYMENT_SERVERS_SUFFIX,
|
|
84
|
-
UPLOAD_TO_DEPLOYMENT_SUFFIX,
|
|
85
|
-
DOWNLOAD_FROM_DEPLOYMENT_SUFFIX,
|
|
86
|
-
GET_DEPLOYMENT_TASK_STATUS_SUFFIX,
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Does this request carry the tool called `suffix` under `prefix`?
|
|
91
|
-
*
|
|
92
|
-
* A malformed or missing snapshot answers "no": assuming a tool exists is the one
|
|
93
|
-
* mistake that cannot be walked back once the model has read the guidance.
|
|
94
|
-
*
|
|
95
|
-
* Exported because the guidance asks this twice. The capability flags (below) ask
|
|
96
|
-
* "does this request have the tool somewhere" (any project will do); the command
|
|
97
|
-
* list next to each tool asks "under WHICH prefix", and that answer must be exact —
|
|
98
|
-
* 提示词里点名一个模型调不到的命令,是这个模块唯一要防的失败。
|
|
99
|
-
*
|
|
100
|
-
* @param {Record<string, unknown> | undefined} tools
|
|
101
|
-
* @param {string} prefix 该项目的工具前缀(含下划线)。
|
|
102
|
-
* @param {string} suffix
|
|
103
|
-
*/
|
|
104
|
-
export const servesTool = (tools, prefix, suffix) =>
|
|
105
|
-
Boolean(tools) && typeof tools === 'object' && `${prefix}${suffix}` in tools;
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Which of the enhance plugin's tools **this project's prefix** carries in this
|
|
109
|
-
* request.
|
|
110
|
-
*
|
|
111
|
-
* 返回布尔组合而不是名字列表:提示词要的是"这段能不能写",而且每个项目各算各的
|
|
112
|
-
* —— 一个项目装了 enhance、另一个没装是可能的。
|
|
113
|
-
*
|
|
114
|
-
* 客户端版本差异同理:enhance 版本独立发布,旧版可能少几个工具,所以逐个判断,
|
|
115
|
-
* 提示词绝不能点名一个模型调不到的工具。
|
|
116
|
-
*
|
|
117
|
-
* @param {Record<string, unknown> | undefined} tools This request's tool record
|
|
118
|
-
* (`event.tools` from the `context` hook).
|
|
119
|
-
* @param {string} prefix 该项目的工具前缀(含下划线)。
|
|
120
|
-
*/
|
|
121
|
-
export function runLifecycleCapabilities(tools, prefix) {
|
|
122
|
-
return {
|
|
123
|
-
canCreateRunConfiguration: servesTool(tools, prefix, CREATE_RUN_CONFIGURATION_SUFFIX),
|
|
124
|
-
canInspectRunConfiguration: servesTool(tools, prefix, INSPECT_RUN_CONFIGURATION_SUFFIX),
|
|
125
|
-
canUpdateRunConfiguration: servesTool(tools, prefix, UPDATE_RUN_CONFIGURATION_SUFFIX),
|
|
126
|
-
canDeleteRunConfiguration: servesTool(tools, prefix, DELETE_RUN_CONFIGURATION_SUFFIX),
|
|
127
|
-
canListRunningConfigurations: servesTool(tools, prefix, LIST_RUNNING_CONFIGURATIONS_SUFFIX),
|
|
128
|
-
canStopRunConfiguration: servesTool(tools, prefix, STOP_RUN_CONFIGURATION_SUFFIX),
|
|
129
|
-
canRerunRunConfiguration: servesTool(tools, prefix, RERUN_RUN_CONFIGURATION_SUFFIX),
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Same question for the deployment group, and the same answer shape — see
|
|
135
|
-
* {@link runLifecycleCapabilities}.
|
|
136
|
-
*
|
|
137
|
-
* The names repeat the tool's subject rather than the group (`canUploadToDeployment`,
|
|
138
|
-
* not `canUpload`): the guidance is one document about two groups, and a flag whose
|
|
139
|
-
* owner is unclear is how the wrong section gets the wrong text.
|
|
140
|
-
*
|
|
141
|
-
* @param {Record<string, unknown> | undefined} tools
|
|
142
|
-
* @param {string} prefix 该项目的工具前缀(含下划线)。
|
|
143
|
-
*/
|
|
144
|
-
export function deploymentCapabilities(tools, prefix) {
|
|
145
|
-
return {
|
|
146
|
-
canListDeploymentServers: servesTool(tools, prefix, LIST_DEPLOYMENT_SERVERS_SUFFIX),
|
|
147
|
-
canUploadToDeployment: servesTool(tools, prefix, UPLOAD_TO_DEPLOYMENT_SUFFIX),
|
|
148
|
-
canDownloadFromDeployment: servesTool(tools, prefix, DOWNLOAD_FROM_DEPLOYMENT_SUFFIX),
|
|
149
|
-
canGetDeploymentTaskStatus: servesTool(tools, prefix, GET_DEPLOYMENT_TASK_STATUS_SUFFIX),
|
|
150
|
-
};
|
|
151
|
-
}
|