@aipanel/dsh-plugin 1.2.27 → 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 +147 -94
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -628,6 +628,22 @@ function parseTscDiags(rawOutput, filePath, projectDir, source = "tsc") {
628
628
  }
629
629
  return diags;
630
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
+ }
631
647
  async function runTypeCheck(filePath, cwd) {
632
648
  const dir = cwd;
633
649
  const projectDir = filePath ? findTsconfigDir(filePath) ?? dir : dir;
@@ -658,21 +674,7 @@ async function runTypeCheck(filePath, cwd) {
658
674
  }
659
675
  const diagnostics = parseTscDiags(rawOutput, filePath, projectDir, engine.source);
660
676
  if (filePath) {
661
- const resolved = path.resolve(filePath);
662
- const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS\d+:/;
663
- const lines = rawOutput.split("\n");
664
- const filtered = [];
665
- let keep = false;
666
- for (const line of lines) {
667
- const m = errorLinePat.exec(line);
668
- if (m) {
669
- keep = path.resolve(projectDir, m[1]) === resolved;
670
- } else if (!/^\s/.test(line)) {
671
- keep = false;
672
- }
673
- if (keep) filtered.push(line);
674
- }
675
- rawOutput = filtered.join("\n");
677
+ rawOutput = filterTscOutputForFile(rawOutput, filePath, projectDir);
676
678
  }
677
679
  log2.debug("type-check finished", {
678
680
  engine: engine.source,
@@ -682,6 +684,32 @@ async function runTypeCheck(filePath, cwd) {
682
684
  });
683
685
  return { rawOutput, exitCode, diagnostics, source: engine.source };
684
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;
712
+ }
685
713
  async function runAllChecks(pattern, cwd) {
686
714
  log2.debug("runAllChecks", { pattern, cwd });
687
715
  const [eslintOutput, tscOutput] = await Promise.all([
@@ -690,6 +718,18 @@ async function runAllChecks(pattern, cwd) {
690
718
  ]);
691
719
  return { eslintOutput, tscOutput };
692
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
+ }
693
733
  async function runProjectDiagnostics(workspace) {
694
734
  const tscDirs = fs.existsSync(path.join(workspace, "tsconfig.json")) ? [workspace] : findAllTsconfigDirs(workspace);
695
735
  log2.debug("Tsc dirs to check", { count: tscDirs.length, dirs: tscDirs });
@@ -711,18 +751,36 @@ function tscSectionTitle(tscOutput) {
711
751
  function lintSectionTitle(lintOutput) {
712
752
  return lintOutput.engines?.length ? lintOutput.engines.join(" + ") : "ESLint";
713
753
  }
714
- 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);
715
768
  const parts = [];
716
- parts.push(`## ${lintSectionTitle(eslintOutput)}
769
+ if (!options.onlyFindings || lintLines) {
770
+ parts.push(`## ${lintSectionTitle(eslintOutput)}
717
771
 
718
- ` + (eslintOutput.text || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898"));
719
- const tscLines = tscOutput.rawOutput.trim();
720
- parts.push(`## ${tscSectionTitle(tscOutput)}
772
+ ` + (lintLines || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898"));
773
+ }
774
+ if (!options.onlyFindings || tscLines) {
775
+ parts.push(`## ${tscSectionTitle(tscOutput)}
721
776
 
722
777
  ` + (tscLines || "\u6CA1\u6709\u53D1\u73B0\u7C7B\u578B\u9519\u8BEF"));
723
- return `${title}
778
+ }
779
+ if (parts.length === 0) return "";
780
+ const body = parts.join("\n\n");
781
+ return title ? `${title}
724
782
 
725
- ` + parts.join("\n\n");
783
+ ${body}` : body;
726
784
  }
727
785
 
728
786
  // ../../core/es/common/utils.mjs
@@ -971,46 +1029,66 @@ function buildNodeContext(e) {
971
1029
  if (e.previewPageUrl) lines.push(`\u7528\u6237\u9009\u4E2D\u8282\u70B9\u65F6\u7684\u9875\u9762 URL\uFF1A${e.previewPageUrl}`);
972
1030
  return lines.join("\n");
973
1031
  }
974
- function buildPluginMessage(text) {
1032
+ function buildPluginMessage(text, notice) {
975
1033
  return {
976
1034
  role: "user",
977
1035
  id: randomUUID(),
978
1036
  content: [{ type: "text", text }],
979
- source: { kind: "plugin", plugin: name }
1037
+ source: { kind: "plugin", plugin: name, ...notice }
980
1038
  };
981
1039
  }
982
- function registerPtcEdit(targets, rootKey, exec, result, decision, cwd) {
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) {
983
1048
  if (!MUTATING_TOOLS.has(exec.name)) return;
984
1049
  if (result.isError) return;
985
1050
  if (decision.kind !== "accept") return;
986
- const rawArgs = exec.arguments;
987
- const filePath = typeof rawArgs?.file_path === "string" ? rawArgs.file_path : rawArgs?.filePath;
988
- if (typeof filePath !== "string" || !filePath) return;
989
- if (!isJsFile(filePath)) return;
990
- 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);
991
1056
  if (!files) {
992
1057
  files = /* @__PURE__ */ new Set();
993
- targets.set(rootKey, files);
1058
+ pending.set(agent, files);
994
1059
  }
995
1060
  files.add(path2.resolve(cwd, filePath));
996
1061
  }
997
- 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
+ };
998
1077
  const blocks = [];
999
1078
  for (const file of files) {
1000
- const { eslintOutput, tscOutput } = await runAllChecks(file, cwd).catch(
1001
- () => ({
1002
- eslintOutput: {},
1003
- tscOutput: { rawOutput: "", exitCode: 0 }
1004
- })
1005
- );
1006
- if (!eslintOutput.text && !tscOutput.rawOutput.trim()) continue;
1007
- blocks.push(
1008
- 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 }
1009
1085
  );
1086
+ if (!section) continue;
1087
+ blocks.push(section);
1010
1088
  }
1011
- 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
1012
1090
 
1013
- ${blocks.join("\n\n")}` : "";
1091
+ ${blocks.join("\n\n")}`) : "";
1014
1092
  }
1015
1093
  function toDiagnosticEntries(items) {
1016
1094
  return items.map((d) => ({
@@ -1189,64 +1267,39 @@ function apply(ctx, config = {}) {
1189
1267
  }
1190
1268
  };
1191
1269
  tools.register(diagnosticsTool);
1192
- const pendingPtcEdits = /* @__PURE__ */ new Map();
1270
+ const pendingEdits = /* @__PURE__ */ new WeakMap();
1193
1271
  ctx.on(
1194
1272
  "tools/post-execute",
1195
1273
  async (exec, result, next) => {
1196
1274
  const decision = await next();
1197
1275
  if (!autoDiagnose) return decision;
1198
- if (exec.parent !== void 0) {
1199
- registerPtcEdit(pendingPtcEdits, String(exec.rootCallId), exec, result, decision, cwd);
1200
- return decision;
1201
- }
1202
- const rootKey = String(exec.rootCallId);
1203
- const ptcTargets = pendingPtcEdits.get(rootKey);
1204
- if (ptcTargets !== void 0) {
1205
- pendingPtcEdits.delete(rootKey);
1206
- if (decision.kind !== "accept") return decision;
1207
- const ptcDiagText = await collectPtcDiagnostics(ptcTargets, cwd);
1208
- if (!ptcDiagText) return decision;
1209
- return {
1210
- ...decision,
1211
- additionalContexts: [
1212
- ...decision.additionalContexts ?? [],
1213
- buildPluginMessage(ptcDiagText)
1214
- ]
1215
- };
1216
- }
1217
- if (!MUTATING_TOOLS.has(exec.name)) return decision;
1218
- if (result.isError) return decision;
1219
- if (decision.kind !== "accept") return decision;
1220
- const rawArgs = exec.arguments;
1221
- const filePath = typeof rawArgs?.file_path === "string" ? rawArgs.file_path : rawArgs?.filePath;
1222
- if (typeof filePath !== "string" || !filePath) return decision;
1223
- if (!isJsFile(filePath)) return decision;
1224
- const { eslintOutput, tscOutput } = await runAllChecks(
1225
- path2.resolve(cwd, filePath),
1226
- cwd
1227
- ).catch(() => ({
1228
- eslintOutput: {},
1229
- tscOutput: { rawOutput: "", exitCode: 0 }
1230
- }));
1231
- const parts = [];
1232
- if (tscOutput.rawOutput.trim())
1233
- parts.push(`## ${tscSectionTitle(tscOutput)}
1234
-
1235
- ` + tscOutput.rawOutput.trim());
1236
- if (eslintOutput.text)
1237
- parts.push(`## ${lintSectionTitle(eslintOutput)}
1238
-
1239
- ` + eslintOutput.text);
1240
- const diagText = parts.join("\n\n");
1241
- if (!diagText) return decision;
1242
- 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;
1243
1291
  return {
1244
- kind: "accept",
1245
- content: [...existing, { type: "text", text: `
1246
-
1247
- ${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
+ ]
1248
1300
  };
1249
- }
1301
+ },
1302
+ { prepend: true }
1250
1303
  );
1251
1304
  }
1252
1305
  if (vitePort > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipanel/dsh-plugin",
3
- "version": "1.2.27",
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",
@@ -16,7 +16,7 @@
16
16
  "vue-tsc": "^3.3.9"
17
17
  },
18
18
  "devDependencies": {
19
- "@aipanel/core": "1.2.27",
19
+ "@aipanel/core": "1.2.28",
20
20
  "@deepseek-ai/cordis": "^4.0.2",
21
21
  "@deepseek-ai/dsh-agent": "^0.1.6-alpha.2",
22
22
  "@deepseek-ai/dsh-llm": "^0.1.6-alpha.2",