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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tt-help-cli-ycl",
3
- "version": "1.4.71",
3
+ "version": "1.4.72",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
  }
@@ -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
- "[llm-analyzer] 无日志,跳过(进程可能无 stderr 输出)",
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] 异常: ${e.message}`);
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 日志分析模块 — 定期收集各进程日志,调用 LLM 分析错误并给出建议
2
+ * LLM 日志分析模块 — 供服务端调用(传入 llmFetch),或客户端独立使用(读环境变量)
3
3
  *
4
- * 每 10 分钟执行一次,分析结果通过心跳上报到 watch 服务端。
4
+ * 分析结果通过心跳上报到 watch 服务端展示。
5
5
  */
6
6
 
7
7
  // ── 分析入口 ──
8
8
 
9
9
  /**
10
10
  * 对所有进程的日志调用 LLM 分析
11
- * @param {Array<{cmdId:string, logs:string[], uptimeSeconds:number, restartCount:number}>} processLogs
12
- * @returns {Promise<Array<{cmdId:string, severity:string, summary:string, suggestions:string[]}>>}
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
- try {
18
- const { llmFetch } = await import("../watch/llm-config.js");
19
- console.error("[llm-analyzer] llmFetch 模块加载成功");
20
- const results = [];
18
+ // 如果没有传入 llmFetch,使用内置的轻量调用(客户端模式)
19
+ if (!llmFetch) {
20
+ llmFetch = await createStandaloneFetch();
21
+ if (!llmFetch) return [];
22
+ }
21
23
 
22
- for (const proc of processLogs) {
23
- try {
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] 分析 ${proc.cmdId.substring(0, 40)}... (${proc.logs.length} 行日志)`,
88
+ `[llm-analyzer] ${proc.cmdId.substring(0, 30)} → ${parsed.severity}: ${parsed.summary}`,
26
89
  );
27
- const analysis = await analyzeOneProcess(llmFetch, proc);
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)} 分析失败: ${e.message}`,
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] llmFetch 加载失败: ${e.message}`);
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