@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 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
- sleep,
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
- const timer = log.timer("getSessions", { retries, projectDir });
61
- let lastError = null;
62
- for (let i = 0; i < retries; i++) {
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
- timer.end(`Found ${sessions.length} sessions`);
76
+ log.debug(`Found ${sessions.length} sessions`, { operation: "getSessions" });
75
77
  return sessionsWithUrl;
76
- } catch (e) {
77
- lastError = e instanceof Error ? e : new Error(String(e));
78
- log.debug(`Attempt ${i + 1} failed: ${lastError.message}`, {
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
- const timer = log.timer("createSession", { retries, title, projectDir });
94
- let lastError = null;
95
- for (let i = 0; i < retries; i++) {
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
- timer.end(`Created session: ${session.id}`);
107
+ log.debug(`Created session: ${session.id}`, { operation: "createSession" });
120
108
  return sessionWithUrl;
121
- } catch (e) {
122
- lastError = e instanceof Error ? e : new Error(String(e));
123
- log.debug(`Attempt ${i + 1} failed: ${lastError.message}`, {
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
- const timer = log.timer("deleteSession", { sessionId, retries });
139
- let lastError = null;
140
- for (let i = 0; i < retries; i++) {
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
- timer.end(`Deleted session: ${sessionId}`);
153
- return;
154
- } catch (e) {
155
- lastError = e instanceof Error ? e : new Error(String(e));
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
- const timer = log.timer("getToolIds", { retries });
174
- let lastError = null;
175
- for (let i = 0; i < retries; i++) {
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
- timer.end(`Found ${toolIds.length} tools`);
140
+ log.debug(`Found ${toolIds.length} tools`, { operation: "getToolIds" });
186
141
  return toolIds;
187
- } catch (e) {
188
- lastError = e instanceof Error ? e : new Error(String(e));
189
- log.debug(`Attempt ${i + 1} failed: ${lastError.message}`, {
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
- timer.end(`Using existing session: ${matchingSession.id}`);
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
- timer.end(`Created new session: ${newSession.id}`);
161
+ log.debug(`Created new session: ${newSession.id}`, { operation: "getOrCreateSession" });
220
162
  return url;
221
163
  }
222
164
  }
@@ -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
- VSCODE_EXTENSION_PORT,
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
- const bridgePath = resolveFormatBridgePath();
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
- OPENCODE_CONFIG_DIR: stateDir
153
+ [OPENCODE_ENV.CONFIG_DIR]: stateDir
227
154
  };
228
155
  if (configDir) {
229
- env.OPENCODE_CONFIG_DIR = configDir;
156
+ env[OPENCODE_ENV.CONFIG_DIR] = configDir;
230
157
  log.debug("Set OPENCODE_CONFIG_DIR", { configDir });
231
158
  }
232
159
  if (contextApiUrl) {
233
- env.OPENCODE_CONTEXT_API_URL = contextApiUrl;
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.OPENCODE_VITE_LOGS_API_URL = logsApiUrl;
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.OPENCODE_LOG_FILES_JSON = logFilesJson;
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.OPENCODE_VERBOSE = "1";
172
+ env[OPENCODE_ENV.VERBOSE] = "1";
246
173
  log.debug("Set OPENCODE_VERBOSE=1");
247
174
  }
248
175
  if (enableLsp) {
249
- env.OPENCODE_ENABLE_LINT = "1";
176
+ env[OPENCODE_ENV.ENABLE_LINT] = "1";
250
177
  log.debug("Set OPENCODE_ENABLE_LINT=1");
251
178
  }
252
179
  if (vueDevtoolsApiUrl) {
253
- env.OPENCODE_VUE_DEVTOOLS_API_URL = vueDevtoolsApiUrl;
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.OPENCODE_WORKSPACE = workspace;
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.OPENCODE_VERBOSE === "1") {
15
+ if (process.env[OPENCODE_ENV.VERBOSE] === "1") {
13
16
  setVerbose(true);
14
17
  }
15
18
  const log = createLogger("EditDiagnostics");
16
- const EDIT_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "apply_patch"]);
17
- const isLintEnabled = () => process.env.OPENCODE_ENABLE_LINT === "1";
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.OPENCODE_WORKSPACE || process.cwd();
24
+ const workspace = process.env[OPENCODE_ENV.WORKSPACE] || process.cwd();
22
25
  const runDiagnosticsTool = tool({
23
- description: `\u8FD0\u884C ESLint \u548C vue-tsc \u7C7B\u578B\u68C0\u67E5\uFF0C\u8FD4\u56DE\u8BCA\u65AD\u7ED3\u679C\u3002
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 { spawn } from "child_process";
2
- import { createLogger } from "@aipanel/core/node";
3
- const log = createLogger("OpenCodeSystem");
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 new Promise((resolve) => {
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
- const KILL_ORPHAN_TIMEOUT = 5e3;
41
- async function killOrphanOpenCodeProcesses() {
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
- const timer = log.timer("getSessions", { retries, projectDir });
86
- let lastError = null;
87
- for (let i = 0; i < retries; i++) {
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
- timer.end(`Found ${sessions.length} sessions`);
101
+ log.debug(`Found ${sessions.length} sessions`, { operation: "getSessions" });
100
102
  return sessionsWithUrl;
101
- } catch (e) {
102
- lastError = e instanceof Error ? e : new Error(String(e));
103
- log.debug(`Attempt ${i + 1} failed: ${lastError.message}`, {
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
- const timer = log.timer("createSession", { retries, title, projectDir });
119
- let lastError = null;
120
- for (let i = 0; i < retries; i++) {
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
- timer.end(`Created session: ${session.id}`);
132
+ log.debug(`Created session: ${session.id}`, { operation: "createSession" });
145
133
  return sessionWithUrl;
146
- } catch (e) {
147
- lastError = e instanceof Error ? e : new Error(String(e));
148
- log.debug(`Attempt ${i + 1} failed: ${lastError.message}`, {
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
- const timer = log.timer("deleteSession", { sessionId, retries });
164
- let lastError = null;
165
- for (let i = 0; i < retries; i++) {
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
- timer.end(`Deleted session: ${sessionId}`);
178
- return;
179
- } catch (e) {
180
- lastError = e instanceof Error ? e : new Error(String(e));
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
- const timer = log.timer("getToolIds", { retries });
199
- let lastError = null;
200
- for (let i = 0; i < retries; i++) {
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
- timer.end(`Found ${toolIds.length} tools`);
165
+ log.debug(`Found ${toolIds.length} tools`, { operation: "getToolIds" });
211
166
  return toolIds;
212
- } catch (e) {
213
- lastError = e instanceof Error ? e : new Error(String(e));
214
- log.debug(`Attempt ${i + 1} failed: ${lastError.message}`, {
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
- timer.end(`Using existing session: ${matchingSession.id}`);
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
- timer.end(`Created new session: ${newSession.id}`);
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>;
@@ -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
- const bridgePath = resolveFormatBridgePath();
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
- OPENCODE_CONFIG_DIR: stateDir
181
+ [import_node.OPENCODE_ENV.CONFIG_DIR]: stateDir
251
182
  };
252
183
  if (configDir) {
253
- env.OPENCODE_CONFIG_DIR = configDir;
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.OPENCODE_CONTEXT_API_URL = contextApiUrl;
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.OPENCODE_VITE_LOGS_API_URL = logsApiUrl;
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.OPENCODE_LOG_FILES_JSON = logFilesJson;
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.OPENCODE_VERBOSE = "1";
200
+ env[import_node.OPENCODE_ENV.VERBOSE] = "1";
270
201
  log.debug("Set OPENCODE_VERBOSE=1");
271
202
  }
272
203
  if (enableLsp) {
273
- env.OPENCODE_ENABLE_LINT = "1";
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.OPENCODE_VUE_DEVTOOLS_API_URL = vueDevtoolsApiUrl;
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.OPENCODE_WORKSPACE = workspace;
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.OPENCODE_VERBOSE === "1") {
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 = /* @__PURE__ */ new Set(["edit", "write", "apply_patch"]);
42
- const isLintEnabled = () => process.env.OPENCODE_ENABLE_LINT === "1";
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.OPENCODE_WORKSPACE || process.cwd();
46
+ const workspace = process.env[import_node.OPENCODE_ENV.WORKSPACE] || process.cwd();
47
47
  const runDiagnosticsTool = (0, import_plugin.tool)({
48
- description: `\u8FD0\u884C ESLint \u548C vue-tsc \u7C7B\u578B\u68C0\u67E5\uFF0C\u8FD4\u56DE\u8BCA\u65AD\u7ED3\u679C\u3002
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
- const log = (0, import_node.createLogger)("OpenCodeSystem");
28
- async function checkOpenCodeInstalled() {
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 new Promise((resolve) => {
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
- const KILL_ORPHAN_TIMEOUT = 5e3;
65
- async function killOrphanOpenCodeProcesses() {
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.9",
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.9"
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;