@aipanel/dsh-plugin 1.2.4 → 1.2.6

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 +708 -87
  2. package/package.json +8 -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,99 +272,462 @@ var JS_EXTENSIONS = /* @__PURE__ */ new Set([
14
272
  ".cts",
15
273
  ".vue"
16
274
  ]);
17
- var MUTATING_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "apply_patch"]);
18
- var STDOUT_MAX_BYTES = 2e5;
19
- var STDOUT_SPILL_MAX_BYTES = 2e6;
20
- var STDERR_MAX_BYTES = 1e5;
21
- var GRACE_MS = 3e4;
22
- function apply(ctx, config = {}) {
23
- const cwd = config.cwd ?? process.cwd();
24
- const autoDiagnose = config.autoDiagnose ?? true;
25
- const tools = ctx.tools;
26
- const subprocess = ctx.subprocess;
27
- async function collectOutput(argv, signal) {
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;
28
377
  try {
29
- const exe = await subprocess.resolveExecutable("npx", void 0, signal);
30
- const proc = subprocess.spawn({
31
- argv: [exe, ...argv],
32
- cwd,
33
- stdio: {
34
- stdin: "ignore",
35
- stdout: { maxBytes: STDOUT_MAX_BYTES, spill: { maxBytes: STDOUT_SPILL_MAX_BYTES } },
36
- stderr: { maxBytes: STDERR_MAX_BYTES }
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 }
37
419
  },
38
- graceMs: GRACE_MS,
39
- signal
420
+ message: `[TS${code}] ${message}`,
421
+ source: "vue-tsc"
40
422
  });
41
- await proc.done;
42
- return { text: proc.collected.stdout?.readFrom(0)?.text ?? "" };
43
- } catch (e) {
44
- return { text: "", error: String(e) };
45
423
  }
46
424
  }
47
- async function runDiagnostics(filePath, signal) {
48
- const parts = [];
49
- const tsc = await collectOutput(["tsc", "--noEmit", "--pretty", "false"], signal);
50
- if (tsc.error) parts.push("## tsc\n\n(diagnostics skipped: " + tsc.error + ")");
51
- else if (tsc.text.trim()) parts.push("## tsc\n\n" + tsc.text.trim());
52
- const eslint = await collectOutput(["eslint", filePath, "--format", "compact"], signal);
53
- if (eslint.text.trim()) parts.push("## eslint\n\n" + eslint.text.trim());
54
- return parts.join("\n\n");
55
- }
56
- const diagnosticsTool = {
57
- name: "run_diagnostics",
58
- description: "Run ESLint and TypeScript type checks on filePath and report problems. Use after editing code to verify no lint/type errors before proceeding.",
59
- parameters: {
60
- type: "object",
61
- additionalProperties: false,
62
- properties: {
63
- filePath: {
64
- type: "string",
65
- description: "Absolute or cwd-relative file path to diagnose"
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";
66
453
  }
67
- },
68
- required: ["filePath"]
69
- },
70
- output: {
71
- schema: { type: "string" },
72
- render: (_args, value) => [{ type: "text", text: value }]
73
- },
74
- async execute(args, exec) {
75
- const filePath = args?.filePath;
76
- const target = typeof filePath === "string" ? path.resolve(cwd, filePath) : cwd;
77
- return runDiagnostics(target, exec.signal).catch((e) => "diagnostics failed: " + String(e));
78
- }
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 ?? [])
79
501
  };
80
- tools.register(diagnosticsTool);
81
- ctx.on(
82
- "tools/post-execute",
83
- async (exec, result, next) => {
84
- const decision = await next();
85
- if (!autoDiagnose) return decision;
86
- if (!MUTATING_TOOLS.has(exec.name)) return decision;
87
- if (result.isError) return decision;
88
- if (decision.kind !== "accept") return decision;
89
- const filePath = exec.arguments?.filePath;
90
- if (typeof filePath !== "string" || !filePath) return decision;
91
- if (!JS_EXTENSIONS.has(path.extname(filePath))) return decision;
92
- const diag = await runDiagnostics(path.resolve(cwd, filePath), exec.signal).catch(
93
- () => "diagnostics unavailable"
94
- );
95
- const message = {
96
- role: "user",
97
- id: randomUUID(),
98
- content: [
99
- {
100
- type: "text",
101
- text: `Auto diagnostics after ${exec.name} (${filePath}):
102
- ${diag}`
502
+ return { eslintOutput, tscOutput: mergedTsc };
503
+ }
504
+
505
+ // dsh-plugin/src/index.ts
506
+ var name = "aipanel";
507
+ var inject = ["tools"];
508
+ var MUTATING_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "apply_patch"]);
509
+ var DEFAULT_CONTEXT_API_PATH = "/__aipanel_context__";
510
+ function collectNodeIds(text) {
511
+ const ids = [];
512
+ const re = new RegExp("@\u8282\u70B9\\[(n[0-9a-z]+)\\]", "g");
513
+ let m;
514
+ while ((m = re.exec(text)) !== null) ids.push(m[1]);
515
+ return ids;
516
+ }
517
+ function buildNodeContext(e) {
518
+ const lines = [`\u8282\u70B9 ID\uFF1A${e.id ?? ""}`];
519
+ if (e.filePath) lines.push(`\u6E90\u7801\u6587\u4EF6\u8DEF\u5F84\uFF1A${e.filePath}${e.line ? `:${e.line}` : ""}`);
520
+ if (e.line) lines.push(`\u4EE3\u7801\u6240\u5728\u884C\u53F7\uFF1A${e.line}`);
521
+ if (e.column) lines.push(`\u4EE3\u7801\u6240\u5728\u5217\u53F7\uFF1A${e.column}`);
522
+ if (e.description) lines.push(`DOM \u5143\u7D20\u9009\u62E9\u5668\uFF1A${e.description}`);
523
+ if (e.innerText) lines.push(`DOM \u5143\u7D20\u5185\u90E8\u6587\u672C\uFF1A${e.innerText.slice(0, 200)}`);
524
+ if (e.previewPageUrl) lines.push(`\u7528\u6237\u9009\u4E2D\u8282\u70B9\u65F6\u7684\u9875\u9762 URL\uFF1A${e.previewPageUrl}`);
525
+ if (e.previewPageTitle) lines.push(`\u9875\u9762\u6807\u9898\uFF1A${e.previewPageTitle}`);
526
+ return lines.join("\n");
527
+ }
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
+ }
558
+ function apply(ctx, config = {}) {
559
+ const cwd = config.cwd ?? process.cwd();
560
+ const enableDiagnostics = config.enableDiagnostics ?? false;
561
+ const autoDiagnose = config.autoDiagnose ?? process.env.OPENCODE_ENABLE_LINT === "1";
562
+ const vitePort = config.vitePort ?? 0;
563
+ const contextApiPath = config.contextApiPath ?? DEFAULT_CONTEXT_API_PATH;
564
+ const tools = ctx.tools;
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"
103
576
  }
577
+ }
578
+ },
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) }
104
619
  ],
105
- source: { kind: "plugin", plugin: name }
106
- };
107
- return { kind: "accept", additionalContexts: [message] };
108
- }
109
- );
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
+ }
677
+ if (vitePort > 0) {
678
+ const contextBase = `http://127.0.0.1:${vitePort}${contextApiPath}`;
679
+ ctx.on(
680
+ "agent/pre-step",
681
+ async ({ signal }, next) => {
682
+ const decision = await next();
683
+ if (decision.kind === "reject") return decision;
684
+ const ids = /* @__PURE__ */ new Set();
685
+ for (const message of decision.messages) {
686
+ if (message.source.kind !== "user") continue;
687
+ for (const block of message.content) {
688
+ if (block.type !== "text") continue;
689
+ for (const id of collectNodeIds(block.text)) ids.add(id);
690
+ }
691
+ }
692
+ if (ids.size === 0) return decision;
693
+ let elements = [];
694
+ try {
695
+ const res = await fetch(contextBase, { signal });
696
+ if (res.ok) {
697
+ const pc = await res.json();
698
+ elements = pc.selectedElements ?? [];
699
+ }
700
+ } catch {
701
+ }
702
+ const byId = /* @__PURE__ */ new Map();
703
+ for (const el of elements) {
704
+ if (el.id) byId.set(el.id, el);
705
+ }
706
+ const injected = [...ids].map((id) => byId.get(id)).filter((el) => el !== void 0);
707
+ if (injected.length === 0) return decision;
708
+ const contextText = injected.map(buildNodeContext).join("\n\n---\n\n");
709
+ const contextMessage = {
710
+ role: "user",
711
+ id: randomUUID(),
712
+ content: [
713
+ {
714
+ type: "text",
715
+ text: `\u4EE5\u4E0B\u662F\u7528\u6237\u5F15\u7528\u8282\u70B9\u7684\u5B8C\u6574\u4E0A\u4E0B\u6587\uFF08\u8282\u70B9 ID \u4E0E\u6D88\u606F\u4E2D\u7684 @\u8282\u70B9[id] \u6807\u8BB0\u5BF9\u5E94\uFF09\uFF1A
716
+
717
+ ${contextText}`
718
+ }
719
+ ],
720
+ source: { kind: "plugin", plugin: name }
721
+ };
722
+ try {
723
+ await fetch(contextBase, { method: "DELETE", signal });
724
+ } catch {
725
+ }
726
+ return { ...decision, messages: [...decision.messages, contextMessage] };
727
+ },
728
+ { prepend: true }
729
+ );
730
+ }
110
731
  }
111
732
  export {
112
733
  apply,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipanel/dsh-plugin",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "type": "module",
5
5
  "description": "AIPanel for DeepSeek Harness (dsh):注入审查工具 run_diagnostics、编辑后自动诊断。",
6
6
  "main": "./dist/index.js",
@@ -11,15 +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",
19
+ "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
16
20
  "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
17
- "@deepseek-ai/dsh-subprocess": "^0.1.1-rc.2",
18
21
  "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
19
- "esbuild": "^0.25.0"
22
+ "esbuild": "^0.25.0",
23
+ "@aipanel/core": "1.2.6"
20
24
  },
21
25
  "scripts": {
22
- "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",
23
27
  "typecheck": "tsc -p tsconfig.json"
24
28
  }
25
29
  }