@aipanel/provider-opencode 1.3.4 → 1.3.5

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/constants.js CHANGED
@@ -16,7 +16,6 @@ const DEFAULT_OPENCODE_SETTINGS = {
16
16
  }
17
17
  };
18
18
  const DEFAULT_OPENCODE_PROVIDER_OPTIONS = {
19
- enableLsp: true,
20
19
  enablePrettier: true
21
20
  };
22
21
  export {
@@ -1,4 +1,4 @@
1
1
  import type { ResultPromise } from "execa";
2
2
  import type { WebOptions } from "./types";
3
- export declare function prepareOpenCodeRuntime(cwd: string, vitePort: number, viteHost: string, enableLsp?: boolean, enablePrettier?: boolean): string;
3
+ export declare function prepareOpenCodeRuntime(cwd: string, vitePort: number, viteHost: string, enablePrettier?: boolean): string;
4
4
  export declare function startOpenCodeWeb(options: WebOptions): ResultPromise;
@@ -11,9 +11,9 @@ import {
11
11
  } from "@aipanel/core/node";
12
12
  const pluginsDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "plugins");
13
13
  const log = createLogger("OpenCodeWeb");
14
- function prepareOpenCodeRuntime(cwd, vitePort, viteHost, enableLsp, enablePrettier) {
14
+ function prepareOpenCodeRuntime(cwd, vitePort, viteHost, enablePrettier) {
15
15
  const cacheDir = path.join(cwd, AIPANEL_CACHE_DIR, "opencode");
16
- log.debug("Setting up OpenCode runtime", { cacheDir, enableLsp });
16
+ log.debug("Setting up OpenCode runtime", { cacheDir, enablePrettier });
17
17
  if (!fs.existsSync(cacheDir)) {
18
18
  fs.mkdirSync(cacheDir, { recursive: true });
19
19
  }
@@ -50,7 +50,7 @@ function startOpenCodeWeb(options) {
50
50
  logsApiUrl,
51
51
  logFilesJson,
52
52
  verbose,
53
- enableLsp,
53
+ diagnosticsJson,
54
54
  vueDevtoolsApiUrl
55
55
  } = options;
56
56
  const stateDir = createStateDirectory(cwd);
@@ -61,7 +61,7 @@ function startOpenCodeWeb(options) {
61
61
  logsApiUrl,
62
62
  logFilesJson,
63
63
  verbose,
64
- enableLsp
64
+ hasDiagnostics: diagnosticsJson !== void 0
65
65
  });
66
66
  const env = buildProcessEnv(
67
67
  stateDir,
@@ -70,7 +70,7 @@ function startOpenCodeWeb(options) {
70
70
  logsApiUrl,
71
71
  logFilesJson,
72
72
  verbose,
73
- enableLsp,
73
+ diagnosticsJson,
74
74
  vueDevtoolsApiUrl,
75
75
  cwd
76
76
  );
@@ -142,7 +142,7 @@ function resolvePluginEntries(sourceDir) {
142
142
  log.debug("Resolved plugin entries", { count: entries.length, entries });
143
143
  return entries;
144
144
  }
145
- function buildProcessEnv(stateDir, configDir, contextApiUrl, logsApiUrl, logFilesJson, verbose, enableLsp, vueDevtoolsApiUrl, workspace) {
145
+ function buildProcessEnv(stateDir, configDir, contextApiUrl, logsApiUrl, logFilesJson, verbose, diagnosticsJson, vueDevtoolsApiUrl, workspace) {
146
146
  const env = {
147
147
  ...Object.fromEntries(
148
148
  Object.entries(process.env).filter(([, v]) => v !== void 0)
@@ -171,9 +171,9 @@ function buildProcessEnv(stateDir, configDir, contextApiUrl, logsApiUrl, logFile
171
171
  env[OPENCODE_ENV.VERBOSE] = "1";
172
172
  log.debug("Set OPENCODE_VERBOSE=1");
173
173
  }
174
- if (enableLsp) {
175
- env[OPENCODE_ENV.ENABLE_LINT] = "1";
176
- log.debug("Set OPENCODE_ENABLE_LINT=1");
174
+ if (diagnosticsJson) {
175
+ env[OPENCODE_ENV.DIAGNOSTICS] = diagnosticsJson;
176
+ log.debug("Set OPENCODE_DIAGNOSTICS");
177
177
  }
178
178
  if (vueDevtoolsApiUrl) {
179
179
  env[OPENCODE_ENV.VUE_DEVTOOLS_API_URL] = vueDevtoolsApiUrl;
@@ -4,10 +4,11 @@ import { tool } from "@opencode-ai/plugin";
4
4
  import {
5
5
  setVerbose,
6
6
  createLogger,
7
- runAllChecks,
8
- runProjectDiagnostics,
9
- formatDiagnosticsSections,
10
- isJsFile,
7
+ collectDiagnostics,
8
+ renderDiagnostics,
9
+ resolveDiagnosticsPolicy,
10
+ runDiagnostics,
11
+ DEFAULT_DIAGNOSTICS_POLICY,
11
12
  MUTATING_TOOLS,
12
13
  OPENCODE_ENV,
13
14
  DIAGNOSTICS_TOOL_DESCRIPTION
@@ -17,83 +18,86 @@ if (process.env[OPENCODE_ENV.VERBOSE] === "1") {
17
18
  }
18
19
  const log = createLogger("EditDiagnostics");
19
20
  const EDIT_TOOLS = MUTATING_TOOLS;
20
- const isLintEnabled = () => process.env[OPENCODE_ENV.ENABLE_LINT] === "1";
21
+ function readPolicy() {
22
+ const raw = process.env[OPENCODE_ENV.DIAGNOSTICS];
23
+ let parsed;
24
+ if (raw) {
25
+ try {
26
+ parsed = JSON.parse(raw);
27
+ } catch {
28
+ log.warn("OPENCODE_DIAGNOSTICS is not valid JSON; falling back to defaults");
29
+ }
30
+ }
31
+ return resolveDiagnosticsPolicy(
32
+ [DEFAULT_DIAGNOSTICS_POLICY, parsed],
33
+ (message) => log.warn(message)
34
+ );
35
+ }
36
+ function truncate(text, maxChars) {
37
+ if (text.length <= maxChars) return text;
38
+ return `${text.slice(0, maxChars)}
39
+
40
+ \u2026\uFF08\u8BCA\u65AD\u8F93\u51FA\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF1B\u53EF\u8C03\u7528 run_diagnostics \u67E5\u770B\u5B8C\u6574\u7ED3\u679C\uFF09`;
41
+ }
21
42
  var edit_diagnostics_default = {
22
43
  id: "vite-plugin-aipanel/edit-diagnostics",
23
44
  async server() {
24
45
  const workspace = process.env[OPENCODE_ENV.WORKSPACE] || process.cwd();
46
+ const policy = readPolicy();
25
47
  const runDiagnosticsTool = tool({
26
48
  description: DIAGNOSTICS_TOOL_DESCRIPTION,
27
49
  args: {
28
50
  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")
29
51
  },
30
52
  async execute(args, context) {
31
- const { filePath } = args;
32
- const workspace2 = context.directory;
33
- if (filePath) {
34
- const resolved = path.resolve(workspace2, filePath);
35
- log.debug("run_diagnostics called (single file)", {
36
- filePath: resolved,
37
- workspace: workspace2,
38
- sessionID: context.sessionID
39
- });
40
- if (!fs.existsSync(resolved)) {
41
- return `\u6587\u4EF6\u4E0D\u5B58\u5728: ${resolved}`;
42
- }
43
- const { eslintOutput: eslintOutput2, tscOutput: tscOutput2 } = await runAllChecks(resolved, workspace2);
44
- return formatDiagnosticsSections(
45
- `\u8BCA\u65AD\u7ED3\u679C: ${path.relative(workspace2, resolved)}`,
46
- eslintOutput2,
47
- tscOutput2
48
- );
53
+ const cwd = context.directory;
54
+ let target;
55
+ let title;
56
+ if (args.filePath) {
57
+ const resolved = path.resolve(cwd, args.filePath);
58
+ if (!fs.existsSync(resolved)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${resolved}`);
59
+ target = { kind: "file", file: resolved, cwd };
60
+ title = `\u8BCA\u65AD\u7ED3\u679C: ${path.relative(cwd, resolved)}`;
61
+ } else {
62
+ target = { kind: "project", cwd };
63
+ title = "\u5168\u91CF\u8BCA\u65AD\u7ED3\u679C";
49
64
  }
50
- log.debug("run_diagnostics called (full project)", {
51
- workspace: workspace2,
52
- sessionID: context.sessionID
53
- });
54
- const { eslintOutput, tscOutput } = await runProjectDiagnostics(workspace2);
55
- return formatDiagnosticsSections("\u5168\u91CF\u8BCA\u65AD\u7ED3\u679C", eslintOutput, tscOutput);
65
+ const body = renderDiagnostics(await runDiagnostics(target, policy, "manual"));
66
+ return body ? `${title}
67
+
68
+ ${body}` : title;
56
69
  }
57
70
  });
58
- return {
71
+ const hooks = {
59
72
  "tool.execute.after": async (input, output) => {
60
73
  if (!EDIT_TOOLS.has(input.tool)) return;
61
- if (!isLintEnabled()) return;
74
+ if (!policy.auto) return;
62
75
  const filePath = input.args?.filePath || "";
63
- if (!filePath || !isJsFile(filePath)) return;
64
- log.debug("Executing after hook", {
65
- tool: input.tool,
66
- filePath,
67
- processCwd: workspace,
68
- lintEnabled: isLintEnabled()
76
+ if (!filePath) return;
77
+ const resolved = path.resolve(workspace, filePath);
78
+ log.debug("Executing after hook", { tool: input.tool, filePath, processCwd: workspace });
79
+ const result = await runDiagnostics(
80
+ { kind: "edited", files: [resolved], cwd: workspace },
81
+ policy,
82
+ "edit"
83
+ );
84
+ const text = renderDiagnostics(result, {
85
+ onlyFindings: true,
86
+ maxFindingsPerSection: policy.maxFindingsPerSection
69
87
  });
70
- const { eslintOutput, tscOutput } = await runAllChecks(filePath, workspace);
71
- const diagText = formatDiagnosticsSections("", eslintOutput, tscOutput, {
72
- onlyFindings: true
73
- });
74
- log.debug("Diagnostics result", {
75
- filePath,
76
- eslintError: !!eslintOutput.text,
77
- tscError: tscOutput.exitCode !== 0
78
- });
79
- if (diagText) {
80
- output.output += "\n\n" + diagText;
81
- }
82
- const anyError = !!eslintOutput.text || tscOutput.exitCode !== 0;
83
- if (anyError) {
88
+ if (text) output.output += "\n\n" + truncate(text, policy.maxMessageChars);
89
+ const items = collectDiagnostics(result);
90
+ if (items.length > 0) {
84
91
  const meta = output.metadata ?? (output.metadata = {});
85
92
  const existing = meta.diagnostics ?? (meta.diagnostics = {});
86
- const diags = [
87
- ...eslintOutput.diagnostics ?? [],
88
- ...tscOutput.diagnostics ?? []
89
- ];
90
- existing[filePath] = [...existing[filePath] ?? [], ...diags];
93
+ for (const item of items) {
94
+ const key = item.file ?? resolved;
95
+ existing[key] = [...existing[key] ?? [], item];
96
+ }
91
97
  }
92
- },
93
- tool: {
94
- run_diagnostics: runDiagnosticsTool
95
98
  }
96
99
  };
100
+ return policy.exposeTool ? { ...hooks, tool: { run_diagnostics: runDiagnosticsTool } } : hooks;
97
101
  }
98
102
  };
99
103
  export {
package/es/provider.js CHANGED
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import http from "http";
5
- import { RETRY_DELAY } from "@aipanel/core";
5
+ import { DEFAULT_DIAGNOSTICS_POLICY, RETRY_DELAY, resolveDiagnosticsPolicy } from "@aipanel/core";
6
6
  import { createLogger } from "@aipanel/core/node";
7
7
  import { DEFAULT_OPENCODE_PROVIDER_OPTIONS } from "./constants.js";
8
8
  import { OpenCodeAPI } from "./api.js";
@@ -78,7 +78,6 @@ Please install OpenCode first:
78
78
  options.cwd,
79
79
  options.vitePort,
80
80
  options.viteHost,
81
- this.opts.enableLsp,
82
81
  this.opts.enablePrettier
83
82
  );
84
83
  log.debug("Starting OpenCode Web process", {
@@ -97,7 +96,8 @@ Please install OpenCode first:
97
96
  logsApiUrl: options.logsApiUrl,
98
97
  logFilesJson: this.opts.logFiles ? JSON.stringify(this.opts.logFiles) : void 0,
99
98
  verbose: options.verbose,
100
- enableLsp: this.opts.enableLsp,
99
+ // 诊断策略随 env 下发给 opencode 插件(单一来源;插件侧解析成 DiagnosticsPolicy)
100
+ diagnosticsJson: JSON.stringify(this.opts.diagnostics),
101
101
  enablePrettier: this.opts.enablePrettier,
102
102
  vueDevtoolsApiUrl: options.vueDevtoolsApiUrl
103
103
  });
@@ -241,15 +241,17 @@ function toChatSession(s) {
241
241
  };
242
242
  }
243
243
  function resolveOpenCodeOptions(options) {
244
- if (!options) return { ...DEFAULT_OPENCODE_PROVIDER_OPTIONS };
245
- const po = options.providerOptions ?? {};
244
+ const po = options?.providerOptions ?? {};
246
245
  return {
247
246
  ...DEFAULT_OPENCODE_PROVIDER_OPTIONS,
248
- language: po.language ?? options.language,
249
- settings: po.settings ?? options.settings,
250
- logFiles: options.logFiles,
251
- enableLsp: po.enableLsp ?? options.enableLsp,
252
- enablePrettier: po.enablePrettier ?? options.enablePrettier
247
+ language: po.language ?? options?.language,
248
+ settings: po.settings ?? options?.settings,
249
+ logFiles: options?.logFiles,
250
+ enablePrettier: po.enablePrettier ?? options?.enablePrettier,
251
+ diagnostics: resolveDiagnosticsPolicy(
252
+ [DEFAULT_DIAGNOSTICS_POLICY, po.diagnostics],
253
+ (message) => log.warn(message)
254
+ )
253
255
  };
254
256
  }
255
257
  export {
package/es/types.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * OpenCode Provider 专属类型
3
3
  * 所有与 OpenCode Web 绑定的类型自包含于此,核心层不感知。
4
4
  */
5
- import type { LogFileConfig } from "@aipanel/core";
5
+ import type { DiagnosticsPolicy, LogFileConfig } from "@aipanel/core";
6
6
  /**
7
7
  * OpenCode 界面语言选项
8
8
  */
@@ -83,13 +83,20 @@ export type OpenCodeProviderOptions = {
83
83
  settings?: OpenCodeSettings;
84
84
  /** 自定义日志文件配置 */
85
85
  logFiles?: LogFileConfig[];
86
- /** 启用 LSP 诊断(TypeScript + ESLint),agent 编辑文件后自动返回错误信息,默认 false */
87
- enableLsp?: boolean;
86
+ /**
87
+ * 诊断配置(检查来源 + 触发 + 投递;契约见 @aipanel/core 的 DiagnosticsPolicy)。
88
+ * 用户只写要覆盖的字段,provider 归一化成完整策略后随 env 下发。
89
+ */
90
+ diagnostics?: Partial<DiagnosticsPolicy>;
88
91
  /** 启用代码格式化功能(prettier),默认 true */
89
92
  enablePrettier?: boolean;
90
93
  /** 允许 Provider 自定义扩展字段(schema 由具体 Provider 定义) */
91
94
  [key: string]: unknown;
92
95
  };
96
+ /** 归一化后的 Provider 选项:诊断策略一定是完整的 */
97
+ export type OpenCodeResolvedOptions = OpenCodeProviderOptions & {
98
+ diagnostics: DiagnosticsPolicy;
99
+ };
93
100
  /**
94
101
  * OpenCode Web 服务启动选项(进程管理内部类型)
95
102
  */
@@ -114,8 +121,8 @@ export interface WebOptions {
114
121
  logFilesJson?: string;
115
122
  /** 启用 verbose 模式(环境变量透传,调试日志输出) */
116
123
  verbose?: boolean;
117
- /** 启用 LSP / 质量门禁(环境变量透传,控制 block-on-error 插件运行) */
118
- enableLsp?: boolean;
124
+ /** 诊断策略 JSON(DiagnosticsPolicy):随 env 下发给 opencode 插件 */
125
+ diagnosticsJson?: string;
119
126
  /** 启用代码格式化功能(prettier) */
120
127
  enablePrettier?: boolean;
121
128
  /** Vue DevTools API 地址(环境变量透传给 OpenCode 插件) */
package/lib/constants.cjs CHANGED
@@ -40,7 +40,6 @@ const DEFAULT_OPENCODE_SETTINGS = {
40
40
  }
41
41
  };
42
42
  const DEFAULT_OPENCODE_PROVIDER_OPTIONS = {
43
- enableLsp: true,
44
43
  enablePrettier: true
45
44
  };
46
45
  // Annotate the CommonJS export names for ESM import in node:
@@ -39,9 +39,9 @@ var import_node = require("@aipanel/core/node");
39
39
  const import_meta = {};
40
40
  const pluginsDir = import_path.default.join(import_path.default.dirname((0, import_url.fileURLToPath)(import_meta.url)), "plugins");
41
41
  const log = (0, import_node.createLogger)("OpenCodeWeb");
42
- function prepareOpenCodeRuntime(cwd, vitePort, viteHost, enableLsp, enablePrettier) {
42
+ function prepareOpenCodeRuntime(cwd, vitePort, viteHost, enablePrettier) {
43
43
  const cacheDir = import_path.default.join(cwd, import_node.AIPANEL_CACHE_DIR, "opencode");
44
- log.debug("Setting up OpenCode runtime", { cacheDir, enableLsp });
44
+ log.debug("Setting up OpenCode runtime", { cacheDir, enablePrettier });
45
45
  if (!import_fs.default.existsSync(cacheDir)) {
46
46
  import_fs.default.mkdirSync(cacheDir, { recursive: true });
47
47
  }
@@ -78,7 +78,7 @@ function startOpenCodeWeb(options) {
78
78
  logsApiUrl,
79
79
  logFilesJson,
80
80
  verbose,
81
- enableLsp,
81
+ diagnosticsJson,
82
82
  vueDevtoolsApiUrl
83
83
  } = options;
84
84
  const stateDir = createStateDirectory(cwd);
@@ -89,7 +89,7 @@ function startOpenCodeWeb(options) {
89
89
  logsApiUrl,
90
90
  logFilesJson,
91
91
  verbose,
92
- enableLsp
92
+ hasDiagnostics: diagnosticsJson !== void 0
93
93
  });
94
94
  const env = buildProcessEnv(
95
95
  stateDir,
@@ -98,7 +98,7 @@ function startOpenCodeWeb(options) {
98
98
  logsApiUrl,
99
99
  logFilesJson,
100
100
  verbose,
101
- enableLsp,
101
+ diagnosticsJson,
102
102
  vueDevtoolsApiUrl,
103
103
  cwd
104
104
  );
@@ -170,7 +170,7 @@ function resolvePluginEntries(sourceDir) {
170
170
  log.debug("Resolved plugin entries", { count: entries.length, entries });
171
171
  return entries;
172
172
  }
173
- function buildProcessEnv(stateDir, configDir, contextApiUrl, logsApiUrl, logFilesJson, verbose, enableLsp, vueDevtoolsApiUrl, workspace) {
173
+ function buildProcessEnv(stateDir, configDir, contextApiUrl, logsApiUrl, logFilesJson, verbose, diagnosticsJson, vueDevtoolsApiUrl, workspace) {
174
174
  const env = {
175
175
  ...Object.fromEntries(
176
176
  Object.entries(process.env).filter(([, v]) => v !== void 0)
@@ -199,9 +199,9 @@ function buildProcessEnv(stateDir, configDir, contextApiUrl, logsApiUrl, logFile
199
199
  env[import_node.OPENCODE_ENV.VERBOSE] = "1";
200
200
  log.debug("Set OPENCODE_VERBOSE=1");
201
201
  }
202
- if (enableLsp) {
203
- env[import_node.OPENCODE_ENV.ENABLE_LINT] = "1";
204
- log.debug("Set OPENCODE_ENABLE_LINT=1");
202
+ if (diagnosticsJson) {
203
+ env[import_node.OPENCODE_ENV.DIAGNOSTICS] = diagnosticsJson;
204
+ log.debug("Set OPENCODE_DIAGNOSTICS");
205
205
  }
206
206
  if (vueDevtoolsApiUrl) {
207
207
  env[import_node.OPENCODE_ENV.VUE_DEVTOOLS_API_URL] = vueDevtoolsApiUrl;
@@ -1,4 +1,4 @@
1
1
  import type { ResultPromise } from "execa";
2
2
  import type { WebOptions } from "./types";
3
- export declare function prepareOpenCodeRuntime(cwd: string, vitePort: number, viteHost: string, enableLsp?: boolean, enablePrettier?: boolean): string;
3
+ export declare function prepareOpenCodeRuntime(cwd: string, vitePort: number, viteHost: string, enablePrettier?: boolean): string;
4
4
  export declare function startOpenCodeWeb(options: WebOptions): ResultPromise;
@@ -39,82 +39,85 @@ if (process.env[import_node.OPENCODE_ENV.VERBOSE] === "1") {
39
39
  }
40
40
  const log = (0, import_node.createLogger)("EditDiagnostics");
41
41
  const EDIT_TOOLS = import_node.MUTATING_TOOLS;
42
- const isLintEnabled = () => process.env[import_node.OPENCODE_ENV.ENABLE_LINT] === "1";
42
+ function readPolicy() {
43
+ const raw = process.env[import_node.OPENCODE_ENV.DIAGNOSTICS];
44
+ let parsed;
45
+ if (raw) {
46
+ try {
47
+ parsed = JSON.parse(raw);
48
+ } catch {
49
+ log.warn("OPENCODE_DIAGNOSTICS is not valid JSON; falling back to defaults");
50
+ }
51
+ }
52
+ return (0, import_node.resolveDiagnosticsPolicy)(
53
+ [import_node.DEFAULT_DIAGNOSTICS_POLICY, parsed],
54
+ (message) => log.warn(message)
55
+ );
56
+ }
57
+ function truncate(text, maxChars) {
58
+ if (text.length <= maxChars) return text;
59
+ return `${text.slice(0, maxChars)}
60
+
61
+ \u2026\uFF08\u8BCA\u65AD\u8F93\u51FA\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF1B\u53EF\u8C03\u7528 run_diagnostics \u67E5\u770B\u5B8C\u6574\u7ED3\u679C\uFF09`;
62
+ }
43
63
  var edit_diagnostics_default = {
44
64
  id: "vite-plugin-aipanel/edit-diagnostics",
45
65
  async server() {
46
66
  const workspace = process.env[import_node.OPENCODE_ENV.WORKSPACE] || process.cwd();
67
+ const policy = readPolicy();
47
68
  const runDiagnosticsTool = (0, import_plugin.tool)({
48
69
  description: import_node.DIAGNOSTICS_TOOL_DESCRIPTION,
49
70
  args: {
50
71
  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")
51
72
  },
52
73
  async execute(args, context) {
53
- const { filePath } = args;
54
- const workspace2 = context.directory;
55
- if (filePath) {
56
- const resolved = import_node_path.default.resolve(workspace2, filePath);
57
- log.debug("run_diagnostics called (single file)", {
58
- filePath: resolved,
59
- workspace: workspace2,
60
- sessionID: context.sessionID
61
- });
62
- if (!import_node_fs.default.existsSync(resolved)) {
63
- return `\u6587\u4EF6\u4E0D\u5B58\u5728: ${resolved}`;
64
- }
65
- const { eslintOutput: eslintOutput2, tscOutput: tscOutput2 } = await (0, import_node.runAllChecks)(resolved, workspace2);
66
- return (0, import_node.formatDiagnosticsSections)(
67
- `\u8BCA\u65AD\u7ED3\u679C: ${import_node_path.default.relative(workspace2, resolved)}`,
68
- eslintOutput2,
69
- tscOutput2
70
- );
74
+ const cwd = context.directory;
75
+ let target;
76
+ let title;
77
+ if (args.filePath) {
78
+ const resolved = import_node_path.default.resolve(cwd, args.filePath);
79
+ if (!import_node_fs.default.existsSync(resolved)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${resolved}`);
80
+ target = { kind: "file", file: resolved, cwd };
81
+ title = `\u8BCA\u65AD\u7ED3\u679C: ${import_node_path.default.relative(cwd, resolved)}`;
82
+ } else {
83
+ target = { kind: "project", cwd };
84
+ title = "\u5168\u91CF\u8BCA\u65AD\u7ED3\u679C";
71
85
  }
72
- log.debug("run_diagnostics called (full project)", {
73
- workspace: workspace2,
74
- sessionID: context.sessionID
75
- });
76
- const { eslintOutput, tscOutput } = await (0, import_node.runProjectDiagnostics)(workspace2);
77
- return (0, import_node.formatDiagnosticsSections)("\u5168\u91CF\u8BCA\u65AD\u7ED3\u679C", eslintOutput, tscOutput);
86
+ const body = (0, import_node.renderDiagnostics)(await (0, import_node.runDiagnostics)(target, policy, "manual"));
87
+ return body ? `${title}
88
+
89
+ ${body}` : title;
78
90
  }
79
91
  });
80
- return {
92
+ const hooks = {
81
93
  "tool.execute.after": async (input, output) => {
82
94
  if (!EDIT_TOOLS.has(input.tool)) return;
83
- if (!isLintEnabled()) return;
95
+ if (!policy.auto) return;
84
96
  const filePath = input.args?.filePath || "";
85
- if (!filePath || !(0, import_node.isJsFile)(filePath)) return;
86
- log.debug("Executing after hook", {
87
- tool: input.tool,
88
- filePath,
89
- processCwd: workspace,
90
- lintEnabled: isLintEnabled()
97
+ if (!filePath) return;
98
+ const resolved = import_node_path.default.resolve(workspace, filePath);
99
+ log.debug("Executing after hook", { tool: input.tool, filePath, processCwd: workspace });
100
+ const result = await (0, import_node.runDiagnostics)(
101
+ { kind: "edited", files: [resolved], cwd: workspace },
102
+ policy,
103
+ "edit"
104
+ );
105
+ const text = (0, import_node.renderDiagnostics)(result, {
106
+ onlyFindings: true,
107
+ maxFindingsPerSection: policy.maxFindingsPerSection
91
108
  });
92
- const { eslintOutput, tscOutput } = await (0, import_node.runAllChecks)(filePath, workspace);
93
- const diagText = (0, import_node.formatDiagnosticsSections)("", eslintOutput, tscOutput, {
94
- onlyFindings: true
95
- });
96
- log.debug("Diagnostics result", {
97
- filePath,
98
- eslintError: !!eslintOutput.text,
99
- tscError: tscOutput.exitCode !== 0
100
- });
101
- if (diagText) {
102
- output.output += "\n\n" + diagText;
103
- }
104
- const anyError = !!eslintOutput.text || tscOutput.exitCode !== 0;
105
- if (anyError) {
109
+ if (text) output.output += "\n\n" + truncate(text, policy.maxMessageChars);
110
+ const items = (0, import_node.collectDiagnostics)(result);
111
+ if (items.length > 0) {
106
112
  const meta = output.metadata ?? (output.metadata = {});
107
113
  const existing = meta.diagnostics ?? (meta.diagnostics = {});
108
- const diags = [
109
- ...eslintOutput.diagnostics ?? [],
110
- ...tscOutput.diagnostics ?? []
111
- ];
112
- existing[filePath] = [...existing[filePath] ?? [], ...diags];
114
+ for (const item of items) {
115
+ const key = item.file ?? resolved;
116
+ existing[key] = [...existing[key] ?? [], item];
117
+ }
113
118
  }
114
- },
115
- tool: {
116
- run_diagnostics: runDiagnosticsTool
117
119
  }
118
120
  };
121
+ return policy.exposeTool ? { ...hooks, tool: { run_diagnostics: runDiagnosticsTool } } : hooks;
119
122
  }
120
123
  };
package/lib/provider.cjs CHANGED
@@ -110,7 +110,6 @@ Please install OpenCode first:
110
110
  options.cwd,
111
111
  options.vitePort,
112
112
  options.viteHost,
113
- this.opts.enableLsp,
114
113
  this.opts.enablePrettier
115
114
  );
116
115
  log.debug("Starting OpenCode Web process", {
@@ -129,7 +128,8 @@ Please install OpenCode first:
129
128
  logsApiUrl: options.logsApiUrl,
130
129
  logFilesJson: this.opts.logFiles ? JSON.stringify(this.opts.logFiles) : void 0,
131
130
  verbose: options.verbose,
132
- enableLsp: this.opts.enableLsp,
131
+ // 诊断策略随 env 下发给 opencode 插件(单一来源;插件侧解析成 DiagnosticsPolicy)
132
+ diagnosticsJson: JSON.stringify(this.opts.diagnostics),
133
133
  enablePrettier: this.opts.enablePrettier,
134
134
  vueDevtoolsApiUrl: options.vueDevtoolsApiUrl
135
135
  });
@@ -273,15 +273,17 @@ function toChatSession(s) {
273
273
  };
274
274
  }
275
275
  function resolveOpenCodeOptions(options) {
276
- if (!options) return { ...import_constants.DEFAULT_OPENCODE_PROVIDER_OPTIONS };
277
- const po = options.providerOptions ?? {};
276
+ const po = options?.providerOptions ?? {};
278
277
  return {
279
278
  ...import_constants.DEFAULT_OPENCODE_PROVIDER_OPTIONS,
280
- language: po.language ?? options.language,
281
- settings: po.settings ?? options.settings,
282
- logFiles: options.logFiles,
283
- enableLsp: po.enableLsp ?? options.enableLsp,
284
- enablePrettier: po.enablePrettier ?? options.enablePrettier
279
+ language: po.language ?? options?.language,
280
+ settings: po.settings ?? options?.settings,
281
+ logFiles: options?.logFiles,
282
+ enablePrettier: po.enablePrettier ?? options?.enablePrettier,
283
+ diagnostics: (0, import_core.resolveDiagnosticsPolicy)(
284
+ [import_core.DEFAULT_DIAGNOSTICS_POLICY, po.diagnostics],
285
+ (message) => log.warn(message)
286
+ )
285
287
  };
286
288
  }
287
289
  // Annotate the CommonJS export names for ESM import in node:
package/lib/types.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * OpenCode Provider 专属类型
3
3
  * 所有与 OpenCode Web 绑定的类型自包含于此,核心层不感知。
4
4
  */
5
- import type { LogFileConfig } from "@aipanel/core";
5
+ import type { DiagnosticsPolicy, LogFileConfig } from "@aipanel/core";
6
6
  /**
7
7
  * OpenCode 界面语言选项
8
8
  */
@@ -83,13 +83,20 @@ export type OpenCodeProviderOptions = {
83
83
  settings?: OpenCodeSettings;
84
84
  /** 自定义日志文件配置 */
85
85
  logFiles?: LogFileConfig[];
86
- /** 启用 LSP 诊断(TypeScript + ESLint),agent 编辑文件后自动返回错误信息,默认 false */
87
- enableLsp?: boolean;
86
+ /**
87
+ * 诊断配置(检查来源 + 触发 + 投递;契约见 @aipanel/core 的 DiagnosticsPolicy)。
88
+ * 用户只写要覆盖的字段,provider 归一化成完整策略后随 env 下发。
89
+ */
90
+ diagnostics?: Partial<DiagnosticsPolicy>;
88
91
  /** 启用代码格式化功能(prettier),默认 true */
89
92
  enablePrettier?: boolean;
90
93
  /** 允许 Provider 自定义扩展字段(schema 由具体 Provider 定义) */
91
94
  [key: string]: unknown;
92
95
  };
96
+ /** 归一化后的 Provider 选项:诊断策略一定是完整的 */
97
+ export type OpenCodeResolvedOptions = OpenCodeProviderOptions & {
98
+ diagnostics: DiagnosticsPolicy;
99
+ };
93
100
  /**
94
101
  * OpenCode Web 服务启动选项(进程管理内部类型)
95
102
  */
@@ -114,8 +121,8 @@ export interface WebOptions {
114
121
  logFilesJson?: string;
115
122
  /** 启用 verbose 模式(环境变量透传,调试日志输出) */
116
123
  verbose?: boolean;
117
- /** 启用 LSP / 质量门禁(环境变量透传,控制 block-on-error 插件运行) */
118
- enableLsp?: boolean;
124
+ /** 诊断策略 JSON(DiagnosticsPolicy):随 env 下发给 opencode 插件 */
125
+ diagnosticsJson?: string;
119
126
  /** 启用代码格式化功能(prettier) */
120
127
  enablePrettier?: boolean;
121
128
  /** Vue DevTools API 地址(环境变量透传给 OpenCode 插件) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipanel/provider-opencode",
3
- "version": "1.3.4",
3
+ "version": "1.3.5",
4
4
  "type": "module",
5
5
  "main": "lib/index.cjs",
6
6
  "module": "es/index.js",
@@ -24,7 +24,7 @@
24
24
  "vitest": "^4.1.0"
25
25
  },
26
26
  "dependencies": {
27
- "@aipanel/core": "1.3.4",
27
+ "@aipanel/core": "1.3.5",
28
28
  "@opencode-ai/plugin": "^1.18.0",
29
29
  "execa": "^9.6.1"
30
30
  },