@aipanel/dsh-plugin 1.2.26 → 1.2.28

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 +200 -155
  2. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -288,7 +288,7 @@ var PerformanceTimer = class {
288
288
  // ../../core/es/node/diagnostics.mjs
289
289
  import fs from "node:fs";
290
290
  import path from "node:path";
291
- import { exec } from "node:child_process";
291
+ import { execa } from "execa";
292
292
  import { createRequire } from "node:module";
293
293
  var log2 = createNodeLogger("Diagnostics");
294
294
  var JS_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -475,41 +475,35 @@ function parseOxlintOutput(stdout, cwd) {
475
475
  async function runOxlintFiles(pattern, cwd, warnLimit) {
476
476
  const bin = resolveOxlintBin(cwd);
477
477
  if (!bin) return {};
478
- return new Promise((resolve) => {
479
- exec(
480
- // ESLint 默认行为对齐:忽略 node_modules(oxlint 默认不排除)
481
- `node "${bin}" --format=json --ignore-pattern node_modules "${pattern}"`,
482
- { cwd, timeout: 6e4, maxBuffer: 50 * 1024 * 1024 },
483
- (error, stdout, stderr) => {
484
- const killed = error?.killed;
485
- if (killed) {
486
- log2.warn("oxlint timed out", { pattern });
487
- resolve({
488
- text: "[oxlint] \u8FD0\u884C\u5931\u8D25\uFF1A\u68C0\u67E5\u8D85\u65F6\uFF0C\u8BF7\u5C1D\u8BD5\u7F29\u5C0F\u68C0\u67E5\u8303\u56F4\u3002",
489
- diagnostics: [],
490
- engines: ["oxlint"]
491
- });
492
- return;
493
- }
494
- try {
495
- const messages = parseOxlintOutput(stdout, cwd);
496
- log2.debug("oxlint lint", {
497
- pattern,
498
- messageCount: messages.length,
499
- stderr: stderr || void 0
500
- });
501
- resolve(formatLintMessages(messages, { label: "oxlint", source: "oxlint" }, warnLimit));
502
- } catch (e) {
503
- log2.warn("oxlint failed", { pattern, error: e.message });
504
- resolve({
505
- text: `[oxlint] \u8FD0\u884C\u5931\u8D25\uFF1A${e.message}`,
506
- diagnostics: [],
507
- engines: ["oxlint"]
508
- });
509
- }
510
- }
511
- );
512
- });
478
+ const result = await execa(
479
+ "node",
480
+ [bin, "--format=json", "--ignore-pattern", "node_modules", pattern],
481
+ { cwd, timeout: 6e4, maxBuffer: 50 * 1024 * 1024, reject: false }
482
+ );
483
+ if (result.timedOut || result.isTerminated || result.isMaxBuffer) {
484
+ log2.warn("oxlint timed out", { pattern });
485
+ return {
486
+ text: "[oxlint] \u8FD0\u884C\u5931\u8D25\uFF1A\u68C0\u67E5\u8D85\u65F6\uFF0C\u8BF7\u5C1D\u8BD5\u7F29\u5C0F\u68C0\u67E5\u8303\u56F4\u3002",
487
+ diagnostics: [],
488
+ engines: ["oxlint"]
489
+ };
490
+ }
491
+ try {
492
+ const messages = parseOxlintOutput(result.stdout, cwd);
493
+ log2.debug("oxlint lint", {
494
+ pattern,
495
+ messageCount: messages.length,
496
+ stderr: result.stderr || void 0
497
+ });
498
+ return formatLintMessages(messages, { label: "oxlint", source: "oxlint" }, warnLimit);
499
+ } catch (e) {
500
+ log2.warn("oxlint failed", { pattern, error: e.message });
501
+ return {
502
+ text: `[oxlint] \u8FD0\u884C\u5931\u8D25\uFF1A${e.message}`,
503
+ diagnostics: [],
504
+ engines: ["oxlint"]
505
+ };
506
+ }
513
507
  }
514
508
  var _vueTscBin;
515
509
  function resolveVueTscBin() {
@@ -634,6 +628,22 @@ function parseTscDiags(rawOutput, filePath, projectDir, source = "tsc") {
634
628
  }
635
629
  return diags;
636
630
  }
631
+ function filterTscOutputForFile(rawOutput, filePath, projectDir) {
632
+ const resolved = path.resolve(filePath);
633
+ const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS\d+:/;
634
+ const filtered = [];
635
+ let keep = false;
636
+ for (const line of rawOutput.split("\n")) {
637
+ const m = errorLinePat.exec(line);
638
+ if (m) {
639
+ keep = path.resolve(projectDir, m[1]) === resolved;
640
+ } else if (!/^\s/.test(line)) {
641
+ keep = false;
642
+ }
643
+ if (keep) filtered.push(line);
644
+ }
645
+ return filtered.join("\n");
646
+ }
637
647
  async function runTypeCheck(filePath, cwd) {
638
648
  const dir = cwd;
639
649
  const projectDir = filePath ? findTsconfigDir(filePath) ?? dir : dir;
@@ -650,45 +660,55 @@ async function runTypeCheck(filePath, cwd) {
650
660
  }
651
661
  const timeout = filePath ? 6e4 : 12e4;
652
662
  const maxBuffer = filePath ? 10 * 1024 * 1024 : 50 * 1024 * 1024;
653
- return new Promise((resolve) => {
654
- exec(
655
- `node "${engine.bin}" --build --noEmit --pretty false`,
656
- { cwd: projectDir, timeout, maxBuffer },
657
- (error, stdout, stderr) => {
658
- let rawOutput = stdout + stderr;
659
- const killed = error?.killed;
660
- const exitCode = typeof error?.code === "number" ? error.code : killed ? 1 : 0;
661
- if (killed && !rawOutput) {
662
- rawOutput = `${engine.source} \u68C0\u67E5\u8D85\u65F6\uFF0C\u8BF7\u5C1D\u8BD5\u7F29\u5C0F\u68C0\u67E5\u8303\u56F4\u6216\u4F18\u5316\u9879\u76EE\u914D\u7F6E\u3002`;
663
- }
664
- const diagnostics = parseTscDiags(rawOutput, filePath, projectDir, engine.source);
665
- if (filePath) {
666
- const resolved = path.resolve(filePath);
667
- const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS\d+:/;
668
- const lines = rawOutput.split("\n");
669
- const filtered = [];
670
- let keep = false;
671
- for (const line of lines) {
672
- const m = errorLinePat.exec(line);
673
- if (m) {
674
- keep = path.resolve(projectDir, m[1]) === resolved;
675
- } else if (!/^\s/.test(line)) {
676
- keep = false;
677
- }
678
- if (keep) filtered.push(line);
679
- }
680
- rawOutput = filtered.join("\n");
681
- }
682
- log2.debug("type-check finished", {
683
- engine: engine.source,
684
- filePath: filePath || "(all)",
685
- exitCode,
686
- outputLength: rawOutput.length
687
- });
688
- resolve({ rawOutput, exitCode, diagnostics, source: engine.source });
689
- }
690
- );
663
+ const result = await execa("node", [engine.bin, "--build", "--noEmit", "--pretty", "false"], {
664
+ cwd: projectDir,
665
+ timeout,
666
+ maxBuffer,
667
+ reject: false
691
668
  });
669
+ let rawOutput = result.stdout + result.stderr;
670
+ const killed = result.timedOut || result.isTerminated || result.isMaxBuffer;
671
+ const exitCode = typeof result.exitCode === "number" ? result.exitCode : killed ? 1 : 0;
672
+ if (killed && !rawOutput) {
673
+ rawOutput = `${engine.source} \u68C0\u67E5\u8D85\u65F6\uFF0C\u8BF7\u5C1D\u8BD5\u7F29\u5C0F\u68C0\u67E5\u8303\u56F4\u6216\u4F18\u5316\u9879\u76EE\u914D\u7F6E\u3002`;
674
+ }
675
+ const diagnostics = parseTscDiags(rawOutput, filePath, projectDir, engine.source);
676
+ if (filePath) {
677
+ rawOutput = filterTscOutputForFile(rawOutput, filePath, projectDir);
678
+ }
679
+ log2.debug("type-check finished", {
680
+ engine: engine.source,
681
+ filePath: filePath || "(all)",
682
+ exitCode,
683
+ outputLength: rawOutput.length
684
+ });
685
+ return { rawOutput, exitCode, diagnostics, source: engine.source };
686
+ }
687
+ async function runTypeChecksForFiles(files, cwd) {
688
+ const groups = /* @__PURE__ */ new Map();
689
+ for (const file of files) {
690
+ const resolved = path.resolve(file);
691
+ const projectDir = findTsconfigDir(resolved) ?? cwd;
692
+ const group = groups.get(projectDir);
693
+ if (group) group.push(resolved);
694
+ else groups.set(projectDir, [resolved]);
695
+ }
696
+ const results = /* @__PURE__ */ new Map();
697
+ for (const [projectDir, group] of groups) {
698
+ const whole = await runTypeCheck(void 0, projectDir).catch(() => ({
699
+ rawOutput: "",
700
+ exitCode: 0
701
+ }));
702
+ for (const file of group) {
703
+ results.set(file, {
704
+ rawOutput: filterTscOutputForFile(whole.rawOutput, file, projectDir),
705
+ exitCode: whole.exitCode,
706
+ diagnostics: (whole.diagnostics ?? []).filter((d) => d.file === file),
707
+ source: whole.source
708
+ });
709
+ }
710
+ }
711
+ return results;
692
712
  }
693
713
  async function runAllChecks(pattern, cwd) {
694
714
  log2.debug("runAllChecks", { pattern, cwd });
@@ -698,6 +718,18 @@ async function runAllChecks(pattern, cwd) {
698
718
  ]);
699
719
  return { eslintOutput, tscOutput };
700
720
  }
721
+ async function runAllChecksForFiles(files, cwd) {
722
+ const targets = [...new Set(files.map((file) => path.resolve(file)))];
723
+ const tscByFile = await runTypeChecksForFiles(targets, cwd);
724
+ const results = /* @__PURE__ */ new Map();
725
+ for (const file of targets) {
726
+ results.set(file, {
727
+ eslintOutput: await lintFiles(file, cwd),
728
+ tscOutput: tscByFile.get(file) ?? { rawOutput: "", exitCode: 0 }
729
+ });
730
+ }
731
+ return results;
732
+ }
701
733
  async function runProjectDiagnostics(workspace) {
702
734
  const tscDirs = fs.existsSync(path.join(workspace, "tsconfig.json")) ? [workspace] : findAllTsconfigDirs(workspace);
703
735
  log2.debug("Tsc dirs to check", { count: tscDirs.length, dirs: tscDirs });
@@ -719,18 +751,36 @@ function tscSectionTitle(tscOutput) {
719
751
  function lintSectionTitle(lintOutput) {
720
752
  return lintOutput.engines?.length ? lintOutput.engines.join(" + ") : "ESLint";
721
753
  }
722
- function formatDiagnosticsSections(title, eslintOutput, tscOutput) {
754
+ function omittedFindingsHint(omitted) {
755
+ return `\u2026\u8FD8\u6709 ${omitted} \u6761\uFF0C\u5B8C\u6574\u7ED3\u679C\u8BF7\u8C03\u7528 run_diagnostics`;
756
+ }
757
+ function boundFindings(text, maxFindings) {
758
+ if (!maxFindings || !text) return text;
759
+ const lines = text.split("\n").filter((line) => line.trim() !== "");
760
+ if (lines.length <= maxFindings) return text;
761
+ return [...lines.slice(0, maxFindings), omittedFindingsHint(lines.length - maxFindings)].join(
762
+ "\n"
763
+ );
764
+ }
765
+ function formatDiagnosticsSections(title, eslintOutput, tscOutput, options = {}) {
766
+ const lintLines = boundFindings(eslintOutput.text, options.maxFindingsPerSection);
767
+ const tscLines = boundFindings(tscOutput.rawOutput.trim(), options.maxFindingsPerSection);
723
768
  const parts = [];
724
- parts.push(`## ${lintSectionTitle(eslintOutput)}
769
+ if (!options.onlyFindings || lintLines) {
770
+ parts.push(`## ${lintSectionTitle(eslintOutput)}
725
771
 
726
- ` + (eslintOutput.text || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898"));
727
- const tscLines = tscOutput.rawOutput.trim();
728
- parts.push(`## ${tscSectionTitle(tscOutput)}
772
+ ` + (lintLines || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898"));
773
+ }
774
+ if (!options.onlyFindings || tscLines) {
775
+ parts.push(`## ${tscSectionTitle(tscOutput)}
729
776
 
730
777
  ` + (tscLines || "\u6CA1\u6709\u53D1\u73B0\u7C7B\u578B\u9519\u8BEF"));
731
- return `${title}
778
+ }
779
+ if (parts.length === 0) return "";
780
+ const body = parts.join("\n\n");
781
+ return title ? `${title}
732
782
 
733
- ` + parts.join("\n\n");
783
+ ${body}` : body;
734
784
  }
735
785
 
736
786
  // ../../core/es/common/utils.mjs
@@ -979,46 +1029,66 @@ function buildNodeContext(e) {
979
1029
  if (e.previewPageUrl) lines.push(`\u7528\u6237\u9009\u4E2D\u8282\u70B9\u65F6\u7684\u9875\u9762 URL\uFF1A${e.previewPageUrl}`);
980
1030
  return lines.join("\n");
981
1031
  }
982
- function buildPluginMessage(text) {
1032
+ function buildPluginMessage(text, notice) {
983
1033
  return {
984
1034
  role: "user",
985
1035
  id: randomUUID(),
986
1036
  content: [{ type: "text", text }],
987
- source: { kind: "plugin", plugin: name }
1037
+ source: { kind: "plugin", plugin: name, ...notice }
988
1038
  };
989
1039
  }
990
- function registerPtcEdit(targets, rootKey, exec2, result, decision, cwd) {
991
- if (!MUTATING_TOOLS.has(exec2.name)) return;
1040
+ var DIAGNOSTICS_MESSAGE_MAX_CHARS = 4e3;
1041
+ var MAX_FINDINGS_PER_SECTION = 3;
1042
+ function editTarget(exec) {
1043
+ const rawArgs = exec.arguments;
1044
+ const filePath = typeof rawArgs?.file_path === "string" ? rawArgs.file_path : rawArgs?.filePath;
1045
+ return typeof filePath === "string" && filePath ? filePath : void 0;
1046
+ }
1047
+ function registerPendingEdit(pending, exec, result, decision, cwd) {
1048
+ if (!MUTATING_TOOLS.has(exec.name)) return;
992
1049
  if (result.isError) return;
993
1050
  if (decision.kind !== "accept") return;
994
- const rawArgs = exec2.arguments;
995
- const filePath = typeof rawArgs?.file_path === "string" ? rawArgs.file_path : rawArgs?.filePath;
996
- if (typeof filePath !== "string" || !filePath) return;
997
- if (!isJsFile(filePath)) return;
998
- let files = targets.get(rootKey);
1051
+ const filePath = editTarget(exec);
1052
+ if (!filePath || !isJsFile(filePath)) return;
1053
+ const { agent } = exec;
1054
+ if (!agent) return;
1055
+ let files = pending.get(agent);
999
1056
  if (!files) {
1000
1057
  files = /* @__PURE__ */ new Set();
1001
- targets.set(rootKey, files);
1058
+ pending.set(agent, files);
1002
1059
  }
1003
1060
  files.add(path2.resolve(cwd, filePath));
1004
1061
  }
1005
- async function collectPtcDiagnostics(files, cwd) {
1062
+ function truncateDiagnostics(text) {
1063
+ if (text.length <= DIAGNOSTICS_MESSAGE_MAX_CHARS) return text;
1064
+ return `${text.slice(0, DIAGNOSTICS_MESSAGE_MAX_CHARS)}
1065
+
1066
+ \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`;
1067
+ }
1068
+ async function collectPendingDiagnostics(pending, cwd) {
1069
+ const files = [...pending];
1070
+ const checks = await runAllChecksForFiles(files, cwd).catch(
1071
+ () => /* @__PURE__ */ new Map()
1072
+ );
1073
+ const empty = {
1074
+ eslintOutput: {},
1075
+ tscOutput: { rawOutput: "", exitCode: 0 }
1076
+ };
1006
1077
  const blocks = [];
1007
1078
  for (const file of files) {
1008
- const { eslintOutput, tscOutput } = await runAllChecks(file, cwd).catch(
1009
- () => ({
1010
- eslintOutput: {},
1011
- tscOutput: { rawOutput: "", exitCode: 0 }
1012
- })
1013
- );
1014
- if (!eslintOutput.text && !tscOutput.rawOutput.trim()) continue;
1015
- blocks.push(
1016
- formatDiagnosticsSections(`### ${path2.relative(cwd, file)}`, eslintOutput, tscOutput)
1079
+ const { eslintOutput, tscOutput } = checks.get(file) ?? empty;
1080
+ const section = formatDiagnosticsSections(
1081
+ `### ${path2.relative(cwd, file)}`,
1082
+ eslintOutput,
1083
+ tscOutput,
1084
+ { onlyFindings: true, maxFindingsPerSection: MAX_FINDINGS_PER_SECTION }
1017
1085
  );
1086
+ if (!section) continue;
1087
+ blocks.push(section);
1018
1088
  }
1019
- return blocks.length > 0 ? `\u81EA\u52A8\u8BCA\u65AD\uFF08PTC \u6279\u91CF\u7F16\u8F91\u540E\uFF09\uFF1A
1089
+ return blocks.length > 0 ? truncateDiagnostics(`\u81EA\u52A8\u8BCA\u65AD\uFF08\u7F16\u8F91\u540E\uFF09\uFF1A
1020
1090
 
1021
- ${blocks.join("\n\n")}` : "";
1091
+ ${blocks.join("\n\n")}`) : "";
1022
1092
  }
1023
1093
  function toDiagnosticEntries(items) {
1024
1094
  return items.map((d) => ({
@@ -1197,64 +1267,39 @@ function apply(ctx, config = {}) {
1197
1267
  }
1198
1268
  };
1199
1269
  tools.register(diagnosticsTool);
1200
- const pendingPtcEdits = /* @__PURE__ */ new Map();
1270
+ const pendingEdits = /* @__PURE__ */ new WeakMap();
1201
1271
  ctx.on(
1202
1272
  "tools/post-execute",
1203
- async (exec2, result, next) => {
1273
+ async (exec, result, next) => {
1204
1274
  const decision = await next();
1205
1275
  if (!autoDiagnose) return decision;
1206
- if (exec2.parent !== void 0) {
1207
- registerPtcEdit(pendingPtcEdits, String(exec2.rootCallId), exec2, result, decision, cwd);
1208
- return decision;
1209
- }
1210
- const rootKey = String(exec2.rootCallId);
1211
- const ptcTargets = pendingPtcEdits.get(rootKey);
1212
- if (ptcTargets !== void 0) {
1213
- pendingPtcEdits.delete(rootKey);
1214
- if (decision.kind !== "accept") return decision;
1215
- const ptcDiagText = await collectPtcDiagnostics(ptcTargets, cwd);
1216
- if (!ptcDiagText) return decision;
1217
- return {
1218
- ...decision,
1219
- additionalContexts: [
1220
- ...decision.additionalContexts ?? [],
1221
- buildPluginMessage(ptcDiagText)
1222
- ]
1223
- };
1224
- }
1225
- if (!MUTATING_TOOLS.has(exec2.name)) return decision;
1226
- if (result.isError) return decision;
1227
- if (decision.kind !== "accept") return decision;
1228
- const rawArgs = exec2.arguments;
1229
- const filePath = typeof rawArgs?.file_path === "string" ? rawArgs.file_path : rawArgs?.filePath;
1230
- if (typeof filePath !== "string" || !filePath) return decision;
1231
- if (!isJsFile(filePath)) return decision;
1232
- const { eslintOutput, tscOutput } = await runAllChecks(
1233
- path2.resolve(cwd, filePath),
1234
- cwd
1235
- ).catch(() => ({
1236
- eslintOutput: {},
1237
- tscOutput: { rawOutput: "", exitCode: 0 }
1238
- }));
1239
- const parts = [];
1240
- if (tscOutput.rawOutput.trim())
1241
- parts.push(`## ${tscSectionTitle(tscOutput)}
1242
-
1243
- ` + tscOutput.rawOutput.trim());
1244
- if (eslintOutput.text)
1245
- parts.push(`## ${lintSectionTitle(eslintOutput)}
1246
-
1247
- ` + eslintOutput.text);
1248
- const diagText = parts.join("\n\n");
1249
- if (!diagText) return decision;
1250
- const existing = decision.kind === "accept" && decision.content || result.content;
1276
+ registerPendingEdit(pendingEdits, exec, result, decision, cwd);
1277
+ return decision;
1278
+ }
1279
+ );
1280
+ ctx.on(
1281
+ "agent/pre-step",
1282
+ async ({ agent }, next) => {
1283
+ const decision = await next();
1284
+ if (!autoDiagnose) return decision;
1285
+ const files = pendingEdits.get(agent);
1286
+ if (!files || files.size === 0) return decision;
1287
+ pendingEdits.delete(agent);
1288
+ if (decision.kind === "reject") return decision;
1289
+ const text = await collectPendingDiagnostics(files, cwd);
1290
+ if (!text) return decision;
1251
1291
  return {
1252
- kind: "accept",
1253
- content: [...existing, { type: "text", text: `
1254
-
1255
- ${diagText}` }]
1292
+ ...decision,
1293
+ messages: [
1294
+ ...decision.messages,
1295
+ buildPluginMessage(text, {
1296
+ form: "notice",
1297
+ summary: `\u7F16\u8F91\u540E\u81EA\u52A8\u8BCA\u65AD\uFF1A${files.size} \u4E2A\u6587\u4EF6`
1298
+ })
1299
+ ]
1256
1300
  };
1257
- }
1301
+ },
1302
+ { prepend: true }
1258
1303
  );
1259
1304
  }
1260
1305
  if (vitePort > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipanel/dsh-plugin",
3
- "version": "1.2.26",
3
+ "version": "1.2.28",
4
4
  "type": "module",
5
5
  "description": "AIPanel for DeepSeek Harness (dsh):注入审查工具 run_diagnostics、编辑后自动诊断。",
6
6
  "main": "./dist/index.js",
@@ -12,10 +12,11 @@
12
12
  "registry": "https://registry.npmjs.org/"
13
13
  },
14
14
  "dependencies": {
15
+ "execa": "^9.6.1",
15
16
  "vue-tsc": "^3.3.9"
16
17
  },
17
18
  "devDependencies": {
18
- "@aipanel/core": "1.2.26",
19
+ "@aipanel/core": "1.2.28",
19
20
  "@deepseek-ai/cordis": "^4.0.2",
20
21
  "@deepseek-ai/dsh-agent": "^0.1.6-alpha.2",
21
22
  "@deepseek-ai/dsh-llm": "^0.1.6-alpha.2",
@@ -29,7 +30,7 @@
29
30
  "esbuild": "^0.25.0"
30
31
  },
31
32
  "scripts": {
32
- "build": "esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node --format=esm --target=node18 --external:@deepseek-ai/* --external:node:* --external:vue-tsc",
33
+ "build": "esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node --format=esm --target=node18 --external:@deepseek-ai/* --external:node:* --external:vue-tsc --external:execa",
33
34
  "typecheck": "tsc -p tsconfig.json"
34
35
  }
35
36
  }