tt-help-cli-ycl 1.4.71 → 1.4.72
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/package.json +1 -1
- package/src/watch/watchdog-server.js +26 -0
- package/src/watchdog/agent.js +22 -34
- package/src/watchdog/llm-analyzer.js +78 -51
package/package.json
CHANGED
|
@@ -196,6 +196,7 @@ async function handleHeartbeat(params, body, res) {
|
|
|
196
196
|
// 按 userId 返回该客户端的专属命令列表
|
|
197
197
|
const userId = (body.config && body.config.userId) || "";
|
|
198
198
|
const commands = getClientCommands(userId);
|
|
199
|
+
|
|
199
200
|
const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
|
|
200
201
|
console.error(
|
|
201
202
|
`[watchdog ${ts}] 心跳: ${clientId} (${body.hostname || "?"}, user=${userId || "?"}) ${body.processes?.length || 0}进程, 返回${commands.length}条命令`,
|
|
@@ -247,6 +248,27 @@ async function handleClientConfig(params, body, res) {
|
|
|
247
248
|
sendJSON(res, 200, { ok: true });
|
|
248
249
|
}
|
|
249
250
|
|
|
251
|
+
async function handleAnalyzeLogs(params, body, res) {
|
|
252
|
+
const { processLogs } = body;
|
|
253
|
+
if (!processLogs || !Array.isArray(processLogs) || processLogs.length === 0) {
|
|
254
|
+
sendJSON(res, 200, { llmAnalysis: [] });
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
const { analyzeProcessLogs } = await import("../watchdog/llm-analyzer.js");
|
|
259
|
+
const { llmFetch } = await import("./llm-config.js");
|
|
260
|
+
console.error(`[watchdog] LLM 分析 ${processLogs.length} 个进程日志...`);
|
|
261
|
+
const llmAnalysis = await analyzeProcessLogs(processLogs, llmFetch);
|
|
262
|
+
console.error(
|
|
263
|
+
`[watchdog] LLM 分析完成: ${llmAnalysis.map((r) => r.severity).join(",")}`,
|
|
264
|
+
);
|
|
265
|
+
sendJSON(res, 200, { llmAnalysis });
|
|
266
|
+
} catch (e) {
|
|
267
|
+
console.error(`[watchdog] LLM 分析失败: ${e.message}`);
|
|
268
|
+
sendJSON(res, 500, { error: e.message });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
250
272
|
// ── 公开 ──
|
|
251
273
|
|
|
252
274
|
export function registerWatchdogRoutes(httpServer) {
|
|
@@ -277,5 +299,9 @@ export async function tryHandleWatchdogRoute(
|
|
|
277
299
|
await handleClientConfig(params, body, res);
|
|
278
300
|
return true;
|
|
279
301
|
}
|
|
302
|
+
if (method === "POST" && routePath === "/api/watchdog/analyze-logs") {
|
|
303
|
+
await handleAnalyzeLogs(params, body, res);
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
280
306
|
return false;
|
|
281
307
|
}
|
package/src/watchdog/agent.js
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
isGlobalInstall,
|
|
16
16
|
startUpgradeChecker,
|
|
17
17
|
} from "./upgrader.js";
|
|
18
|
-
import { analyzeProcessLogs } from "./llm-analyzer.js";
|
|
19
18
|
import { writeFileSync, existsSync, readFileSync } from "fs";
|
|
20
19
|
import { join } from "path";
|
|
21
20
|
import { homedir } from "os";
|
|
@@ -72,12 +71,12 @@ export class WatchdogAgent {
|
|
|
72
71
|
this.heartbeatTimer = null;
|
|
73
72
|
this.thresholdTimer = null;
|
|
74
73
|
this.llmAnalyzerTimer = null;
|
|
75
|
-
this._llmResults = []; // 最近一次 LLM 分析结果
|
|
76
74
|
this.processManager = new ProcessManager();
|
|
77
75
|
this.consecutiveFailures = 0;
|
|
78
76
|
this.maxFailures = 10;
|
|
79
77
|
this.upgradeChecker = null;
|
|
80
78
|
this.ttHelpVersion = getVersion();
|
|
79
|
+
this._llmResults = []; // 服务端返回的 LLM 分析结果
|
|
81
80
|
this._heartbeatSeq = 0; // 心跳序号
|
|
82
81
|
this._lastProcCount = -1; // 上次进程数
|
|
83
82
|
this._lastCmdCount = -1; // 上次命令数
|
|
@@ -113,7 +112,7 @@ export class WatchdogAgent {
|
|
|
113
112
|
this.processManager.checkThresholds();
|
|
114
113
|
}, 5000);
|
|
115
114
|
|
|
116
|
-
// LLM 日志分析(每 10
|
|
115
|
+
// LLM 日志分析(每 10 分钟调服务端接口)
|
|
117
116
|
this._startLlmAnalyzer();
|
|
118
117
|
|
|
119
118
|
// 自动升级检测(仅全局安装模式)
|
|
@@ -242,46 +241,35 @@ export class WatchdogAgent {
|
|
|
242
241
|
}
|
|
243
242
|
|
|
244
243
|
/**
|
|
245
|
-
* 启动 LLM 日志分析定时器(每 10
|
|
244
|
+
* 启动 LLM 日志分析定时器(每 10 分钟调服务端接口)
|
|
246
245
|
*/
|
|
247
246
|
_startLlmAnalyzer() {
|
|
248
|
-
// 首次 60s 后执行(等进程启动稳定),之后每 10 分钟
|
|
249
247
|
const run = async () => {
|
|
250
248
|
try {
|
|
251
|
-
console.error("[llm-analyzer] 定时触发...");
|
|
252
249
|
const logs = this.processManager.getProcessesLogs();
|
|
253
|
-
if (logs.length === 0)
|
|
250
|
+
if (logs.length === 0) return;
|
|
251
|
+
console.error(
|
|
252
|
+
`[llm-analyzer] 发送 ${logs.length} 个进程日志到服务端分析...`,
|
|
253
|
+
);
|
|
254
|
+
const resp = await fetch(
|
|
255
|
+
`${this.serverUrl}/api/watchdog/analyze-logs`,
|
|
256
|
+
{
|
|
257
|
+
method: "POST",
|
|
258
|
+
headers: { "Content-Type": "application/json" },
|
|
259
|
+
body: JSON.stringify({ processLogs: logs }),
|
|
260
|
+
signal: AbortSignal.timeout(60000),
|
|
261
|
+
},
|
|
262
|
+
);
|
|
263
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
264
|
+
const data = await resp.json();
|
|
265
|
+
if (data.llmAnalysis && data.llmAnalysis.length > 0) {
|
|
266
|
+
this._llmResults = data.llmAnalysis;
|
|
254
267
|
console.error(
|
|
255
|
-
|
|
268
|
+
`[llm-analyzer] 分析完成: ${this._llmResults.map((r) => r.severity).join(",")}`,
|
|
256
269
|
);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
console.error(`[llm-analyzer] 开始分析 ${logs.length} 个进程的日志...`);
|
|
260
|
-
this._llmResults = await analyzeProcessLogs(logs);
|
|
261
|
-
if (this._llmResults.length === 0) {
|
|
262
|
-
this._llmResults = [
|
|
263
|
-
{
|
|
264
|
-
cmdId: "_status",
|
|
265
|
-
severity: "warn",
|
|
266
|
-
summary: "LLM 分析未返回结果,请检查 LLM 配置",
|
|
267
|
-
suggestions: [],
|
|
268
|
-
},
|
|
269
|
-
];
|
|
270
270
|
}
|
|
271
|
-
const summary = this._llmResults
|
|
272
|
-
.map((r) => `${r.cmdId.substring(0, 30)}: ${r.severity}`)
|
|
273
|
-
.join(", ");
|
|
274
|
-
console.error(`[llm-analyzer] 分析完成: ${summary || "无异常"}`);
|
|
275
271
|
} catch (e) {
|
|
276
|
-
console.error(`[llm-analyzer]
|
|
277
|
-
this._llmResults = [
|
|
278
|
-
{
|
|
279
|
-
cmdId: "_status",
|
|
280
|
-
severity: "error",
|
|
281
|
-
summary: "LLM 分析异常: " + e.message.substring(0, 80),
|
|
282
|
-
suggestions: [],
|
|
283
|
-
},
|
|
284
|
-
];
|
|
272
|
+
console.error(`[llm-analyzer] 调用失败: ${e.message}`);
|
|
285
273
|
}
|
|
286
274
|
};
|
|
287
275
|
|
|
@@ -1,79 +1,106 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LLM 日志分析模块 —
|
|
2
|
+
* LLM 日志分析模块 — 供服务端调用(传入 llmFetch),或客户端独立使用(读环境变量)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 分析结果通过心跳上报到 watch 服务端展示。
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
// ── 分析入口 ──
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* 对所有进程的日志调用 LLM 分析
|
|
11
|
-
* @param {Array
|
|
12
|
-
* @
|
|
11
|
+
* @param {Array} processLogs — [{cmdId, logs[], uptimeSeconds, restartCount}]
|
|
12
|
+
* @param {Function} [llmFetch] — 可选,服务端传入的 llmFetch;不传则读环境变量
|
|
13
|
+
* @returns {Promise<Array<{cmdId, severity, summary, suggestions}>>}
|
|
13
14
|
*/
|
|
14
|
-
export async function analyzeProcessLogs(processLogs) {
|
|
15
|
+
export async function analyzeProcessLogs(processLogs, llmFetch) {
|
|
15
16
|
if (!processLogs || processLogs.length === 0) return [];
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
// 如果没有传入 llmFetch,使用内置的轻量调用(客户端模式)
|
|
19
|
+
if (!llmFetch) {
|
|
20
|
+
llmFetch = await createStandaloneFetch();
|
|
21
|
+
if (!llmFetch) return [];
|
|
22
|
+
}
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
const results = [];
|
|
25
|
+
for (const proc of processLogs) {
|
|
26
|
+
try {
|
|
27
|
+
console.error(`[llm-analyzer] 分析 ${proc.cmdId.substring(0, 40)}... (${proc.logs.length} 行日志)`);
|
|
28
|
+
const content = await llmFetch({
|
|
29
|
+
messages: [{ role: "user", content: buildPrompt(proc.cmdId, proc.logs.slice(-200).join("\n"), proc.uptimeSeconds, proc.restartCount) }],
|
|
30
|
+
max_tokens: 256,
|
|
31
|
+
temperature: 0.1,
|
|
32
|
+
enable_thinking: false,
|
|
33
|
+
});
|
|
34
|
+
const parsed = parseResponse(content);
|
|
35
|
+
console.error(`[llm-analyzer] ${proc.cmdId.substring(0, 30)} → ${parsed.severity}: ${parsed.summary}`);
|
|
36
|
+
results.push({ cmdId: proc.cmdId, ...parsed });
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.error(`[llm-analyzer] ${proc.cmdId.substring(0, 30)} 失败: ${e.message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── 客户端独立模式(从环境变量读配置) ──
|
|
45
|
+
|
|
46
|
+
async function createStandaloneFetch() {
|
|
47
|
+
try {
|
|
48
|
+
const { existsSync, readFileSync } = await import("fs");
|
|
49
|
+
const { join } = await import("path");
|
|
50
|
+
const { homedir } = await import("os");
|
|
51
|
+
const homeDir = homedir() || process.env.HOME || process.env.USERPROFILE || "";
|
|
52
|
+
let fileCfg = {};
|
|
53
|
+
try {
|
|
54
|
+
const cfgPath = join(homeDir, ".tt-help.json");
|
|
55
|
+
if (existsSync(cfgPath)) fileCfg = JSON.parse(readFileSync(cfgPath, "utf-8"));
|
|
56
|
+
} catch {}
|
|
57
|
+
const url = process.env.LLM_URL || fileCfg.llmUrl || "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions";
|
|
58
|
+
const apiKey = process.env.LLM_API_KEY || fileCfg.llmApiKey || "";
|
|
59
|
+
const model = process.env.LLM_MODEL || fileCfg.llmModel || "auto";
|
|
60
|
+
if (!apiKey) { console.error("[llm-analyzer] 未配置 LLM_API_KEY"); return null; }
|
|
61
|
+
console.error(`[llm-analyzer] 客户端模式: ${model} @ ${url}`);
|
|
62
|
+
|
|
63
|
+
// 返回兼容 llmFetch 签名的函数
|
|
64
|
+
return async ({ messages, max_tokens = 256, temperature = 0.1 }) => {
|
|
65
|
+
const resp = await fetch(url, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
68
|
+
body: JSON.stringify({ model, messages, max_tokens, temperature }),
|
|
69
|
+
signal: AbortSignal.timeout(30000),
|
|
70
|
+
});
|
|
71
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
72
|
+
const result = await resp.json();
|
|
73
|
+
const content = result?.choices?.[0]?.message?.content;
|
|
74
|
+
if (!content) throw new Error("LLM 返回空内容");
|
|
75
|
+
return content;
|
|
76
|
+
};
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.error(`[llm-analyzer] 客户端模式初始化失败: ${e.message}`);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
256,
|
|
85
|
+
);
|
|
86
|
+
const parsed = parseResponse(content);
|
|
24
87
|
console.error(
|
|
25
|
-
`[llm-analyzer]
|
|
88
|
+
`[llm-analyzer] ${proc.cmdId.substring(0, 30)} → ${parsed.severity}: ${parsed.summary}`,
|
|
26
89
|
);
|
|
27
|
-
|
|
28
|
-
if (analysis) {
|
|
29
|
-
console.error(
|
|
30
|
-
`[llm-analyzer] ${proc.cmdId.substring(0, 30)} → ${analysis.severity}: ${analysis.summary}`,
|
|
31
|
-
);
|
|
32
|
-
results.push(analysis);
|
|
33
|
-
}
|
|
90
|
+
results.push({ cmdId: proc.cmdId, ...parsed });
|
|
34
91
|
} catch (e) {
|
|
35
92
|
console.error(
|
|
36
|
-
`[llm-analyzer] ${proc.cmdId.substring(0, 30)}
|
|
93
|
+
`[llm-analyzer] ${proc.cmdId.substring(0, 30)} 失败: ${e.message}`,
|
|
37
94
|
);
|
|
38
95
|
}
|
|
39
96
|
}
|
|
40
|
-
|
|
41
97
|
return results;
|
|
42
98
|
} catch (e) {
|
|
43
|
-
console.error(`[llm-analyzer]
|
|
99
|
+
console.error(`[llm-analyzer] 异常: ${e.message}`);
|
|
44
100
|
return [];
|
|
45
101
|
}
|
|
46
102
|
}
|
|
47
103
|
|
|
48
|
-
// ── 单进程分析 ──
|
|
49
|
-
|
|
50
|
-
async function analyzeOneProcess(llmFetch, proc) {
|
|
51
|
-
const recentLogs = proc.logs.slice(-200).join("\n"); // 最多取最近 200 行
|
|
52
|
-
const prompt = buildPrompt(
|
|
53
|
-
proc.cmdId,
|
|
54
|
-
recentLogs,
|
|
55
|
-
proc.uptimeSeconds,
|
|
56
|
-
proc.restartCount,
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
const content = await llmFetch({
|
|
60
|
-
messages: [{ role: "user", content: prompt }],
|
|
61
|
-
max_tokens: 256,
|
|
62
|
-
temperature: 0.1,
|
|
63
|
-
enable_thinking: false,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const parsed = parseResponse(content);
|
|
67
|
-
return {
|
|
68
|
-
cmdId: proc.cmdId,
|
|
69
|
-
severity: parsed.severity || "ok",
|
|
70
|
-
summary: parsed.summary || "无异常",
|
|
71
|
-
suggestions: parsed.suggestions || [],
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ── Prompt 构建 ──
|
|
76
|
-
|
|
77
104
|
function buildPrompt(cmdId, logs, uptimeSeconds, restartCount) {
|
|
78
105
|
const uptime =
|
|
79
106
|
uptimeSeconds >= 3600
|