@aipanel/dsh-plugin 1.2.5 → 1.2.7

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.
Files changed (2) hide show
  1. package/dist/index.js +635 -89
  2. package/package.json +7 -4
package/dist/index.js CHANGED
@@ -1,8 +1,266 @@
1
1
  // dsh-plugin/src/index.ts
2
- import path from "node:path";
2
+ import fs2 from "node:fs";
3
+ import path2 from "node:path";
3
4
  import { randomUUID } from "node:crypto";
4
- var name = "aipanel";
5
- var inject = ["tools", "subprocess"];
5
+
6
+ // ../../core/es/constants.mjs
7
+ var LOG_PREFIX = "[vite-plugin-aipanel]";
8
+ var EXT_BROADCAST = {
9
+ PAGE_CONTEXT: "PAGE_CONTEXT",
10
+ THEME_CHANGE: "THEME_CHANGE",
11
+ SERVICE_APPEARED: "SERVICE_APPEARED",
12
+ SERVICE_GONE: "SERVICE_GONE"
13
+ };
14
+ var EXT_MSG = {
15
+ ...EXT_BROADCAST,
16
+ GET_PORT_INFO: "GET_PORT_INFO",
17
+ TAB_SWITCHED: "TAB_SWITCHED",
18
+ REQUEST_PAGE_CONTEXT: "REQUEST_PAGE_CONTEXT",
19
+ SELECTION_START: "SELECTION_START",
20
+ SELECTION_STOP: "SELECTION_STOP",
21
+ CS_QUERY_WINDOW: "__CS_QUERY_WINDOW__"
22
+ };
23
+ var SEVERITY_ERROR = 1;
24
+ var SEVERITY_WARN = 2;
25
+
26
+ // ../../core/es/logger-core.mjs
27
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
28
+ LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
29
+ LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
30
+ LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
31
+ LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
32
+ LogLevel2[LogLevel2["NONE"] = 4] = "NONE";
33
+ return LogLevel2;
34
+ })(LogLevel || {});
35
+ var globalConfig = {
36
+ verbose: false,
37
+ level: 1,
38
+ showTimestamp: true,
39
+ showCaller: true,
40
+ showTrace: false,
41
+ indent: " "
42
+ };
43
+ function getConfig() {
44
+ return globalConfig;
45
+ }
46
+ function formatValue(value, depth = 0) {
47
+ if (depth > 3) return "...";
48
+ if (value === null) return "null";
49
+ if (value === void 0) return "undefined";
50
+ if (typeof value === "string") return depth > 0 ? `"${value}"` : value;
51
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
52
+ if (value instanceof Error) {
53
+ return `${value.name}: ${value.message}${value.stack ? `
54
+ ${value.stack}` : ""}`;
55
+ }
56
+ if (Array.isArray(value)) {
57
+ if (value.length === 0) return "[]";
58
+ if (value.length > 5) {
59
+ const items2 = value.slice(0, 3).map((v) => formatValue(v, depth + 1));
60
+ return `[${items2.join(", ")}, ... ${value.length - 3} more items]`;
61
+ }
62
+ const items = value.map((v) => formatValue(v, depth + 1));
63
+ return `[${items.join(", ")}]`;
64
+ }
65
+ if (typeof value === "object") {
66
+ const entries = Object.entries(value);
67
+ if (entries.length === 0) return "{}";
68
+ if (entries.length > 5) {
69
+ const shown = entries.slice(0, 3).map(([k, v]) => `${k}: ${formatValue(v, depth + 1)}`);
70
+ return `{${shown.join(", ")}, ... ${entries.length - 3} more keys}`;
71
+ }
72
+ const formatted = entries.map(([k, v]) => `${k}: ${formatValue(v, depth + 1)}`);
73
+ return `{${formatted.join(", ")}}`;
74
+ }
75
+ return String(value);
76
+ }
77
+ function formatContext(context) {
78
+ if (!context || Object.keys(context).length === 0) return "";
79
+ const parts = [];
80
+ if (context.module) parts.push(`[${context.module}]`);
81
+ if (context.operation) parts.push(`(${context.operation})`);
82
+ if (context.traceId) parts.push(`trace:${context.traceId}`);
83
+ if (context.duration !== void 0) parts.push(`${context.duration}ms`);
84
+ const extraKeys = Object.keys(context).filter(
85
+ (k) => !["module", "operation", "traceId", "duration", "error"].includes(k)
86
+ );
87
+ if (extraKeys.length > 0) {
88
+ const extra = {};
89
+ extraKeys.forEach((k) => extra[k] = context[k]);
90
+ parts.push(formatValue(extra));
91
+ }
92
+ return parts.join(" ");
93
+ }
94
+
95
+ // ../../core/es/node-logger.mjs
96
+ var __defProp = Object.defineProperty;
97
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
98
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
99
+ var COLORS = {
100
+ reset: "\x1B[0m",
101
+ dim: "\x1B[2m",
102
+ bright: "\x1B[1m",
103
+ red: "\x1B[31m",
104
+ green: "\x1B[32m",
105
+ yellow: "\x1B[33m",
106
+ blue: "\x1B[34m",
107
+ magenta: "\x1B[35m",
108
+ cyan: "\x1B[36m",
109
+ white: "\x1B[37m"
110
+ };
111
+ var LEVEL_COLORS = {
112
+ [LogLevel.DEBUG]: COLORS.cyan,
113
+ [LogLevel.INFO]: COLORS.green,
114
+ [LogLevel.WARN]: COLORS.yellow,
115
+ [LogLevel.ERROR]: COLORS.red,
116
+ [LogLevel.NONE]: COLORS.reset
117
+ };
118
+ var LEVEL_NAMES = {
119
+ [LogLevel.DEBUG]: "DEBUG",
120
+ [LogLevel.INFO]: "INFO",
121
+ [LogLevel.WARN]: "WARN",
122
+ [LogLevel.ERROR]: "ERROR",
123
+ [LogLevel.NONE]: "NONE"
124
+ };
125
+ function getTimestamp() {
126
+ const now = /* @__PURE__ */ new Date();
127
+ const hours = String(now.getHours()).padStart(2, "0");
128
+ const minutes = String(now.getMinutes()).padStart(2, "0");
129
+ const seconds = String(now.getSeconds()).padStart(2, "0");
130
+ const ms = String(now.getMilliseconds()).padStart(3, "0");
131
+ return `${hours}:${minutes}:${seconds}.${ms}`;
132
+ }
133
+ function getCallerInfo(depth = 3) {
134
+ const stack = new Error().stack;
135
+ if (!stack) return "";
136
+ const lines = stack.split("\n");
137
+ const targetLine = lines[depth];
138
+ if (!targetLine) return "";
139
+ const match = targetLine.match(/at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?/);
140
+ if (!match) return "";
141
+ const [, funcName, filePath, line] = match;
142
+ const fileName = filePath.split("/").pop() || filePath;
143
+ const func = funcName || "<anonymous>";
144
+ return `${fileName}:${line} ${func}`;
145
+ }
146
+ function log(level, message, context, ...args) {
147
+ if (level < getConfig().level) return;
148
+ const parts = [];
149
+ parts.push(`${COLORS.dim}[${process.pid}]${COLORS.reset}`);
150
+ if (getConfig().showTimestamp) {
151
+ parts.push(`${COLORS.dim}${getTimestamp()}${COLORS.reset}`);
152
+ }
153
+ const levelColor = LEVEL_COLORS[level];
154
+ const levelName = LEVEL_NAMES[level].padEnd(5);
155
+ parts.push(`${levelColor}${levelName}${COLORS.reset}`);
156
+ parts.push(`${COLORS.bright}${LOG_PREFIX}${COLORS.reset}`);
157
+ const contextStr = formatContext(context);
158
+ if (contextStr) {
159
+ parts.push(`${COLORS.magenta}${contextStr}${COLORS.reset}`);
160
+ }
161
+ parts.push(message);
162
+ if (getConfig().showCaller && level >= LogLevel.WARN) {
163
+ const caller = getCallerInfo(4);
164
+ if (caller) {
165
+ parts.push(`${COLORS.dim}(${caller})${COLORS.reset}`);
166
+ }
167
+ }
168
+ const formattedArgs = args.map((a) => formatValue(a)).join(" ");
169
+ if (formattedArgs) {
170
+ parts.push(formattedArgs);
171
+ }
172
+ if (context?.error) {
173
+ const err = context.error;
174
+ if (err instanceof Error) {
175
+ parts.push(`${COLORS.red}Error: ${err.message}${COLORS.reset}`);
176
+ if (level >= LogLevel.ERROR && getConfig().showTrace && err.stack) {
177
+ console.error(`${COLORS.dim}${err.stack}${COLORS.reset}`);
178
+ }
179
+ } else {
180
+ parts.push(`${COLORS.red}Error: ${formatValue(err)}${COLORS.reset}`);
181
+ }
182
+ }
183
+ const output = parts.join(" ");
184
+ if (level >= LogLevel.ERROR) {
185
+ console.error(output);
186
+ } else if (level === LogLevel.WARN) {
187
+ console.warn(output);
188
+ } else {
189
+ console.log(output);
190
+ }
191
+ }
192
+ var nodeLogger = {
193
+ debug(message, context, ...args) {
194
+ log(LogLevel.DEBUG, message, context, ...args);
195
+ },
196
+ info(message, context, ...args) {
197
+ log(LogLevel.INFO, message, context, ...args);
198
+ },
199
+ warn(message, context, ...args) {
200
+ log(LogLevel.WARN, message, context, ...args);
201
+ },
202
+ error(message, context, ...args) {
203
+ log(LogLevel.ERROR, message, context, ...args);
204
+ },
205
+ group(label, context) {
206
+ if (!getConfig().verbose) return;
207
+ const contextStr = formatContext(context);
208
+ console.log(
209
+ `${COLORS.dim}[${process.pid}]${COLORS.reset} ${COLORS.bright}${LOG_PREFIX}${COLORS.reset} ${COLORS.blue}\u25BC${COLORS.reset} ${label}${contextStr ? ` ${contextStr}` : ""}`
210
+ );
211
+ },
212
+ groupEnd() {
213
+ if (!getConfig().verbose) return;
214
+ }
215
+ };
216
+ function createNodeLogger(module) {
217
+ return {
218
+ debug(message, context, ...args) {
219
+ nodeLogger.debug(message, { ...context, module }, ...args);
220
+ },
221
+ info(message, context, ...args) {
222
+ nodeLogger.info(message, { ...context, module }, ...args);
223
+ },
224
+ warn(message, context, ...args) {
225
+ nodeLogger.warn(message, { ...context, module }, ...args);
226
+ },
227
+ error(message, context, ...args) {
228
+ nodeLogger.error(message, { ...context, module }, ...args);
229
+ },
230
+ timer(operation, context) {
231
+ return new PerformanceTimer(operation, { ...context, module });
232
+ }
233
+ };
234
+ }
235
+ var PerformanceTimer = class {
236
+ constructor(operation, context) {
237
+ __publicField(this, "startTime");
238
+ __publicField(this, "context");
239
+ __publicField(this, "operation");
240
+ this.operation = operation;
241
+ this.context = context || {};
242
+ this.startTime = performance.now();
243
+ nodeLogger.debug(`\u23F1\uFE0F Starting: ${operation}`, this.context);
244
+ }
245
+ end(message) {
246
+ const duration = Math.round(performance.now() - this.startTime);
247
+ const msg = message || `\u2713 Completed: ${this.operation}`;
248
+ nodeLogger.debug(msg, { ...this.context, duration });
249
+ return duration;
250
+ }
251
+ checkpoint(label) {
252
+ const elapsed = Math.round(performance.now() - this.startTime);
253
+ nodeLogger.debug(` \u21B3 ${label}`, { ...this.context, duration: elapsed });
254
+ return elapsed;
255
+ }
256
+ };
257
+
258
+ // ../../core/es/node/diagnostics.mjs
259
+ import fs from "node:fs";
260
+ import path from "node:path";
261
+ import { exec } from "node:child_process";
262
+ import { createRequire } from "node:module";
263
+ var log2 = createNodeLogger("Diagnostics");
6
264
  var JS_EXTENSIONS = /* @__PURE__ */ new Set([
7
265
  ".js",
8
266
  ".jsx",
@@ -14,6 +272,239 @@ var JS_EXTENSIONS = /* @__PURE__ */ new Set([
14
272
  ".cts",
15
273
  ".vue"
16
274
  ]);
275
+ function isJsFile(filePath) {
276
+ return JS_EXTENSIONS.has(path.extname(filePath));
277
+ }
278
+ var ESLintClass;
279
+ function loadESLint(workspace) {
280
+ if (ESLintClass) return;
281
+ log2.debug("Loading eslint", { workspace });
282
+ try {
283
+ const req = createRequire(path.join(workspace, "package.json"));
284
+ const eslintModule = req("eslint");
285
+ ESLintClass ?? (ESLintClass = eslintModule.ESLint ?? eslintModule.FlatESLint);
286
+ log2.debug("eslint loaded", { hasClass: !!ESLintClass });
287
+ } catch (e) {
288
+ log2.warn("eslint not found", { error: e.message });
289
+ }
290
+ }
291
+ async function lintFiles(pattern, cwd, warnLimit = 5) {
292
+ loadESLint(cwd);
293
+ if (!ESLintClass) return {};
294
+ try {
295
+ const eslint = new ESLintClass({ cwd });
296
+ const results = await eslint.lintFiles(pattern);
297
+ const messages = results.flatMap(
298
+ (r) => (r.messages ?? []).map((m) => ({ ...m, filePath: r.filePath }))
299
+ );
300
+ log2.debug("ESLint lint", {
301
+ pattern,
302
+ fileCount: results.length,
303
+ messageCount: messages.length
304
+ });
305
+ if (messages.length === 0) return {};
306
+ const ESLINT_ERROR = 2;
307
+ const ESLINT_WARN = 1;
308
+ const lines = [];
309
+ const errors = messages.filter((m) => m.severity === ESLINT_ERROR);
310
+ const warnings = messages.filter((m) => m.severity === ESLINT_WARN);
311
+ if (errors.length > 0) {
312
+ lines.push(
313
+ ...errors.map(
314
+ (m) => `ERROR [${m.filePath}:${m.line}:${m.column}] ${m.message} (${m.ruleId})`
315
+ )
316
+ );
317
+ }
318
+ if (warnings.length > 0) {
319
+ lines.push(
320
+ ...warnings.slice(0, warnLimit).map((m) => `WARN [${m.filePath}:${m.line}:${m.column}] ${m.message} (${m.ruleId})`)
321
+ );
322
+ if (warnings.length > warnLimit)
323
+ lines.push(`... and ${warnings.length - warnLimit} more warnings`);
324
+ }
325
+ const diagnostics = messages.map((m) => ({
326
+ severity: m.severity === ESLINT_ERROR ? SEVERITY_ERROR : m.severity === ESLINT_WARN ? SEVERITY_WARN : m.severity,
327
+ file: m.filePath,
328
+ range: {
329
+ start: { line: (m.line || 1) - 1, character: (m.column || 1) - 1 },
330
+ end: {
331
+ line: (m.endLine || m.line || 1) - 1,
332
+ character: (m.endColumn || m.column || 1) - 1
333
+ }
334
+ },
335
+ message: `[ESLint] ${m.message} (${m.ruleId})`,
336
+ source: "eslint"
337
+ }));
338
+ return { text: lines.length > 0 ? lines.join("\n") : void 0, diagnostics };
339
+ } catch (err) {
340
+ log2.warn("ESLint failed", { pattern, error: err.message });
341
+ return {};
342
+ }
343
+ }
344
+ var _vueTscBin;
345
+ function resolveVueTscBin() {
346
+ if (_vueTscBin !== void 0) return _vueTscBin;
347
+ try {
348
+ const req = createRequire(import.meta.url);
349
+ _vueTscBin = req.resolve("vue-tsc/bin/vue-tsc.js");
350
+ } catch {
351
+ _vueTscBin = null;
352
+ }
353
+ return _vueTscBin;
354
+ }
355
+ function findTsconfigDir(filePath) {
356
+ const resolved = path.resolve(filePath);
357
+ let dir = path.dirname(resolved);
358
+ log2.debug("findTsconfigDir start", { filePath: resolved });
359
+ while (true) {
360
+ const tsconfigPath = path.join(dir, "tsconfig.json");
361
+ if (fs.existsSync(tsconfigPath)) {
362
+ log2.debug("findTsconfigDir found", { dir, tsconfigPath });
363
+ return dir;
364
+ }
365
+ const parent = path.dirname(dir);
366
+ if (parent === dir) {
367
+ log2.warn("findTsconfigDir not found", { filePath: resolved });
368
+ return null;
369
+ }
370
+ dir = parent;
371
+ }
372
+ }
373
+ function findAllTsconfigDirs(workspace) {
374
+ const dirs = [];
375
+ function walk(dir) {
376
+ let entries;
377
+ try {
378
+ entries = fs.readdirSync(dir, { withFileTypes: true });
379
+ } catch {
380
+ return;
381
+ }
382
+ for (const entry of entries) {
383
+ if (!entry.isDirectory()) continue;
384
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
385
+ const full = path.join(dir, entry.name);
386
+ if (fs.existsSync(path.join(full, "tsconfig.json"))) {
387
+ dirs.push(full);
388
+ }
389
+ walk(full);
390
+ }
391
+ }
392
+ walk(workspace);
393
+ log2.debug("findAllTsconfigDirs result", {
394
+ workspace,
395
+ count: dirs.length,
396
+ dirs: dirs.map((d) => path.relative(workspace, d))
397
+ });
398
+ return dirs;
399
+ }
400
+ function parseTscDiags(rawOutput, filePath, projectDir) {
401
+ const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS(\d+):\s+(.+)$/;
402
+ const diags = [];
403
+ const resolved = filePath ? path.resolve(filePath) : void 0;
404
+ const lines = rawOutput.split("\n");
405
+ for (const line of lines) {
406
+ const match = errorLinePat.exec(line);
407
+ if (match) {
408
+ const [, file, lineNum, col, severity, code, message] = match;
409
+ const resolvedFile = projectDir ? path.resolve(projectDir, file) : path.resolve(file);
410
+ if (resolved) {
411
+ if (resolvedFile !== resolved) continue;
412
+ }
413
+ diags.push({
414
+ severity: severity === "error" ? SEVERITY_ERROR : SEVERITY_WARN,
415
+ file: resolvedFile,
416
+ range: {
417
+ start: { line: Number(lineNum) - 1, character: Number(col) - 1 },
418
+ end: { line: Number(lineNum) - 1, character: Number(col) - 1 }
419
+ },
420
+ message: `[TS${code}] ${message}`,
421
+ source: "vue-tsc"
422
+ });
423
+ }
424
+ }
425
+ return diags;
426
+ }
427
+ async function runVueTsc(filePath, cwd) {
428
+ const dir = cwd;
429
+ const projectDir = filePath ? findTsconfigDir(filePath) ?? dir : dir;
430
+ log2.debug("runVueTsc", {
431
+ filePath: filePath || "(all)",
432
+ cwd: dir,
433
+ projectDir,
434
+ processCwd: process.cwd()
435
+ });
436
+ const bin = resolveVueTscBin();
437
+ if (!bin) {
438
+ log2.warn("vue-tsc bin not found", { projectDir });
439
+ return { rawOutput: "", exitCode: 0 };
440
+ }
441
+ const timeout = filePath ? 6e4 : 12e4;
442
+ const maxBuffer = filePath ? 10 * 1024 * 1024 : 50 * 1024 * 1024;
443
+ return new Promise((resolve) => {
444
+ exec(
445
+ `node "${bin}" --build --noEmit --pretty false`,
446
+ { cwd: projectDir, timeout, maxBuffer },
447
+ (error, stdout, stderr) => {
448
+ let rawOutput = stdout + stderr;
449
+ const killed = error?.killed;
450
+ const exitCode = typeof error?.code === "number" ? error.code : killed ? 1 : 0;
451
+ if (killed && !rawOutput) {
452
+ rawOutput = "vue-tsc \u68C0\u67E5\u8D85\u65F6\uFF0C\u8BF7\u5C1D\u8BD5\u7F29\u5C0F\u68C0\u67E5\u8303\u56F4\u6216\u4F18\u5316\u9879\u76EE\u914D\u7F6E\u3002";
453
+ }
454
+ const diagnostics = parseTscDiags(rawOutput, filePath, projectDir);
455
+ if (filePath) {
456
+ const resolved = path.resolve(filePath);
457
+ const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS\d+:/;
458
+ const lines = rawOutput.split("\n");
459
+ const filtered = [];
460
+ let keep = false;
461
+ for (const line of lines) {
462
+ const m = errorLinePat.exec(line);
463
+ if (m) {
464
+ keep = path.resolve(projectDir, m[1]) === resolved;
465
+ } else if (!/^\s/.test(line)) {
466
+ keep = false;
467
+ }
468
+ if (keep) filtered.push(line);
469
+ }
470
+ rawOutput = filtered.join("\n");
471
+ }
472
+ log2.debug("vue-tsc finished", {
473
+ filePath: filePath || "(all)",
474
+ exitCode,
475
+ outputLength: rawOutput.length
476
+ });
477
+ resolve({ rawOutput, exitCode, diagnostics });
478
+ }
479
+ );
480
+ });
481
+ }
482
+ async function runAllChecks(pattern, cwd) {
483
+ log2.debug("runAllChecks", { pattern, cwd });
484
+ const [eslintOutput, tscOutput] = await Promise.all([
485
+ lintFiles(pattern, cwd),
486
+ runVueTsc(pattern, cwd)
487
+ ]);
488
+ return { eslintOutput, tscOutput };
489
+ }
490
+ async function runProjectDiagnostics(workspace) {
491
+ const tscDirs = fs.existsSync(path.join(workspace, "tsconfig.json")) ? [workspace] : findAllTsconfigDirs(workspace);
492
+ log2.debug("Tsc dirs to check", { count: tscDirs.length, dirs: tscDirs });
493
+ const [eslintOutput, ...tscOutputs] = await Promise.all([
494
+ lintFiles(".", workspace, 10),
495
+ ...tscDirs.map((dir) => runVueTsc(void 0, dir))
496
+ ]);
497
+ const mergedTsc = {
498
+ rawOutput: tscOutputs.flatMap((o) => o.rawOutput).filter(Boolean).join("\n"),
499
+ exitCode: tscOutputs.reduce((max, o) => Math.max(max, o.exitCode), 0),
500
+ diagnostics: tscOutputs.flatMap((o) => o.diagnostics ?? [])
501
+ };
502
+ return { eslintOutput, tscOutput: mergedTsc };
503
+ }
504
+
505
+ // dsh-plugin/src/index.ts
506
+ var name = "aipanel";
507
+ var inject = ["tools"];
17
508
  var MUTATING_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "apply_patch"]);
18
509
  var DEFAULT_CONTEXT_API_PATH = "/__aipanel_context__";
19
510
  function collectNodeIds(text) {
@@ -34,100 +525,155 @@ function buildNodeContext(e) {
34
525
  if (e.previewPageTitle) lines.push(`\u9875\u9762\u6807\u9898\uFF1A${e.previewPageTitle}`);
35
526
  return lines.join("\n");
36
527
  }
37
- var STDOUT_MAX_BYTES = 2e5;
38
- var STDOUT_SPILL_MAX_BYTES = 2e6;
39
- var STDERR_MAX_BYTES = 1e5;
40
- var GRACE_MS = 3e4;
528
+ function toDiagnosticEntries(items) {
529
+ return items.map((d) => ({
530
+ file: d.file ?? "",
531
+ line: d.range.start.line + 1,
532
+ column: d.range.start.character + 1,
533
+ severity: d.severity === SEVERITY_ERROR ? "error" : "warning",
534
+ message: d.message
535
+ }));
536
+ }
537
+ function buildDiagnosticsCanonical(title, eslintOutput, tscOutput) {
538
+ return {
539
+ title,
540
+ sections: [
541
+ { title: "ESLint", text: eslintOutput.text || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898" },
542
+ { title: "vue-tsc", text: tscOutput.rawOutput.trim() || "\u6CA1\u6709\u53D1\u73B0\u7C7B\u578B\u9519\u8BEF" }
543
+ ],
544
+ diagnostics: [
545
+ ...toDiagnosticEntries(eslintOutput.diagnostics ?? []),
546
+ ...toDiagnosticEntries(tscOutput.diagnostics ?? [])
547
+ ]
548
+ };
549
+ }
550
+ function renderDiagnosticsText(value) {
551
+ const body = value.sections.map((s) => `## ${s.title}
552
+
553
+ ${s.text}`).join("\n\n");
554
+ return body ? `${value.title}
555
+
556
+ ${body}` : value.title;
557
+ }
41
558
  function apply(ctx, config = {}) {
42
559
  const cwd = config.cwd ?? process.cwd();
43
- const autoDiagnose = config.autoDiagnose ?? true;
560
+ const enableDiagnostics = config.enableDiagnostics ?? false;
561
+ const autoDiagnose = config.autoDiagnose ?? process.env.OPENCODE_ENABLE_LINT === "1";
44
562
  const vitePort = config.vitePort ?? 0;
45
563
  const contextApiPath = config.contextApiPath ?? DEFAULT_CONTEXT_API_PATH;
46
564
  const tools = ctx.tools;
47
- const subprocess = ctx.subprocess;
48
- async function collectOutput(argv, signal) {
49
- try {
50
- const exe = await subprocess.resolveExecutable("npx", void 0, signal);
51
- const proc = subprocess.spawn({
52
- argv: [exe, ...argv],
53
- cwd,
54
- stdio: {
55
- stdin: "ignore",
56
- stdout: { maxBytes: STDOUT_MAX_BYTES, spill: { maxBytes: STDOUT_SPILL_MAX_BYTES } },
57
- stderr: { maxBytes: STDERR_MAX_BYTES }
58
- },
59
- graceMs: GRACE_MS,
60
- signal
61
- });
62
- await proc.done;
63
- return { text: proc.collected.stdout?.readFrom(0)?.text ?? "" };
64
- } catch (e) {
65
- return { text: "", error: String(e) };
66
- }
67
- }
68
- async function runDiagnostics(filePath, signal) {
69
- const parts = [];
70
- const tsc = await collectOutput(["tsc", "--noEmit", "--pretty", "false"], signal);
71
- if (tsc.error) parts.push("## tsc\n\n(diagnostics skipped: " + tsc.error + ")");
72
- else if (tsc.text.trim()) parts.push("## tsc\n\n" + tsc.text.trim());
73
- const eslint = await collectOutput(["eslint", filePath, "--format", "compact"], signal);
74
- if (eslint.text.trim()) parts.push("## eslint\n\n" + eslint.text.trim());
75
- return parts.join("\n\n");
76
- }
77
- const diagnosticsTool = {
78
- name: "run_diagnostics",
79
- description: "Run ESLint and TypeScript type checks on filePath and report problems. Use after editing code to verify no lint/type errors before proceeding.",
80
- parameters: {
81
- type: "object",
82
- additionalProperties: false,
83
- properties: {
84
- filePath: {
85
- type: "string",
86
- description: "Absolute or cwd-relative file path to diagnose"
565
+ if (enableDiagnostics) {
566
+ const diagnosticsTool = {
567
+ name: "run_diagnostics",
568
+ description: "\u8FD0\u884C ESLint \u548C vue-tsc \u7C7B\u578B\u68C0\u67E5\uFF0C\u8FD4\u56DE\u8BCA\u65AD\u7ED3\u679C\u3002\n\n**\u4F55\u65F6\u4F7F\u7528\u6B64\u5DE5\u5177**\uFF1A\n- \u521A\u5B8C\u6210\u4EE3\u7801\u4FEE\u6539\uFF0C\u60F3\u9A8C\u8BC1\u662F\u5426\u6709 ESLint \u9519\u8BEF\u6216\u7C7B\u578B\u9519\u8BEF\n- \u5728\u63D0\u4EA4\u4EE3\u7801\u524D\u8FDB\u884C\u8D28\u91CF\u68C0\u67E5\n- \u6392\u67E5\u7F16\u8F91\u5668\u672A\u663E\u793A\u4F46\u5B9E\u9645\u5B58\u5728\u7684\u7C7B\u578B\u95EE\u9898\n- \u4E0D\u4F20\u53C2\u6570\u53EF\u5168\u91CF\u8BCA\u65AD\u6574\u4E2A\u9879\u76EE\n\n**\u8BCA\u65AD\u5185\u5BB9**\uFF1A\n- ESLint \u89C4\u5219\u68C0\u67E5\uFF08error \u548C warning\uFF09\n- vue-tsc \u7C7B\u578B\u68C0\u67E5\uFF08TypeScript \u7C7B\u578B\u9519\u8BEF\u548C\u8B66\u544A\uFF09",
569
+ parameters: {
570
+ type: "object",
571
+ additionalProperties: false,
572
+ properties: {
573
+ filePath: {
574
+ type: "string",
575
+ description: "\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"
576
+ }
87
577
  }
88
578
  },
89
- required: ["filePath"]
90
- },
91
- output: {
92
- schema: { type: "string" },
93
- render: (_args, value) => [{ type: "text", text: value }]
94
- },
95
- async execute(args, exec) {
96
- const filePath = args?.filePath;
97
- const target = typeof filePath === "string" ? path.resolve(cwd, filePath) : cwd;
98
- return runDiagnostics(target, exec.signal).catch((e) => "diagnostics failed: " + String(e));
99
- }
100
- };
101
- tools.register(diagnosticsTool);
102
- ctx.on(
103
- "tools/post-execute",
104
- async (exec, result, next) => {
105
- const decision = await next();
106
- if (!autoDiagnose) return decision;
107
- if (!MUTATING_TOOLS.has(exec.name)) return decision;
108
- if (result.isError) return decision;
109
- if (decision.kind !== "accept") return decision;
110
- const filePath = exec.arguments?.filePath;
111
- if (typeof filePath !== "string" || !filePath) return decision;
112
- if (!JS_EXTENSIONS.has(path.extname(filePath))) return decision;
113
- const diag = await runDiagnostics(path.resolve(cwd, filePath), exec.signal).catch(
114
- () => "diagnostics unavailable"
115
- );
116
- const message = {
117
- role: "user",
118
- id: randomUUID(),
119
- content: [
120
- {
121
- type: "text",
122
- text: `Auto diagnostics after ${exec.name} (${filePath}):
123
- ${diag}`
124
- }
579
+ output: {
580
+ // 结构化 canonical 输出:文本分区(模型可见)+ 诊断数组(持久化供 client 渲染)
581
+ schema: {
582
+ type: "object",
583
+ additionalProperties: false,
584
+ properties: {
585
+ title: { type: "string" },
586
+ sections: {
587
+ type: "array",
588
+ items: {
589
+ type: "object",
590
+ additionalProperties: false,
591
+ properties: {
592
+ title: { type: "string" },
593
+ text: { type: "string" }
594
+ },
595
+ required: ["title", "text"]
596
+ }
597
+ },
598
+ diagnostics: {
599
+ type: "array",
600
+ items: {
601
+ type: "object",
602
+ additionalProperties: false,
603
+ properties: {
604
+ file: { type: "string" },
605
+ line: { type: "integer" },
606
+ column: { type: "integer" },
607
+ severity: { type: "string", enum: ["error", "warning"] },
608
+ message: { type: "string" }
609
+ },
610
+ required: ["file", "line", "column", "severity", "message"]
611
+ }
612
+ }
613
+ },
614
+ required: ["title", "sections", "diagnostics"]
615
+ },
616
+ // canonical → 模型可见文本(## ESLint / ## vue-tsc 分区,与 formatDiagnosticsSections 一致)
617
+ render: (_args, value) => [
618
+ { type: "text", text: renderDiagnosticsText(value) }
125
619
  ],
126
- source: { kind: "plugin", plugin: name }
127
- };
128
- return { kind: "accept", additionalContexts: [message] };
129
- }
130
- );
620
+ // 结构化诊断投影进持久化 meta(tool/result.meta),client dsh-client 据此渲染诊断卡片
621
+ presentationMeta: (_args, value) => ({
622
+ diagnostics: value.diagnostics
623
+ })
624
+ },
625
+ async execute(args) {
626
+ const filePath = args?.filePath;
627
+ if (typeof filePath === "string" && filePath) {
628
+ const resolved = path2.resolve(cwd, filePath);
629
+ if (!fs2.existsSync(resolved)) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${resolved}`);
630
+ const { eslintOutput: eslintOutput2, tscOutput: tscOutput2 } = await runAllChecks(resolved, cwd);
631
+ return buildDiagnosticsCanonical(
632
+ `\u8BCA\u65AD\u7ED3\u679C: ${path2.relative(cwd, resolved)}`,
633
+ eslintOutput2,
634
+ tscOutput2
635
+ );
636
+ }
637
+ const { eslintOutput, tscOutput } = await runProjectDiagnostics(cwd);
638
+ return buildDiagnosticsCanonical("\u5168\u91CF\u8BCA\u65AD\u7ED3\u679C", eslintOutput, tscOutput);
639
+ }
640
+ };
641
+ tools.register(diagnosticsTool);
642
+ ctx.on(
643
+ "tools/post-execute",
644
+ async (exec2, result, next) => {
645
+ const decision = await next();
646
+ if (!autoDiagnose) return decision;
647
+ if (exec2.parent !== void 0) return decision;
648
+ if (!MUTATING_TOOLS.has(exec2.name)) return decision;
649
+ if (result.isError) return decision;
650
+ if (decision.kind !== "accept") return decision;
651
+ const rawArgs = exec2.arguments;
652
+ const filePath = typeof rawArgs?.file_path === "string" ? rawArgs.file_path : rawArgs?.filePath;
653
+ if (typeof filePath !== "string" || !filePath) return decision;
654
+ if (!isJsFile(filePath)) return decision;
655
+ const { eslintOutput, tscOutput } = await runAllChecks(
656
+ path2.resolve(cwd, filePath),
657
+ cwd
658
+ ).catch(() => ({
659
+ eslintOutput: {},
660
+ tscOutput: { rawOutput: "", exitCode: 0 }
661
+ }));
662
+ const parts = [];
663
+ if (tscOutput.rawOutput.trim()) parts.push("## vue-tsc\n\n" + tscOutput.rawOutput.trim());
664
+ if (eslintOutput.text) parts.push("## ESLint\n\n" + eslintOutput.text);
665
+ const diagText = parts.join("\n\n");
666
+ if (!diagText) return decision;
667
+ const existing = decision.kind === "accept" && decision.content || result.content;
668
+ return {
669
+ kind: "accept",
670
+ content: [...existing, { type: "text", text: `
671
+
672
+ ${diagText}` }]
673
+ };
674
+ }
675
+ );
676
+ }
131
677
  if (vitePort > 0) {
132
678
  const contextBase = `http://127.0.0.1:${vitePort}${contextApiPath}`;
133
679
  ctx.on(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipanel/dsh-plugin",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "type": "module",
5
5
  "description": "AIPanel for DeepSeek Harness (dsh):注入审查工具 run_diagnostics、编辑后自动诊断。",
6
6
  "main": "./dist/index.js",
@@ -11,16 +11,19 @@
11
11
  "access": "public",
12
12
  "registry": "https://registry.npmjs.org/"
13
13
  },
14
+ "dependencies": {
15
+ "vue-tsc": "^3.3.9"
16
+ },
14
17
  "devDependencies": {
15
18
  "@deepseek-ai/cordis": "^4.0.1",
16
19
  "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
17
20
  "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
18
- "@deepseek-ai/dsh-subprocess": "^0.1.1-rc.2",
19
21
  "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
20
- "esbuild": "^0.25.0"
22
+ "esbuild": "^0.25.0",
23
+ "@aipanel/core": "1.2.7"
21
24
  },
22
25
  "scripts": {
23
- "build": "esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node --format=esm --target=node18 --external:@deepseek-ai/* --external:node:*",
26
+ "build": "esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node --format=esm --target=node18 --external:@deepseek-ai/* --external:node:* --external:vue-tsc",
24
27
  "typecheck": "tsc -p tsconfig.json"
25
28
  }
26
29
  }