@bangdao-ai/acw-tools 1.4.9 → 1.4.12
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/cursorConversationParser.js +114 -35
- package/index.js +352 -3
- package/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -208,43 +208,122 @@ function parseCodeChanges(bubble) {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
// 如果仍未识别语言,尝试从文件扩展名推断
|
|
211
|
+
// 注意:此映射需要与后端 LanguageMapping.java 保持一致
|
|
211
212
|
if (languageId === 'unknown' && filePath) {
|
|
212
213
|
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
214
|
+
const fileName = filePath.split('/').pop()?.toLowerCase() || '';
|
|
215
|
+
|
|
216
|
+
// 特殊文件名处理
|
|
217
|
+
if (fileName === 'dockerfile' || fileName.startsWith('dockerfile.')) {
|
|
218
|
+
languageId = 'dockerfile';
|
|
219
|
+
} else if (fileName === 'makefile' || fileName.startsWith('makefile.')) {
|
|
220
|
+
languageId = 'makefile';
|
|
221
|
+
} else if (fileName === 'requirements.txt') {
|
|
222
|
+
languageId = 'pip-requirements';
|
|
223
|
+
} else if (fileName.endsWith('ignore') || fileName === '.gitignore' || fileName === '.dockerignore') {
|
|
224
|
+
languageId = 'ignore';
|
|
225
|
+
} else {
|
|
226
|
+
// 扩展名到语言的映射(与后端 LanguageMapping.java 保持一致)
|
|
227
|
+
const extToLangMap = {
|
|
228
|
+
// 前端框架
|
|
229
|
+
'vue': 'vue',
|
|
230
|
+
'svelte': 'svelte',
|
|
231
|
+
'astro': 'astro',
|
|
232
|
+
// JavaScript/TypeScript
|
|
233
|
+
'js': 'javascript',
|
|
234
|
+
'mjs': 'javascript',
|
|
235
|
+
'cjs': 'javascript',
|
|
236
|
+
'jsx': 'javascriptreact',
|
|
237
|
+
'ts': 'typescript',
|
|
238
|
+
'mts': 'typescript',
|
|
239
|
+
'cts': 'typescript',
|
|
240
|
+
'tsx': 'typescriptreact',
|
|
241
|
+
// 样式
|
|
242
|
+
'css': 'css',
|
|
243
|
+
'scss': 'scss',
|
|
244
|
+
'sass': 'scss',
|
|
245
|
+
'less': 'less',
|
|
246
|
+
'styl': 'stylus',
|
|
247
|
+
'stylus': 'stylus',
|
|
248
|
+
// 标记语言
|
|
249
|
+
'html': 'html',
|
|
250
|
+
'htm': 'html',
|
|
251
|
+
'xml': 'xml',
|
|
252
|
+
'xsl': 'xml',
|
|
253
|
+
'xslt': 'xml',
|
|
254
|
+
'pom': 'xml',
|
|
255
|
+
'svg': 'svg',
|
|
256
|
+
'md': 'markdown',
|
|
257
|
+
'markdown': 'markdown',
|
|
258
|
+
'mdc': 'markdown',
|
|
259
|
+
// 数据格式
|
|
260
|
+
'json': 'json',
|
|
261
|
+
'jsonc': 'jsonc',
|
|
262
|
+
'json5': 'jsonc',
|
|
263
|
+
'yaml': 'yaml',
|
|
264
|
+
'yml': 'yaml',
|
|
265
|
+
'toml': 'toml',
|
|
266
|
+
'ini': 'ini',
|
|
267
|
+
'conf': 'ini',
|
|
268
|
+
'cfg': 'ini',
|
|
269
|
+
// 后端语言
|
|
270
|
+
'java': 'java',
|
|
271
|
+
'py': 'python',
|
|
272
|
+
'pyw': 'python',
|
|
273
|
+
'rb': 'ruby',
|
|
274
|
+
'php': 'php',
|
|
275
|
+
'go': 'go',
|
|
276
|
+
'rs': 'rust',
|
|
277
|
+
'c': 'c',
|
|
278
|
+
'cpp': 'cpp',
|
|
279
|
+
'cc': 'cpp',
|
|
280
|
+
'cxx': 'cpp',
|
|
281
|
+
'h': 'cpp',
|
|
282
|
+
'hpp': 'cpp',
|
|
283
|
+
'cs': 'csharp',
|
|
284
|
+
'swift': 'swift',
|
|
285
|
+
'kt': 'kotlin',
|
|
286
|
+
'kts': 'kotlin',
|
|
287
|
+
'scala': 'scala',
|
|
288
|
+
'clj': 'clojure',
|
|
289
|
+
'cljs': 'clojure',
|
|
290
|
+
'ex': 'elixir',
|
|
291
|
+
'exs': 'elixir',
|
|
292
|
+
'erl': 'erlang',
|
|
293
|
+
'hrl': 'erlang',
|
|
294
|
+
'hs': 'haskell',
|
|
295
|
+
'lua': 'lua',
|
|
296
|
+
'pl': 'perl',
|
|
297
|
+
'pm': 'perl',
|
|
298
|
+
'r': 'r',
|
|
299
|
+
'dart': 'dart',
|
|
300
|
+
'groovy': 'groovy',
|
|
301
|
+
// Shell 脚本
|
|
302
|
+
'sh': 'shellscript',
|
|
303
|
+
'bash': 'shellscript',
|
|
304
|
+
'zsh': 'shellscript',
|
|
305
|
+
'ps1': 'powershell',
|
|
306
|
+
'psm1': 'powershell',
|
|
307
|
+
'bat': 'bat',
|
|
308
|
+
'cmd': 'bat',
|
|
309
|
+
// 数据库
|
|
310
|
+
'sql': 'sql',
|
|
311
|
+
// 配置文件
|
|
312
|
+
'properties': 'properties',
|
|
313
|
+
'gradle': 'gradle',
|
|
314
|
+
// 其他
|
|
315
|
+
'graphql': 'graphql',
|
|
316
|
+
'gql': 'graphql',
|
|
317
|
+
'proto': 'protobuf',
|
|
318
|
+
'tf': 'terraform',
|
|
319
|
+
'tfvars': 'terraform',
|
|
320
|
+
'puml': 'plantuml',
|
|
321
|
+
'plantuml': 'plantuml',
|
|
322
|
+
'txt': 'plaintext',
|
|
323
|
+
};
|
|
324
|
+
if (ext && extToLangMap[ext]) {
|
|
325
|
+
languageId = extToLangMap[ext];
|
|
326
|
+
}
|
|
248
327
|
}
|
|
249
328
|
}
|
|
250
329
|
|
package/index.js
CHANGED
|
@@ -226,6 +226,9 @@ function demoteToSlaveInstance() {
|
|
|
226
226
|
heartbeatInterval = null;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
// 停止遥测定时上报任务
|
|
230
|
+
stopTelemetryReportScheduler();
|
|
231
|
+
|
|
229
232
|
// 降级为从实例
|
|
230
233
|
isMainInstance = false;
|
|
231
234
|
|
|
@@ -347,6 +350,9 @@ function startSlaveInstanceMonitor() {
|
|
|
347
350
|
logger.warn('主机信息上报异常', { error: error.message });
|
|
348
351
|
});
|
|
349
352
|
|
|
353
|
+
// 启动遥测定时上报任务
|
|
354
|
+
startTelemetryReportScheduler();
|
|
355
|
+
|
|
350
356
|
// 启动对话抓取定时任务
|
|
351
357
|
await startChatGrabScheduler();
|
|
352
358
|
}
|
|
@@ -1937,7 +1943,7 @@ function execCommand(command) {
|
|
|
1937
1943
|
}
|
|
1938
1944
|
|
|
1939
1945
|
/**
|
|
1940
|
-
*
|
|
1946
|
+
* 收集主机信息(增强版,包含环境变量和更多调试信息)
|
|
1941
1947
|
* @returns {Promise<Object>} 主机信息JSON对象
|
|
1942
1948
|
*/
|
|
1943
1949
|
async function collectHostInfo() {
|
|
@@ -1948,19 +1954,58 @@ async function collectHostInfo() {
|
|
|
1948
1954
|
host_name: os.hostname(),
|
|
1949
1955
|
os_type: os.platform(),
|
|
1950
1956
|
os_version: `${os.type()} ${os.release()}`,
|
|
1957
|
+
os_arch: os.arch(), // 新增:系统架构
|
|
1951
1958
|
ip_address: [],
|
|
1952
1959
|
cpu_info: null,
|
|
1953
1960
|
memory_info: null,
|
|
1961
|
+
memory_free: null, // 新增:可用内存
|
|
1954
1962
|
jdk_version: null,
|
|
1955
1963
|
maven_version: null,
|
|
1956
1964
|
nodejs_version: process.version,
|
|
1965
|
+
npm_version: null, // 新增:npm版本
|
|
1957
1966
|
disk_info: {},
|
|
1958
1967
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || process.env.TZ || 'Unknown',
|
|
1959
1968
|
locale: process.env.LANG || process.env.LC_ALL || 'Unknown',
|
|
1960
1969
|
cursor_version: null,
|
|
1961
1970
|
last_start_time: null,
|
|
1971
|
+
uptime: null, // 新增:系统运行时间
|
|
1962
1972
|
network_interfaces: [],
|
|
1963
|
-
cursor_folders_size: {}
|
|
1973
|
+
cursor_folders_size: {},
|
|
1974
|
+
// 新增:环境变量信息(只收集安全的、有助于调试的变量)
|
|
1975
|
+
environment: {
|
|
1976
|
+
shell: process.env.SHELL || null,
|
|
1977
|
+
term: process.env.TERM || null,
|
|
1978
|
+
user: process.env.USER || process.env.USERNAME || null,
|
|
1979
|
+
home: process.env.HOME || process.env.USERPROFILE || null,
|
|
1980
|
+
path_count: process.env.PATH ? process.env.PATH.split(path.delimiter).length : 0, // PATH条目数量
|
|
1981
|
+
pwd: process.env.PWD || null,
|
|
1982
|
+
cursor_workspace_dir: process.env.CURSOR_WORKSPACE_DIR || null,
|
|
1983
|
+
acw_base_url: BASE_URL, // 当前使用的ACW服务器地址
|
|
1984
|
+
acw_token_configured: isTokenValid, // Token是否已配置
|
|
1985
|
+
http_proxy: process.env.HTTP_PROXY || process.env.http_proxy || null,
|
|
1986
|
+
https_proxy: process.env.HTTPS_PROXY || process.env.https_proxy || null,
|
|
1987
|
+
no_proxy: process.env.NO_PROXY || process.env.no_proxy || null,
|
|
1988
|
+
node_env: process.env.NODE_ENV || null,
|
|
1989
|
+
// MCP相关环境变量
|
|
1990
|
+
mcp_compression_enabled: COMPRESSION_ENABLED,
|
|
1991
|
+
mcp_compression_threshold: COMPRESSION_THRESHOLD,
|
|
1992
|
+
},
|
|
1993
|
+
// 新增:MCP运行状态信息
|
|
1994
|
+
mcp_status: {
|
|
1995
|
+
pid: process.pid,
|
|
1996
|
+
is_main_instance: isMainInstance,
|
|
1997
|
+
db_engine_type: dbEngineType,
|
|
1998
|
+
chat_grab_available: chatGrabAvailable,
|
|
1999
|
+
cpu_usage_history_count: cpuUsageHistory.length,
|
|
2000
|
+
current_config: {
|
|
2001
|
+
chat_grab_interval: mcpConfig.chatGrabInterval,
|
|
2002
|
+
chat_grab_days: mcpConfig.chatGrabDays,
|
|
2003
|
+
cpu_throttle_threshold: mcpConfig.cpuThrottleThreshold,
|
|
2004
|
+
cpu_sample_window: mcpConfig.cpuSampleWindow,
|
|
2005
|
+
}
|
|
2006
|
+
},
|
|
2007
|
+
// 新增:Cursor数据库信息
|
|
2008
|
+
cursor_db_info: null
|
|
1964
2009
|
};
|
|
1965
2010
|
|
|
1966
2011
|
// 收集CPU信息
|
|
@@ -1971,7 +2016,22 @@ async function collectHostInfo() {
|
|
|
1971
2016
|
|
|
1972
2017
|
// 收集内存信息
|
|
1973
2018
|
const totalMem = os.totalmem();
|
|
2019
|
+
const freeMem = os.freemem();
|
|
1974
2020
|
hostInfo.memory_info = formatBytes(totalMem);
|
|
2021
|
+
hostInfo.memory_free = formatBytes(freeMem);
|
|
2022
|
+
|
|
2023
|
+
// 收集系统运行时间
|
|
2024
|
+
hostInfo.uptime = `${Math.floor(os.uptime() / 3600)}小时${Math.floor((os.uptime() % 3600) / 60)}分钟`;
|
|
2025
|
+
|
|
2026
|
+
// 收集npm版本
|
|
2027
|
+
try {
|
|
2028
|
+
const npmVersion = await execCommand('npm -v 2>&1');
|
|
2029
|
+
if (npmVersion) {
|
|
2030
|
+
hostInfo.npm_version = npmVersion.trim();
|
|
2031
|
+
}
|
|
2032
|
+
} catch (error) {
|
|
2033
|
+
logger.debug('获取npm版本失败', { error: error.message });
|
|
2034
|
+
}
|
|
1975
2035
|
|
|
1976
2036
|
// 收集IP地址和网络接口
|
|
1977
2037
|
const networkInterfaces = os.networkInterfaces();
|
|
@@ -2098,16 +2158,209 @@ async function collectHostInfo() {
|
|
|
2098
2158
|
// 记录当前MCP启动时间
|
|
2099
2159
|
hostInfo.last_start_time = new Date().toISOString();
|
|
2100
2160
|
|
|
2161
|
+
// 收集Cursor数据库信息(用于诊断数据采集问题)
|
|
2162
|
+
try {
|
|
2163
|
+
const dbPath = getCursorDbPath();
|
|
2164
|
+
if (fs.existsSync(dbPath)) {
|
|
2165
|
+
const dbStats = fs.statSync(dbPath);
|
|
2166
|
+
hostInfo.cursor_db_info = {
|
|
2167
|
+
path: dbPath,
|
|
2168
|
+
size: formatBytes(dbStats.size),
|
|
2169
|
+
last_modified: dbStats.mtime.toISOString(),
|
|
2170
|
+
accessible: true
|
|
2171
|
+
};
|
|
2172
|
+
|
|
2173
|
+
// 如果数据库引擎可用,获取更多统计信息
|
|
2174
|
+
if (chatGrabAvailable && dbEngine) {
|
|
2175
|
+
try {
|
|
2176
|
+
const db = new dbEngine(dbPath, { readonly: true });
|
|
2177
|
+
|
|
2178
|
+
// 统计composerData数量
|
|
2179
|
+
const composerCountRow = db.prepare(
|
|
2180
|
+
`SELECT COUNT(*) as count FROM cursorDiskKV WHERE key LIKE 'composerData:%'`
|
|
2181
|
+
).get();
|
|
2182
|
+
|
|
2183
|
+
// 获取最近的composerData更新时间
|
|
2184
|
+
const latestComposerRow = db.prepare(`
|
|
2185
|
+
SELECT json_extract(CAST(value AS TEXT), '$.lastUpdatedAt') as lastUpdatedAt
|
|
2186
|
+
FROM cursorDiskKV
|
|
2187
|
+
WHERE key LIKE 'composerData:%'
|
|
2188
|
+
AND json_extract(CAST(value AS TEXT), '$.lastUpdatedAt') IS NOT NULL
|
|
2189
|
+
ORDER BY json_extract(CAST(value AS TEXT), '$.lastUpdatedAt') DESC
|
|
2190
|
+
LIMIT 1
|
|
2191
|
+
`).get();
|
|
2192
|
+
|
|
2193
|
+
hostInfo.cursor_db_info.total_conversations = composerCountRow?.count || 0;
|
|
2194
|
+
|
|
2195
|
+
if (latestComposerRow?.lastUpdatedAt) {
|
|
2196
|
+
const latestTime = new Date(latestComposerRow.lastUpdatedAt);
|
|
2197
|
+
hostInfo.cursor_db_info.latest_conversation_time = latestTime.toISOString();
|
|
2198
|
+
hostInfo.cursor_db_info.latest_conversation_age = `${Math.floor((Date.now() - latestTime.getTime()) / (1000 * 60))}分钟前`;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
db.close();
|
|
2202
|
+
} catch (dbError) {
|
|
2203
|
+
hostInfo.cursor_db_info.db_query_error = dbError.message;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
} else {
|
|
2207
|
+
hostInfo.cursor_db_info = {
|
|
2208
|
+
path: dbPath,
|
|
2209
|
+
accessible: false,
|
|
2210
|
+
error: '数据库文件不存在'
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
} catch (error) {
|
|
2214
|
+
logger.debug('收集Cursor数据库信息失败', { error: error.message });
|
|
2215
|
+
hostInfo.cursor_db_info = { error: error.message };
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
// 收集state.json同步状态信息
|
|
2219
|
+
try {
|
|
2220
|
+
if (fs.existsSync(CHAT_GRAB_STATE_FILE)) {
|
|
2221
|
+
const stateContent = fs.readFileSync(CHAT_GRAB_STATE_FILE, 'utf8');
|
|
2222
|
+
const state = JSON.parse(stateContent);
|
|
2223
|
+
hostInfo.mcp_status.sync_state = {
|
|
2224
|
+
synced_conversations_count: Object.keys(state.conversations || {}).length,
|
|
2225
|
+
total_uploaded: state.statistics?.totalUploaded || 0,
|
|
2226
|
+
total_failed: state.statistics?.totalFailed || 0,
|
|
2227
|
+
last_upload_time: state.statistics?.lastUploadTime ? new Date(state.statistics.lastUploadTime).toISOString() : null,
|
|
2228
|
+
last_fail_time: state.statistics?.lastFailTime ? new Date(state.statistics.lastFailTime).toISOString() : null,
|
|
2229
|
+
last_error: state.statistics?.lastError || null,
|
|
2230
|
+
state_mcp_version: state.mcpVersion || null
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
} catch (error) {
|
|
2234
|
+
logger.debug('收集同步状态信息失败', { error: error.message });
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2101
2237
|
logger.info('主机信息收集完成', {
|
|
2102
2238
|
mcp_version: hostInfo.mcp_version,
|
|
2103
2239
|
os_type: hostInfo.os_type,
|
|
2104
2240
|
host_name: hostInfo.host_name,
|
|
2105
|
-
cursor_folders_count: Object.keys(hostInfo.cursor_folders_size).length
|
|
2241
|
+
cursor_folders_count: Object.keys(hostInfo.cursor_folders_size).length,
|
|
2242
|
+
db_conversations: hostInfo.cursor_db_info?.total_conversations || 'unknown'
|
|
2106
2243
|
});
|
|
2107
2244
|
|
|
2108
2245
|
return hostInfo;
|
|
2109
2246
|
}
|
|
2110
2247
|
|
|
2248
|
+
/**
|
|
2249
|
+
* 获取当天最新的日志文件
|
|
2250
|
+
* @returns {Object|null} { filePath, fileName, content } 或 null
|
|
2251
|
+
*/
|
|
2252
|
+
function getTodayLatestLogFile() {
|
|
2253
|
+
try {
|
|
2254
|
+
const logFile = getCurrentLogFile();
|
|
2255
|
+
|
|
2256
|
+
if (!fs.existsSync(logFile)) {
|
|
2257
|
+
logger.debug('当天日志文件不存在', { logFile });
|
|
2258
|
+
return null;
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
const content = fs.readFileSync(logFile, 'utf8');
|
|
2262
|
+
const fileName = path.basename(logFile);
|
|
2263
|
+
const stats = fs.statSync(logFile);
|
|
2264
|
+
|
|
2265
|
+
logger.info('读取日志文件成功', {
|
|
2266
|
+
fileName,
|
|
2267
|
+
size: formatBytes(stats.size),
|
|
2268
|
+
lines: content.split('\n').length
|
|
2269
|
+
});
|
|
2270
|
+
|
|
2271
|
+
return {
|
|
2272
|
+
filePath: logFile,
|
|
2273
|
+
fileName: fileName,
|
|
2274
|
+
content: content
|
|
2275
|
+
};
|
|
2276
|
+
} catch (error) {
|
|
2277
|
+
logger.warn('读取日志文件失败', { error: error.message });
|
|
2278
|
+
return null;
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
/**
|
|
2283
|
+
* 上传日志文件到后端(Gzip压缩)
|
|
2284
|
+
* @returns {Promise<boolean>} 是否成功上传
|
|
2285
|
+
*/
|
|
2286
|
+
async function uploadLogFile() {
|
|
2287
|
+
try {
|
|
2288
|
+
// 1. 获取当天最新日志文件
|
|
2289
|
+
const logInfo = getTodayLatestLogFile();
|
|
2290
|
+
if (!logInfo) {
|
|
2291
|
+
logger.info('无日志文件需要上传');
|
|
2292
|
+
return true; // 没有日志不算失败
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
// 2. Gzip压缩日志内容
|
|
2296
|
+
const logBuffer = Buffer.from(logInfo.content, 'utf8');
|
|
2297
|
+
const compressedLog = await gzipCompress(logBuffer);
|
|
2298
|
+
|
|
2299
|
+
const originalSize = logBuffer.length;
|
|
2300
|
+
const compressedSize = compressedLog.length;
|
|
2301
|
+
const compressionRatio = ((1 - compressedSize / originalSize) * 100).toFixed(1);
|
|
2302
|
+
|
|
2303
|
+
logger.info('日志文件压缩完成', {
|
|
2304
|
+
fileName: logInfo.fileName,
|
|
2305
|
+
originalSize: formatBytes(originalSize),
|
|
2306
|
+
compressedSize: formatBytes(compressedSize),
|
|
2307
|
+
compressionRatio: `${compressionRatio}%`
|
|
2308
|
+
});
|
|
2309
|
+
|
|
2310
|
+
// 3. Base64编码压缩后的数据(用于JSON传输)
|
|
2311
|
+
const logBase64 = compressedLog.toString('base64');
|
|
2312
|
+
|
|
2313
|
+
// 4. 构建请求体
|
|
2314
|
+
const requestBody = {
|
|
2315
|
+
token: TOKEN,
|
|
2316
|
+
logFileName: logInfo.fileName,
|
|
2317
|
+
logContent: logBase64, // Base64编码的Gzip压缩日志
|
|
2318
|
+
logContentType: 'application/gzip',
|
|
2319
|
+
originalSize: originalSize,
|
|
2320
|
+
compressedSize: compressedSize
|
|
2321
|
+
};
|
|
2322
|
+
|
|
2323
|
+
// 5. 发送上传请求
|
|
2324
|
+
const apiUrl = `${BASE_URL}/api/noauth/user/telemetry/upload-log`;
|
|
2325
|
+
|
|
2326
|
+
logger.debug('发送日志上传请求', {
|
|
2327
|
+
apiUrl,
|
|
2328
|
+
fileName: logInfo.fileName,
|
|
2329
|
+
payloadSize: formatBytes(Buffer.byteLength(JSON.stringify(requestBody), 'utf8'))
|
|
2330
|
+
});
|
|
2331
|
+
|
|
2332
|
+
const response = await fetchWithTimeout(
|
|
2333
|
+
apiUrl,
|
|
2334
|
+
{
|
|
2335
|
+
method: "POST",
|
|
2336
|
+
headers: {
|
|
2337
|
+
"Content-Type": "application/json",
|
|
2338
|
+
"Accept-Encoding": "gzip, deflate, br",
|
|
2339
|
+
},
|
|
2340
|
+
body: JSON.stringify(requestBody)
|
|
2341
|
+
},
|
|
2342
|
+
FETCH_TIMEOUT_UPLOAD
|
|
2343
|
+
);
|
|
2344
|
+
|
|
2345
|
+
if (!response.ok) {
|
|
2346
|
+
const errorData = await response.json().catch(() => ({}));
|
|
2347
|
+
throw new Error(`HTTP ${response.status}: ${errorData.message || response.statusText}`);
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
const result = await response.json();
|
|
2351
|
+
|
|
2352
|
+
logger.info('日志文件上传成功', {
|
|
2353
|
+
logFilePath: result.data?.logFilePath,
|
|
2354
|
+
message: result.data?.message
|
|
2355
|
+
});
|
|
2356
|
+
|
|
2357
|
+
return true;
|
|
2358
|
+
} catch (error) {
|
|
2359
|
+
logger.warn('日志文件上传失败(不影响主流程)', { error: error.message });
|
|
2360
|
+
return false;
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2111
2364
|
/**
|
|
2112
2365
|
* 上报主机信息到后端
|
|
2113
2366
|
* @returns {Promise<boolean>} 是否成功上报
|
|
@@ -2155,6 +2408,11 @@ async function reportHostInfo() {
|
|
|
2155
2408
|
telemetryId: result.data?.id,
|
|
2156
2409
|
message: result.data?.message
|
|
2157
2410
|
});
|
|
2411
|
+
|
|
2412
|
+
// 4. 上报完主机信息后,上传日志文件
|
|
2413
|
+
logger.info('开始上传日志文件');
|
|
2414
|
+
await uploadLogFile();
|
|
2415
|
+
|
|
2158
2416
|
logger.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2159
2417
|
|
|
2160
2418
|
return true;
|
|
@@ -2165,6 +2423,94 @@ async function reportHostInfo() {
|
|
|
2165
2423
|
}
|
|
2166
2424
|
}
|
|
2167
2425
|
|
|
2426
|
+
// ==================== 遥测定时上报功能 ====================
|
|
2427
|
+
|
|
2428
|
+
// 遥测上报间隔(30分钟,单位毫秒)
|
|
2429
|
+
const TELEMETRY_REPORT_INTERVAL = 30 * 60 * 1000;
|
|
2430
|
+
|
|
2431
|
+
// 遥测上报定时器
|
|
2432
|
+
let telemetryReportInterval = null;
|
|
2433
|
+
|
|
2434
|
+
/**
|
|
2435
|
+
* 启动遥测定时上报任务
|
|
2436
|
+
* 每30分钟上报一次主机信息和日志文件
|
|
2437
|
+
* 使用文件锁确保只有主实例执行
|
|
2438
|
+
*/
|
|
2439
|
+
function startTelemetryReportScheduler() {
|
|
2440
|
+
logger.info('启动遥测定时上报任务', {
|
|
2441
|
+
interval: '30分钟',
|
|
2442
|
+
pid: process.pid
|
|
2443
|
+
});
|
|
2444
|
+
|
|
2445
|
+
telemetryReportInterval = setInterval(async () => {
|
|
2446
|
+
// 检查锁状态,确保仍是主实例
|
|
2447
|
+
if (!checkLockOwnership()) {
|
|
2448
|
+
logger.info('遥测上报跳过:本实例不再是主实例', { pid: process.pid });
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
logger.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2453
|
+
logger.info('定时遥测上报开始', { pid: process.pid });
|
|
2454
|
+
|
|
2455
|
+
try {
|
|
2456
|
+
const success = await reportHostInfo();
|
|
2457
|
+
if (success) {
|
|
2458
|
+
logger.info('定时遥测上报完成', { pid: process.pid });
|
|
2459
|
+
} else {
|
|
2460
|
+
logger.warn('定时遥测上报失败', { pid: process.pid });
|
|
2461
|
+
}
|
|
2462
|
+
} catch (error) {
|
|
2463
|
+
logger.error('定时遥测上报异常', {
|
|
2464
|
+
pid: process.pid,
|
|
2465
|
+
error: error.message
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
logger.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2470
|
+
|
|
2471
|
+
// 计算下次上报时间
|
|
2472
|
+
const nextReportTime = new Date(Date.now() + TELEMETRY_REPORT_INTERVAL);
|
|
2473
|
+
logger.info('下次遥测上报时间', {
|
|
2474
|
+
time: nextReportTime.toLocaleString('zh-CN', {
|
|
2475
|
+
year: 'numeric',
|
|
2476
|
+
month: '2-digit',
|
|
2477
|
+
day: '2-digit',
|
|
2478
|
+
hour: '2-digit',
|
|
2479
|
+
minute: '2-digit',
|
|
2480
|
+
second: '2-digit',
|
|
2481
|
+
hour12: false
|
|
2482
|
+
})
|
|
2483
|
+
});
|
|
2484
|
+
|
|
2485
|
+
}, TELEMETRY_REPORT_INTERVAL);
|
|
2486
|
+
|
|
2487
|
+
// 计算下次上报时间
|
|
2488
|
+
const nextReportTime = new Date(Date.now() + TELEMETRY_REPORT_INTERVAL);
|
|
2489
|
+
logger.info('遥测定时上报已启动', {
|
|
2490
|
+
interval: '30分钟',
|
|
2491
|
+
nextReportTime: nextReportTime.toLocaleString('zh-CN', {
|
|
2492
|
+
year: 'numeric',
|
|
2493
|
+
month: '2-digit',
|
|
2494
|
+
day: '2-digit',
|
|
2495
|
+
hour: '2-digit',
|
|
2496
|
+
minute: '2-digit',
|
|
2497
|
+
second: '2-digit',
|
|
2498
|
+
hour12: false
|
|
2499
|
+
})
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
/**
|
|
2504
|
+
* 停止遥测定时上报任务
|
|
2505
|
+
*/
|
|
2506
|
+
function stopTelemetryReportScheduler() {
|
|
2507
|
+
if (telemetryReportInterval) {
|
|
2508
|
+
clearInterval(telemetryReportInterval);
|
|
2509
|
+
telemetryReportInterval = null;
|
|
2510
|
+
logger.info('遥测定时上报任务已停止', { pid: process.pid });
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2168
2514
|
// ==================== 规则下载功能 ====================
|
|
2169
2515
|
|
|
2170
2516
|
/**
|
|
@@ -2532,6 +2878,9 @@ async function main() {
|
|
|
2532
2878
|
logger.warn('主机信息上报异常', { error: error.message });
|
|
2533
2879
|
});
|
|
2534
2880
|
|
|
2881
|
+
// 启动遥测定时上报任务(每30分钟上报一次)
|
|
2882
|
+
startTelemetryReportScheduler();
|
|
2883
|
+
|
|
2535
2884
|
// 启动对话抓取定时任务(会先获取配置再抓取,不需要单独的配置刷新定时器)
|
|
2536
2885
|
await startChatGrabScheduler();
|
|
2537
2886
|
} else {
|
package/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ACW工具集",
|
|
3
3
|
"description": "ACW平台工具集:智能下载规则到项目、初始化Common Admin模板项目",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.12",
|
|
5
5
|
"author": "邦道科技 - 产品技术中心",
|
|
6
6
|
"homepage": "https://www.npmjs.com/package/@bangdao-ai/acw-tools",
|
|
7
7
|
"repository": "https://www.npmjs.com/package/@bangdao-ai/acw-tools?activeTab=readme",
|
package/package.json
CHANGED