opencode-idea 0.0.15 → 0.0.16
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 +10 -1
- package/package.json +1 -1
- package/src/ide-launcher.js +24 -1
- package/src/mcp/idea.js +23 -4
- package/src/plugin.js +27 -5
package/README.md
CHANGED
|
@@ -64,6 +64,9 @@ setup(启动, 每项目一次):
|
|
|
64
64
|
|
|
65
65
|
在会话里输入 `/open-in-idea`:
|
|
66
66
|
|
|
67
|
+
> **非 macOS**:第 1、3 步的自动打开被跳过(没有对应的启动命令),请**先手动打开 IDE 和当前项目**,再执行本命令 ——
|
|
68
|
+
> 端口在监听就会正常接入,并同样等到 `idea_*` 工具就绪才返回。
|
|
69
|
+
|
|
67
70
|
1. **让运行中的 IDEA 实例打开/激活当前项目**:`open -a "IntelliJ IDEA" <项目目录>` —— **复用已运行的实例**(不会重复开新实例),
|
|
68
71
|
项目已打开时只把它切到前台。**无论 MCP 是否已就绪都会执行这一步**:端点能应答只说明 IDEA 在运行(可能开的是**别的**项目),
|
|
69
72
|
不代表当前项目已打开;
|
|
@@ -325,6 +328,10 @@ export { default } from "/Users/yutao/Projects/opencode-idea/src/index.js";
|
|
|
325
328
|
(`get_project_modules`,带 `IJ_MCP_SERVER_PROJECT_PATH`):返回模块 = 已打开 → 跳过 `open -a`;报
|
|
326
329
|
`Unable to determine the target project` = 未打开 → `open -a` 打开。MCP 关闭时会用一次短探测并立即给出设置提示。
|
|
327
330
|
项目刚打开时的短暂"项目未就绪"由工具失败分类兜底(「IDE 在、但项目没打开」→ 重新打开并让模型重试)。
|
|
331
|
+
- **项目路径按 IDE 的写法归一化**:IDE 收的是系统无关形式(正斜杠),其自带配置示例在 Windows 上也是
|
|
332
|
+
`"E:/gitea/secondev-demo"`。OpenCode 的 `location` 在 Windows 上是原生反斜杠,所以 `src/mcp/idea.js` 的
|
|
333
|
+
`ideProjectPath()` 把它转成正斜杠,**注册与探测走同一个函数**,两者必须一致,否则会用一种写法注册、又用另一种
|
|
334
|
+
写法去问,永远匹配不上。macOS 上是原样透传。
|
|
328
335
|
- IDE **2026.2+** 提供 Streamable HTTP 端点 `/stream`,这是插件**唯一**使用的传输(`type: "remote"`)。注册本身不启动任何子进程,
|
|
329
336
|
所以没有"桥进程被 IDE 拖死后变成僵尸"这一类问题。`/open-in-idea` 仍要求 IDE MCP 已在监听(冷启动约数十秒),因此会先等就绪再注册。
|
|
330
337
|
- **空闲驱逐(已实测)**:OpenCode 把一个 Location 的服务缓存在 `LayerMap` 里,`idleTimeToLive` = **60 分钟**;空闲到期即整体驱逐,
|
|
@@ -339,7 +346,9 @@ export { default } from "/Users/yutao/Projects/opencode-idea/src/index.js";
|
|
|
339
346
|
- `/close-in-idea` 会**注销 IDE MCP** 并删标记 + 清空注入的环境。
|
|
340
347
|
- 插件**完全不再检查 `.idea` 目录**:是否是 IDEA 项目只由项目标记(`/open-in-idea` 设置、`/close-in-idea` 清除)决定。
|
|
341
348
|
(`/open-in-idea` 会让 IDEA 打开并导入该项目,`.idea` 由 IDEA 自己生成,插件无需也不应据此判断。)
|
|
342
|
-
-
|
|
349
|
+
- **自动拉起 IDE 仅实现 macOS** (`open -a`)。其他平台(包括 Windows)没有对应的启动命令,**请手动打开 IDE 和项目**,再执行
|
|
350
|
+
`/open-in-idea` —— 端口在监听就会正常接入,其余能力(注册、探测、自愈、工具暴露)与 macOS 完全一致。
|
|
351
|
+
IDE 里打开 MCP 服务(2026.2+)并启用 Brave Mode;首次打开若弹 Trust 对话框,需确认后重试。
|
|
343
352
|
- IDE MCP 没有「打开项目」工具,因此打开动作走 OS/CLI,而非 MCP。
|
|
344
353
|
- 插件热重载会断开 IDE 连接,需重跑 `/open-in-idea`。
|
|
345
354
|
- 插件不修改任何工具的定义、描述或可用性;IDE MCP 工具以 `idea_*` 直接暴露给模型 (`codemode: false`),不用 `execute` 包裹。
|
package/package.json
CHANGED
package/src/ide-launcher.js
CHANGED
|
@@ -16,6 +16,23 @@ import { spawn } from 'node:child_process';
|
|
|
16
16
|
/** Default macOS app name for IntelliJ IDEA. */
|
|
17
17
|
export const DEFAULT_IDE_APP = 'IntelliJ IDEA';
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Can this platform launch the IDE at all?
|
|
21
|
+
*
|
|
22
|
+
* Launching goes through macOS `open -a`, which is the only launcher this
|
|
23
|
+
* plugin implements. On Windows there is no `open` command, so a spawn there
|
|
24
|
+
* fails with ENOENT — silently, as a rejected promise that resolves `false`.
|
|
25
|
+
* Rather than pretend, other platforms resolve no app at all and the caller
|
|
26
|
+
* asks the user to open the IDE by hand.
|
|
27
|
+
*
|
|
28
|
+
* A function, not a constant: the answer is read when a command runs, so tests
|
|
29
|
+
* can simulate another platform by replacing it. A constant would be bound at
|
|
30
|
+
* import time and never re-read.
|
|
31
|
+
*/
|
|
32
|
+
export function autoLaunchSupported() {
|
|
33
|
+
return process.platform === 'darwin';
|
|
34
|
+
}
|
|
35
|
+
|
|
19
36
|
/** Aliases accepted by the `openInIde` option. */
|
|
20
37
|
export const IDE_APPS = {
|
|
21
38
|
idea: 'IntelliJ IDEA',
|
|
@@ -26,10 +43,16 @@ export const IDE_APPS = {
|
|
|
26
43
|
/**
|
|
27
44
|
* Resolve the `openInIde` option to a macOS app name, or `undefined` to disable.
|
|
28
45
|
*
|
|
46
|
+
* `undefined` covers two distinct reasons, and the caller tells them apart via
|
|
47
|
+
* `autoLaunchSupported()`: the user turned launching off, or this platform has
|
|
48
|
+
* no launcher. They deserve different user-facing wording.
|
|
49
|
+
*
|
|
29
50
|
* @param {boolean | string | undefined} value
|
|
51
|
+
* @param {{ platform?: string }} [options]
|
|
30
52
|
* @returns {string | undefined}
|
|
31
53
|
*/
|
|
32
|
-
export function resolveIdeApp(value) {
|
|
54
|
+
export function resolveIdeApp(value, { platform = process.platform } = {}) {
|
|
55
|
+
if (platform !== 'darwin') return undefined;
|
|
33
56
|
if (value === false) return undefined;
|
|
34
57
|
if (typeof value !== 'string') return DEFAULT_IDE_APP;
|
|
35
58
|
return IDE_APPS[value.toLowerCase()] ?? value;
|
package/src/mcp/idea.js
CHANGED
|
@@ -18,6 +18,25 @@
|
|
|
18
18
|
|
|
19
19
|
export const PROJECT_HEADER = 'IJ_MCP_SERVER_PROJECT_PATH';
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* The project path in the form the IDE matches against.
|
|
23
|
+
*
|
|
24
|
+
* The IDE takes the project selector in system-independent form (forward
|
|
25
|
+
* slashes) — its own MCP client configuration ships
|
|
26
|
+
* `IJ_MCP_SERVER_PROJECT_PATH: "E:/gitea/secondev-demo"`, even on Windows.
|
|
27
|
+
* OpenCode's `location` is the platform-native path, so on Windows it carries
|
|
28
|
+
* backslashes (`E:\gitea\secondev-demo`) that the IDE does not necessarily
|
|
29
|
+
* match. Normalizing here keeps one spelling on the wire for BOTH senders
|
|
30
|
+
* below; they must agree, or the plugin asks about the project with one path
|
|
31
|
+
* and registers it with another and never matches. On macOS this is a no-op.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} projectPath
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
export function ideProjectPath(projectPath) {
|
|
37
|
+
return projectPath.replace(/\\/g, '/');
|
|
38
|
+
}
|
|
39
|
+
|
|
21
40
|
/** OpenCode MCP server name registered for the IDE. */
|
|
22
41
|
export const IDEA_SERVER_NAME = 'idea';
|
|
23
42
|
|
|
@@ -51,7 +70,7 @@ export function serverConfig(port, projectPath, options = {}) {
|
|
|
51
70
|
const config = {
|
|
52
71
|
type: 'remote',
|
|
53
72
|
url: `http://127.0.0.1:${port}${MCP_STREAM_PATH}`,
|
|
54
|
-
headers: { [PROJECT_HEADER]: projectPath },
|
|
73
|
+
headers: { [PROJECT_HEADER]: ideProjectPath(projectPath) },
|
|
55
74
|
codemode: false,
|
|
56
75
|
};
|
|
57
76
|
return withExecutionTimeout(config, options);
|
|
@@ -103,7 +122,7 @@ export async function probePort(port, options = {}) {
|
|
|
103
122
|
params: {
|
|
104
123
|
protocolVersion: PROTOCOL_VERSION,
|
|
105
124
|
capabilities: {},
|
|
106
|
-
clientInfo: { name: 'opencode-idea', version: '0.0.
|
|
125
|
+
clientInfo: { name: 'opencode-idea', version: '0.0.16' },
|
|
107
126
|
},
|
|
108
127
|
}),
|
|
109
128
|
signal: controller.signal,
|
|
@@ -160,7 +179,7 @@ export async function callTool(port, projectPath, name, args = {}, options = {})
|
|
|
160
179
|
headers: {
|
|
161
180
|
'Content-Type': 'application/json',
|
|
162
181
|
Accept: 'application/json, text/event-stream',
|
|
163
|
-
[PROJECT_HEADER]: projectPath,
|
|
182
|
+
[PROJECT_HEADER]: ideProjectPath(projectPath),
|
|
164
183
|
...(sessionId ? { 'Mcp-Session-Id': sessionId } : {}),
|
|
165
184
|
},
|
|
166
185
|
body: JSON.stringify(payload),
|
|
@@ -174,7 +193,7 @@ export async function callTool(port, projectPath, name, args = {}, options = {})
|
|
|
174
193
|
params: {
|
|
175
194
|
protocolVersion: PROTOCOL_VERSION,
|
|
176
195
|
capabilities: {},
|
|
177
|
-
clientInfo: { name: 'opencode-idea', version: '0.0.
|
|
196
|
+
clientInfo: { name: 'opencode-idea', version: '0.0.16' },
|
|
178
197
|
},
|
|
179
198
|
});
|
|
180
199
|
// The IDE's Streamable-HTTP endpoint binds follow-up requests to the session
|
package/src/plugin.js
CHANGED
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
} from './mcp/idea.js';
|
|
54
54
|
import { currentProjectPath } from './project.js';
|
|
55
55
|
import {
|
|
56
|
+
autoLaunchSupported,
|
|
56
57
|
createLaunchGuard,
|
|
57
58
|
openInIde,
|
|
58
59
|
resolveIdeApp,
|
|
@@ -296,7 +297,17 @@ function rewriteToolError(event, message) {
|
|
|
296
297
|
|
|
297
298
|
/**
|
|
298
299
|
* @typedef {{
|
|
299
|
-
* status:
|
|
300
|
+
* status:
|
|
301
|
+
* | 'connected'
|
|
302
|
+
* | 'tools-loading'
|
|
303
|
+
* | 'mcp-unavailable'
|
|
304
|
+
* | 'launch-failed'
|
|
305
|
+
* | 'disabled'
|
|
306
|
+
* | 'manual-launch'
|
|
307
|
+
* | 'closed',
|
|
308
|
+
* // `manual-launch` = this platform has no launcher at all (the user must
|
|
309
|
+
* // open the IDE by hand); `disabled` = the user turned launching off. Both
|
|
310
|
+
* // mean "no app to open", so only `autoLaunchSupported()` tells them apart.
|
|
300
311
|
* port?: number,
|
|
301
312
|
* opened?: boolean,
|
|
302
313
|
* unavailableNoticeSent?: boolean
|
|
@@ -332,6 +343,8 @@ export function ideFeedback(result) {
|
|
|
332
343
|
return '无法拉起 IntelliJ IDEA(未找到应用或启动失败)。请确认已安装 IDEA,或手动打开后重试 /open-in-idea。';
|
|
333
344
|
case 'disabled':
|
|
334
345
|
return '插件已通过 openInIde: false 关闭 IDE 启动。请手动打开 IntelliJ IDEA,在 Settings → MCP Server 开启 MCP 服务并启用 Brave Mode,然后重试 /open-in-idea。';
|
|
346
|
+
case 'manual-launch':
|
|
347
|
+
return '当前系统不支持自动打开 IDE(仅 macOS 实现了启动)。请手动打开 IntelliJ IDEA,在 Settings → MCP Server 开启 MCP 服务并启用 Brave Mode,然后再次执行 /open-in-idea。';
|
|
335
348
|
case 'closed':
|
|
336
349
|
return '已关闭当前项目的 IDEA 接入:已注销 IDE MCP 并清除项目标记。如需重新接入,请再次执行 /open-in-idea。';
|
|
337
350
|
default:
|
|
@@ -692,7 +705,11 @@ export default {
|
|
|
692
705
|
}
|
|
693
706
|
return { status: 'connected', port: serving, unavailableNoticeSent: false };
|
|
694
707
|
}
|
|
695
|
-
|
|
708
|
+
// No app to open. Either the user turned launching off, or this platform
|
|
709
|
+
// has no launcher — say which, so the notice is actionable.
|
|
710
|
+
if (!ideApp) {
|
|
711
|
+
return { status: autoLaunchSupported() ? 'disabled' : 'manual-launch' };
|
|
712
|
+
}
|
|
696
713
|
if (guard.exhausted()) {
|
|
697
714
|
await reportUnavailable(false);
|
|
698
715
|
return { status: 'mcp-unavailable', opened: false, unavailableNoticeSent: launchNoticeSent };
|
|
@@ -983,9 +1000,14 @@ export default {
|
|
|
983
1000
|
if (!(await readMark())) return;
|
|
984
1001
|
const kind = classifyIdeaFailure(event.error);
|
|
985
1002
|
if (kind === 'none') return;
|
|
986
|
-
if (kind === 'project'
|
|
987
|
-
// The IDE is up; the
|
|
988
|
-
//
|
|
1003
|
+
if (kind === 'project') {
|
|
1004
|
+
// The IDE is up and answering; only the PROJECT is missing. That is
|
|
1005
|
+
// never a connection problem, so this must never fall through to
|
|
1006
|
+
// the reconnect below — on a platform with no launcher there is
|
|
1007
|
+
// nothing to call, and falling through would report "reconnected"
|
|
1008
|
+
// and send the model round after round after a call that can only
|
|
1009
|
+
// fail. Leave the IDE's own message, which names the real cause.
|
|
1010
|
+
if (!ideApp) return;
|
|
989
1011
|
await openInIde(projectPath, { app: ideApp });
|
|
990
1012
|
rewriteToolError(event, '该项目已在 IDEA 中重新打开,请重试刚才的操作。');
|
|
991
1013
|
return;
|