@aipanel/provider-opencode 1.2.9 → 1.2.11
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/es/api.d.ts +1 -0
- package/es/api.js +35 -93
- package/es/opencode-web.js +12 -89
- package/es/plugins/edit-diagnostics.js +9 -16
- package/es/system.js +6 -166
- package/lib/api.cjs +34 -92
- package/lib/api.d.ts +1 -0
- package/lib/opencode-web.cjs +10 -83
- package/lib/plugins/edit-diagnostics.cjs +5 -15
- package/lib/system.cjs +5 -165
- package/package.json +2 -2
- package/es/plugins/edit-diagnostics.d.ts +0 -16
- package/lib/plugins/edit-diagnostics.d.ts +0 -16
package/es/api.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare class OpenCodeAPI {
|
|
|
8
8
|
/** 构建代理 iframe URL(旧版格式:/{base64(projectDir)}/session/{id}) */
|
|
9
9
|
buildSessionProxyUrl(projectDir: string, sessionId: string): string;
|
|
10
10
|
private createHttpRequest;
|
|
11
|
+
private retryLog;
|
|
11
12
|
getSessions(projectDir: string, retries?: number): Promise<SessionInfo[]>;
|
|
12
13
|
createSession(projectDir: string, retries?: number, title?: string): Promise<SessionInfo>;
|
|
13
14
|
deleteSession(sessionId: string, retries?: number): Promise<void>;
|
package/es/api.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
DEFAULT_RETRIES,
|
|
7
7
|
RETRY_DELAY,
|
|
8
8
|
CHROME_DEVTOOLS_PORT,
|
|
9
|
-
|
|
9
|
+
withRetries,
|
|
10
10
|
base64Encode
|
|
11
11
|
} from "@aipanel/core";
|
|
12
12
|
import { PerformanceTimer, createLogger } from "@aipanel/core/node";
|
|
@@ -56,12 +56,14 @@ class OpenCodeAPI {
|
|
|
56
56
|
req.end();
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
+
retryLog(operation, n, error) {
|
|
60
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61
|
+
log.debug(`Attempt ${n} failed: ${message}, retrying in ${RETRY_DELAY}ms`, { operation });
|
|
62
|
+
}
|
|
59
63
|
async getSessions(projectDir, retries = DEFAULT_RETRIES) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
log.debug(`Attempt ${i + 1}/${retries}`, { operation: "getSessions", projectDir });
|
|
64
|
+
return withRetries(
|
|
65
|
+
async (attempt) => {
|
|
66
|
+
log.debug(`Attempt ${attempt + 1}/${retries}`, { operation: "getSessions", projectDir });
|
|
65
67
|
const sessions = await this.createHttpRequest({
|
|
66
68
|
hostname: this.hostname,
|
|
67
69
|
port: this.getPort(),
|
|
@@ -71,30 +73,16 @@ class OpenCodeAPI {
|
|
|
71
73
|
...s,
|
|
72
74
|
url: s.directory && s.id ? this.buildSessionProxyUrl(s.directory, s.id) : ""
|
|
73
75
|
}));
|
|
74
|
-
|
|
76
|
+
log.debug(`Found ${sessions.length} sessions`, { operation: "getSessions" });
|
|
75
77
|
return sessionsWithUrl;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
operation: "getSessions"
|
|
80
|
-
});
|
|
81
|
-
if (i < retries - 1) {
|
|
82
|
-
log.debug(`Retrying in ${RETRY_DELAY}ms...`, {
|
|
83
|
-
operation: "getSessions"
|
|
84
|
-
});
|
|
85
|
-
await sleep(RETRY_DELAY);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
timer.end("\u274C All retries exhausted");
|
|
90
|
-
throw lastError;
|
|
78
|
+
},
|
|
79
|
+
{ attempts: retries, onRetry: (n, e) => this.retryLog("getSessions", n, e) }
|
|
80
|
+
);
|
|
91
81
|
}
|
|
92
82
|
async createSession(projectDir, retries = DEFAULT_RETRIES, title) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
log.debug(`Attempt ${i + 1}/${retries}`, {
|
|
83
|
+
return withRetries(
|
|
84
|
+
async (attempt) => {
|
|
85
|
+
log.debug(`Attempt ${attempt + 1}/${retries}`, {
|
|
98
86
|
operation: "createSession",
|
|
99
87
|
title,
|
|
100
88
|
projectDir
|
|
@@ -116,30 +104,16 @@ class OpenCodeAPI {
|
|
|
116
104
|
...session,
|
|
117
105
|
url: this.buildSessionProxyUrl(projectDir, session.id)
|
|
118
106
|
};
|
|
119
|
-
|
|
107
|
+
log.debug(`Created session: ${session.id}`, { operation: "createSession" });
|
|
120
108
|
return sessionWithUrl;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
operation: "createSession"
|
|
125
|
-
});
|
|
126
|
-
if (i < retries - 1) {
|
|
127
|
-
log.debug(`Retrying in ${RETRY_DELAY}ms...`, {
|
|
128
|
-
operation: "createSession"
|
|
129
|
-
});
|
|
130
|
-
await sleep(RETRY_DELAY);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
timer.end("\u274C All retries exhausted");
|
|
135
|
-
throw lastError;
|
|
109
|
+
},
|
|
110
|
+
{ attempts: retries, onRetry: (n, e) => this.retryLog("createSession", n, e) }
|
|
111
|
+
);
|
|
136
112
|
}
|
|
137
113
|
async deleteSession(sessionId, retries = DEFAULT_RETRIES) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
log.debug(`Attempt ${i + 1}/${retries}`, {
|
|
114
|
+
await withRetries(
|
|
115
|
+
async (attempt) => {
|
|
116
|
+
log.debug(`Attempt ${attempt + 1}/${retries}`, {
|
|
143
117
|
operation: "deleteSession",
|
|
144
118
|
sessionId
|
|
145
119
|
});
|
|
@@ -149,59 +123,27 @@ class OpenCodeAPI {
|
|
|
149
123
|
path: `/session/${sessionId}`,
|
|
150
124
|
method: "DELETE"
|
|
151
125
|
});
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
log.debug(`Attempt ${i + 1} failed: ${lastError.message}`, {
|
|
157
|
-
operation: "deleteSession",
|
|
158
|
-
sessionId
|
|
159
|
-
});
|
|
160
|
-
if (i < retries - 1) {
|
|
161
|
-
log.debug(`Retrying in ${RETRY_DELAY}ms...`, {
|
|
162
|
-
operation: "deleteSession",
|
|
163
|
-
sessionId
|
|
164
|
-
});
|
|
165
|
-
await sleep(RETRY_DELAY);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
timer.end("\u274C All retries exhausted");
|
|
170
|
-
throw lastError;
|
|
126
|
+
log.debug(`Deleted session: ${sessionId}`, { operation: "deleteSession" });
|
|
127
|
+
},
|
|
128
|
+
{ attempts: retries, onRetry: (n, e) => this.retryLog("deleteSession", n, e) }
|
|
129
|
+
);
|
|
171
130
|
}
|
|
172
131
|
async getToolIds(retries = DEFAULT_RETRIES) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
log.debug(`Attempt ${i + 1}/${retries}`, {
|
|
178
|
-
operation: "getToolIds"
|
|
179
|
-
});
|
|
132
|
+
return withRetries(
|
|
133
|
+
async (attempt) => {
|
|
134
|
+
log.debug(`Attempt ${attempt + 1}/${retries}`, { operation: "getToolIds" });
|
|
180
135
|
const toolIds = await this.createHttpRequest({
|
|
181
136
|
hostname: this.hostname,
|
|
182
137
|
port: this.getPort(),
|
|
183
138
|
path: "/experimental/tool/ids"
|
|
184
139
|
});
|
|
185
|
-
|
|
140
|
+
log.debug(`Found ${toolIds.length} tools`, { operation: "getToolIds" });
|
|
186
141
|
return toolIds;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
operation: "getToolIds"
|
|
191
|
-
});
|
|
192
|
-
if (i < retries - 1) {
|
|
193
|
-
log.debug(`Retrying in ${RETRY_DELAY}ms...`, {
|
|
194
|
-
operation: "getToolIds"
|
|
195
|
-
});
|
|
196
|
-
await sleep(RETRY_DELAY);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
timer.end("\u274C All retries exhausted");
|
|
201
|
-
throw lastError;
|
|
142
|
+
},
|
|
143
|
+
{ attempts: retries, onRetry: (n, e) => this.retryLog("getToolIds", n, e) }
|
|
144
|
+
);
|
|
202
145
|
}
|
|
203
146
|
async getOrCreateSession(projectDir) {
|
|
204
|
-
const timer = log.timer("getOrCreateSession", { projectDir });
|
|
205
147
|
log.debug("Getting sessions...", { projectDir });
|
|
206
148
|
const sessions = await this.getSessions(projectDir);
|
|
207
149
|
log.debug(`Found ${sessions.length} sessions`, {
|
|
@@ -210,13 +152,13 @@ class OpenCodeAPI {
|
|
|
210
152
|
const matchingSession = sessions.find((s) => s.directory === projectDir);
|
|
211
153
|
if (matchingSession) {
|
|
212
154
|
const url2 = this.buildSessionProxyUrl(projectDir, matchingSession.id);
|
|
213
|
-
|
|
155
|
+
log.debug(`Using existing session: ${matchingSession.id}`, { operation: "getOrCreateSession" });
|
|
214
156
|
return url2;
|
|
215
157
|
}
|
|
216
158
|
log.debug("Creating new session...", { projectDir });
|
|
217
159
|
const newSession = await this.createSession(projectDir);
|
|
218
160
|
const url = this.buildSessionProxyUrl(projectDir, newSession.id);
|
|
219
|
-
|
|
161
|
+
log.debug(`Created new session: ${newSession.id}`, { operation: "getOrCreateSession" });
|
|
220
162
|
return url;
|
|
221
163
|
}
|
|
222
164
|
}
|
package/es/opencode-web.js
CHANGED
|
@@ -4,16 +4,11 @@ import path from "path";
|
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
5
5
|
import {
|
|
6
6
|
AIPANEL_CACHE_DIR,
|
|
7
|
-
DEFAULT_HOSTNAME,
|
|
8
7
|
MCP_API_PATH,
|
|
9
|
-
|
|
10
|
-
VSCODE_ROUTE_HEALTH,
|
|
11
|
-
ENV_VSCODE_PORT,
|
|
8
|
+
OPENCODE_ENV,
|
|
12
9
|
createLogger,
|
|
13
|
-
getProcessLogBuffer
|
|
14
|
-
createPackageRequire
|
|
10
|
+
getProcessLogBuffer
|
|
15
11
|
} from "@aipanel/core/node";
|
|
16
|
-
const require2 = createPackageRequire();
|
|
17
12
|
const pluginsDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "plugins");
|
|
18
13
|
const log = createLogger("OpenCodeWeb");
|
|
19
14
|
function prepareOpenCodeRuntime(cwd, vitePort, enableLsp, enablePrettier) {
|
|
@@ -128,75 +123,7 @@ function buildFormatterConfig(enablePrettier) {
|
|
|
128
123
|
log.debug("enablePrettier is false, formatter disabled");
|
|
129
124
|
return false;
|
|
130
125
|
}
|
|
131
|
-
|
|
132
|
-
if (!bridgePath) {
|
|
133
|
-
log.debug("format-bridge not found, using built-in formatters");
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
log.debug("Format bridge configured");
|
|
137
|
-
if (!isFormatServiceRunning()) {
|
|
138
|
-
log.debug("VS Code format service not running, using built-in formatters only");
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
141
|
-
log.debug("VS Code format service detected, enabling bridge");
|
|
142
|
-
log.info("\u5DF2\u8FDE\u63A5 VS Code \u683C\u5F0F\u5316\u670D\u52A1");
|
|
143
|
-
const extensions = [
|
|
144
|
-
".ts",
|
|
145
|
-
".tsx",
|
|
146
|
-
".mts",
|
|
147
|
-
".cts",
|
|
148
|
-
".js",
|
|
149
|
-
".jsx",
|
|
150
|
-
".mjs",
|
|
151
|
-
".cjs",
|
|
152
|
-
".vue",
|
|
153
|
-
".svelte",
|
|
154
|
-
".astro",
|
|
155
|
-
".css",
|
|
156
|
-
".scss",
|
|
157
|
-
".sass",
|
|
158
|
-
".less",
|
|
159
|
-
".pcss",
|
|
160
|
-
".html",
|
|
161
|
-
".htm",
|
|
162
|
-
".xml",
|
|
163
|
-
".svg",
|
|
164
|
-
".json",
|
|
165
|
-
".jsonc",
|
|
166
|
-
".yaml",
|
|
167
|
-
".yml",
|
|
168
|
-
".toml",
|
|
169
|
-
".md",
|
|
170
|
-
".mdx",
|
|
171
|
-
".graphql",
|
|
172
|
-
".gql"
|
|
173
|
-
];
|
|
174
|
-
return {
|
|
175
|
-
format_bridge: {
|
|
176
|
-
command: ["node", bridgePath, "$FILE"],
|
|
177
|
-
extensions
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
let _formatServiceRunning;
|
|
182
|
-
function isFormatServiceRunning() {
|
|
183
|
-
if (_formatServiceRunning !== void 0) return _formatServiceRunning;
|
|
184
|
-
try {
|
|
185
|
-
require2("child_process").execSync(
|
|
186
|
-
`node -e "const h=require('http');h.get('http://${DEFAULT_HOSTNAME}:${VSCODE_EXTENSION_PORT}${VSCODE_ROUTE_HEALTH}',r=>{r.resume();process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))"`,
|
|
187
|
-
{ timeout: 500, stdio: "ignore" }
|
|
188
|
-
);
|
|
189
|
-
_formatServiceRunning = true;
|
|
190
|
-
} catch {
|
|
191
|
-
_formatServiceRunning = false;
|
|
192
|
-
}
|
|
193
|
-
return _formatServiceRunning;
|
|
194
|
-
}
|
|
195
|
-
function resolveFormatBridgePath() {
|
|
196
|
-
const viteEntry = require2.resolve("vite-plugin-aipanel");
|
|
197
|
-
const bridgePath = path.resolve(path.dirname(viteEntry), "utils", "format-bridge.cjs");
|
|
198
|
-
if (fs.existsSync(bridgePath)) return bridgePath;
|
|
199
|
-
return void 0;
|
|
126
|
+
return true;
|
|
200
127
|
}
|
|
201
128
|
function resolveSourcePluginsDir() {
|
|
202
129
|
const candidatePaths = [pluginsDir];
|
|
@@ -223,44 +150,40 @@ function buildProcessEnv(stateDir, configDir, contextApiUrl, logsApiUrl, logFile
|
|
|
223
150
|
),
|
|
224
151
|
XDG_STATE_HOME: stateDir,
|
|
225
152
|
// 指向缓存目录,OpenCode 通过 opencode.json 中 plugins 字段加载插件
|
|
226
|
-
|
|
153
|
+
[OPENCODE_ENV.CONFIG_DIR]: stateDir
|
|
227
154
|
};
|
|
228
155
|
if (configDir) {
|
|
229
|
-
env.
|
|
156
|
+
env[OPENCODE_ENV.CONFIG_DIR] = configDir;
|
|
230
157
|
log.debug("Set OPENCODE_CONFIG_DIR", { configDir });
|
|
231
158
|
}
|
|
232
159
|
if (contextApiUrl) {
|
|
233
|
-
env.
|
|
160
|
+
env[OPENCODE_ENV.CONTEXT_API_URL] = contextApiUrl;
|
|
234
161
|
log.debug("Set OPENCODE_CONTEXT_API_URL", { contextApiUrl });
|
|
235
162
|
}
|
|
236
163
|
if (logsApiUrl) {
|
|
237
|
-
env.
|
|
164
|
+
env[OPENCODE_ENV.VITE_LOGS_API_URL] = logsApiUrl;
|
|
238
165
|
log.debug("Set OPENCODE_VITE_LOGS_API_URL", { logsApiUrl });
|
|
239
166
|
}
|
|
240
167
|
if (logFilesJson) {
|
|
241
|
-
env.
|
|
168
|
+
env[OPENCODE_ENV.LOG_FILES_JSON] = logFilesJson;
|
|
242
169
|
log.debug("Set OPENCODE_LOG_FILES_JSON", { logFilesJson });
|
|
243
170
|
}
|
|
244
171
|
if (verbose) {
|
|
245
|
-
env.
|
|
172
|
+
env[OPENCODE_ENV.VERBOSE] = "1";
|
|
246
173
|
log.debug("Set OPENCODE_VERBOSE=1");
|
|
247
174
|
}
|
|
248
175
|
if (enableLsp) {
|
|
249
|
-
env.
|
|
176
|
+
env[OPENCODE_ENV.ENABLE_LINT] = "1";
|
|
250
177
|
log.debug("Set OPENCODE_ENABLE_LINT=1");
|
|
251
178
|
}
|
|
252
179
|
if (vueDevtoolsApiUrl) {
|
|
253
|
-
env.
|
|
180
|
+
env[OPENCODE_ENV.VUE_DEVTOOLS_API_URL] = vueDevtoolsApiUrl;
|
|
254
181
|
log.debug("Set OPENCODE_VUE_DEVTOOLS_API_URL", { vueDevtoolsApiUrl });
|
|
255
182
|
}
|
|
256
183
|
if (workspace) {
|
|
257
|
-
env.
|
|
184
|
+
env[OPENCODE_ENV.WORKSPACE] = workspace;
|
|
258
185
|
log.debug("Set OPENCODE_WORKSPACE", { workspace });
|
|
259
186
|
}
|
|
260
|
-
if (isFormatServiceRunning()) {
|
|
261
|
-
env[ENV_VSCODE_PORT] = String(VSCODE_EXTENSION_PORT);
|
|
262
|
-
log.debug("Set OPENCODE_VSCODE_PORT");
|
|
263
|
-
}
|
|
264
187
|
return env;
|
|
265
188
|
}
|
|
266
189
|
export {
|
|
@@ -7,30 +7,23 @@ import {
|
|
|
7
7
|
runAllChecks,
|
|
8
8
|
runProjectDiagnostics,
|
|
9
9
|
formatDiagnosticsSections,
|
|
10
|
-
isJsFile
|
|
10
|
+
isJsFile,
|
|
11
|
+
MUTATING_TOOLS,
|
|
12
|
+
OPENCODE_ENV,
|
|
13
|
+
DIAGNOSTICS_TOOL_DESCRIPTION
|
|
11
14
|
} from "@aipanel/core/node";
|
|
12
|
-
if (process.env.
|
|
15
|
+
if (process.env[OPENCODE_ENV.VERBOSE] === "1") {
|
|
13
16
|
setVerbose(true);
|
|
14
17
|
}
|
|
15
18
|
const log = createLogger("EditDiagnostics");
|
|
16
|
-
const EDIT_TOOLS =
|
|
17
|
-
const isLintEnabled = () => process.env.
|
|
19
|
+
const EDIT_TOOLS = MUTATING_TOOLS;
|
|
20
|
+
const isLintEnabled = () => process.env[OPENCODE_ENV.ENABLE_LINT] === "1";
|
|
18
21
|
var edit_diagnostics_default = {
|
|
19
22
|
id: "vite-plugin-aipanel/edit-diagnostics",
|
|
20
23
|
async server() {
|
|
21
|
-
const workspace = process.env.
|
|
24
|
+
const workspace = process.env[OPENCODE_ENV.WORKSPACE] || process.cwd();
|
|
22
25
|
const runDiagnosticsTool = tool({
|
|
23
|
-
description:
|
|
24
|
-
|
|
25
|
-
**\u4F55\u65F6\u4F7F\u7528\u6B64\u5DE5\u5177**\uFF1A
|
|
26
|
-
- \u521A\u5B8C\u6210\u4EE3\u7801\u4FEE\u6539\uFF0C\u60F3\u9A8C\u8BC1\u662F\u5426\u6709 ESLint \u9519\u8BEF\u6216\u7C7B\u578B\u9519\u8BEF
|
|
27
|
-
- \u5728\u63D0\u4EA4\u4EE3\u7801\u524D\u8FDB\u884C\u8D28\u91CF\u68C0\u67E5
|
|
28
|
-
- \u6392\u67E5\u7F16\u8F91\u5668\u672A\u663E\u793A\u4F46\u5B9E\u9645\u5B58\u5728\u7684\u7C7B\u578B\u95EE\u9898
|
|
29
|
-
- \u4E0D\u4F20\u53C2\u6570\u53EF\u5168\u91CF\u8BCA\u65AD\u6574\u4E2A\u9879\u76EE
|
|
30
|
-
|
|
31
|
-
**\u8BCA\u65AD\u5185\u5BB9**\uFF1A
|
|
32
|
-
- ESLint \u89C4\u5219\u68C0\u67E5\uFF08error \u548C warning\uFF09
|
|
33
|
-
- vue-tsc \u7C7B\u578B\u68C0\u67E5\uFF08TypeScript \u7C7B\u578B\u9519\u8BEF\u548C\u8B66\u544A\uFF09`,
|
|
26
|
+
description: DIAGNOSTICS_TOOL_DESCRIPTION,
|
|
34
27
|
args: {
|
|
35
28
|
filePath: tool.schema.string().optional().describe("\u8981\u8BCA\u65AD\u7684\u6587\u4EF6\u8DEF\u5F84\uFF08\u7EDD\u5BF9\u8DEF\u5F84\u6216\u76F8\u5BF9\u8DEF\u5F84\uFF09\uFF0C\u4E0D\u4F20\u5219\u5168\u91CF\u8BCA\u65AD\u6574\u4E2A\u9879\u76EE")
|
|
36
29
|
},
|
package/es/system.js
CHANGED
|
@@ -1,172 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
async function checkOpenCodeInstalled() {
|
|
5
|
-
const timer = log.timer("checkOpenCodeInstalled");
|
|
6
|
-
return new Promise((resolve) => {
|
|
7
|
-
log.debug("Checking if OpenCode is installed...");
|
|
8
|
-
const proc = spawn("opencode", ["--version"], { stdio: "ignore", shell: true });
|
|
9
|
-
proc.on("close", (code) => {
|
|
10
|
-
const installed = code === 0;
|
|
11
|
-
timer.end(installed ? "\u2713 OpenCode is installed" : "\u274C OpenCode not found");
|
|
12
|
-
resolve(installed);
|
|
13
|
-
});
|
|
14
|
-
proc.on("error", (err) => {
|
|
15
|
-
log.debug("Failed to check OpenCode installation", { error: err.message });
|
|
16
|
-
timer.end("\u274C Check failed");
|
|
17
|
-
resolve(false);
|
|
18
|
-
});
|
|
19
|
-
});
|
|
1
|
+
import { checkCliInstalled, getCliVersion, killOrphanCliProcesses } from "@aipanel/core/node";
|
|
2
|
+
function checkOpenCodeInstalled() {
|
|
3
|
+
return checkCliInstalled("opencode");
|
|
20
4
|
}
|
|
21
5
|
function getOpenCodeVersion() {
|
|
22
|
-
return
|
|
23
|
-
const proc = spawn("opencode", ["--version"], { stdio: "pipe", shell: true });
|
|
24
|
-
let output = "";
|
|
25
|
-
proc.stdout?.on("data", (data) => {
|
|
26
|
-
output += data.toString();
|
|
27
|
-
});
|
|
28
|
-
proc.on("close", (code) => {
|
|
29
|
-
if (code === 0 && output.trim()) {
|
|
30
|
-
resolve(output.trim());
|
|
31
|
-
} else {
|
|
32
|
-
resolve(null);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
proc.on("error", () => {
|
|
36
|
-
resolve(null);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
6
|
+
return getCliVersion("opencode");
|
|
39
7
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const timer = log.timer("killOrphanOpenCodeProcesses");
|
|
43
|
-
log.debug("Looking for orphan OpenCode processes (PPID=1)");
|
|
44
|
-
return new Promise((resolve) => {
|
|
45
|
-
let settled = false;
|
|
46
|
-
const done = (count) => {
|
|
47
|
-
if (settled) return;
|
|
48
|
-
settled = true;
|
|
49
|
-
resolve(count);
|
|
50
|
-
};
|
|
51
|
-
const timeout = setTimeout(() => {
|
|
52
|
-
log.warn("Kill orphan processes timed out, skipping");
|
|
53
|
-
timer.end("\u26A0 Timeout, skipped");
|
|
54
|
-
done(0);
|
|
55
|
-
}, KILL_ORPHAN_TIMEOUT);
|
|
56
|
-
const wrappedResolve = (count) => {
|
|
57
|
-
clearTimeout(timeout);
|
|
58
|
-
done(count);
|
|
59
|
-
};
|
|
60
|
-
if (process.platform === "win32") {
|
|
61
|
-
killOrphanProcessesOnWindows(wrappedResolve, timer);
|
|
62
|
-
} else {
|
|
63
|
-
killOrphanProcessesOnUnix(wrappedResolve, timer);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
function killOrphanProcessesOnWindows(resolve, timer) {
|
|
68
|
-
log.debug("Using Windows method to find orphan processes");
|
|
69
|
-
const proc = spawn(
|
|
70
|
-
"wmic",
|
|
71
|
-
["process", "where", 'name="opencode.exe"', "get", "processid,parentprocessid"],
|
|
72
|
-
{ stdio: "pipe" }
|
|
73
|
-
);
|
|
74
|
-
let output = "";
|
|
75
|
-
proc.stdout?.on("data", (data) => {
|
|
76
|
-
output += data.toString();
|
|
77
|
-
});
|
|
78
|
-
proc.on("close", () => {
|
|
79
|
-
const lines = output.split("\n").filter((line) => line.trim());
|
|
80
|
-
const pidsToKill = [];
|
|
81
|
-
lines.forEach((line) => {
|
|
82
|
-
const parts = line.trim().split(/\s+/);
|
|
83
|
-
if (parts.length >= 2) {
|
|
84
|
-
const ppid = parts[0];
|
|
85
|
-
const pid = parts[1];
|
|
86
|
-
if (ppid === "1" && pid && !isNaN(Number(pid))) {
|
|
87
|
-
pidsToKill.push(pid);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
if (pidsToKill.length > 0) {
|
|
92
|
-
log.debug(`Found ${pidsToKill.length} orphan processes`, { pids: pidsToKill });
|
|
93
|
-
let killedCount = 0;
|
|
94
|
-
let completedCount = 0;
|
|
95
|
-
pidsToKill.forEach((pid) => {
|
|
96
|
-
const killProc = spawn("taskkill", ["/F", "/PID", pid], { stdio: "ignore" });
|
|
97
|
-
killProc.on("close", (code) => {
|
|
98
|
-
completedCount++;
|
|
99
|
-
if (code === 0) {
|
|
100
|
-
killedCount++;
|
|
101
|
-
log.debug(`Killed orphan process ${pid}`);
|
|
102
|
-
}
|
|
103
|
-
if (completedCount === pidsToKill.length) {
|
|
104
|
-
timer.end(`\u2713 Killed ${killedCount} orphan processes`);
|
|
105
|
-
resolve(killedCount);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
} else {
|
|
110
|
-
log.debug("No orphan processes found");
|
|
111
|
-
timer.end("No orphan processes found");
|
|
112
|
-
resolve(0);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
proc.on("error", (err) => {
|
|
116
|
-
log.debug("Failed to find orphan processes", { error: err.message });
|
|
117
|
-
timer.end("\u274C Failed to find orphan processes");
|
|
118
|
-
resolve(0);
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
function killOrphanProcessesOnUnix(resolve, timer) {
|
|
122
|
-
log.debug("Using Unix method to find orphan processes");
|
|
123
|
-
const proc = spawn("ps", ["-e", "-o", "pid,ppid,comm"], { stdio: "pipe" });
|
|
124
|
-
let output = "";
|
|
125
|
-
proc.stdout?.on("data", (data) => {
|
|
126
|
-
output += data.toString();
|
|
127
|
-
});
|
|
128
|
-
proc.on("close", () => {
|
|
129
|
-
const lines = output.split("\n");
|
|
130
|
-
const pidsToKill = [];
|
|
131
|
-
lines.forEach((line) => {
|
|
132
|
-
const trimmed = line.trim();
|
|
133
|
-
if (trimmed.includes("opencode")) {
|
|
134
|
-
const parts = trimmed.split(/\s+/);
|
|
135
|
-
if (parts.length >= 3) {
|
|
136
|
-
const pid = parts[0];
|
|
137
|
-
const ppid = parts[1];
|
|
138
|
-
const comm = parts.slice(2).join(" ");
|
|
139
|
-
if (ppid === "1" && comm.includes("opencode")) {
|
|
140
|
-
pidsToKill.push(pid);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
if (pidsToKill.length > 0) {
|
|
146
|
-
log.debug(`Found ${pidsToKill.length} orphan processes`, { pids: pidsToKill });
|
|
147
|
-
const killProc = spawn("kill", ["-9", ...pidsToKill], { stdio: "ignore" });
|
|
148
|
-
killProc.on("close", (code) => {
|
|
149
|
-
const killedCount = code === 0 ? pidsToKill.length : 0;
|
|
150
|
-
timer.end(
|
|
151
|
-
killedCount > 0 ? `\u2713 Killed ${killedCount} orphan processes` : "\u274C Failed to kill processes"
|
|
152
|
-
);
|
|
153
|
-
resolve(killedCount);
|
|
154
|
-
});
|
|
155
|
-
killProc.on("error", () => {
|
|
156
|
-
timer.end("\u274C Failed to kill processes");
|
|
157
|
-
resolve(0);
|
|
158
|
-
});
|
|
159
|
-
} else {
|
|
160
|
-
log.debug("No orphan processes found");
|
|
161
|
-
timer.end("No orphan processes found");
|
|
162
|
-
resolve(0);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
proc.on("error", (err) => {
|
|
166
|
-
log.debug("Failed to find orphan processes", { error: err.message });
|
|
167
|
-
timer.end("\u274C Failed to find orphan processes");
|
|
168
|
-
resolve(0);
|
|
169
|
-
});
|
|
8
|
+
function killOrphanOpenCodeProcesses() {
|
|
9
|
+
return killOrphanCliProcesses("opencode", { match: "opencode", winName: "opencode.exe", label: "opencode" });
|
|
170
10
|
}
|
|
171
11
|
export {
|
|
172
12
|
checkOpenCodeInstalled,
|
package/lib/api.cjs
CHANGED
|
@@ -81,12 +81,14 @@ class OpenCodeAPI {
|
|
|
81
81
|
req.end();
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
|
+
retryLog(operation, n, error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
log.debug(`Attempt ${n} failed: ${message}, retrying in ${import_core.RETRY_DELAY}ms`, { operation });
|
|
87
|
+
}
|
|
84
88
|
async getSessions(projectDir, retries = import_core.DEFAULT_RETRIES) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
log.debug(`Attempt ${i + 1}/${retries}`, { operation: "getSessions", projectDir });
|
|
89
|
+
return (0, import_core.withRetries)(
|
|
90
|
+
async (attempt) => {
|
|
91
|
+
log.debug(`Attempt ${attempt + 1}/${retries}`, { operation: "getSessions", projectDir });
|
|
90
92
|
const sessions = await this.createHttpRequest({
|
|
91
93
|
hostname: this.hostname,
|
|
92
94
|
port: this.getPort(),
|
|
@@ -96,30 +98,16 @@ class OpenCodeAPI {
|
|
|
96
98
|
...s,
|
|
97
99
|
url: s.directory && s.id ? this.buildSessionProxyUrl(s.directory, s.id) : ""
|
|
98
100
|
}));
|
|
99
|
-
|
|
101
|
+
log.debug(`Found ${sessions.length} sessions`, { operation: "getSessions" });
|
|
100
102
|
return sessionsWithUrl;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
operation: "getSessions"
|
|
105
|
-
});
|
|
106
|
-
if (i < retries - 1) {
|
|
107
|
-
log.debug(`Retrying in ${import_core.RETRY_DELAY}ms...`, {
|
|
108
|
-
operation: "getSessions"
|
|
109
|
-
});
|
|
110
|
-
await (0, import_core.sleep)(import_core.RETRY_DELAY);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
timer.end("\u274C All retries exhausted");
|
|
115
|
-
throw lastError;
|
|
103
|
+
},
|
|
104
|
+
{ attempts: retries, onRetry: (n, e) => this.retryLog("getSessions", n, e) }
|
|
105
|
+
);
|
|
116
106
|
}
|
|
117
107
|
async createSession(projectDir, retries = import_core.DEFAULT_RETRIES, title) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
log.debug(`Attempt ${i + 1}/${retries}`, {
|
|
108
|
+
return (0, import_core.withRetries)(
|
|
109
|
+
async (attempt) => {
|
|
110
|
+
log.debug(`Attempt ${attempt + 1}/${retries}`, {
|
|
123
111
|
operation: "createSession",
|
|
124
112
|
title,
|
|
125
113
|
projectDir
|
|
@@ -141,30 +129,16 @@ class OpenCodeAPI {
|
|
|
141
129
|
...session,
|
|
142
130
|
url: this.buildSessionProxyUrl(projectDir, session.id)
|
|
143
131
|
};
|
|
144
|
-
|
|
132
|
+
log.debug(`Created session: ${session.id}`, { operation: "createSession" });
|
|
145
133
|
return sessionWithUrl;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
operation: "createSession"
|
|
150
|
-
});
|
|
151
|
-
if (i < retries - 1) {
|
|
152
|
-
log.debug(`Retrying in ${import_core.RETRY_DELAY}ms...`, {
|
|
153
|
-
operation: "createSession"
|
|
154
|
-
});
|
|
155
|
-
await (0, import_core.sleep)(import_core.RETRY_DELAY);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
timer.end("\u274C All retries exhausted");
|
|
160
|
-
throw lastError;
|
|
134
|
+
},
|
|
135
|
+
{ attempts: retries, onRetry: (n, e) => this.retryLog("createSession", n, e) }
|
|
136
|
+
);
|
|
161
137
|
}
|
|
162
138
|
async deleteSession(sessionId, retries = import_core.DEFAULT_RETRIES) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
log.debug(`Attempt ${i + 1}/${retries}`, {
|
|
139
|
+
await (0, import_core.withRetries)(
|
|
140
|
+
async (attempt) => {
|
|
141
|
+
log.debug(`Attempt ${attempt + 1}/${retries}`, {
|
|
168
142
|
operation: "deleteSession",
|
|
169
143
|
sessionId
|
|
170
144
|
});
|
|
@@ -174,59 +148,27 @@ class OpenCodeAPI {
|
|
|
174
148
|
path: `/session/${sessionId}`,
|
|
175
149
|
method: "DELETE"
|
|
176
150
|
});
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
log.debug(`Attempt ${i + 1} failed: ${lastError.message}`, {
|
|
182
|
-
operation: "deleteSession",
|
|
183
|
-
sessionId
|
|
184
|
-
});
|
|
185
|
-
if (i < retries - 1) {
|
|
186
|
-
log.debug(`Retrying in ${import_core.RETRY_DELAY}ms...`, {
|
|
187
|
-
operation: "deleteSession",
|
|
188
|
-
sessionId
|
|
189
|
-
});
|
|
190
|
-
await (0, import_core.sleep)(import_core.RETRY_DELAY);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
timer.end("\u274C All retries exhausted");
|
|
195
|
-
throw lastError;
|
|
151
|
+
log.debug(`Deleted session: ${sessionId}`, { operation: "deleteSession" });
|
|
152
|
+
},
|
|
153
|
+
{ attempts: retries, onRetry: (n, e) => this.retryLog("deleteSession", n, e) }
|
|
154
|
+
);
|
|
196
155
|
}
|
|
197
156
|
async getToolIds(retries = import_core.DEFAULT_RETRIES) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
try {
|
|
202
|
-
log.debug(`Attempt ${i + 1}/${retries}`, {
|
|
203
|
-
operation: "getToolIds"
|
|
204
|
-
});
|
|
157
|
+
return (0, import_core.withRetries)(
|
|
158
|
+
async (attempt) => {
|
|
159
|
+
log.debug(`Attempt ${attempt + 1}/${retries}`, { operation: "getToolIds" });
|
|
205
160
|
const toolIds = await this.createHttpRequest({
|
|
206
161
|
hostname: this.hostname,
|
|
207
162
|
port: this.getPort(),
|
|
208
163
|
path: "/experimental/tool/ids"
|
|
209
164
|
});
|
|
210
|
-
|
|
165
|
+
log.debug(`Found ${toolIds.length} tools`, { operation: "getToolIds" });
|
|
211
166
|
return toolIds;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
operation: "getToolIds"
|
|
216
|
-
});
|
|
217
|
-
if (i < retries - 1) {
|
|
218
|
-
log.debug(`Retrying in ${import_core.RETRY_DELAY}ms...`, {
|
|
219
|
-
operation: "getToolIds"
|
|
220
|
-
});
|
|
221
|
-
await (0, import_core.sleep)(import_core.RETRY_DELAY);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
timer.end("\u274C All retries exhausted");
|
|
226
|
-
throw lastError;
|
|
167
|
+
},
|
|
168
|
+
{ attempts: retries, onRetry: (n, e) => this.retryLog("getToolIds", n, e) }
|
|
169
|
+
);
|
|
227
170
|
}
|
|
228
171
|
async getOrCreateSession(projectDir) {
|
|
229
|
-
const timer = log.timer("getOrCreateSession", { projectDir });
|
|
230
172
|
log.debug("Getting sessions...", { projectDir });
|
|
231
173
|
const sessions = await this.getSessions(projectDir);
|
|
232
174
|
log.debug(`Found ${sessions.length} sessions`, {
|
|
@@ -235,13 +177,13 @@ class OpenCodeAPI {
|
|
|
235
177
|
const matchingSession = sessions.find((s) => s.directory === projectDir);
|
|
236
178
|
if (matchingSession) {
|
|
237
179
|
const url2 = this.buildSessionProxyUrl(projectDir, matchingSession.id);
|
|
238
|
-
|
|
180
|
+
log.debug(`Using existing session: ${matchingSession.id}`, { operation: "getOrCreateSession" });
|
|
239
181
|
return url2;
|
|
240
182
|
}
|
|
241
183
|
log.debug("Creating new session...", { projectDir });
|
|
242
184
|
const newSession = await this.createSession(projectDir);
|
|
243
185
|
const url = this.buildSessionProxyUrl(projectDir, newSession.id);
|
|
244
|
-
|
|
186
|
+
log.debug(`Created new session: ${newSession.id}`, { operation: "getOrCreateSession" });
|
|
245
187
|
return url;
|
|
246
188
|
}
|
|
247
189
|
}
|
package/lib/api.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare class OpenCodeAPI {
|
|
|
8
8
|
/** 构建代理 iframe URL(旧版格式:/{base64(projectDir)}/session/{id}) */
|
|
9
9
|
buildSessionProxyUrl(projectDir: string, sessionId: string): string;
|
|
10
10
|
private createHttpRequest;
|
|
11
|
+
private retryLog;
|
|
11
12
|
getSessions(projectDir: string, retries?: number): Promise<SessionInfo[]>;
|
|
12
13
|
createSession(projectDir: string, retries?: number, title?: string): Promise<SessionInfo>;
|
|
13
14
|
deleteSession(sessionId: string, retries?: number): Promise<void>;
|
package/lib/opencode-web.cjs
CHANGED
|
@@ -37,7 +37,6 @@ var import_path = __toESM(require("path"));
|
|
|
37
37
|
var import_url = require("url");
|
|
38
38
|
var import_node = require("@aipanel/core/node");
|
|
39
39
|
const import_meta = {};
|
|
40
|
-
const require2 = (0, import_node.createPackageRequire)();
|
|
41
40
|
const pluginsDir = import_path.default.join(import_path.default.dirname((0, import_url.fileURLToPath)(import_meta.url)), "plugins");
|
|
42
41
|
const log = (0, import_node.createLogger)("OpenCodeWeb");
|
|
43
42
|
function prepareOpenCodeRuntime(cwd, vitePort, enableLsp, enablePrettier) {
|
|
@@ -152,75 +151,7 @@ function buildFormatterConfig(enablePrettier) {
|
|
|
152
151
|
log.debug("enablePrettier is false, formatter disabled");
|
|
153
152
|
return false;
|
|
154
153
|
}
|
|
155
|
-
|
|
156
|
-
if (!bridgePath) {
|
|
157
|
-
log.debug("format-bridge not found, using built-in formatters");
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
log.debug("Format bridge configured");
|
|
161
|
-
if (!isFormatServiceRunning()) {
|
|
162
|
-
log.debug("VS Code format service not running, using built-in formatters only");
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
log.debug("VS Code format service detected, enabling bridge");
|
|
166
|
-
log.info("\u5DF2\u8FDE\u63A5 VS Code \u683C\u5F0F\u5316\u670D\u52A1");
|
|
167
|
-
const extensions = [
|
|
168
|
-
".ts",
|
|
169
|
-
".tsx",
|
|
170
|
-
".mts",
|
|
171
|
-
".cts",
|
|
172
|
-
".js",
|
|
173
|
-
".jsx",
|
|
174
|
-
".mjs",
|
|
175
|
-
".cjs",
|
|
176
|
-
".vue",
|
|
177
|
-
".svelte",
|
|
178
|
-
".astro",
|
|
179
|
-
".css",
|
|
180
|
-
".scss",
|
|
181
|
-
".sass",
|
|
182
|
-
".less",
|
|
183
|
-
".pcss",
|
|
184
|
-
".html",
|
|
185
|
-
".htm",
|
|
186
|
-
".xml",
|
|
187
|
-
".svg",
|
|
188
|
-
".json",
|
|
189
|
-
".jsonc",
|
|
190
|
-
".yaml",
|
|
191
|
-
".yml",
|
|
192
|
-
".toml",
|
|
193
|
-
".md",
|
|
194
|
-
".mdx",
|
|
195
|
-
".graphql",
|
|
196
|
-
".gql"
|
|
197
|
-
];
|
|
198
|
-
return {
|
|
199
|
-
format_bridge: {
|
|
200
|
-
command: ["node", bridgePath, "$FILE"],
|
|
201
|
-
extensions
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
let _formatServiceRunning;
|
|
206
|
-
function isFormatServiceRunning() {
|
|
207
|
-
if (_formatServiceRunning !== void 0) return _formatServiceRunning;
|
|
208
|
-
try {
|
|
209
|
-
require2("child_process").execSync(
|
|
210
|
-
`node -e "const h=require('http');h.get('http://${import_node.DEFAULT_HOSTNAME}:${import_node.VSCODE_EXTENSION_PORT}${import_node.VSCODE_ROUTE_HEALTH}',r=>{r.resume();process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))"`,
|
|
211
|
-
{ timeout: 500, stdio: "ignore" }
|
|
212
|
-
);
|
|
213
|
-
_formatServiceRunning = true;
|
|
214
|
-
} catch {
|
|
215
|
-
_formatServiceRunning = false;
|
|
216
|
-
}
|
|
217
|
-
return _formatServiceRunning;
|
|
218
|
-
}
|
|
219
|
-
function resolveFormatBridgePath() {
|
|
220
|
-
const viteEntry = require2.resolve("vite-plugin-aipanel");
|
|
221
|
-
const bridgePath = import_path.default.resolve(import_path.default.dirname(viteEntry), "utils", "format-bridge.cjs");
|
|
222
|
-
if (import_fs.default.existsSync(bridgePath)) return bridgePath;
|
|
223
|
-
return void 0;
|
|
154
|
+
return true;
|
|
224
155
|
}
|
|
225
156
|
function resolveSourcePluginsDir() {
|
|
226
157
|
const candidatePaths = [pluginsDir];
|
|
@@ -247,44 +178,40 @@ function buildProcessEnv(stateDir, configDir, contextApiUrl, logsApiUrl, logFile
|
|
|
247
178
|
),
|
|
248
179
|
XDG_STATE_HOME: stateDir,
|
|
249
180
|
// 指向缓存目录,OpenCode 通过 opencode.json 中 plugins 字段加载插件
|
|
250
|
-
|
|
181
|
+
[import_node.OPENCODE_ENV.CONFIG_DIR]: stateDir
|
|
251
182
|
};
|
|
252
183
|
if (configDir) {
|
|
253
|
-
env.
|
|
184
|
+
env[import_node.OPENCODE_ENV.CONFIG_DIR] = configDir;
|
|
254
185
|
log.debug("Set OPENCODE_CONFIG_DIR", { configDir });
|
|
255
186
|
}
|
|
256
187
|
if (contextApiUrl) {
|
|
257
|
-
env.
|
|
188
|
+
env[import_node.OPENCODE_ENV.CONTEXT_API_URL] = contextApiUrl;
|
|
258
189
|
log.debug("Set OPENCODE_CONTEXT_API_URL", { contextApiUrl });
|
|
259
190
|
}
|
|
260
191
|
if (logsApiUrl) {
|
|
261
|
-
env.
|
|
192
|
+
env[import_node.OPENCODE_ENV.VITE_LOGS_API_URL] = logsApiUrl;
|
|
262
193
|
log.debug("Set OPENCODE_VITE_LOGS_API_URL", { logsApiUrl });
|
|
263
194
|
}
|
|
264
195
|
if (logFilesJson) {
|
|
265
|
-
env.
|
|
196
|
+
env[import_node.OPENCODE_ENV.LOG_FILES_JSON] = logFilesJson;
|
|
266
197
|
log.debug("Set OPENCODE_LOG_FILES_JSON", { logFilesJson });
|
|
267
198
|
}
|
|
268
199
|
if (verbose) {
|
|
269
|
-
env.
|
|
200
|
+
env[import_node.OPENCODE_ENV.VERBOSE] = "1";
|
|
270
201
|
log.debug("Set OPENCODE_VERBOSE=1");
|
|
271
202
|
}
|
|
272
203
|
if (enableLsp) {
|
|
273
|
-
env.
|
|
204
|
+
env[import_node.OPENCODE_ENV.ENABLE_LINT] = "1";
|
|
274
205
|
log.debug("Set OPENCODE_ENABLE_LINT=1");
|
|
275
206
|
}
|
|
276
207
|
if (vueDevtoolsApiUrl) {
|
|
277
|
-
env.
|
|
208
|
+
env[import_node.OPENCODE_ENV.VUE_DEVTOOLS_API_URL] = vueDevtoolsApiUrl;
|
|
278
209
|
log.debug("Set OPENCODE_VUE_DEVTOOLS_API_URL", { vueDevtoolsApiUrl });
|
|
279
210
|
}
|
|
280
211
|
if (workspace) {
|
|
281
|
-
env.
|
|
212
|
+
env[import_node.OPENCODE_ENV.WORKSPACE] = workspace;
|
|
282
213
|
log.debug("Set OPENCODE_WORKSPACE", { workspace });
|
|
283
214
|
}
|
|
284
|
-
if (isFormatServiceRunning()) {
|
|
285
|
-
env[import_node.ENV_VSCODE_PORT] = String(import_node.VSCODE_EXTENSION_PORT);
|
|
286
|
-
log.debug("Set OPENCODE_VSCODE_PORT");
|
|
287
|
-
}
|
|
288
215
|
return env;
|
|
289
216
|
}
|
|
290
217
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -34,28 +34,18 @@ var import_node_fs = __toESM(require("node:fs"));
|
|
|
34
34
|
var import_node_path = __toESM(require("node:path"));
|
|
35
35
|
var import_plugin = require("@opencode-ai/plugin");
|
|
36
36
|
var import_node = require("@aipanel/core/node");
|
|
37
|
-
if (process.env.
|
|
37
|
+
if (process.env[import_node.OPENCODE_ENV.VERBOSE] === "1") {
|
|
38
38
|
(0, import_node.setVerbose)(true);
|
|
39
39
|
}
|
|
40
40
|
const log = (0, import_node.createLogger)("EditDiagnostics");
|
|
41
|
-
const EDIT_TOOLS =
|
|
42
|
-
const isLintEnabled = () => process.env.
|
|
41
|
+
const EDIT_TOOLS = import_node.MUTATING_TOOLS;
|
|
42
|
+
const isLintEnabled = () => process.env[import_node.OPENCODE_ENV.ENABLE_LINT] === "1";
|
|
43
43
|
var edit_diagnostics_default = {
|
|
44
44
|
id: "vite-plugin-aipanel/edit-diagnostics",
|
|
45
45
|
async server() {
|
|
46
|
-
const workspace = process.env.
|
|
46
|
+
const workspace = process.env[import_node.OPENCODE_ENV.WORKSPACE] || process.cwd();
|
|
47
47
|
const runDiagnosticsTool = (0, import_plugin.tool)({
|
|
48
|
-
description:
|
|
49
|
-
|
|
50
|
-
**\u4F55\u65F6\u4F7F\u7528\u6B64\u5DE5\u5177**\uFF1A
|
|
51
|
-
- \u521A\u5B8C\u6210\u4EE3\u7801\u4FEE\u6539\uFF0C\u60F3\u9A8C\u8BC1\u662F\u5426\u6709 ESLint \u9519\u8BEF\u6216\u7C7B\u578B\u9519\u8BEF
|
|
52
|
-
- \u5728\u63D0\u4EA4\u4EE3\u7801\u524D\u8FDB\u884C\u8D28\u91CF\u68C0\u67E5
|
|
53
|
-
- \u6392\u67E5\u7F16\u8F91\u5668\u672A\u663E\u793A\u4F46\u5B9E\u9645\u5B58\u5728\u7684\u7C7B\u578B\u95EE\u9898
|
|
54
|
-
- \u4E0D\u4F20\u53C2\u6570\u53EF\u5168\u91CF\u8BCA\u65AD\u6574\u4E2A\u9879\u76EE
|
|
55
|
-
|
|
56
|
-
**\u8BCA\u65AD\u5185\u5BB9**\uFF1A
|
|
57
|
-
- ESLint \u89C4\u5219\u68C0\u67E5\uFF08error \u548C warning\uFF09
|
|
58
|
-
- vue-tsc \u7C7B\u578B\u68C0\u67E5\uFF08TypeScript \u7C7B\u578B\u9519\u8BEF\u548C\u8B66\u544A\uFF09`,
|
|
48
|
+
description: import_node.DIAGNOSTICS_TOOL_DESCRIPTION,
|
|
59
49
|
args: {
|
|
60
50
|
filePath: import_plugin.tool.schema.string().optional().describe("\u8981\u8BCA\u65AD\u7684\u6587\u4EF6\u8DEF\u5F84\uFF08\u7EDD\u5BF9\u8DEF\u5F84\u6216\u76F8\u5BF9\u8DEF\u5F84\uFF09\uFF0C\u4E0D\u4F20\u5219\u5168\u91CF\u8BCA\u65AD\u6574\u4E2A\u9879\u76EE")
|
|
61
51
|
},
|
package/lib/system.cjs
CHANGED
|
@@ -22,175 +22,15 @@ __export(system_exports, {
|
|
|
22
22
|
killOrphanOpenCodeProcesses: () => killOrphanOpenCodeProcesses
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(system_exports);
|
|
25
|
-
var import_child_process = require("child_process");
|
|
26
25
|
var import_node = require("@aipanel/core/node");
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const timer = log.timer("checkOpenCodeInstalled");
|
|
30
|
-
return new Promise((resolve) => {
|
|
31
|
-
log.debug("Checking if OpenCode is installed...");
|
|
32
|
-
const proc = (0, import_child_process.spawn)("opencode", ["--version"], { stdio: "ignore", shell: true });
|
|
33
|
-
proc.on("close", (code) => {
|
|
34
|
-
const installed = code === 0;
|
|
35
|
-
timer.end(installed ? "\u2713 OpenCode is installed" : "\u274C OpenCode not found");
|
|
36
|
-
resolve(installed);
|
|
37
|
-
});
|
|
38
|
-
proc.on("error", (err) => {
|
|
39
|
-
log.debug("Failed to check OpenCode installation", { error: err.message });
|
|
40
|
-
timer.end("\u274C Check failed");
|
|
41
|
-
resolve(false);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
26
|
+
function checkOpenCodeInstalled() {
|
|
27
|
+
return (0, import_node.checkCliInstalled)("opencode");
|
|
44
28
|
}
|
|
45
29
|
function getOpenCodeVersion() {
|
|
46
|
-
return
|
|
47
|
-
const proc = (0, import_child_process.spawn)("opencode", ["--version"], { stdio: "pipe", shell: true });
|
|
48
|
-
let output = "";
|
|
49
|
-
proc.stdout?.on("data", (data) => {
|
|
50
|
-
output += data.toString();
|
|
51
|
-
});
|
|
52
|
-
proc.on("close", (code) => {
|
|
53
|
-
if (code === 0 && output.trim()) {
|
|
54
|
-
resolve(output.trim());
|
|
55
|
-
} else {
|
|
56
|
-
resolve(null);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
proc.on("error", () => {
|
|
60
|
-
resolve(null);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
30
|
+
return (0, import_node.getCliVersion)("opencode");
|
|
63
31
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const timer = log.timer("killOrphanOpenCodeProcesses");
|
|
67
|
-
log.debug("Looking for orphan OpenCode processes (PPID=1)");
|
|
68
|
-
return new Promise((resolve) => {
|
|
69
|
-
let settled = false;
|
|
70
|
-
const done = (count) => {
|
|
71
|
-
if (settled) return;
|
|
72
|
-
settled = true;
|
|
73
|
-
resolve(count);
|
|
74
|
-
};
|
|
75
|
-
const timeout = setTimeout(() => {
|
|
76
|
-
log.warn("Kill orphan processes timed out, skipping");
|
|
77
|
-
timer.end("\u26A0 Timeout, skipped");
|
|
78
|
-
done(0);
|
|
79
|
-
}, KILL_ORPHAN_TIMEOUT);
|
|
80
|
-
const wrappedResolve = (count) => {
|
|
81
|
-
clearTimeout(timeout);
|
|
82
|
-
done(count);
|
|
83
|
-
};
|
|
84
|
-
if (process.platform === "win32") {
|
|
85
|
-
killOrphanProcessesOnWindows(wrappedResolve, timer);
|
|
86
|
-
} else {
|
|
87
|
-
killOrphanProcessesOnUnix(wrappedResolve, timer);
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
function killOrphanProcessesOnWindows(resolve, timer) {
|
|
92
|
-
log.debug("Using Windows method to find orphan processes");
|
|
93
|
-
const proc = (0, import_child_process.spawn)(
|
|
94
|
-
"wmic",
|
|
95
|
-
["process", "where", 'name="opencode.exe"', "get", "processid,parentprocessid"],
|
|
96
|
-
{ stdio: "pipe" }
|
|
97
|
-
);
|
|
98
|
-
let output = "";
|
|
99
|
-
proc.stdout?.on("data", (data) => {
|
|
100
|
-
output += data.toString();
|
|
101
|
-
});
|
|
102
|
-
proc.on("close", () => {
|
|
103
|
-
const lines = output.split("\n").filter((line) => line.trim());
|
|
104
|
-
const pidsToKill = [];
|
|
105
|
-
lines.forEach((line) => {
|
|
106
|
-
const parts = line.trim().split(/\s+/);
|
|
107
|
-
if (parts.length >= 2) {
|
|
108
|
-
const ppid = parts[0];
|
|
109
|
-
const pid = parts[1];
|
|
110
|
-
if (ppid === "1" && pid && !isNaN(Number(pid))) {
|
|
111
|
-
pidsToKill.push(pid);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
if (pidsToKill.length > 0) {
|
|
116
|
-
log.debug(`Found ${pidsToKill.length} orphan processes`, { pids: pidsToKill });
|
|
117
|
-
let killedCount = 0;
|
|
118
|
-
let completedCount = 0;
|
|
119
|
-
pidsToKill.forEach((pid) => {
|
|
120
|
-
const killProc = (0, import_child_process.spawn)("taskkill", ["/F", "/PID", pid], { stdio: "ignore" });
|
|
121
|
-
killProc.on("close", (code) => {
|
|
122
|
-
completedCount++;
|
|
123
|
-
if (code === 0) {
|
|
124
|
-
killedCount++;
|
|
125
|
-
log.debug(`Killed orphan process ${pid}`);
|
|
126
|
-
}
|
|
127
|
-
if (completedCount === pidsToKill.length) {
|
|
128
|
-
timer.end(`\u2713 Killed ${killedCount} orphan processes`);
|
|
129
|
-
resolve(killedCount);
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
} else {
|
|
134
|
-
log.debug("No orphan processes found");
|
|
135
|
-
timer.end("No orphan processes found");
|
|
136
|
-
resolve(0);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
proc.on("error", (err) => {
|
|
140
|
-
log.debug("Failed to find orphan processes", { error: err.message });
|
|
141
|
-
timer.end("\u274C Failed to find orphan processes");
|
|
142
|
-
resolve(0);
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
function killOrphanProcessesOnUnix(resolve, timer) {
|
|
146
|
-
log.debug("Using Unix method to find orphan processes");
|
|
147
|
-
const proc = (0, import_child_process.spawn)("ps", ["-e", "-o", "pid,ppid,comm"], { stdio: "pipe" });
|
|
148
|
-
let output = "";
|
|
149
|
-
proc.stdout?.on("data", (data) => {
|
|
150
|
-
output += data.toString();
|
|
151
|
-
});
|
|
152
|
-
proc.on("close", () => {
|
|
153
|
-
const lines = output.split("\n");
|
|
154
|
-
const pidsToKill = [];
|
|
155
|
-
lines.forEach((line) => {
|
|
156
|
-
const trimmed = line.trim();
|
|
157
|
-
if (trimmed.includes("opencode")) {
|
|
158
|
-
const parts = trimmed.split(/\s+/);
|
|
159
|
-
if (parts.length >= 3) {
|
|
160
|
-
const pid = parts[0];
|
|
161
|
-
const ppid = parts[1];
|
|
162
|
-
const comm = parts.slice(2).join(" ");
|
|
163
|
-
if (ppid === "1" && comm.includes("opencode")) {
|
|
164
|
-
pidsToKill.push(pid);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
if (pidsToKill.length > 0) {
|
|
170
|
-
log.debug(`Found ${pidsToKill.length} orphan processes`, { pids: pidsToKill });
|
|
171
|
-
const killProc = (0, import_child_process.spawn)("kill", ["-9", ...pidsToKill], { stdio: "ignore" });
|
|
172
|
-
killProc.on("close", (code) => {
|
|
173
|
-
const killedCount = code === 0 ? pidsToKill.length : 0;
|
|
174
|
-
timer.end(
|
|
175
|
-
killedCount > 0 ? `\u2713 Killed ${killedCount} orphan processes` : "\u274C Failed to kill processes"
|
|
176
|
-
);
|
|
177
|
-
resolve(killedCount);
|
|
178
|
-
});
|
|
179
|
-
killProc.on("error", () => {
|
|
180
|
-
timer.end("\u274C Failed to kill processes");
|
|
181
|
-
resolve(0);
|
|
182
|
-
});
|
|
183
|
-
} else {
|
|
184
|
-
log.debug("No orphan processes found");
|
|
185
|
-
timer.end("No orphan processes found");
|
|
186
|
-
resolve(0);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
proc.on("error", (err) => {
|
|
190
|
-
log.debug("Failed to find orphan processes", { error: err.message });
|
|
191
|
-
timer.end("\u274C Failed to find orphan processes");
|
|
192
|
-
resolve(0);
|
|
193
|
-
});
|
|
32
|
+
function killOrphanOpenCodeProcesses() {
|
|
33
|
+
return (0, import_node.killOrphanCliProcesses)("opencode", { match: "opencode", winName: "opencode.exe", label: "opencode" });
|
|
194
34
|
}
|
|
195
35
|
// Annotate the CommonJS export names for ESM import in node:
|
|
196
36
|
0 && (module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aipanel/provider-opencode",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.cjs",
|
|
6
6
|
"module": "es/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@opencode-ai/plugin": "^1.18.0",
|
|
25
25
|
"execa": "^9.6.1",
|
|
26
|
-
"@aipanel/core": "1.2.
|
|
26
|
+
"@aipanel/core": "1.2.11"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "pagoda-cli build",
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview 编辑后诊断插件
|
|
3
|
-
* @description edit/write 工具执行后:
|
|
4
|
-
* 1. ESLint 检查(Node API)
|
|
5
|
-
* 2. vue-tsc 类型检查(过滤当前文件诊断)
|
|
6
|
-
* 3. 诊断结果追加到工具输出,供 Agent 查看(不做回滚)
|
|
7
|
-
*
|
|
8
|
-
* 诊断引擎(ESLint/vue-tsc/格式化/全量诊断)统一由 @aipanel/core/node 提供,
|
|
9
|
-
* 与 dsh 侧审查工具共用同一实现,保证行为一致。
|
|
10
|
-
*/
|
|
11
|
-
import type { Hooks } from "@opencode-ai/plugin";
|
|
12
|
-
declare const _default: {
|
|
13
|
-
id: string;
|
|
14
|
-
server(): Promise<Hooks>;
|
|
15
|
-
};
|
|
16
|
-
export default _default;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview 编辑后诊断插件
|
|
3
|
-
* @description edit/write 工具执行后:
|
|
4
|
-
* 1. ESLint 检查(Node API)
|
|
5
|
-
* 2. vue-tsc 类型检查(过滤当前文件诊断)
|
|
6
|
-
* 3. 诊断结果追加到工具输出,供 Agent 查看(不做回滚)
|
|
7
|
-
*
|
|
8
|
-
* 诊断引擎(ESLint/vue-tsc/格式化/全量诊断)统一由 @aipanel/core/node 提供,
|
|
9
|
-
* 与 dsh 侧审查工具共用同一实现,保证行为一致。
|
|
10
|
-
*/
|
|
11
|
-
import type { Hooks } from "@opencode-ai/plugin";
|
|
12
|
-
declare const _default: {
|
|
13
|
-
id: string;
|
|
14
|
-
server(): Promise<Hooks>;
|
|
15
|
-
};
|
|
16
|
-
export default _default;
|