hypercore-cli 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/dist/api-XGC7D5AW.js +162 -0
- package/dist/auth-DNQWYQKT.js +21 -0
- package/dist/background-2EGCAAQH.js +14 -0
- package/dist/backlog-Q2NZCLNY.js +24 -0
- package/dist/chunk-2CMSCWQW.js +162 -0
- package/dist/chunk-2LJ2DVEB.js +167 -0
- package/dist/chunk-3RPFCQKJ.js +288 -0
- package/dist/chunk-43OLRXM5.js +263 -0
- package/dist/chunk-4DVYJAJL.js +57 -0
- package/dist/chunk-6OL3GA3P.js +173 -0
- package/dist/chunk-AUHU7ALH.js +2023 -0
- package/dist/chunk-B6A2AKLN.js +139 -0
- package/dist/chunk-BE46C7JW.js +46 -0
- package/dist/chunk-CUVAUOXL.js +58 -0
- package/dist/chunk-GH7E2OJE.js +223 -0
- package/dist/chunk-GOOTEPBK.js +271 -0
- package/dist/chunk-GPPMJYSM.js +133 -0
- package/dist/chunk-GU2FZQ6A.js +69 -0
- package/dist/chunk-IOPKN5GD.js +190 -0
- package/dist/chunk-IXOIOGR5.js +1505 -0
- package/dist/chunk-KRPOPWGA.js +251 -0
- package/dist/chunk-MGLJ53QN.js +219 -0
- package/dist/chunk-MV4TTRYX.js +533 -0
- package/dist/chunk-OPZYEVYR.js +150 -0
- package/dist/chunk-QTSLP47C.js +166 -0
- package/dist/chunk-R3GPQC7I.js +393 -0
- package/dist/chunk-RKB2JOV2.js +43 -0
- package/dist/chunk-RNG3K465.js +80 -0
- package/dist/chunk-TGTYKBGC.js +86 -0
- package/dist/chunk-U5SGAIMM.js +681 -0
- package/dist/chunk-V5UHPPSY.js +140 -0
- package/dist/chunk-WHLVZCQY.js +245 -0
- package/dist/chunk-XDRCBMZZ.js +66 -0
- package/dist/chunk-XOS6HPEF.js +134 -0
- package/dist/chunk-ZSBHUGWR.js +262 -0
- package/dist/claude-NSQ442XD.js +12 -0
- package/dist/commands-CK3WFAGI.js +128 -0
- package/dist/commands-U63OEO5J.js +1044 -0
- package/dist/commands-ZE6GD3WC.js +232 -0
- package/dist/config-4EW42BSF.js +8 -0
- package/dist/config-loader-SXO674TF.js +24 -0
- package/dist/diagnose-AFW3ZTZ4.js +12 -0
- package/dist/display-IIUBEYWN.js +58 -0
- package/dist/extractor-QV53W2YJ.js +129 -0
- package/dist/history-WMSCHERZ.js +180 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +406 -0
- package/dist/instance-registry-YSIJXSO7.js +15 -0
- package/dist/keybindings-JAAMLH3G.js +15 -0
- package/dist/loader-WHNTZTLP.js +58 -0
- package/dist/network-MM6YWPGO.js +279 -0
- package/dist/notify-HPTALZDC.js +14 -0
- package/dist/openai-compat-UQWJXBEK.js +12 -0
- package/dist/permissions-JUKXMNDH.js +10 -0
- package/dist/prompt-QV45TXRL.js +166 -0
- package/dist/quality-ST7PPNFR.js +16 -0
- package/dist/repl-RT3AHL7M.js +3375 -0
- package/dist/roadmap-5OBEKROY.js +17 -0
- package/dist/server-PORT7OEG.js +57 -0
- package/dist/session-4VUNDWLH.js +21 -0
- package/dist/skills-V4A35XKG.js +175 -0
- package/dist/store-Y4LU5QTO.js +25 -0
- package/dist/team-HO7Z4SIM.js +385 -0
- package/dist/telemetry-6R4EIE6O.js +30 -0
- package/dist/test-runner-ZQH5Y6OJ.js +619 -0
- package/dist/theme-3SYJ3UQA.js +14 -0
- package/dist/upgrade-7TGI3SXO.js +83 -0
- package/dist/verify-JUDKTPKZ.js +14 -0
- package/dist/web/static/app.js +562 -0
- package/dist/web/static/index.html +132 -0
- package/dist/web/static/mirror.css +1001 -0
- package/dist/web/static/mirror.html +184 -0
- package/dist/web/static/mirror.js +1125 -0
- package/dist/web/static/onboard.css +302 -0
- package/dist/web/static/onboard.html +140 -0
- package/dist/web/static/onboard.js +260 -0
- package/dist/web/static/style.css +602 -0
- package/dist/web/static/workspace.css +1568 -0
- package/dist/web/static/workspace.html +408 -0
- package/dist/web/static/workspace.js +1683 -0
- package/dist/web-Z5HSCQHW.js +39 -0
- package/package.json +67 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getEventsSummary,
|
|
3
|
+
readEvents
|
|
4
|
+
} from "./chunk-2CMSCWQW.js";
|
|
5
|
+
import {
|
|
6
|
+
addItem,
|
|
7
|
+
listItems
|
|
8
|
+
} from "./chunk-MGLJ53QN.js";
|
|
9
|
+
|
|
10
|
+
// src/admin/diagnose.ts
|
|
11
|
+
async function runRuleDiagnosis(days = 7) {
|
|
12
|
+
const findings = [];
|
|
13
|
+
const events = await readEvents(days);
|
|
14
|
+
const summary = await getEventsSummary(days);
|
|
15
|
+
if (events.length === 0) {
|
|
16
|
+
return [{
|
|
17
|
+
severity: "warning",
|
|
18
|
+
title: "\u65E0\u4E8B\u4EF6\u6570\u636E",
|
|
19
|
+
detail: `\u8FD1 ${days} \u5929\u6CA1\u6709\u91C7\u96C6\u5230\u4EFB\u4F55\u4E8B\u4EF6`,
|
|
20
|
+
evidence: "\u4E8B\u4EF6\u6587\u4EF6\u4E3A\u7A7A\u6216\u4E0D\u5B58\u5728",
|
|
21
|
+
suggestion: "\u786E\u8BA4 telemetry \u5DF2\u542F\u7528\uFF0C\u6B63\u5E38\u4F7F\u7528 Hypercore \u540E\u4F1A\u81EA\u52A8\u91C7\u96C6",
|
|
22
|
+
suggestedType: "bugfix",
|
|
23
|
+
suggestedPriority: "A",
|
|
24
|
+
suggestedWuxing: "\u6728"
|
|
25
|
+
}];
|
|
26
|
+
}
|
|
27
|
+
for (const [cmd, stats] of Object.entries(summary.cmdCounts)) {
|
|
28
|
+
const failRate = stats.total >= 3 ? stats.fail / stats.total : 0;
|
|
29
|
+
if (failRate > 0.1) {
|
|
30
|
+
const failEvents = events.filter(
|
|
31
|
+
(e) => e.event === "cmd_exec" && e.cmd === cmd && !e.ok
|
|
32
|
+
);
|
|
33
|
+
const errorMessages = failEvents.map((e) => String(e.err || "")).filter(Boolean);
|
|
34
|
+
const topError = mostCommon(errorMessages) || "\u672A\u77E5";
|
|
35
|
+
findings.push({
|
|
36
|
+
severity: "high",
|
|
37
|
+
title: `${cmd} \u5931\u8D25\u7387 ${(failRate * 100).toFixed(0)}%`,
|
|
38
|
+
detail: `\u8FD1 ${days} \u5929\u5185 ${cmd} \u6267\u884C ${stats.total} \u6B21\uFF0C\u5931\u8D25 ${stats.fail} \u6B21`,
|
|
39
|
+
evidence: `\u5E38\u89C1\u9519\u8BEF: ${topError} (${stats.fail}/${stats.total})`,
|
|
40
|
+
suggestion: `\u6392\u67E5 ${cmd} \u547D\u4EE4\u7684\u9519\u8BEF\u5904\u7406\u903B\u8F91`,
|
|
41
|
+
suggestedType: "bugfix",
|
|
42
|
+
suggestedPriority: failRate > 0.2 ? "S" : "A",
|
|
43
|
+
suggestedWuxing: "\u6728"
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const err of summary.errors) {
|
|
48
|
+
if (err.count >= 3) {
|
|
49
|
+
findings.push({
|
|
50
|
+
severity: "high",
|
|
51
|
+
title: `${err.type} \u9519\u8BEF\u9891\u53D1 (${err.count}\u6B21)`,
|
|
52
|
+
detail: `\u8FD1 ${days} \u5929\u5185 ${err.type} \u7C7B\u578B\u9519\u8BEF\u51FA\u73B0 ${err.count} \u6B21`,
|
|
53
|
+
evidence: `\u9519\u8BEF\u7C7B\u578B\u7EDF\u8BA1: ${err.type} = ${err.count}`,
|
|
54
|
+
suggestion: "\u68C0\u67E5\u9519\u8BEF\u6839\u56E0\u5E76\u4FEE\u590D",
|
|
55
|
+
suggestedType: "bugfix",
|
|
56
|
+
suggestedPriority: err.count >= 5 ? "S" : "A",
|
|
57
|
+
suggestedWuxing: "\u6728"
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const lineEvents = events.filter((e) => e.event === "line_run");
|
|
62
|
+
const lineStats = {};
|
|
63
|
+
for (const e of lineEvents) {
|
|
64
|
+
const name = String(e.line || "unknown");
|
|
65
|
+
if (!lineStats[name]) lineStats[name] = { durations: [], fails: 0, total: 0 };
|
|
66
|
+
lineStats[name].total++;
|
|
67
|
+
if (e.duration_ms) lineStats[name].durations.push(e.duration_ms);
|
|
68
|
+
if (!e.ok) lineStats[name].fails++;
|
|
69
|
+
}
|
|
70
|
+
for (const [name, stats] of Object.entries(lineStats)) {
|
|
71
|
+
if (stats.durations.length >= 3) {
|
|
72
|
+
const avg = stats.durations.reduce((a, b) => a + b, 0) / stats.durations.length;
|
|
73
|
+
const p95 = stats.durations.sort((a, b) => a - b)[Math.floor(stats.durations.length * 0.95)];
|
|
74
|
+
if (p95 > 3e4) {
|
|
75
|
+
findings.push({
|
|
76
|
+
severity: "warning",
|
|
77
|
+
title: `\u751F\u4EA7\u7EBF "${name}" \u8017\u65F6\u504F\u9AD8`,
|
|
78
|
+
detail: `\u5E73\u5747 ${(avg / 1e3).toFixed(1)}s, p95 ${(p95 / 1e3).toFixed(1)}s (${stats.total}\u6B21\u8FD0\u884C)`,
|
|
79
|
+
evidence: `\u8017\u65F6\u6570\u636E: avg=${(avg / 1e3).toFixed(1)}s, p95=${(p95 / 1e3).toFixed(1)}s`,
|
|
80
|
+
suggestion: "\u68C0\u67E5 prompt \u957F\u5EA6\u6216\u8003\u8651\u5207\u6362\u66F4\u5FEB\u7684\u6A21\u578B",
|
|
81
|
+
suggestedType: "improvement",
|
|
82
|
+
suggestedPriority: "A",
|
|
83
|
+
suggestedWuxing: "\u6728"
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const allCmds = events.filter((e) => e.event === "cmd_exec").map((e) => String(e.cmd));
|
|
89
|
+
const knownCmds = ["/model", "/team", "/memory", "/network", "/admin", "/run", "/git", "/session"];
|
|
90
|
+
for (const cmd of knownCmds) {
|
|
91
|
+
if (!allCmds.includes(cmd) && events.length > 20) {
|
|
92
|
+
findings.push({
|
|
93
|
+
severity: "opportunity",
|
|
94
|
+
title: `${cmd} \u8FD1 ${days} \u5929\u672A\u4F7F\u7528`,
|
|
95
|
+
detail: `\u5728 ${events.length} \u6761\u4E8B\u4EF6\u4E2D\u6CA1\u6709 ${cmd} \u7684\u4F7F\u7528\u8BB0\u5F55`,
|
|
96
|
+
evidence: `${cmd} \u8C03\u7528\u6B21\u6570: 0`,
|
|
97
|
+
suggestion: "\u8BC4\u4F30\u8BE5\u529F\u80FD\u662F\u5426\u9700\u8981\u6539\u8FDB\u5165\u53E3\u6216\u8003\u8651\u4E0B\u7EBF",
|
|
98
|
+
suggestedType: "idea",
|
|
99
|
+
suggestedPriority: "C",
|
|
100
|
+
suggestedWuxing: "\u91D1"
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const sessionEnds = events.filter((e) => e.event === "session_end");
|
|
105
|
+
if (sessionEnds.length >= 4) {
|
|
106
|
+
const mid = Math.floor(sessionEnds.length / 2);
|
|
107
|
+
const firstHalf = sessionEnds.slice(0, mid);
|
|
108
|
+
const secondHalf = sessionEnds.slice(mid);
|
|
109
|
+
const avgFirst = firstHalf.reduce((s, e) => s + (e.rounds || 0), 0) / firstHalf.length;
|
|
110
|
+
const avgSecond = secondHalf.reduce((s, e) => s + (e.rounds || 0), 0) / secondHalf.length;
|
|
111
|
+
if (avgFirst > 0 && avgSecond / avgFirst < 0.6) {
|
|
112
|
+
findings.push({
|
|
113
|
+
severity: "warning",
|
|
114
|
+
title: `\u4F1A\u8BDD\u6DF1\u5EA6\u4E0B\u964D (${avgFirst.toFixed(1)} \u2192 ${avgSecond.toFixed(1)} \u8F6E)`,
|
|
115
|
+
detail: `\u524D\u534A\u671F\u5E73\u5747 ${avgFirst.toFixed(1)} \u8F6E\uFF0C\u540E\u534A\u671F ${avgSecond.toFixed(1)} \u8F6E`,
|
|
116
|
+
evidence: `\u4E0B\u964D\u5E45\u5EA6: ${((1 - avgSecond / avgFirst) * 100).toFixed(0)}%`,
|
|
117
|
+
suggestion: "\u5206\u6790\u7528\u6237\u662F\u5426\u63D0\u524D\u653E\u5F03\u8FD8\u662F\u9700\u6C42\u53D8\u7B80\u77ED",
|
|
118
|
+
suggestedType: "idea",
|
|
119
|
+
suggestedPriority: "B",
|
|
120
|
+
suggestedWuxing: "\u6C34"
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return findings;
|
|
125
|
+
}
|
|
126
|
+
function buildDiagnosePrompt(summary, ruleFindings) {
|
|
127
|
+
const parts = [];
|
|
128
|
+
parts.push(`\u4F60\u662F\u8D85\u534F\u4F53 (Hypercore) \u9879\u76EE\u7684\u5185\u89C2\u8BCA\u65AD\u5F15\u64CE\u3002\u4EE5\u4E0B\u662F\u8FD1 ${summary.period.days} \u5929\u7684\u4F7F\u7528\u6570\u636E\u6458\u8981\u3002`);
|
|
129
|
+
parts.push(`\u8BF7\u5206\u6790\u5E76\u8865\u5145\u89C4\u5219\u5F15\u64CE\u53EF\u80FD\u9057\u6F0F\u7684\u6D1E\u5BDF\u3002
|
|
130
|
+
`);
|
|
131
|
+
parts.push(`## \u57FA\u7840\u7EDF\u8BA1`);
|
|
132
|
+
parts.push(`- \u4E8B\u4EF6\u603B\u6570: ${summary.totalEvents}`);
|
|
133
|
+
parts.push(`- \u4F1A\u8BDD\u6570: ${summary.totalSessions}`);
|
|
134
|
+
parts.push(`- \u5E73\u5747\u4F1A\u8BDD\u8F6E\u6B21: ${summary.avgSessionRounds}`);
|
|
135
|
+
parts.push(`- \u7EDF\u8BA1\u5468\u671F: ${summary.period.from} \u2192 ${summary.period.to}
|
|
136
|
+
`);
|
|
137
|
+
if (Object.keys(summary.cmdCounts).length > 0) {
|
|
138
|
+
parts.push(`## \u547D\u4EE4\u4F7F\u7528\u7EDF\u8BA1`);
|
|
139
|
+
for (const [cmd, stats] of Object.entries(summary.cmdCounts)) {
|
|
140
|
+
const failPart = stats.fail > 0 ? ` (\u5931\u8D25${stats.fail}\u6B21, \u7387${(stats.fail / stats.total * 100).toFixed(0)}%)` : "";
|
|
141
|
+
parts.push(`- ${cmd}: ${stats.total}\u6B21${failPart}`);
|
|
142
|
+
}
|
|
143
|
+
parts.push("");
|
|
144
|
+
}
|
|
145
|
+
if (summary.errors.length > 0) {
|
|
146
|
+
parts.push(`## \u9519\u8BEF\u7EDF\u8BA1`);
|
|
147
|
+
for (const e of summary.errors) {
|
|
148
|
+
parts.push(`- ${e.type}: ${e.count}\u6B21`);
|
|
149
|
+
}
|
|
150
|
+
parts.push("");
|
|
151
|
+
}
|
|
152
|
+
if (ruleFindings.length > 0) {
|
|
153
|
+
parts.push(`## \u89C4\u5219\u5F15\u64CE\u5DF2\u53D1\u73B0`);
|
|
154
|
+
for (const f of ruleFindings) {
|
|
155
|
+
parts.push(`- [${f.severity}] ${f.title}: ${f.detail}`);
|
|
156
|
+
}
|
|
157
|
+
parts.push("");
|
|
158
|
+
}
|
|
159
|
+
parts.push(`## \u4F60\u7684\u4EFB\u52A1`);
|
|
160
|
+
parts.push(`\u57FA\u4E8E\u4EE5\u4E0A\u6570\u636E\uFF0C\u8F93\u51FA\uFF1A`);
|
|
161
|
+
parts.push(`1. \u89C4\u5219\u5F15\u64CE\u53EF\u80FD\u9057\u6F0F\u7684\u95EE\u9898\u6216\u6A21\u5F0F`);
|
|
162
|
+
parts.push(`2. \u4F7F\u7528\u8D8B\u52BF\u7684\u6DF1\u5C42\u89E3\u8BFB`);
|
|
163
|
+
parts.push(`3. \u4EA7\u54C1\u6539\u8FDB\u673A\u4F1A`);
|
|
164
|
+
parts.push(`
|
|
165
|
+
\u6BCF\u6761\u53D1\u73B0\u8BF7\u7528\u4EE5\u4E0B\u683C\u5F0F\uFF1A`);
|
|
166
|
+
parts.push(`### [severity: high/warning/opportunity] \u6807\u9898`);
|
|
167
|
+
parts.push(`- \u8BE6\u60C5: ...`);
|
|
168
|
+
parts.push(`- \u8BC1\u636E: ...`);
|
|
169
|
+
parts.push(`- \u5EFA\u8BAE: ...`);
|
|
170
|
+
parts.push(`- \u7C7B\u578B: bugfix/feature/improvement/idea`);
|
|
171
|
+
parts.push(`- \u4F18\u5148\u7EA7: S/A/B/C`);
|
|
172
|
+
parts.push(`- \u4E94\u884C: \u6728/\u706B/\u6C34/\u91D1/\u571F`);
|
|
173
|
+
parts.push(`
|
|
174
|
+
\u5982\u679C\u6CA1\u6709\u989D\u5916\u53D1\u73B0\uFF0C\u76F4\u63A5\u8BF4"\u89C4\u5219\u5F15\u64CE\u5DF2\u8986\u76D6\u4E3B\u8981\u95EE\u9898"\u3002`);
|
|
175
|
+
return parts.join("\n");
|
|
176
|
+
}
|
|
177
|
+
function parseAIFindings(text) {
|
|
178
|
+
const findings = [];
|
|
179
|
+
const blocks = text.split(/###\s+\[/).filter(Boolean);
|
|
180
|
+
for (const block of blocks) {
|
|
181
|
+
const severityMatch = block.match(/^(high|warning|opportunity)\]\s*(.+)/);
|
|
182
|
+
if (!severityMatch) continue;
|
|
183
|
+
const severity = severityMatch[1];
|
|
184
|
+
const title = severityMatch[2].trim();
|
|
185
|
+
const detail = extractField(block, "\u8BE6\u60C5") || "";
|
|
186
|
+
const evidence = extractField(block, "\u8BC1\u636E") || "";
|
|
187
|
+
const suggestion = extractField(block, "\u5EFA\u8BAE") || "";
|
|
188
|
+
const typeStr = extractField(block, "\u7C7B\u578B") || "idea";
|
|
189
|
+
const priorityStr = extractField(block, "\u4F18\u5148\u7EA7") || "B";
|
|
190
|
+
const wuxingStr = extractField(block, "\u4E94\u884C") || "\u6728";
|
|
191
|
+
findings.push({
|
|
192
|
+
severity,
|
|
193
|
+
title,
|
|
194
|
+
detail,
|
|
195
|
+
evidence,
|
|
196
|
+
suggestion,
|
|
197
|
+
suggestedType: ["bugfix", "feature", "improvement", "idea"].includes(typeStr) ? typeStr : "idea",
|
|
198
|
+
suggestedPriority: ["S", "A", "B", "C"].includes(priorityStr) ? priorityStr : "B",
|
|
199
|
+
suggestedWuxing: ["\u6728", "\u706B", "\u6C34", "\u91D1", "\u571F"].includes(wuxingStr) ? wuxingStr : "\u6728"
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return findings;
|
|
203
|
+
}
|
|
204
|
+
function extractField(block, fieldName) {
|
|
205
|
+
const regex = new RegExp(`-\\s*${fieldName}[\uFF1A:]\\s*(.+)`, "i");
|
|
206
|
+
const match = block.match(regex);
|
|
207
|
+
return match ? match[1].trim() : "";
|
|
208
|
+
}
|
|
209
|
+
async function runAIDiagnosis(ctx, days = 7) {
|
|
210
|
+
const summary = await getEventsSummary(days);
|
|
211
|
+
const ruleFindings = await runRuleDiagnosis(days);
|
|
212
|
+
const prompt = buildDiagnosePrompt(summary, ruleFindings);
|
|
213
|
+
let aiRawText = "";
|
|
214
|
+
const aiFindings = [];
|
|
215
|
+
try {
|
|
216
|
+
if (ctx.config.modelConfig.sdkType === "openai") {
|
|
217
|
+
const { streamOpenAIChat } = await import("./openai-compat-UQWJXBEK.js");
|
|
218
|
+
const OpenAI = (await import("openai")).default;
|
|
219
|
+
const client = ctx.getClient();
|
|
220
|
+
const result = await streamOpenAIChat(client, [
|
|
221
|
+
{ role: "system", content: "\u4F60\u662F\u8D85\u534F\u4F53\u9879\u76EE\u7684\u5185\u89C2\u8BCA\u65AD\u5F15\u64CE\u3002\u5206\u6790\u6570\u636E\uFF0C\u8F93\u51FA\u7CBE\u51C6\u8BCA\u65AD\u3002\u7528\u4E2D\u6587\u56DE\u590D\u3002" },
|
|
222
|
+
{ role: "user", content: prompt }
|
|
223
|
+
], {
|
|
224
|
+
model: ctx.config.modelConfig.model,
|
|
225
|
+
tools: [],
|
|
226
|
+
onChunk: (text) => {
|
|
227
|
+
process.stdout.write(text);
|
|
228
|
+
},
|
|
229
|
+
onToolCall: () => {
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
aiRawText = result.content.trim();
|
|
233
|
+
} else {
|
|
234
|
+
const { streamCallLLM } = await import("./claude-NSQ442XD.js");
|
|
235
|
+
const Anthropic = (await import("@anthropic-ai/sdk")).default;
|
|
236
|
+
const client = ctx.getClient();
|
|
237
|
+
const result = await streamCallLLM(client, {
|
|
238
|
+
systemPrompt: "\u4F60\u662F\u8D85\u534F\u4F53\u9879\u76EE\u7684\u5185\u89C2\u8BCA\u65AD\u5F15\u64CE\u3002\u5206\u6790\u6570\u636E\uFF0C\u8F93\u51FA\u7CBE\u51C6\u8BCA\u65AD\u3002\u7528\u4E2D\u6587\u56DE\u590D\u3002",
|
|
239
|
+
userPrompt: prompt,
|
|
240
|
+
history: [],
|
|
241
|
+
tools: [],
|
|
242
|
+
model: ctx.config.modelConfig.model,
|
|
243
|
+
onText: (text) => {
|
|
244
|
+
process.stdout.write(text);
|
|
245
|
+
},
|
|
246
|
+
onToolCall: () => {
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
aiRawText = result.output.trim();
|
|
250
|
+
}
|
|
251
|
+
aiFindings.push(...parseAIFindings(aiRawText));
|
|
252
|
+
} catch (err) {
|
|
253
|
+
aiRawText = `AI \u8BCA\u65AD\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`;
|
|
254
|
+
}
|
|
255
|
+
return { ruleFindings, aiFindings, aiRawText };
|
|
256
|
+
}
|
|
257
|
+
async function findingsToBacklog(findings, targetVersion) {
|
|
258
|
+
const existing = await listItems();
|
|
259
|
+
const existingTitles = new Set(existing.map((i) => i.title.toLowerCase()));
|
|
260
|
+
const createdIds = [];
|
|
261
|
+
for (const f of findings) {
|
|
262
|
+
if (existingTitles.has(f.title.toLowerCase())) continue;
|
|
263
|
+
const item = await addItem({
|
|
264
|
+
title: f.title,
|
|
265
|
+
description: f.detail,
|
|
266
|
+
type: f.suggestedType,
|
|
267
|
+
wuxing: f.suggestedWuxing,
|
|
268
|
+
priority: f.suggestedPriority,
|
|
269
|
+
targetVersion,
|
|
270
|
+
source: "auto-diagnose",
|
|
271
|
+
evidence: f.evidence
|
|
272
|
+
});
|
|
273
|
+
createdIds.push(item.id);
|
|
274
|
+
}
|
|
275
|
+
return createdIds;
|
|
276
|
+
}
|
|
277
|
+
function mostCommon(arr) {
|
|
278
|
+
if (arr.length === 0) return void 0;
|
|
279
|
+
const counts = {};
|
|
280
|
+
for (const s of arr) counts[s] = (counts[s] || 0) + 1;
|
|
281
|
+
return Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export {
|
|
285
|
+
runRuleDiagnosis,
|
|
286
|
+
runAIDiagnosis,
|
|
287
|
+
findingsToBacklog
|
|
288
|
+
};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hookManager
|
|
3
|
+
} from "./chunk-B6A2AKLN.js";
|
|
4
|
+
|
|
5
|
+
// src/llm/claude.ts
|
|
6
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
7
|
+
function createLLMClient(config) {
|
|
8
|
+
const options = {
|
|
9
|
+
apiKey: config.apiKey
|
|
10
|
+
};
|
|
11
|
+
if (config.baseURL) {
|
|
12
|
+
options.baseURL = config.baseURL;
|
|
13
|
+
}
|
|
14
|
+
return new Anthropic(options);
|
|
15
|
+
}
|
|
16
|
+
function buildMessages(history, userPrompt) {
|
|
17
|
+
const messages = [];
|
|
18
|
+
if (history && history.length > 0) {
|
|
19
|
+
for (const msg of history) {
|
|
20
|
+
messages.push({
|
|
21
|
+
role: msg.role,
|
|
22
|
+
content: msg.content
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
messages.push({ role: "user", content: userPrompt });
|
|
27
|
+
return messages;
|
|
28
|
+
}
|
|
29
|
+
async function triggerToolHook(event, context) {
|
|
30
|
+
try {
|
|
31
|
+
return await hookManager.trigger(event, context);
|
|
32
|
+
} catch {
|
|
33
|
+
return { intercepted: false };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function callLLM(client, options) {
|
|
37
|
+
const {
|
|
38
|
+
systemPrompt,
|
|
39
|
+
userPrompt,
|
|
40
|
+
tools,
|
|
41
|
+
model = "claude-sonnet-4-20250514",
|
|
42
|
+
maxTokens = 8192,
|
|
43
|
+
history,
|
|
44
|
+
onText,
|
|
45
|
+
onToolCall,
|
|
46
|
+
onThinking,
|
|
47
|
+
onToolCallDone
|
|
48
|
+
} = options;
|
|
49
|
+
const toolDefinitions = tools.map((t) => ({
|
|
50
|
+
name: t.definition.name,
|
|
51
|
+
description: t.definition.description,
|
|
52
|
+
input_schema: t.definition.input_schema
|
|
53
|
+
}));
|
|
54
|
+
const messages = buildMessages(history, userPrompt);
|
|
55
|
+
let totalOutput = "";
|
|
56
|
+
let currentRoundText = "";
|
|
57
|
+
const allToolCalls = [];
|
|
58
|
+
let totalInputTokens = 0;
|
|
59
|
+
let totalOutputTokens = 0;
|
|
60
|
+
let maxIterations = 10;
|
|
61
|
+
while (maxIterations-- > 0) {
|
|
62
|
+
currentRoundText = "";
|
|
63
|
+
const response = await client.messages.create({
|
|
64
|
+
model,
|
|
65
|
+
max_tokens: maxTokens,
|
|
66
|
+
system: systemPrompt,
|
|
67
|
+
messages,
|
|
68
|
+
tools: toolDefinitions.length > 0 ? toolDefinitions : void 0
|
|
69
|
+
});
|
|
70
|
+
totalInputTokens += response.usage.input_tokens;
|
|
71
|
+
totalOutputTokens += response.usage.output_tokens;
|
|
72
|
+
let hasToolUse = false;
|
|
73
|
+
let thinkingEmitted = false;
|
|
74
|
+
const toolResults = [];
|
|
75
|
+
for (const block of response.content) {
|
|
76
|
+
if (block.type === "text") {
|
|
77
|
+
totalOutput += block.text;
|
|
78
|
+
currentRoundText += block.text;
|
|
79
|
+
onText?.(block.text);
|
|
80
|
+
} else if (block.type === "tool_use") {
|
|
81
|
+
hasToolUse = true;
|
|
82
|
+
if (!thinkingEmitted && currentRoundText.trim()) {
|
|
83
|
+
onThinking?.(currentRoundText.trim());
|
|
84
|
+
thinkingEmitted = true;
|
|
85
|
+
}
|
|
86
|
+
const tool = tools.find((t) => t.definition.name === block.name);
|
|
87
|
+
const toolInput = block.input;
|
|
88
|
+
const toolInputText = JSON.stringify(toolInput);
|
|
89
|
+
onToolCall?.(block.name, toolInput);
|
|
90
|
+
let result;
|
|
91
|
+
const toolStart = Date.now();
|
|
92
|
+
const hookResult = await triggerToolHook("onToolCall", {
|
|
93
|
+
model,
|
|
94
|
+
toolName: block.name,
|
|
95
|
+
toolInput: toolInputText
|
|
96
|
+
});
|
|
97
|
+
if (hookResult.intercepted) {
|
|
98
|
+
result = `Error: Hook intercepted tool call - ${hookResult.reason || "blocked"}`;
|
|
99
|
+
} else if (tool) {
|
|
100
|
+
try {
|
|
101
|
+
result = await tool.handler(toolInput);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
result = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
result = `Error: Tool "${block.name}" not found`;
|
|
107
|
+
}
|
|
108
|
+
const toolDuration = Date.now() - toolStart;
|
|
109
|
+
onToolCallDone?.(block.name, toolDuration);
|
|
110
|
+
await triggerToolHook("onToolResult", {
|
|
111
|
+
model,
|
|
112
|
+
toolName: block.name,
|
|
113
|
+
toolInput: toolInputText,
|
|
114
|
+
toolResult: result
|
|
115
|
+
});
|
|
116
|
+
allToolCalls.push({
|
|
117
|
+
toolName: block.name,
|
|
118
|
+
input: toolInput,
|
|
119
|
+
output: result
|
|
120
|
+
});
|
|
121
|
+
toolResults.push({
|
|
122
|
+
type: "tool_result",
|
|
123
|
+
tool_use_id: block.id,
|
|
124
|
+
content: result
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (hasToolUse) {
|
|
129
|
+
messages.push({ role: "assistant", content: response.content });
|
|
130
|
+
messages.push({ role: "user", content: toolResults });
|
|
131
|
+
} else {
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
if (response.stop_reason === "end_turn") break;
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
output: totalOutput,
|
|
138
|
+
responseText: allToolCalls.length > 0 ? currentRoundText : totalOutput,
|
|
139
|
+
toolCalls: allToolCalls,
|
|
140
|
+
tokenUsage: {
|
|
141
|
+
inputTokens: totalInputTokens,
|
|
142
|
+
outputTokens: totalOutputTokens
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async function streamCallLLM(client, options) {
|
|
147
|
+
const {
|
|
148
|
+
systemPrompt,
|
|
149
|
+
userPrompt,
|
|
150
|
+
tools,
|
|
151
|
+
model = "claude-sonnet-4-20250514",
|
|
152
|
+
maxTokens = 8192,
|
|
153
|
+
history,
|
|
154
|
+
onText,
|
|
155
|
+
onToolCall,
|
|
156
|
+
onThinking,
|
|
157
|
+
onToolCallDone
|
|
158
|
+
} = options;
|
|
159
|
+
const toolDefinitions = tools.map((t) => ({
|
|
160
|
+
name: t.definition.name,
|
|
161
|
+
description: t.definition.description,
|
|
162
|
+
input_schema: t.definition.input_schema
|
|
163
|
+
}));
|
|
164
|
+
const messages = buildMessages(history, userPrompt);
|
|
165
|
+
let totalOutput = "";
|
|
166
|
+
let currentRoundText = "";
|
|
167
|
+
const allToolCalls = [];
|
|
168
|
+
let totalInputTokens = 0;
|
|
169
|
+
let totalOutputTokens = 0;
|
|
170
|
+
let maxIterations = 10;
|
|
171
|
+
while (maxIterations-- > 0) {
|
|
172
|
+
currentRoundText = "";
|
|
173
|
+
const stream = client.messages.stream({
|
|
174
|
+
model,
|
|
175
|
+
max_tokens: maxTokens,
|
|
176
|
+
system: systemPrompt,
|
|
177
|
+
messages,
|
|
178
|
+
tools: toolDefinitions.length > 0 ? toolDefinitions : void 0
|
|
179
|
+
});
|
|
180
|
+
stream.on("text", (text) => {
|
|
181
|
+
totalOutput += text;
|
|
182
|
+
currentRoundText += text;
|
|
183
|
+
onText?.(text);
|
|
184
|
+
});
|
|
185
|
+
const response = await stream.finalMessage();
|
|
186
|
+
totalInputTokens += response.usage.input_tokens;
|
|
187
|
+
totalOutputTokens += response.usage.output_tokens;
|
|
188
|
+
let hasToolUse = false;
|
|
189
|
+
let thinkingEmitted = false;
|
|
190
|
+
const toolResults = [];
|
|
191
|
+
for (const block of response.content) {
|
|
192
|
+
if (block.type === "tool_use") {
|
|
193
|
+
hasToolUse = true;
|
|
194
|
+
if (!thinkingEmitted && currentRoundText.trim()) {
|
|
195
|
+
onThinking?.(currentRoundText.trim());
|
|
196
|
+
thinkingEmitted = true;
|
|
197
|
+
}
|
|
198
|
+
const toolInput = block.input;
|
|
199
|
+
const toolInputText = JSON.stringify(toolInput);
|
|
200
|
+
onToolCall?.(block.name, toolInput);
|
|
201
|
+
const tool = tools.find((t) => t.definition.name === block.name);
|
|
202
|
+
let result;
|
|
203
|
+
const toolStart = Date.now();
|
|
204
|
+
const hookResult = await triggerToolHook("onToolCall", {
|
|
205
|
+
model,
|
|
206
|
+
toolName: block.name,
|
|
207
|
+
toolInput: toolInputText
|
|
208
|
+
});
|
|
209
|
+
if (hookResult.intercepted) {
|
|
210
|
+
result = `Error: Hook intercepted tool call - ${hookResult.reason || "blocked"}`;
|
|
211
|
+
} else if (tool) {
|
|
212
|
+
try {
|
|
213
|
+
result = await tool.handler(toolInput);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
result = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
result = `Error: Tool "${block.name}" not found`;
|
|
219
|
+
}
|
|
220
|
+
const toolDuration = Date.now() - toolStart;
|
|
221
|
+
onToolCallDone?.(block.name, toolDuration);
|
|
222
|
+
await triggerToolHook("onToolResult", {
|
|
223
|
+
model,
|
|
224
|
+
toolName: block.name,
|
|
225
|
+
toolInput: toolInputText,
|
|
226
|
+
toolResult: result
|
|
227
|
+
});
|
|
228
|
+
allToolCalls.push({
|
|
229
|
+
toolName: block.name,
|
|
230
|
+
input: toolInput,
|
|
231
|
+
output: result
|
|
232
|
+
});
|
|
233
|
+
toolResults.push({
|
|
234
|
+
type: "tool_result",
|
|
235
|
+
tool_use_id: block.id,
|
|
236
|
+
content: result
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (hasToolUse) {
|
|
241
|
+
messages.push({ role: "assistant", content: response.content });
|
|
242
|
+
messages.push({ role: "user", content: toolResults });
|
|
243
|
+
} else {
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
if (response.stop_reason === "end_turn") break;
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
output: totalOutput,
|
|
250
|
+
responseText: allToolCalls.length > 0 ? currentRoundText : totalOutput,
|
|
251
|
+
toolCalls: allToolCalls,
|
|
252
|
+
tokenUsage: {
|
|
253
|
+
inputTokens: totalInputTokens,
|
|
254
|
+
outputTokens: totalOutputTokens
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export {
|
|
260
|
+
createLLMClient,
|
|
261
|
+
callLLM,
|
|
262
|
+
streamCallLLM
|
|
263
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/ui/notify.ts
|
|
2
|
+
import { exec } from "child_process";
|
|
3
|
+
import os from "os";
|
|
4
|
+
var notificationsEnabled = true;
|
|
5
|
+
function setNotificationsEnabled(enabled) {
|
|
6
|
+
notificationsEnabled = enabled;
|
|
7
|
+
}
|
|
8
|
+
function isNotificationsEnabled() {
|
|
9
|
+
return notificationsEnabled;
|
|
10
|
+
}
|
|
11
|
+
function sendNotification(title, body, subtitle) {
|
|
12
|
+
if (!notificationsEnabled) return;
|
|
13
|
+
const platform = os.platform();
|
|
14
|
+
try {
|
|
15
|
+
if (platform === "darwin") {
|
|
16
|
+
const subtitlePart = subtitle ? ` subtitle "${escapeOsascript(subtitle)}"` : "";
|
|
17
|
+
const script = `display notification "${escapeOsascript(body)}" with title "${escapeOsascript(title)}"${subtitlePart} sound name "default"`;
|
|
18
|
+
exec(`osascript -e '${script}'`, () => {
|
|
19
|
+
});
|
|
20
|
+
} else if (platform === "linux") {
|
|
21
|
+
const escapedTitle = escapeShell(title);
|
|
22
|
+
const escapedBody = escapeShell(body);
|
|
23
|
+
exec(`notify-send "${escapedTitle}" "${escapedBody}" --app-name=Hypercore`, () => {
|
|
24
|
+
});
|
|
25
|
+
} else if (platform === "win32") {
|
|
26
|
+
const escapedTitle = title.replace(/'/g, "''");
|
|
27
|
+
const escapedBody = body.replace(/'/g, "''");
|
|
28
|
+
const ps = `[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null; $template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02); $textNodes = $template.GetElementsByTagName('text'); $textNodes.Item(0).AppendChild($template.CreateTextNode('${escapedTitle}')); $textNodes.Item(1).AppendChild($template.CreateTextNode('${escapedBody}')); $toast = [Windows.UI.Notifications.ToastNotification]::new($template); [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Hypercore').Show($toast)`;
|
|
29
|
+
exec(`powershell -Command "${ps}"`, () => {
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function bellNotify() {
|
|
36
|
+
process.stdout.write("\x07");
|
|
37
|
+
}
|
|
38
|
+
function notifyIfLong(title, body, durationMs, thresholdMs = 1e4) {
|
|
39
|
+
if (durationMs >= thresholdMs) {
|
|
40
|
+
sendNotification(title, body);
|
|
41
|
+
bellNotify();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function escapeOsascript(str) {
|
|
45
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
46
|
+
}
|
|
47
|
+
function escapeShell(str) {
|
|
48
|
+
return str.replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/`/g, "\\`");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
setNotificationsEnabled,
|
|
53
|
+
isNotificationsEnabled,
|
|
54
|
+
sendNotification,
|
|
55
|
+
bellNotify,
|
|
56
|
+
notifyIfLong
|
|
57
|
+
};
|