opencode-idea 0.1.2 → 0.1.4

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 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
- > 命令时,引导模型用 `ide_execute_run_configuration`(配置 **由用户**先在 IDE 里建好),而不是原生 shell;不需要项目
14
- > 环境的命令照常走原生 shell。 **IDE 内置终端 (`ide_execute_terminal_command`) 已被隐藏** —— 实测它并不继承项目环境,
15
- > 对原生 shell 没有任何优势。详见「Run Configuration 全套」。
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 的提示词文档,条件直接用 `{{#if …}}`
146
- 写在那几行外面,所以打开模板就能看到最终渲染成什么样;`src/template.js` 是渲染器(支持 `{{var}}` / `{{#if}}` /
147
- `{{else}}`,**不做 HTML 转义** —— 输出是 Markdown,`<jar>!/<entry>` 和反引号必须原样过)。
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
- | 部署 | `ide_list_deployment_servers` / `ide_upload_to_deployment` / `ide_download_from_deployment` / `ide_get_deployment_task_status`(**仅配套插件 + Ultimate**) |
171
+ | 终端 | `ide_project_sdk_terminal` / `ide_terminal_read` / `ide_terminal_interrupt` / `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,不用原生 shell** —— 判据是这条命令 **依赖项目的运行时版本**
280
- (IDE 里配好的 JDK / Node / Python 等)。是 → run configuration;不是 (看文件、git、临时脚本)→ 原生 shell。
281
- run configuration 会继承开发时具体配的那套运行时,所以它就是个应用;配置必须 **先在 IDEA 里存在**(由用户建),
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
- > **为什么不再用 IDE 终端**:`ide_execute_terminal_command` 之前被当成"自动继承 IDE 项目环境"的通道而保留暴露,但实测
288
+ > **为什么内置 IDE 终端仍然隐藏**:`ide_execute_terminal_command` 之前被当成"自动继承 IDE 项目环境"的通道而保留暴露,但实测
285
289
  > **它拿不到项目配好的 JDK / Node / Python 版本**,和原生 shell 没有区别。既然没有优势,就把它从模型可见的工具表里
286
- > 隐藏掉 (`HIDDEN_IDE_TOOL_SUFFIXES`),命令执行的路由改由 run configuration 承担。
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
- 工具表里 —— 插件侧不需要单独声明 (也因此 **装不装这个 IDEA 插件,插件侧的代码都不用改**);
297
- 2. **条件引导**:只有这次请求的工具快照里真的有它们,才在「运行配置」节里逐个点名。**没装插件时整节连同标题一起消失**,
298
- 引导不会提到一个调不到的工具。其中一条值得单独说:**启动 / 重启常驻进程就用 `ide_rerun_run_configuration`,
299
- 不要用 `pkill` / `kill` / shell** —— 这正是它要解决的问题:外部杀掉的进程 IDE 并不知道,Run 窗口仍显示运行中,
300
- 下次启动就会撞端口;
301
- 3. **工具名清单**写在 `src/mcp/enhance.js`(与原生 IDE 的 `src/mcp/idea.js` 分开放:原生走原生,插件的走插件的),
302
- 与该仓库的 `RunLifecycleSchemas.kt` 保持同步。工具按 **存在的子集**逐个点名 (而不是「有就全写」),这样旧版本的配套
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,85 @@ 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
- | `ide_list_deployment_servers` | 本项目可用的服务器(名称 / 项目级还是应用级 / 协议 / host / port / **本项目里的路径映射**),以及**项目级**的部署选项与危险组合告警。**只读 IDE 配置,不联网** |
415
- | `ide_upload_to_deployment` | 把映射到的**本地目录**传到服务器(等同 IDE 的 Upload to …)。`mode` 默认 `mirror` —— **会删掉服务器上多出来的文件**;`upload` 则保留远端多余文件。**破坏性** |
416
- | `ide_download_from_deployment` | 把服务器上映射目录的内容拉回本地(等同 IDE 的 Download from …),**以服务器为准**:两边都有的文件被远端覆盖(相同则不写)。**没有"保留我的版本"这个选项** |
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
- - **条件引导更严** —— 部署工具除了配套插件,还要求 `com.jetbrains.plugins.webDeployment`,而它只在 **Ultimate** 里存在,
422
- 所以 Community IDE 上这一节永远不出现。而且 **这一节整节 (含标题) 都由配套插件提供**,一个工具都没有时它 **完全消失**
423
- —— 不能像「运行配置」那样留一个空标题 (那一节还有内置的 `get_run_configurations` / `execute_run_configuration` 撑着)。
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
- 这节存在的理由是 **一条路由规则**:往服务器传 / 取文件要走这些工具, **不要自己拼 `ssh` / `rsync` / `scp`** ——
430
- IDE 的连接、路径映射、排除规则都在它后面,自己拼的那份与 IDE 看到、管理的不是同一份。文案里同时点明两件容易读错的事:
426
+ 这节存在的理由是 **一条路由规则**:往服务器传 / 取文件要走这些工具 —— IDE 的连接、路径映射、排除规则都在它后面,
427
+ 手工拼的那份与 IDE 看到、管理的不是同一份。文案里同时点明两件容易读错的事:
431
428
  `mirror` 是 **默认值**且 **会删远端文件**(而 MCP 路径上没有确认步骤),下载 **以远端为准**、没有"保留我的版本"。
432
429
 
433
- 还有一个语义必须写明,否则模型会把正常的中间态当失败去重试:上传 / 下载都在 IDE 后台跑,单次调用只等到 `waitMs`
434
- (默认 10 秒)为止,返回 `state: "in_progress"` 是 **正常**的 —— 拿 `handle` 调 `get_deployment_task_status` 接着看,
435
- **不要用重复调上传 / 下载来代替**(那样拿不到进度)。
430
+ **同步只是上半段**:每条 deployment 都属于某台 Server,传完之后在那台 Server 上启动刚传上去的二进制、重启服务、看日志,
431
+ 走 `execute_in_remote_host`(`serverName` 用服务器名,不是 deployment 名)。**这两步都不要用 `scp` / `rsync` / `ssh`
432
+ 手工顶替** —— 手工那条路既没有 IDE 的路径映射,也常常根本没有权限(实测过一次:模型拿到服务器名之后转去读
433
+ `~/.ssh/config`、逐个试用户名,失败后回头找用户要密码,而 `execute_in_remote_host` 一直都在工具表里)。
434
+
435
+ 还有一个语义必须写明,否则模型会把正常的中间态当失败去重试:上传 / 下载默认**同步等结果**,传完立刻返回
436
+ `state: "completed"`;只有超过 300 秒等待窗口还没传完,才返回 `state: "in_progress"` + `handle`,这是 **正常**的
437
+ (它也可能以 **error 结果**回来)—— 拿 `handle` 调 `get_deployment_task_status` 接着看,**不要用重复调上传 / 下载
438
+ 来代替**(那样会再起一次传输)。`completed` 之后 `counters` 才可信,而且数的是**操作次数**,不是文件数
439
+ (删掉整个目录也只算 1)。
436
440
 
437
441
  > **这一组没有 skill,和 run configuration 不同。** run configuration 要 skill 是因为"某种类型怎么写"是一批得逐类型实测的
438
- > 模板 (`assets/types/`);部署没有这层知识 —— 四个工具的 schema 已经说清 `serverName` / `mode` 怎么填,而"该不该传、传给谁"
439
- > 是模型当场看 `list_deployment_servers` 的判断。再加一个 skill 只是把同一段话抄第二遍。
442
+ > 模板 (`assets/types/`);部署没有这层知识 —— 四个工具的 schema 已经说清 `serverName` / `deploymentName` / `mode` 怎么填,
443
+ > 而"该不该传、传给谁"是模型当场看 `list_server_deployments` 的判断。再加一个 skill 只是把同一段话抄第二遍。
444
+
445
+ ## 终端全套 (配套插件 `idea-mcp-enhance`)
446
+
447
+ IDE 内置的 `execute_terminal_command` **不继承项目环境**(实测拿不到项目配好的 JDK / Node / Python),所以它被隐藏。配套插件
448
+ 补上的是一个**真正的 IDE 终端**,外加远程 SSH 终端和一套"接着用同一个 tab"的读写工具:
449
+
450
+ | 工具 (OpenCode 里叫 `ide_*`) | 作用 |
451
+ | --- | --- |
452
+ | `ide_project_sdk_terminal` | 在本项目 SDK 终端里跑**一条**命令,返回输出 + `tab`。**默认复用**本项目的默认 tab(第一次调用创建,`list_terminals` 该组的 `default` 就是它);传 `tab` 指定、传 `newTab: true` 新开(不抢默认)、带 `env` 时强制新开(`env` 只在 shell 启动时生效)。命令跑得久时先返回 `running: true`(同一个 `tab`,命令继续跑),之后用 `ide_terminal_read` 接着看。终端环境由插件注入:项目 JDK(`PATH`)、Maven home、Node runtime / 包管理器、Go 的 `GOROOT` / `GOPATH` / `GOBIN`、Python SDK / venv(`PATH` + `VIRTUAL_ENV`),用户传的 `env` 最后覆盖 |
453
+ | `ide_list_terminals` | 按来源分组列出 MCP tab:`mcp_project_sdk` 一组、每台远程服务器各一组;每组带一个组级 `default`(该来源的默认 tab)和组内 `tabs` 数组。用户手开的 tab 不出现 |
454
+ | `ide_terminal_read` | 读已有 tab,**立即返回、不等待**:默认读上次之后的增量,`tail: N` 读最近 N 个命令段(本地带每段 `exitCode`),`full: true` 读整屏,`running` 表示命令还在不在跑 |
455
+ | `ide_terminal_interrupt` | 往已有 tab 发**一次 Ctrl-C**。只在命令卡住时用 —— 执行工具会等 shell 空闲,卡住的命令会把它们一直挡住;它不接受任何文本,只能打断,不能拿来跑命令 |
456
+ | `ide_execute_in_remote_host` | 通过本项目的一台 Deployment **SFTP** 服务器开真实 SSH 终端。`serverName` 取 `servers[].name`。**默认复用这台服务器的默认 tab**(第一次调用创建,`list_terminals` 该组的 `default` 就是它),传 `tab` 指定 / `newTab: true` 换干净 shell;新 tab 命名 `mcp-<服务器名>-<序号>`(如 `mcp-mbpmiddleware-1`)。`loginShell` 默认 `true`(套 `zsh -lic` / `bash -lc`),`strictHostKey` 默认 `false`(accept-new) |
457
+
458
+ 路由:引导里写的是 **"这条命令在哪台机器上、要不要项目运行时"** 两刀切 —— 远程机器(部署机 / 开发机 / 用户点名的主机)
459
+ 走 `ide_execute_in_remote_host`(服务器名不确定先 `ide_list_server_deployments`);本机 + 要运行时(IDE 里配好的
460
+ JDK / Node / Python / Maven / Go)走 `ide_project_sdk_terminal`;本机 + 不要运行时(`git` / `ls` / `curl` / `docker` /
461
+ `ps`)仍走原生 `shell`。**常驻进程**(dev server / watch)有 run configuration 就用它;没有就用终端起在 tab 里,
462
+ 跑满等待窗口还没结束会返回 `running: true`,之后用 `ide_terminal_read` 跟日志。
463
+
464
+ **等太久会转成异步**:命令跑得久,这次调用先返回 `running: true` + 同一个 tab,命令继续跑;之后用 `ide_terminal_read`
465
+ 每隔几秒看一眼进度,直到 `running` 变 false —— 不要因为没拿到结果就重跑一遍。超时不是调用方能设的参数,它是工具自身的
466
+ 行为(也没有"主动异步"选项)。引导里也明写了**禁止用 `shell` 的 `sleep` 干等**(实测会话里出现过 `sleep 60` /
467
+ `sleep 120` / `sleep 1500`,全是旧版 12 秒上限逼出来的)。
468
+
469
+ **终端默认复用**:同一台机器上的连续命令落在同一个 tab —— `cd` / `export` / 后台进程都还在,不必每条重贴 `cd ... &&`,
470
+ 也不用自己记 tab 号;要干净 shell 传 `newTab: true`,要指定某条传 `tab`。`ide_terminal_read` / `ide_terminal_interrupt`
471
+ 管的是"接着看 / 打断",不是"再跑一条"的替代。这条规则在「命令执行」和「终端」两处都写了,因为它最容易被绕成"每条命令一个 tab"。
472
+
473
+ 三条容易读错的语义,引导里都点名了:
474
+
475
+ - **每种来源一条默认 tab** —— 本地一条、每台服务器各一条,第一次调用自动创建,`list_terminals` 在该组的 `default` 字段里报出它;
476
+ 不传 `tab` 就永远落在它上面(不会随使用历史漂移),默认 tab 被关掉后下次调用重建一条;一条命令要独立环境就传
477
+ `newTab: true` / `env`。跨服务器复用会被直接拒绝(`tab_server_mismatch`);
478
+ - **远程没有"命令结束"信号** —— `exitCode` 永远 `null`,`running` 也可能 `null`;别等退出码,按输出判断或继续 `ide_terminal_read` 读;
479
+ - **tab 只在这次 IDE 会话里有效** —— 关掉就失效(报 `terminal_closed` / 找不到),这时才重开一个;没有"关闭 tab"的工具,
480
+ 用完不必主动关,用户可以直接在 IDE 里接手。
481
+
482
+ opencode-idea 对它做的仍是**同一套两件事**(透传 / 无条件引导):本地三个工具(Terminal 插件在就有)和
483
+ `execute_in_remote_host`(还要 `remote-run`,仅 Ultimate)都**默认齐全**,引导直接点名,不再按工具快照裁剪。
484
+ 契约以该仓库的 `TerminalSchemas.kt` 为准。
440
485
 
441
486
  ## 前置条件
442
487
 
488
+ - **必须安装配套插件 [`idea-mcp-enhance`](https://github.com/syllr/idea-mcp-enhance)(IDEA 2026.2+)**。引导默认它的工具都在
489
+ (运行配置启停 / 部署 / 终端),**不做"装没装 / 有没有这个工具"的判断**;部署与远程终端还要求 Ultimate。
443
490
  - JetBrains IDE 已开启 MCP 服务 (IDE 设置里的 MCP Server / AI Assistant MCP),版本需 **2026.2+**(提供 Streamable HTTP 端点
444
491
  `/stream`);若关闭,`/open-in-ide` 会立即提示如何重新开启。
445
492
  - IDE 侧需开启 **Brave Mode**(否则需要确认的 MCP 工具调用会停下来等用户确认);MCP 未就绪时通知会同时提醒开启它。
@@ -573,7 +620,8 @@ export { default } from "/Users/yutao/IdeaProjects/opencode-idea/src/index.js";
573
620
  (0.0.6 就是这样丢了 `/open-in-ide`)。完整字段是 `id` / `name` / `description?` / `autoinvoke?` / `path` / `content`。
574
621
  - 一批用不到的 IDE MCP 工具被硬编码隐藏(`src/plugin.js` 的 `HIDDEN_IDE_TOOL_SUFFIXES`,**共 48 个后缀**):
575
622
  VCS(`git_status` / `get_repositories`)、Router 派发工具(`execute_tool`)、Debugger(全部 `xdebug_*`)、Dev Kit MCP、
576
- Inspection KTS MCP、解释器环境 MCP、**IDE 内置终端**(`execute_terminal_command`:实测不继承项目环境)、
623
+ Inspection KTS MCP、解释器环境 MCP、**IDE 内置终端**(`execute_terminal_command`:实测不继承项目环境,已由配套插件的
624
+ `project_sdk_terminal` 取代)、
577
625
  **整个 Database 组**(数据源由用户在 IDE 里配置)、**有原生对应的文件搜索**(`search_text` / `search_regex` / `search_file`)、
578
626
  以及**建文件**(`create_new_file`)。配套插件自带的那个写文件工具同样隐藏。**`apply_patch` 不在隐藏列表里**。
579
627
  存的是**后缀**,清洗时按当前每个项目前缀**各删一遍** —— 只处理 `ide_` 会让子项目那几份原样送到模型面前。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-idea",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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,7 +12,6 @@
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",
17
16
  "src/mcp/idea-tool-output.js",
18
17
  "src/project.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`,`serverName` 传 `mbpmiddleware`、`mode` 传 `upload`(默认 `mirror`
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:
@@ -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 conditionals visible as
5
- // `{{#if …}}` around the lines they control — and building it by concatenating
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 `{{#if can…}}` blocks gate the companion plugin's tools (`mcp/enhance.js`),
59
- // which only exist when the user installed it. Nothing in the document says so:
60
- // which plugin a tool came from is this plugin's plumbing, not the model's
61
- // business — it either sees the tool or it does not. The deployment section is
62
- // gated on ONE of those flags (`canListDeploymentServers`) rather than on a flag of
63
- // its own: the four tools are registered together, so one of them answers for the
64
- // section — heading included, which is why the gate wraps the `###` line too.
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
- * **配套插件那条线要逐项目过滤**(见 {@link ENHANCE_SUFFIXES}):内置工具每个项目都有,列全即可;
129
- * 而 `idea-mcp-enhance` 的工具是**按项目分别存在**的(装了插件的那个 IDE 有,另一个可能没有;
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, tools) {
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, options = {}) {
140
+ export function buildIdeGuidanceText(projects) {
214
141
  return renderTemplate(TEMPLATE, {
215
142
  projectCount: projects.length,
216
143
  routeTable: routeTableFor(projects),
217
- ...mergeRunLifecycle(projects, options.tools),
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 which run-lifecycle tools
256
- * this request carries, so the idempotence check compares the built text rather
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, options = {}) {
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, options);
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,18 @@
7
7
  2. **IDE 工具补自带工具做不到的**:符号与定义用 `<前缀>_search_symbol` + `<前缀>_read_file`;改名用
8
8
  `<前缀>_rename_refactoring`;单文件检查用 `<前缀>_lint_files` / `<前缀>_get_file_problems`;跨模块构建用
9
9
  `<前缀>_build_project`。
10
- 3. **`shell` 用于必须起进程的命令**:`pnpm` / `git` / `docker` / `curl` / `psql`、迁移与生成。
11
- 4. **校验节奏**:改动过程中对改动的文件用 `lint_files` / `get_file_problems`;全仓 `pnpm typecheck` 在收尾跑一次。
12
- 5. **批量修改**:逐文件用 `edit`,或一次用 `<前缀>_apply_patch`。
13
-
14
- 本会话接入了 {{projectCount}} 个 JetBrains 项目。**每个功能在所有项目下都各有一份工具**,命令名是
10
+ 3. **要项目运行时环境的命令用项目终端**:{{cmd_project_sdk_terminal}};本机上 `git` / `ls` / `curl` / `docker` 这类与项目
11
+ 无关的仍走 `shell`。远程机器不是这条 —— 见第 4 条。细则见「命令执行」与「终端」。
12
+ 4. **远程机器上的命令走 `<前缀>_execute_in_remote_host`**:用户说"去 xxx 上执行""部署机上跑一下""连某台机器看看",
13
+ 先 `<前缀>_list_server_deployments` 拿服务器名,再用它在那台机器上跑。凭据、代理、路径映射都在 IDE 的那台
14
+ Server 后面 —— `serverName` 就是这些连接信息的唯一入口。
15
+ 5. **终端是复用的,不是一次性的**:同一来源(本机 / 每台远程服务器)有一条**默认 tab**,不传 `tab` 的命令都落在它上面。
16
+ 细则见「终端」的复用规则。
17
+ 6. **校验节奏**:改一处只在收尾跑 `lint_files` (要细节再 `get_file_problems`);改一大片或动了接口 / 依赖就用
18
+ `build_project`;全仓检查只在收尾。
19
+ 7. **批量修改**:逐文件用 `edit`,或一次用 `<前缀>_apply_patch`。
20
+
21
+ 本会话接入了 {{projectCount}} 个 JetBrains 项目。**每个 IDE 功能在所有项目下都各有一份工具**,命令名是
15
22
  `<项目前缀>_<工具名>`;下文写 `<前缀>` 就是指这一截。
16
23
 
17
24
  ## 本会话接入的项目与各自的 MCP 工具
@@ -24,8 +31,8 @@
24
31
  **规则:操作哪个目录下的文件/代码,就用那个目录对应的命令。** IDE 按项目解析 —— 用错前缀时,调用要么报错,
25
32
  要么作用在另一个项目的代码上。
26
33
 
27
- 按需读文件:要了解某个子项目自己的约定,读它的 `<子项目目录>/AGENTS.md`(它不会自动进入本会话的上下文,
28
- 本会话是 root 打开的那一个)。
34
+ 按需读文件:要了解某个子项目自己的约定,读它的 `<子项目目录>/AGENTS.md` —— 它不会自动进入本会话的上下文
35
+ (这里加载的是 root 的那一份)。
29
36
 
30
37
  ## 路由:先看路径在不在项目里
31
38
 
@@ -33,22 +40,25 @@
33
40
  项目外的路径(`/tmp`、别的仓库、用户主目录)不在 IDE 的解析范围内。
34
41
 
35
42
  - **项目内** —— **OpenCode 自带的工具为主**,它们更快。**它们做不了、或者做得很差的,才用 IDE 工具**:
36
- 符号检索、依赖实现、重构、格式化、校验、运行配置、跨文件补丁。
43
+ 符号检索、依赖实现、重构、格式化、校验、运行配置、项目终端、跨文件补丁。
37
44
  **两边都不顺手时,你自己判断用 `shell` 里的命令** —— 那是第三选择,不是第一选择。
38
- - **项目外** —— 用 OpenCode 自带的工具;IDE 工具只对项目内路径生效。
45
+ - **项目外、但还在本机** —— 用 OpenCode 自带的工具;IDE 工具只对项目内路径生效。
46
+ - **远程机器(部署机 / 开发机 / 用户点名的那台主机)** —— 它不属于上面两类,是 IDE 里配好的一台 Deployment Server:
47
+ 跑命令走 `<前缀>_execute_in_remote_host`,传文件走 `<前缀>_upload_to_deployment` / `<前缀>_download_from_deployment`。
48
+ 凭据与路径映射都由这台 Server 提供。
39
49
 
40
50
  ## 总则 (所有项目通用)
41
51
 
42
52
  - **OpenCode 自带的工具直接调用** —— 工具列表里直接列出的那些(`read` / `grep` / `glob` / `edit` / `write` / `shell`),
43
53
  参数按各自 schema 传。它们就在当前工具表里,直接调用即可。
44
54
  - **读文件用 `read` 工具** —— 要读某一段就传 `offset`(从第几行起)+ `limit`(读几行),输出自带行号。
45
- - **搜文本用 `grep` 工具** —— 它按 `.gitignore` 排除,`node_modules` / `dist` 不进结果,范围用 `path` 收窄。
55
+ - **搜文本用 `grep` 工具** —— 它按 `.gitignore` 排除,依赖目录 / 构建产物不进结果,范围用 `path` 收窄。
46
56
  要看命中行前后的大段上下文:先用它拿到**行号**,再用 `read` + `offset` / `limit` 读那一段。
47
57
  - **工具输出不够用时,按这个顺序调整**:① 调参数重试 → ② 缩小路径范围重试 → ③ 换工具。
48
58
  换用 `shell` 的判断依据是"这个工具确实没有这个能力"。
49
59
  典型:`<前缀>_list_directory_tree` 的输出到 `maxDepth` 边界就停 —— 调大 `maxDepth` 再试一次。
50
60
  - **改文件**:改一个符号用 `<前缀>_rename_refactoring`;改一段文字用 `edit` / `write` 工具;
51
- 同一段文字铺到多个文件用 `<前缀>_apply_patch`;跑命令用 `shell` 工具。
61
+ 同一段文字铺到多个文件用 `<前缀>_apply_patch`。跑命令的路由见「命令执行」。
52
62
  改代码始终落在具体的那几行上,其余内容保持原样。
53
63
  - **改完代码自己先查一遍**:`lint_files` / `build_project`,怎么挑见「校验」。
54
64
  - **文档类文件 (Markdown / 文档) 改完之后,用 `<前缀>_reformat_file` 过一遍** —— 表格对齐、换行宽度、缩进交给 IDE
@@ -57,17 +67,30 @@
57
67
 
58
68
  ## 命令执行 (所有项目通用)
59
69
 
60
- - **跑命令用 `shell` 工具**;要项目环境的(IDE 里配好的 JDK / Node / Python 等)先看有没有现成的 run configuration ——
61
- 有就跑它 (常驻用 `rerun`,跑完即退用 `execute`);没有、又只是一次性的,用 `shell` 工具并显式指定解释器。
62
- 一次性的命令留在 `shell` 里,配置留给会反复用到的流程。
70
+ - **按"这条命令在哪台机器上、要不要项目运行时"分流**:
71
+ - **远程机器**(部署机 / 开发机 / 用户说了"去 xxx 上"的那台)→ {{cmd_execute_in_remote_host}}。服务器名字不确定,
72
+ 就先 {{cmd_list_server_deployments}} —— **它的返回就是"能直接跑命令的机器"清单**,`servers[].name` 直接用。
73
+ - **本机 + 要项目运行时**(IDE 里配好的 JDK / Node / Python / Maven / Go,以及依赖它们的构建、脚本、生成、迁移)→
74
+ {{cmd_project_sdk_terminal}}。它开的是 IDE 里真正的终端,自动带上项目 SDK 与 Maven / Node / Python 的路径;
75
+ `workingDirectory` 默认项目根,`env` 里的值最后覆盖。
76
+ - **本机 + 不要项目运行时**(`git` / `ls` / `cat` / `curl` / `docker` / `ps`)→ `shell` 工具。
77
+ - **每种来源一条默认 tab,整趟任务复用**:不传 `tab` 的命令永远落在该来源的默认 tab 上(`project_sdk_terminal` 管本机、
78
+ `execute_in_remote_host` 管每台服务器)—— `cd` / `export` / 后台进程都留在那个 shell 里,不必每条命令重贴
79
+ `cd ... &&` 前缀。
80
+ - **等太久会转成异步**:命令跑得久,这次调用先返回 `running: true` 和同一个 `tab`,命令还在里面跑 —— 用
81
+ 结果从 {{cmd_terminal_read}} 取,每隔几秒看一眼,直到 `running` 变 false。
82
+ - **卡住的命令用 {{cmd_terminal_interrupt}} 打断**:它在命令还跑着的时候发 Ctrl-C —— 执行工具帮不了这个忙
83
+ (它们会等 shell 空闲,卡住的命令会把它们一直挡住)。
84
+ - **常驻进程**(dev server / watch / 长时间跑的服务)有 run configuration 就用 `rerun` 起它;
85
+ 没有就用项目终端起在 tab 里;它跑满等待窗口还没结束会返回 `running: true`,之后用 {{cmd_terminal_read}} 跟日志。
86
+ - **一次性的命令就地跑完,反复发生的流程才固化成 run configuration** —— 配置是**项目资产**,不是"跑命令"
87
+ 的手段。判据只有一条: **这件事以后会不会以同样的形态再发生?** 会 (构建 / 生成 / 迁移 / 起服务 / 部署这类)
88
+ 就 **主动提**一次要不要固化成配置,并把配置名 / 类型 / 命令 / 工作目录 / 为什么值得留摊开;
89
+ 不会 (查条数据、看个日志、试一条命令) 就地跑完算。
63
90
  - **建 / 改 / 删运行配置,走 `idea-run-config-manager` skill**(服务化应用的配置归 `idea-standalone`):
64
91
  它落手用的就是「运行配置」里的那组工具 —— 配置文件由工具写入和删除。
65
- {{#if canCreateRunConfiguration}}
66
- {{else}}
67
- 本会话没有新建工具,"建"这一步它只能讲清配置名、实际执行的命令、工作目录、为什么值得留, **请用户在 IDE 里创建**
68
- {{/if}}
69
92
 
70
- ## 工具 (每条第一行是各项目的命令,接着是它的用法)
93
+ ## 工具 (每条先列各项目的命令名,再写用法)
71
94
 
72
95
  ### 符号 (只有 IDE 的语义索引能给)
73
96
 
@@ -79,11 +102,9 @@
79
102
  (类 / 接口 / 方法 / 函数 / 字段 / 类型 / 常量这类 **有名字的定义**)的**声明位置**,不是"哪段文本提到过它"(那是
80
103
  `grep` 工具的事);带上 `include_external` 才会连依赖库和标准库一起搜 —— 这是 `grep` / `glob` 工具够不着的地方。
81
104
  - **查某个库的 API**:代码里 import 的包名就是线索,直接「包名 + 符号名」当 `q` —— Java 用全限定名
82
- (`com.nimbusds.jose.util.Base64`)、Go 用 `pkg.Symbol`(`gorm.DeletedAt`)、Python 给全模块路径
83
- (`torch.nn.Module`)、Node/TS 用 import 的那层包名(`vue.Teleport`)。**调研一个库有哪些 API**:拿包名当 `q`
84
- (`fastapi` → 入口文件就是导出清单),再读它的入口。
85
- - **Node/TS 例外**:带 `-` 或 `@scope/` 的包名这么拼是 0 条(`element-plus`);改用 import 的符号名,并用 `paths` 圈到
86
- 那个 `node_modules`(`paths: ['frontend/user/**']` 就收敛到一处)。
105
+ (`<包名>.<类名>`)、Go 用 `pkg.Symbol`(`<包名>.<导出符号>`)、Python 给全模块路径
106
+ (`<模块>.<类>`)、Node/TS 用 import 的那层包名(`<包名>.<导出名>`)。**调研一个库有哪些 API**:拿包名当 `q`
107
+ (入口文件通常就是导出清单),再读它的入口。
87
108
  - **搜不到 = 依赖没进来**:先把它加进依赖,或改用 web 搜索 / GitHub 看源码。
88
109
  - **同名符号往往有多处定义**(标准库 / 依赖 / 测试各一份):按调用处的 import 与参数类型来选。
89
110
  - **它返回的路径原样交给同一个项目的 `<前缀>_read_file`** —— 那个路径的基准是**它所在的项目**,不是会话工作目录;
@@ -95,11 +116,10 @@
95
116
 
96
117
  ### 项目与编辑器
97
118
 
98
- - {{cmd_list_directory_tree}} **项目视角的目录树**(尊重内容根与排除项):`maxDepth` 控深度。
99
- **它是绝对深度上限,实际只展开到 `maxDepth-1` 层;省略时默认 5(只展开 4 层)。** 要看全就传一个明显够大的值
100
- (如 50),不会报错、也没有额外代价。**输出到边界就停,不带任何截断标记** —— 所以"某个目录后面没有子项"有两种
101
- 可能:真空目录,或者被你截断了。判据:树的深度正好顶到你传的值,或者**包目录 / 源码目录显示为空** —— 那就是被截断了。
102
- **把 `maxDepth` 调大重试,或把 `directoryPath` 直接指到那个子目录再看一层。**
119
+ - {{cmd_list_directory_tree}} **项目视角的目录树**(尊重内容根与排除项):`maxDepth` 控深度,是**绝对上限但只展开
120
+ `maxDepth-1` 层**(省略时默认 5,即 4 层);要看全就传明显够大的值(如 50),没有额外代价。**输出到边界就停,不带截断
121
+ 标记** —— 目录显示为空也可能是被切了;判据是树深度顶到传入值,或**包 / 源码目录显示为空**。调大 `maxDepth`,或把
122
+ `directoryPath` 指到那个子目录再看一层。
103
123
  - {{cmd_get_project_modules}} **项目模块及类型**。
104
124
  - {{cmd_get_project_dependencies}} **项目直接声明的依赖**。
105
125
  - {{cmd_get_all_open_file_paths}} **用户在 IDE 里打开了哪些文件**(含当前激活的那个)。
@@ -120,74 +140,53 @@
120
140
  - 返回里 `with N usages` 的 N 是**真引用数**;比预期少,说明有引用没被解析到(文件没进 IDE 索引),
121
141
  这时**先确认索引到那个模块**,再处理剩下的引用。
122
142
  - 改的本来就是文字(配置 key、模板占位符、文档、SQL)、**就是要改那句字符串**、或者文件 IDE 没索引(生成物、
123
- vendor 目录)时,用 `edit` / `write` / `apply_patch`。动手前先用 `grep` 工具确认命中范围。
143
+ 第三方目录)时,用 `edit` / `write` / `apply_patch`。动手前先用 `grep` 工具确认命中范围。
124
144
  - {{cmd_reformat_file}} **按 IDE 的代码风格重排**。
125
145
  - {{cmd_apply_patch}} **一次改多个文件**:`input` 传补丁正文,Codex 格式(`*** Begin Patch` / `*** Update File: <路径>`
126
146
  / `@@` / `-` 旧行 / `+` 新行)与 unified git diff 都收。`edit` / `write` 工具都只管一个文件。
127
147
  - 它**按文本**改,适合同一段文字铺到多个文件;**改符号走上面那条**。
128
- - 它**只碰补丁里写明的那几行**,其余内容保持原样。
129
- - 上下文对不上时那一个 hunk 失败、**该文件原样不动**,返回里点名是哪个文件(`<路径>: Hunk context not found`)。
130
- - 多个文件是**逐个应用**的:返回里 `N out of M operations applied`,失败的那些读一下再改;
131
- 改完用 `grep` 工具确认落盘。
148
+ - 只碰补丁里写明的那几行。上下文对不上时该文件**原样不动**(`<路径>: Hunk context not found`);多个文件按返回的
149
+ `N out of M operations applied` 逐个补,改完用 `grep` 工具确认落盘。
132
150
  - {{cmd_lint_files}} **批量检查**:`files` 传数组;只列有问题的文件。
133
151
  - {{cmd_get_file_problems}} **单文件检查**:`filePath`;结果在 `errors`。
134
152
  - {{cmd_build_project}} **构建校验**:`filesToRebuild` 可只构建指定文件;有问题返回在 `problems`,没返回即没问题。
135
153
 
136
154
  **改完代码,自己先查一遍再收尾。** 查一次很便宜,而问题一旦漏到用户看见、或留到构建 / 测试炸了才暴露,回头纠正的
137
- 代价就高一个量级。 校验跟着改动范围走 —— 这一轮属于"改一大片"还是"改一处",见下面两条。
138
-
139
- - **这次要改一大片**(多文件、跨模块、"顺手把这几处一起对齐")→ 全部改完再 `build_project` 一次,
140
- 在完整状态上检查。
141
- - **这次只改一处,或在同一个文件上反复微调** → 告一段落时,把这轮改过的文件一次 `lint_files` 掉;要看单个
142
- 文件的细节再上 `get_file_problems`。
143
- - **改了签名 / 接口 / import / 依赖**这类"别处会跟着坏"的地方 → 走 `build_project`;只动了文件内部的实现 →
144
- lint 就够。
155
+ 代价就高一个量级。 校验跟着改动范围走 —— 这一轮属于"改一大片"还是"改一处",按下面两条判断。
156
+
157
+ - **改一大片**(多文件、跨模块、"顺手把这几处一起对齐"),或**动了签名 / 接口 / import / 依赖**这类"别处会跟着坏"的
158
+ 地方 → 全部改完再 `build_project` 一次,在完整状态上检查。
159
+ - **只改一处,或只在文件内部微调** → 告一段落时把这轮改过的文件跑一次 `lint_files`;要看单个文件的细节再上
160
+ `get_file_problems`。
145
161
  - 查出来的问题 **改掉,或说清为什么不改**。
146
- - 校验走上面这些工具;先调用一次,索引没同步或环境不对时它会明说。
147
- - **全仓类型检查是收尾动作,不是编辑循环**:同一任务只在最后跑一次 `pnpm typecheck`;过程中只对改动文件跑
148
- `lint_files` / `get_file_problems`。局部检查用这两个工具,全仓 typecheck 留给收尾。
149
- - 只有改依赖清单、生成 Prisma Client、跑迁移这类必须执行命令的动作,才额外调用 `pnpm`;一个任务通常不超过两次。
162
+ - 校验就用上面这些工具;索引没同步或环境不对时,第一次调用就会明说。
163
+ - **全仓检查是收尾动作,不是编辑循环**:同一任务只在最后跑一次;过程中只对改动文件跑 `lint_files` /
164
+ `get_file_problems`。
150
165
 
151
166
  ### 运行配置
152
167
 
153
168
  - {{cmd_get_run_configurations}} **项目里有哪些运行配置**:传 `filePath` 可列该文件的运行入口。
154
169
  配置由 IDE 自己存储(通常在项目目录之外),用这个工具读。
155
- - {{cmd_execute_run_configuration}} **跑已有配置**:配置里的 `EXECUTE_IN_TERMINAL` 要是 `false`(共享出去的配置都这样)
156
- ;覆盖参数 (`programArguments` / `workingDirectory` / `envs`)需配置支持 `supportsDynamicLaunchOverrides`,否则会直接报错。
157
- {{#if canCreateRunConfiguration}}
170
+ - {{cmd_execute_run_configuration}} **跑已有配置**:配置里的 `EXECUTE_IN_TERMINAL` 要是 `false`(共享出去的配置都这样);
171
+ 覆盖参数 (`programArguments` / `workingDirectory` / `envs`) 需配置支持 `supportsDynamicLaunchOverrides`,否则会直接报错。
158
172
  - {{cmd_create_run_configuration}} **新建任意类型的项目级配置**:内容来源三选一 —— `cloneFrom`(复制一个现成的,最稳;
159
173
  字段名不确定时只走它)、`configurationXml`(模板里的 XML 片段)、平台模板 + `options` / `attributes` / `envs` 补丁。
160
- 它是 **项目资产**,不是"跑命令"的手段 —— 只是要跑一条命令就用 `shell` 工具。
161
- **要不要建、建什么、怎么建、档案怎么记,全按上面那个 skill 走。** 判据只有一条: **这件事以后会不会以同样的形态再发生?**
162
- 会 (改表后同步 DDL、跑迁移、生成代码、打包、起 dev server、往某个环境部署这类)就 **主动提**一次
163
- "要不要固化成 run configuration",并把配置名 / 类型 / 命令 / 工作目录 / 为什么值得留摊开;不会 (查条数据、看个日志、
164
- 试一条命令)就用 `shell` 工具跑完算。
174
+ **建不建按上面那条判据**;怎么建、档案怎么记,全按 `idea-run-config-manager` skill 走。
165
175
  **改完同步档案**:新增 / 删除 / 改名之后按 skill 更新 `AGENTS.md` 里的档案块 —— 档案和项目里的配置对不上,比没有档案更糟。
166
- {{/if}}
167
- {{#if canInspectRunConfiguration}}
168
176
  - {{cmd_inspect_run_configuration}} **看一个配置的全貌**:定义 (options / envs / before-launch)、命名解析 (`win` /
169
177
  mac-linux、base、env)、当前活实例、历史运行及各自的日志文件路径。 **改之前先用它拿当前值**;服务起没起来想看日志,先用
170
178
  `list_running_configurations` 的 `logFile`,要翻"上一次为什么挂了"再走这里。
171
- {{/if}}
172
- {{#if canUpdateRunConfiguration}}
173
179
  - {{cmd_update_run_configuration}} **改字段或改名**:`options` / `envs` / `attributes` 是补丁 (`null` = 删),`newName` 改名并
174
180
  **连带更新别人对它的引用**、迁移历史日志。改之前先 `inspect` 拿 `configurationXml` 与当前值。
175
- {{/if}}
176
- {{#if canDeleteRunConfiguration}}
177
181
  - {{cmd_delete_run_configuration}} **删配置**:在跑或被人引用时拒绝;`force = true` 才会连引用一起清掉。
178
- {{/if}}
179
- {{#if canListRunningConfigurations}}
180
182
  - {{cmd_list_running_configurations}} **当前真正在跑的配置**(几个实例 / 各自 pid / 退出码,每个实例还带自己的日志文件路径
181
183
  `logFile`):停 / 重启前后都先用它看状态。
182
- {{/if}}
183
- {{#if canStopRunConfiguration}}
184
184
  - {{cmd_stop_run_configuration}} **停掉该配置的全部实例并等它们退出**:没在跑也返回成功。
185
- {{/if}}
186
- {{#if canRerunRunConfiguration}}
187
185
  - {{cmd_rerun_run_configuration}} **启动 / 重启常驻进程**:在跑就重启、没在跑就启动 —— 改完代码要重启就直接用它,
188
186
  有没有热重载都一样;读 `started` 判断新进程是否真的起来了。这次运行的输出在 `output`,完整日志文件路径在
189
187
  `fullOutputPath`。启停都走这个工具,IDE 的 Run 窗口状态才会跟着更新。
190
- {{/if}}
188
+ **before-launch 很久**(要先构建 / 跑另一个配置)时,调用会一直等到它结束;等不起就传 `waitForStart: false`,
189
+ 提交后自己查 {{cmd_list_running_configurations}}。
191
190
 
192
191
  **配置名怎么读**:`[os@]base[@env]`,`@` 分割、最多三段 —— `os` = `win`(缺省 macOS / Linux)、`base` = 配置本体(必填)、
193
192
  `env` = 环境名(缺省 = 默认环境)。`win@base`(换平台)与 `base@env`(换环境)都是 `base` 的 **变体**,跑哪一个:
@@ -197,27 +196,72 @@
197
196
  变体、没有默认版的,就用它自己那个 —— "10 个服务"可以是"3 个 `@dev` + 7 个默认",不必逐条问。
198
197
  - 一条配置同时有 `base` 与 `base@<env>`、又没说走哪个环境 → **问一次走哪个环境**,选定后本会话默认走这套。
199
198
  - 一个环境怎么接上(启动参数 / 环境变量 / 配置文件)是项目自己的事:照现成的同类变体来,或问用户。
200
- {{#if canListDeploymentServers}}
199
+
200
+ ### 终端 (本机环境 · 远程主机 · 复用同一个 tab)
201
+
202
+ - {{cmd_project_sdk_terminal}} **在本项目 SDK 终端里跑一条命令,返回输出与 tab id**。`command` 必填;`workingDirectory`
203
+ 默认项目根;`env` 最后覆盖自动注入的环境;`input` 在命令后写一次(自带换行);`maxChars` 只保留最新输出。
204
+ **默认复用**:命令打进本项目的默认 tab(第一次调用创建,`list_terminals` 该组的 `default` 就是它),`cd` / `export` /
205
+ 后台进程都还在;`workingDirectory` 会在那个 tab 里 `cd` 过去。`env` 只能在新 shell 生效,传了它就会新开一个(不会抢默认);
206
+ 要指定某条 tab 传 `tab`,要干净 shell 传 `newTab: true`(同样不抢默认)。命令跑得久时先返回 `running: true`,
207
+ 用 {{cmd_terminal_read}} 接着看。
208
+ - {{cmd_list_terminals}} **本项目的终端 tab 清单**:按来源分组 —— `mcp_project_sdk` 一组、每台远程服务器各一组;每组带一个
209
+ 组级 `default`(该来源的默认 tab)和组内 `tabs` 数组(状态、当前目录、最后一条命令)。用户自己开的 tab 不出现;
210
+ 只有 `addressable: true` 的能交给 {{cmd_terminal_read}} / {{cmd_terminal_interrupt}}。
211
+ - {{cmd_terminal_read}} **读已有 tab,立即返回、不等待**:`target` 取 `local`(`project_sdk_terminal` 建的)或 `remote`
212
+ (`execute_in_remote_host` 建的)。默认读**上次之后的新输出**,`tail: N` 读最近 N 个命令段(本地带每段的 `exitCode`),
213
+ `full: true` 读整屏。返回里的 `running` 说明命令还在不在跑。
214
+ - {{cmd_terminal_interrupt}} **往已有 tab 发一次 Ctrl-C**:同样传 `target` + `tab`。只在命令卡住时用 —— 执行工具会等
215
+ shell 空闲,卡住的命令会把它们一直挡住。它不接受任何文本,只能打断,不能拿来跑命令。
216
+ - {{cmd_execute_in_remote_host}} **在远程主机的真实 SSH 终端里跑命令** —— 主机与凭据来自本项目的一台 Deployment
217
+ SFTP 服务器,名字传 `serverName`(那台**服务器**的名字,不是 deployment 名)。`loginShell` 默认 `true`(套
218
+ `zsh -lic` / `bash -lc`,PATH 与工具链才会加载);`strictHostKey` 默认 `false`(接受并记录新主机密钥)。
219
+ **远程命令一律走它** —— 凭据、代理、路径映射都在 IDE 这边。
220
+ **默认复用这台服务器的默认 tab**(第一次调用创建,`list_terminals` 该组的 `default` 就是它;整趟任务只重连一次 SSH),
221
+ `cd` / `export` / 后台进程都在原地;传 `tab` 指定某条,传 `newTab: true` 换一个干净 shell。
222
+ 远程没有"命令结束"信号:**`exitCode` 永远为 `null`,`running` 也可能为 `null`** —— 别等 `exitCode`,看输出,或接着用
223
+ {{cmd_terminal_read}} 读。
224
+ - **`list_server_deployments` 报出来的服务器就是能直接跑命令的机器**:拿到名字立即用 `execute_in_remote_host`。
225
+ `hasCredentials` / `validationIssue` 有问题时把它报给用户,由用户在 IDE 里处理。
226
+
227
+ **复用规则 (本地 / 远程一套):每种来源一条默认 tab。**
228
+
229
+ 1. 直接调 `project_sdk_terminal`(本机)或 `execute_in_remote_host`(远程),不传 `tab` 就落在该来源的默认 tab 上。
230
+ 不用先 `list_terminals`,也不用把 tab id 一路记着。
231
+ 2. **不必每条命令重贴 `cd ... &&`**:复用同一个 shell,目录、`export` 的变量、后台进程都还在。要换目录就传
232
+ `workingDirectory`(会在那个 tab 里 `cd` 过去),别再用 `cd` 拼在命令前面。
233
+ 3. **什么时候要新 tab**:两条命令会互相干扰、要独立环境的 `env`、或想从头来一遍 —— 传 `newTab: true`。
234
+ 它不会抢走默认身份;默认 tab 被关掉后,下次调用会重建一条。
235
+ 4. 要盯输出用 {{cmd_terminal_read}}:`read` 读增量或 `tail: N` 读最近几段。**它不是"再跑一条"的替代**。
236
+ 5. 本地 tab 只跑本机命令,远程 tab 只跑那台机器上的命令 —— 别拿本地 tab 敲远程命令(反之亦然);跨服务器同理。
237
+
238
+ **tab 只在这次 IDE 会话里有效**:tab 一关(报 `terminal_closed` / 找不到 tab)才重新开一个;用完不必主动关 —— 用户能看见,
239
+ 也能直接在 IDE 里接手。
201
240
 
202
241
  ### 服务器文件同步
203
242
 
204
243
  往服务器传文件 / 从服务器取文件,走这一组 —— IDE 的连接、路径映射、排除规则都在它们后面,传的就是 IDE 在管的那一份。
205
244
 
206
- - {{cmd_list_deployment_servers}} **本项目能用的部署服务器**:名称 (后面几条用它当 `serverName`)
207
- 、项目级还是应用级、协议、host / port、在本项目里的路径映射,以及 **项目级**的部署选项与危险组合告警。 **只读 IDE
208
- 配置、不联网**;动手前先看 `warnings[]`。
209
- {{#if canUploadToDeployment}}
210
- - {{cmd_upload_to_deployment}} **把映射到的本地目录传到服务器**(等同 IDE 的 Upload to …)。`mode` 默认 `mirror` ——
245
+ - {{cmd_list_server_deployments}} **本项目能用的服务器,以及每台服务器上的 deployment**。先分清两层:
246
+ **Server 是连接目标,deployment 是挂在它上面的一条路径映射**;一台 Server 可以有 **0 个 deployment** —— 那样它仍能给
247
+ `execute_in_remote_host` 用,但**上传 / 下载没有目标**。
248
+ - `servers[].name` → **远程终端**的 `serverName`;`servers[].deployments[].name` → **上传 / 下载**的 `deploymentName`
249
+ (由本地路径最后一段派生,**全局唯一**)。
250
+ - 每台 Server 还带 `level`(项目级 / 应用级)、`accessType`、`host` / `port`、`rootFolder`、`hasCredentials`、
251
+ `validationIssue`、`isDefault`;返回里另有**整个项目共用**的 `options` 与 `warnings[]`(**只读 IDE 配置、不联网**)。
252
+ - **`hasCredentials: false` 的服务器连不上** —— IDE 会弹模态登录框,MCP 调用会卡死;动手前先看 `warnings[]`。
253
+ - {{cmd_upload_to_deployment}} **把一条 deployment(本地映射目录)传到服务器**(等同 IDE 的 Upload to …)。
254
+ `deploymentName` 传 `servers[].deployments[].name` —— **不是 `serverName`**。`mode` 默认 `mirror` ——
211
255
  **会删掉服务器上多出来的文件**,使两边一致;要留住它们就传 `upload`。 **破坏性操作**。
212
- {{/if}}
213
- {{#if canDownloadFromDeployment}}
214
- - {{cmd_download_from_deployment}} **把服务器上映射目录的内容拉回本地**(等同 IDE 的 Download from …), **以服务器为准**:
215
- 两边都有的文件会被远端覆盖 (内容相同则不写)。 **没有"保留我的版本"这个选项**。
216
- {{/if}}
217
- {{#if canGetDeploymentTaskStatus}}
256
+ - {{cmd_download_from_deployment}} **把一条 deployment 从服务器拉回本地**(等同 IDE 的 Download from …), **以服务器为准**:
257
+ 两边都有的文件会被远端覆盖 (内容相同则不写)。同样传 `deploymentName`。 **没有"保留我的版本"这个选项**。
218
258
  - {{cmd_get_deployment_task_status}} **查一次传输的状态**:`handle` 用上传 / 下载返回的那个 —— **它自己不启动任何传输**。
219
- {{/if}}
220
259
 
221
- 上传 / 下载都在 IDE 后台跑,单次调用只等到 `waitMs`(默认 10 秒)为止;返回 `in_progress` 是 **正常**的,不是失败 —— 拿
222
- `handle` 去查进度;重复调用上传 / 下载会再起一次传输。
223
- {{/if}}
260
+ 上传 / 下载默认**同步等结果**:传完立刻返回 `state: "completed"`。只有超过等待窗口(300 秒)还没传完,才会返回
261
+ `state: "in_progress"` + `handle` —— 这 **正常**,不是失败(它也可能以 error 结果回来);拿 `handle` 调
262
+ `get_deployment_task_status` 接着看 —— 重复调上传 / 下载会再起一次传输。到 `completed` 之后
263
+ `counters` 才可信,而且它数的是**操作次数**,不是文件数(删掉整个目录也只算 1)。
264
+
265
+ **同步只是上半段**:传完之后在那台 Server 上继续操作(启动刚传上去的二进制、重启服务、看日志)—— 用
266
+ `execute_in_remote_host`,`serverName` 取 `servers[].name`。上传下载与远程执行走的是同一台 Server、同一套凭据和
267
+ 路径映射 —— 两条路径连的是同一个地方。
@@ -1,11 +1,13 @@
1
1
  // Arguments this plugin OWNS on the IDE's own MCP tools.
2
2
  //
3
- // Three files, three different subjects — kept apart on purpose:
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
- // * `enhance.js` a companion plugin's tools, which may not be installed
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
  //
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.2' },
130
+ clientInfo: { name: 'opencode-idea', version: '0.1.4' },
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
- // 配套插件 `idea-mcp-enhance` 的两组工具(run-lifecycle / deployment)也按每个项目分别判断,
1381
- // 所以一个项目装了 enhance、另一个没装时,各节的内容本来就不一样。
1382
- appendIdeGuidance(event?.system, currentProjects(), {tools});
1379
+ // 逐项目注入:每条工具后面直接列各项目的命令名,模型不需要自己换算前缀。
1380
+ appendIdeGuidance(event?.system, currentProjects());
1383
1381
  });
1384
1382
 
1385
1383
  // 自愈入口(钩子型)。OpenCode 会 await `prompt`,所以在这里修好的连接会在
@@ -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
- }