opencode-idea 0.1.0 → 0.1.1
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 +15 -3
- package/package.json +1 -1
- package/src/ide-guidance.template.md +61 -44
- package/src/mcp/idea-tool-defaults.js +50 -0
- package/src/mcp/idea.js +1 -1
- package/src/plugin.js +16 -1
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ configure_ide_project({ path: "modelserver", ide: "pycharm" }) // 配置一个
|
|
|
138
138
|
|
|
139
139
|
## 按能力选工具 (直接调用引导)
|
|
140
140
|
|
|
141
|
-
IDE MCP 可用时,插件通过 **系统提示注入**告诉模型 **哪些事该走 IDE
|
|
141
|
+
IDE MCP 可用时,插件通过 **系统提示注入**告诉模型 **哪些事该走 IDE**;除下面「工具参数与输出」那几处刻意的接管
|
|
142
142
|
(只做减法:从 schema 里删掉一个参数),不修改任何工具 (原生或 IDEA) 的定义、描述或可用性:
|
|
143
143
|
|
|
144
144
|
1. **系统提示注入**:通过 `ctx.session.hook("context", …)` 往每次请求的 system 里追加一段引导。
|
|
@@ -224,7 +224,7 @@ IDE MCP 可用时,插件通过 **系统提示注入**告诉模型 **哪些事该
|
|
|
224
224
|
所以引导里写了:返回的 `usages` 数比预期少,就是有引用没被解析到 —— **先确认 IDE 索引到那个模块**,
|
|
225
225
|
别拿文本替换去"补"漏掉的引用。
|
|
226
226
|
|
|
227
|
-
##
|
|
227
|
+
## 工具参数与输出 (插件接管的几处)
|
|
228
228
|
|
|
229
229
|
有些 IDEA 工具**不传某个参数就会回答一个你没问的问题**。这种参数由本插件完全接管,所以要**两半一起做** ——
|
|
230
230
|
只做一半都是错的:
|
|
@@ -248,6 +248,18 @@ IDE MCP 可用时,插件通过 **系统提示注入**告诉模型 **哪些事该
|
|
|
248
248
|
schema 变换**绝不能**用来加建议,那是引导文本的职责。
|
|
249
249
|
- 只对**点名的工具**生效,其余工具的参数一字不改。两半都**不依赖项目配置**。
|
|
250
250
|
|
|
251
|
+
### 另两处只"抬下限 / 补提示",不固定值
|
|
252
|
+
|
|
253
|
+
`list_directory_tree` 有两处接管,都不用固定值 —— 模型仍然需要传 `maxDepth`,所以 schema 不删它:
|
|
254
|
+
|
|
255
|
+
- **调用前**(`RAISED_ARGUMENTS`):`maxDepth` 抬到至少 **50**,更大的值照用。它是绝对深度上限,却只展开
|
|
256
|
+
`maxDepth-1` 层,`6` 正好断在 Java 包目录上;而输出到边界时没有任何标记,一棵被切的树和空目录长得一模一样。
|
|
257
|
+
- **返回后**(`appendDirectoryTreeTruncationNotice`):树顶到边界时在结果末尾**追加**一句截断提示,原内容不动。
|
|
258
|
+
起因是一次真实会话:模型把截断后的空包目录读成"工具看不到",整条查询链退回 `shell` 里的 `find` / `grep`,从那
|
|
259
|
+
以后丢掉 `.gitignore` 规则。
|
|
260
|
+
|
|
261
|
+
前者不依赖模型知道这个语义,后者不依赖模型读提示词 —— 两边都是"即使模型不配合也不会被骗"。
|
|
262
|
+
|
|
251
263
|
## Run Configuration 全套 (配套插件 `idea-mcp-enhance`)
|
|
252
264
|
|
|
253
265
|
IDE 内置的 MCP Server 只能 **启动已有**的 run configuration (`execute_run_configuration`), **不能新建、不能停止、不能重启、
|
|
@@ -585,7 +597,7 @@ export { default } from "/Users/yutao/IdeaProjects/opencode-idea/src/index.js";
|
|
|
585
597
|
IDE MCP 没有「打开项目」工具,因此打开动作走 OS/CLI,而非 MCP。
|
|
586
598
|
- 插件热重载会断开 IDE 连接,需重跑 `/open-in-ide`。
|
|
587
599
|
- IDE 工具以 `<项目前缀>_*` 直接暴露给模型(`codemode: false`),不用 `execute` 包裹;
|
|
588
|
-
插件不修改任何**它不接管**的工具定义或描述,唯一的例外是 `rebuild` 参数(
|
|
600
|
+
插件不修改任何**它不接管**的工具定义或描述,唯一的例外是 `rebuild` 参数(见「工具参数与输出」)。
|
|
589
601
|
|
|
590
602
|
## License
|
|
591
603
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# IDE MCP:OpenCode 自带工具的补充
|
|
2
2
|
|
|
3
|
+
## 工具使用铁律(先读)
|
|
4
|
+
|
|
5
|
+
1. **读、搜、列、改都用 OpenCode 自带工具**:读文件用 `read`(读片段传 `offset` / `limit`),搜内容用 `grep`,
|
|
6
|
+
列文件用 `glob`,改文件用 `edit` / `write`。
|
|
7
|
+
2. **IDE 工具补自带工具做不到的**:符号与定义用 `<前缀>_search_symbol` + `<前缀>_read_file`;改名用
|
|
8
|
+
`<前缀>_rename_refactoring`;单文件检查用 `<前缀>_lint_files` / `<前缀>_get_file_problems`;跨模块构建用
|
|
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
|
+
|
|
3
14
|
本会话接入了 {{projectCount}} 个 JetBrains 项目。**每个功能在所有项目下都各有一份工具**,命令名是
|
|
4
15
|
`<项目前缀>_<工具名>`;下文写 `<前缀>` 就是指这一截。
|
|
5
16
|
|
|
@@ -10,44 +21,47 @@
|
|
|
10
21
|
**子项目就是根目录下的目录**,只是被另一个 IDE 单独打开、配了另一套工具。所以这里说的"根项目",指的是
|
|
11
22
|
**根目录中扣掉这些子项目之后的部分**。
|
|
12
23
|
|
|
13
|
-
**规则:操作哪个目录下的文件/代码,就用那个目录对应的命令。** IDE
|
|
14
|
-
|
|
24
|
+
**规则:操作哪个目录下的文件/代码,就用那个目录对应的命令。** IDE 按项目解析 —— 用错前缀时,调用要么报错,
|
|
25
|
+
要么作用在另一个项目的代码上。
|
|
15
26
|
|
|
16
27
|
按需读文件:要了解某个子项目自己的约定,读它的 `<子项目目录>/AGENTS.md`(它不会自动进入本会话的上下文,
|
|
17
28
|
本会话是 root 打开的那一个)。
|
|
18
29
|
|
|
19
30
|
## 路由:先看路径在不在项目里
|
|
20
31
|
|
|
21
|
-
**IDE
|
|
22
|
-
项目外的路径(`/tmp`、别的仓库、用户主目录)
|
|
32
|
+
**IDE 工具只在项目内使用。** 请求的路径落在**某个已打开项目**的目录树内,IDE 才会去解析它;
|
|
33
|
+
项目外的路径(`/tmp`、别的仓库、用户主目录)不在 IDE 的解析范围内。
|
|
23
34
|
|
|
24
35
|
- **项目内** —— **OpenCode 自带的工具为主**,它们更快。**它们做不了、或者做得很差的,才用 IDE 工具**:
|
|
25
36
|
符号检索、依赖实现、重构、格式化、校验、运行配置、跨文件补丁。
|
|
26
37
|
**两边都不顺手时,你自己判断用 `shell` 里的命令** —— 那是第三选择,不是第一选择。
|
|
27
|
-
- **项目外** ——
|
|
38
|
+
- **项目外** —— 用 OpenCode 自带的工具;IDE 工具只对项目内路径生效。
|
|
28
39
|
|
|
29
40
|
## 总则 (所有项目通用)
|
|
30
41
|
|
|
31
42
|
- **OpenCode 自带的工具直接调用** —— 工具列表里直接列出的那些(`read` / `grep` / `glob` / `edit` / `write` / `shell`),
|
|
32
|
-
参数按各自 schema
|
|
33
|
-
- **读文件用 `read` 工具** —— 要读某一段就传 `offset`(从第几行起)+ `limit`(读几行)
|
|
43
|
+
参数按各自 schema 传。它们就在当前工具表里,直接调用即可。
|
|
44
|
+
- **读文件用 `read` 工具** —— 要读某一段就传 `offset`(从第几行起)+ `limit`(读几行),输出自带行号。
|
|
34
45
|
- **搜文本用 `grep` 工具** —— 它按 `.gitignore` 排除,`node_modules` / `dist` 不进结果,范围用 `path` 收窄。
|
|
35
|
-
|
|
46
|
+
要看命中行前后的大段上下文:先用它拿到**行号**,再用 `read` + `offset` / `limit` 读那一段。
|
|
47
|
+
- **工具输出不够用时,按这个顺序调整**:① 调参数重试 → ② 缩小路径范围重试 → ③ 换工具。
|
|
48
|
+
换用 `shell` 的判断依据是"这个工具确实没有这个能力"。
|
|
49
|
+
典型:`<前缀>_list_directory_tree` 的输出到 `maxDepth` 边界就停 —— 调大 `maxDepth` 再试一次。
|
|
36
50
|
- **改文件**:改一个符号用 `<前缀>_rename_refactoring`;改一段文字用 `edit` / `write` 工具;
|
|
37
51
|
同一段文字铺到多个文件用 `<前缀>_apply_patch`;跑命令用 `shell` 工具。
|
|
38
|
-
|
|
52
|
+
改代码始终落在具体的那几行上,其余内容保持原样。
|
|
39
53
|
- **改完代码自己先查一遍**:`lint_files` / `build_project`,怎么挑见「校验」。
|
|
40
|
-
- **文档类文件 (Markdown / 文档) 改完之后,用 `<前缀>_reformat_file` 过一遍** ——
|
|
41
|
-
|
|
42
|
-
-
|
|
54
|
+
- **文档类文件 (Markdown / 文档) 改完之后,用 `<前缀>_reformat_file` 过一遍** —— 表格对齐、换行宽度、缩进交给 IDE
|
|
55
|
+
统一。它从磁盘重读,外部写完直接格式化是安全的。
|
|
56
|
+
- 某条功能下面没有你正要操作的那个项目的命令,说明那个项目没接上:提示用户执行 `/open-in-ide`。
|
|
43
57
|
|
|
44
58
|
## 命令执行 (所有项目通用)
|
|
45
59
|
|
|
46
60
|
- **跑命令用 `shell` 工具**;要项目环境的(IDE 里配好的 JDK / Node / Python 等)先看有没有现成的 run configuration ——
|
|
47
|
-
有就跑它 (常驻用 `rerun`,跑完即退用 `execute`);没有、又只是一次性的,用 `shell`
|
|
48
|
-
|
|
61
|
+
有就跑它 (常驻用 `rerun`,跑完即退用 `execute`);没有、又只是一次性的,用 `shell` 工具并显式指定解释器。
|
|
62
|
+
一次性的命令留在 `shell` 里,配置留给会反复用到的流程。
|
|
49
63
|
- **建 / 改 / 删运行配置,走 `idea-run-config-manager` skill**(服务化应用的配置归 `idea-standalone`):
|
|
50
|
-
|
|
64
|
+
它落手用的就是「运行配置」里的那组工具 —— 配置文件由工具写入和删除。
|
|
51
65
|
{{#if canCreateRunConfiguration}}
|
|
52
66
|
{{else}}
|
|
53
67
|
本会话没有新建工具,"建"这一步它只能讲清配置名、实际执行的命令、工作目录、为什么值得留, **请用户在 IDE 里创建**
|
|
@@ -59,7 +73,7 @@
|
|
|
59
73
|
|
|
60
74
|
**要读任何类 / 方法 / 字段的源码,就两步**:`<前缀>_search_symbol`(`q` 传名字, **带 `include_external: true`**)找到位置 →
|
|
61
75
|
`<前缀>_read_file` 原样读它给的路径。 **依赖库 / 三方包 / 平台 jar 里的实现**正是它最该用的场合 —— IDE 索引里那份就是项目
|
|
62
|
-
|
|
76
|
+
编译时真正用的依赖,读出来还是反编译源码。
|
|
63
77
|
|
|
64
78
|
- {{cmd_search_symbol}} **`q` 传查询文本,固定带上 `include_external: true`。** **本插件最不可替代的能力**在于它搜的是**符号**
|
|
65
79
|
(类 / 接口 / 方法 / 函数 / 字段 / 类型 / 常量这类 **有名字的定义**)的**声明位置**,不是"哪段文本提到过它"(那是
|
|
@@ -70,10 +84,10 @@
|
|
|
70
84
|
(`fastapi` → 入口文件就是导出清单),再读它的入口。
|
|
71
85
|
- **Node/TS 例外**:带 `-` 或 `@scope/` 的包名这么拼是 0 条(`element-plus`);改用 import 的符号名,并用 `paths` 圈到
|
|
72
86
|
那个 `node_modules`(`paths: ['frontend/user/**']` 就收敛到一处)。
|
|
73
|
-
- **搜不到 = 依赖没进来**:先把它加进依赖,或改用 web 搜索 / GitHub
|
|
74
|
-
- **同名符号往往有多处定义**(标准库 / 依赖 / 测试各一份):按调用处的 import
|
|
75
|
-
- **它返回的路径原样交给同一个项目的 `<前缀>_read_file`** ——
|
|
76
|
-
|
|
87
|
+
- **搜不到 = 依赖没进来**:先把它加进依赖,或改用 web 搜索 / GitHub 看源码。
|
|
88
|
+
- **同名符号往往有多处定义**(标准库 / 依赖 / 测试各一份):按调用处的 import 与参数类型来选。
|
|
89
|
+
- **它返回的路径原样交给同一个项目的 `<前缀>_read_file`** —— 那个路径的基准是**它所在的项目**,不是会话工作目录;
|
|
90
|
+
交回 `read` 工具时,子项目的相对路径会落到另一个位置。
|
|
77
91
|
- **依赖索引是按项目的**:用 **它所在项目** 前缀的工具搜,拿别的项目的工具搜不到库。
|
|
78
92
|
- {{cmd_get_symbol_info}} **一个符号的签名 / 文档 / 声明位置**:`filePath` + `line` + `column`。
|
|
79
93
|
- {{cmd_analyze_calls}} **调用关系**:`symbolFqn` 传符号名,`analysisKind` 取 `INCOMING_CALLS` / `OUTGOING_CALLS`;`depth`
|
|
@@ -82,6 +96,10 @@
|
|
|
82
96
|
### 项目与编辑器
|
|
83
97
|
|
|
84
98
|
- {{cmd_list_directory_tree}} **项目视角的目录树**(尊重内容根与排除项):`maxDepth` 控深度。
|
|
99
|
+
**它是绝对深度上限,实际只展开到 `maxDepth-1` 层;省略时默认 5(只展开 4 层)。** 要看全就传一个明显够大的值
|
|
100
|
+
(如 50),不会报错、也没有额外代价。**输出到边界就停,不带任何截断标记** —— 所以"某个目录后面没有子项"有两种
|
|
101
|
+
可能:真空目录,或者被你截断了。判据:树的深度正好顶到你传的值,或者**包目录 / 源码目录显示为空** —— 那就是被截断了。
|
|
102
|
+
**把 `maxDepth` 调大重试,或把 `directoryPath` 直接指到那个子目录再看一层。**
|
|
85
103
|
- {{cmd_get_project_modules}} **项目模块及类型**。
|
|
86
104
|
- {{cmd_get_project_dependencies}} **项目直接声明的依赖**。
|
|
87
105
|
- {{cmd_get_all_open_file_paths}} **用户在 IDE 里打开了哪些文件**(含当前激活的那个)。
|
|
@@ -90,7 +108,7 @@
|
|
|
90
108
|
### 读文件
|
|
91
109
|
|
|
92
110
|
- {{cmd_read_file}} **按项目解析路径读文件** —— 普通文件用 `read` 工具就够,它只在三种情况下用得着:
|
|
93
|
-
`search_symbol` 返回的路径(它的基准是那个项目,不是会话工作目录);**压缩包内的条目**(`read`
|
|
111
|
+
`search_symbol` 返回的路径(它的基准是那个项目,不是会话工作目录);**压缩包内的条目**(`read` 工具读不到压缩包内部,
|
|
94
112
|
而它还能把 `.class` **反编译成源码**);路径按那个项目解析更省事的时候。
|
|
95
113
|
压缩包路径两种写法:`<路径>.jar!/<条目>`(相对或绝对都行)或 `jar://<**绝对**路径>.jar!/<条目>`(`jar://` 只认绝对路径)。
|
|
96
114
|
|
|
@@ -98,42 +116,42 @@
|
|
|
98
116
|
|
|
99
117
|
- {{cmd_rename_refactoring}} **改一个符号(变量 / 函数 / 类 / 字段 / 方法),哪怕散落在几十个文件里,优先用它** ——
|
|
100
118
|
它按**语义**更新所有**真引用**:跨文件一起改,类名连**文件名**一起改。
|
|
101
|
-
- **它只改代码**:注释、字符串字面量、别的语言的同名符号原样不动 ——
|
|
102
|
-
会把这些一起改掉,那是改了程序的输出。
|
|
119
|
+
- **它只改代码**:注释、字符串字面量、别的语言的同名符号原样不动 —— 名字只落在真正的引用上。
|
|
103
120
|
- 返回里 `with N usages` 的 N 是**真引用数**;比预期少,说明有引用没被解析到(文件没进 IDE 索引),
|
|
104
|
-
|
|
105
|
-
-
|
|
106
|
-
|
|
121
|
+
这时**先确认索引到那个模块**,再处理剩下的引用。
|
|
122
|
+
- 改的本来就是文字(配置 key、模板占位符、文档、SQL)、**就是要改那句字符串**、或者文件 IDE 没索引(生成物、
|
|
123
|
+
vendor 目录)时,用 `edit` / `write` / `apply_patch`。动手前先用 `grep` 工具确认命中范围。
|
|
107
124
|
- {{cmd_reformat_file}} **按 IDE 的代码风格重排**。
|
|
108
125
|
- {{cmd_apply_patch}} **一次改多个文件**:`input` 传补丁正文,Codex 格式(`*** Begin Patch` / `*** Update File: <路径>`
|
|
109
126
|
/ `@@` / `-` 旧行 / `+` 新行)与 unified git diff 都收。`edit` / `write` 工具都只管一个文件。
|
|
110
127
|
- 它**按文本**改,适合同一段文字铺到多个文件;**改符号走上面那条**。
|
|
111
|
-
-
|
|
128
|
+
- 它**只碰补丁里写明的那几行**,其余内容保持原样。
|
|
112
129
|
- 上下文对不上时那一个 hunk 失败、**该文件原样不动**,返回里点名是哪个文件(`<路径>: Hunk context not found`)。
|
|
113
130
|
- 多个文件是**逐个应用**的:返回里 `N out of M operations applied`,失败的那些读一下再改;
|
|
114
|
-
改完用 `grep`
|
|
131
|
+
改完用 `grep` 工具确认落盘。
|
|
115
132
|
- {{cmd_lint_files}} **批量检查**:`files` 传数组;只列有问题的文件。
|
|
116
133
|
- {{cmd_get_file_problems}} **单文件检查**:`filePath`;结果在 `errors`。
|
|
117
134
|
- {{cmd_build_project}} **构建校验**:`filesToRebuild` 可只构建指定文件;有问题返回在 `problems`,没返回即没问题。
|
|
118
135
|
|
|
119
136
|
**改完代码,自己先查一遍再收尾。** 查一次很便宜,而问题一旦漏到用户看见、或留到构建 / 测试炸了才暴露,回头纠正的
|
|
120
|
-
代价就高一个量级。
|
|
137
|
+
代价就高一个量级。 校验跟着改动范围走 —— 这一轮属于"改一大片"还是"改一处",见下面两条。
|
|
121
138
|
|
|
122
|
-
- **这次要改一大片**(多文件、跨模块、"顺手把这几处一起对齐")→ 全部改完再 `build_project`
|
|
123
|
-
|
|
139
|
+
- **这次要改一大片**(多文件、跨模块、"顺手把这几处一起对齐")→ 全部改完再 `build_project` 一次,
|
|
140
|
+
在完整状态上检查。
|
|
124
141
|
- **这次只改一处,或在同一个文件上反复微调** → 告一段落时,把这轮改过的文件一次 `lint_files` 掉;要看单个
|
|
125
142
|
文件的细节再上 `get_file_problems`。
|
|
126
143
|
- **改了签名 / 接口 / import / 依赖**这类"别处会跟着坏"的地方 → 走 `build_project`;只动了文件内部的实现 →
|
|
127
144
|
lint 就够。
|
|
128
|
-
- 查出来的问题
|
|
129
|
-
-
|
|
130
|
-
|
|
145
|
+
- 查出来的问题 **改掉,或说清为什么不改**。
|
|
146
|
+
- 校验走上面这些工具;先调用一次,索引没同步或环境不对时它会明说。
|
|
147
|
+
- **全仓类型检查是收尾动作,不是编辑循环**:同一任务只在最后跑一次 `pnpm typecheck`;过程中只对改动文件跑
|
|
148
|
+
`lint_files` / `get_file_problems`。局部检查用这两个工具,全仓 typecheck 留给收尾。
|
|
149
|
+
- 只有改依赖清单、生成 Prisma Client、跑迁移这类必须执行命令的动作,才额外调用 `pnpm`;一个任务通常不超过两次。
|
|
131
150
|
|
|
132
151
|
### 运行配置
|
|
133
152
|
|
|
134
153
|
- {{cmd_get_run_configurations}} **项目里有哪些运行配置**:传 `filePath` 可列该文件的运行入口。
|
|
135
|
-
|
|
136
|
-
存在 IDE 自己存储里 (不在项目目录)的那些,翻文件是看不到的。
|
|
154
|
+
配置由 IDE 自己存储(通常在项目目录之外),用这个工具读。
|
|
137
155
|
- {{cmd_execute_run_configuration}} **跑已有配置**:配置里的 `EXECUTE_IN_TERMINAL` 要是 `false`(共享出去的配置都这样)
|
|
138
156
|
;覆盖参数 (`programArguments` / `workingDirectory` / `envs`)需配置支持 `supportsDynamicLaunchOverrides`,否则会直接报错。
|
|
139
157
|
{{#if canCreateRunConfiguration}}
|
|
@@ -143,8 +161,8 @@
|
|
|
143
161
|
**要不要建、建什么、怎么建、档案怎么记,全按上面那个 skill 走。** 判据只有一条: **这件事以后会不会以同样的形态再发生?**
|
|
144
162
|
会 (改表后同步 DDL、跑迁移、生成代码、打包、起 dev server、往某个环境部署这类)就 **主动提**一次
|
|
145
163
|
"要不要固化成 run configuration",并把配置名 / 类型 / 命令 / 工作目录 / 为什么值得留摊开;不会 (查条数据、看个日志、
|
|
146
|
-
试一条命令)就用 `shell`
|
|
147
|
-
|
|
164
|
+
试一条命令)就用 `shell` 工具跑完算。
|
|
165
|
+
**改完同步档案**:新增 / 删除 / 改名之后按 skill 更新 `AGENTS.md` 里的档案块 —— 档案和项目里的配置对不上,比没有档案更糟。
|
|
148
166
|
{{/if}}
|
|
149
167
|
{{#if canInspectRunConfiguration}}
|
|
150
168
|
- {{cmd_inspect_run_configuration}} **看一个配置的全貌**:定义 (options / envs / before-launch)、命名解析 (`win` /
|
|
@@ -168,23 +186,22 @@
|
|
|
168
186
|
{{#if canRerunRunConfiguration}}
|
|
169
187
|
- {{cmd_rerun_run_configuration}} **启动 / 重启常驻进程**:在跑就重启、没在跑就启动 —— 改完代码要重启就直接用它,
|
|
170
188
|
有没有热重载都一样;读 `started` 判断新进程是否真的起来了。这次运行的输出在 `output`,完整日志文件路径在
|
|
171
|
-
`fullOutputPath
|
|
189
|
+
`fullOutputPath`。启停都走这个工具,IDE 的 Run 窗口状态才会跟着更新。
|
|
172
190
|
{{/if}}
|
|
173
191
|
|
|
174
192
|
**配置名怎么读**:`[os@]base[@env]`,`@` 分割、最多三段 —— `os` = `win`(缺省 macOS / Linux)、`base` = 配置本体(必填)、
|
|
175
193
|
`env` = 环境名(缺省 = 默认环境)。`win@base`(换平台)与 `base@env`(换环境)都是 `base` 的 **变体**,跑哪一个:
|
|
176
194
|
|
|
177
|
-
- **平台**:macOS / Linux 用 `base`,Windows 用 `win@base` —— `win@base`
|
|
195
|
+
- **平台**:macOS / Linux 用 `base`,Windows 用 `win@base` —— `win@base` 不存在时 **交回用户**。
|
|
178
196
|
- **环境**:不带 `@` 的就是 **默认环境**。一批一起起时 **逐条解析**:有 `base@<要的环境>` 就用它,没有就用默认;只有 `@env`
|
|
179
197
|
变体、没有默认版的,就用它自己那个 —— "10 个服务"可以是"3 个 `@dev` + 7 个默认",不必逐条问。
|
|
180
|
-
- 一条配置同时有 `base` 与 `base@<env>`、又没说走哪个环境 →
|
|
198
|
+
- 一条配置同时有 `base` 与 `base@<env>`、又没说走哪个环境 → **问一次走哪个环境**,选定后本会话默认走这套。
|
|
181
199
|
- 一个环境怎么接上(启动参数 / 环境变量 / 配置文件)是项目自己的事:照现成的同类变体来,或问用户。
|
|
182
200
|
{{#if canListDeploymentServers}}
|
|
183
201
|
|
|
184
202
|
### 服务器文件同步
|
|
185
203
|
|
|
186
|
-
往服务器传文件 / 从服务器取文件,走这一组 ——
|
|
187
|
-
IDE 在管的那一份。
|
|
204
|
+
往服务器传文件 / 从服务器取文件,走这一组 —— IDE 的连接、路径映射、排除规则都在它们后面,传的就是 IDE 在管的那一份。
|
|
188
205
|
|
|
189
206
|
- {{cmd_list_deployment_servers}} **本项目能用的部署服务器**:名称 (后面几条用它当 `serverName`)
|
|
190
207
|
、项目级还是应用级、协议、host / port、在本项目里的路径映射,以及 **项目级**的部署选项与危险组合告警。 **只读 IDE
|
|
@@ -202,5 +219,5 @@ IDE 在管的那一份。
|
|
|
202
219
|
{{/if}}
|
|
203
220
|
|
|
204
221
|
上传 / 下载都在 IDE 后台跑,单次调用只等到 `waitMs`(默认 10 秒)为止;返回 `in_progress` 是 **正常**的,不是失败 —— 拿
|
|
205
|
-
`handle`
|
|
222
|
+
`handle` 去查进度;重复调用上传 / 下载会再起一次传输。
|
|
206
223
|
{{/if}}
|
|
@@ -100,6 +100,56 @@ export function applyIdeToolDefaults(tool, input, prefixes) {
|
|
|
100
100
|
return changed;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Arguments this plugin only ever RAISES to a floor, never pins.
|
|
105
|
+
*
|
|
106
|
+
* 和 [`FORCED_ARGUMENTS`] 的区别是**单向**:调用方给了更大的值就照用,只把"太小或没给"抬到下限。
|
|
107
|
+
* 所以参数必须继续留在 schema 里 —— 模型仍然需要它,`hideForcedArguments` 也不碰它。
|
|
108
|
+
*
|
|
109
|
+
* `list_directory_tree` 的 `maxDepth` 是**绝对深度上限**,但实际只展开到 `maxDepth-1` 层,省略时默认 5
|
|
110
|
+
* (只展开 4 层)。实测(2026-10,真实 MCP 调用):`maxDepth=6` 时 Java 包树正好断在包目录那一层,而输出里
|
|
111
|
+
* 没有任何截断标记 —— 一棵被切的树和一个空目录长得一模一样,模型据此判定"工具看不到这个目录",
|
|
112
|
+
* 整条查询链退回 `shell` 里的 `find` / `grep` / `sed`,从此丢掉 `.gitignore` 规则。抬到 50 是实测
|
|
113
|
+
* 无副作用的量级(同一目录 50 与 8 输出完全一致)。
|
|
114
|
+
*
|
|
115
|
+
* @type {ReadonlyArray<{ suffix: string, argument: string, minimum: number }>}
|
|
116
|
+
*/
|
|
117
|
+
export const RAISED_ARGUMENTS = [
|
|
118
|
+
{
|
|
119
|
+
suffix: 'list_directory_tree',
|
|
120
|
+
argument: 'maxDepth',
|
|
121
|
+
minimum: 50,
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Raise a floor argument on one call. Never lowers, never pins.
|
|
127
|
+
*
|
|
128
|
+
* Mutates `input` in place and reports whether anything changed. A non-numeric
|
|
129
|
+
* value counts as "not given": this function cannot vouch for what a string
|
|
130
|
+
* `"6"` means, so it replaces it with the floor rather than trusting it.
|
|
131
|
+
*
|
|
132
|
+
* @param {unknown} tool Effective tool name for this call (`event.tool`).
|
|
133
|
+
* @param {unknown} input Mutable argument draft for this call (`event.input`).
|
|
134
|
+
* @param {readonly string[]} prefixes 本会话所有项目的工具前缀(`ide_` 等)。
|
|
135
|
+
* @returns {boolean} Whether `input` was modified.
|
|
136
|
+
*/
|
|
137
|
+
export function raiseMinimumArguments(tool, input, prefixes) {
|
|
138
|
+
if (typeof tool !== 'string') return false;
|
|
139
|
+
if (input === null || typeof input !== 'object') return false;
|
|
140
|
+
|
|
141
|
+
let changed = false;
|
|
142
|
+
for (const raised of RAISED_ARGUMENTS) {
|
|
143
|
+
const matches = prefixes.some((prefix) => tool === `${prefix}${raised.suffix}`);
|
|
144
|
+
if (!matches) continue;
|
|
145
|
+
const current = input[raised.argument];
|
|
146
|
+
if (typeof current === 'number' && Number.isFinite(current) && current >= raised.minimum) continue;
|
|
147
|
+
input[raised.argument] = raised.minimum;
|
|
148
|
+
changed = true;
|
|
149
|
+
}
|
|
150
|
+
return changed;
|
|
151
|
+
}
|
|
152
|
+
|
|
103
153
|
/**
|
|
104
154
|
* Drop one property from a JSON schema, returning a new object.
|
|
105
155
|
*
|
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.1' },
|
|
131
131
|
},
|
|
132
132
|
});
|
|
133
133
|
// The IDE's Streamable-HTTP endpoint binds follow-up requests to the session
|
package/src/plugin.js
CHANGED
|
@@ -55,7 +55,8 @@ import {existsSync} from 'node:fs';
|
|
|
55
55
|
import {resolve} from 'node:path';
|
|
56
56
|
|
|
57
57
|
import {callTool, serverConfig} from './mcp/idea.js';
|
|
58
|
-
import {applyIdeToolDefaults, hideForcedArguments} from './mcp/idea-tool-defaults.js';
|
|
58
|
+
import {applyIdeToolDefaults, hideForcedArguments, raiseMinimumArguments} from './mcp/idea-tool-defaults.js';
|
|
59
|
+
import {appendDirectoryTreeTruncationNotice} from './mcp/idea-tool-output.js';
|
|
59
60
|
import {currentProjectPath} from './project.js';
|
|
60
61
|
import {
|
|
61
62
|
appNameFor,
|
|
@@ -1431,6 +1432,8 @@ export default {
|
|
|
1431
1432
|
toolBeforeRegistration = await ctx.tool.hook('execute.before', (event) => {
|
|
1432
1433
|
if (typeof event?.tool !== 'string') return;
|
|
1433
1434
|
applyIdeToolDefaults(event.tool, event.input, prefixes());
|
|
1435
|
+
// 下限抬升与"固定值"是两件事:前者保留调用方更大的值,所以参数继续留在 schema 里。
|
|
1436
|
+
raiseMinimumArguments(event.tool, event.input, prefixes());
|
|
1434
1437
|
});
|
|
1435
1438
|
}
|
|
1436
1439
|
|
|
@@ -1441,6 +1444,18 @@ export default {
|
|
|
1441
1444
|
// "the project is not open in the IDE" do.
|
|
1442
1445
|
if (typeof ctx.tool?.hook === 'function') {
|
|
1443
1446
|
toolRegistration = await ctx.tool.hook('execute.after', async (event) => {
|
|
1447
|
+
if (event?.status === 'completed') {
|
|
1448
|
+
// 输出侧:目录树顶到 maxDepth 边界时补一句看得见的截断提示
|
|
1449
|
+
// (静默截断正是模型把"被切"读成"空目录"的原因,见 `mcp/idea-tool-output.js`)。
|
|
1450
|
+
if (typeof event?.tool !== 'string' || !isIdeToolName(event.tool)) return;
|
|
1451
|
+
const next = appendDirectoryTreeTruncationNotice(event.result, {
|
|
1452
|
+
tool: event.tool,
|
|
1453
|
+
input: event.input,
|
|
1454
|
+
prefixes: prefixes(),
|
|
1455
|
+
});
|
|
1456
|
+
if (next !== event.result) event.result = next;
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1444
1459
|
if (event?.status !== 'error') return;
|
|
1445
1460
|
if (typeof event?.tool !== 'string' || !isIdeToolName(event.tool)) return;
|
|
1446
1461
|
if (!config) return;
|