@myskyline_ai/ccdebug 0.2.1
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 +201 -0
- package/README.md +129 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +674 -0
- package/dist/html-generator.d.ts +24 -0
- package/dist/html-generator.d.ts.map +1 -0
- package/dist/html-generator.js +141 -0
- package/dist/index-generator.d.ts +29 -0
- package/dist/index-generator.d.ts.map +1 -0
- package/dist/index-generator.js +271 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/interceptor-loader.js +59 -0
- package/dist/interceptor.d.ts +46 -0
- package/dist/interceptor.d.ts.map +1 -0
- package/dist/interceptor.js +555 -0
- package/dist/log-file-manager.d.ts +15 -0
- package/dist/log-file-manager.d.ts.map +1 -0
- package/dist/log-file-manager.js +41 -0
- package/dist/shared-conversation-processor.d.ts +114 -0
- package/dist/shared-conversation-processor.d.ts.map +1 -0
- package/dist/shared-conversation-processor.js +663 -0
- package/dist/token-extractor.js +28 -0
- package/dist/types.d.ts +95 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/frontend/dist/index.global.js +1522 -0
- package/frontend/dist/styles.css +985 -0
- package/frontend/template.html +19 -0
- package/package.json +83 -0
- package/web/debug.html +14 -0
- package/web/dist/assets/index-BIP9r3RA.js +48 -0
- package/web/dist/assets/index-BIP9r3RA.js.map +1 -0
- package/web/dist/assets/index-De3gn-G-.css +1 -0
- package/web/dist/favicon.svg +4 -0
- package/web/dist/index.html +15 -0
- package/web/index.html +14 -0
- package/web/package.json +47 -0
- package/web/server/conversation-parser.d.ts +47 -0
- package/web/server/conversation-parser.d.ts.map +1 -0
- package/web/server/conversation-parser.js +564 -0
- package/web/server/conversation-parser.js.map +1 -0
- package/web/server/index.d.ts +16 -0
- package/web/server/index.d.ts.map +1 -0
- package/web/server/index.js +60 -0
- package/web/server/index.js.map +1 -0
- package/web/server/log-file-manager.d.ts +98 -0
- package/web/server/log-file-manager.d.ts.map +1 -0
- package/web/server/log-file-manager.js +512 -0
- package/web/server/log-file-manager.js.map +1 -0
- package/web/server/src/types/index.d.ts +68 -0
- package/web/server/src/types/index.d.ts.map +1 -0
- package/web/server/src/types/index.js +3 -0
- package/web/server/src/types/index.js.map +1 -0
- package/web/server/test-path.js +48 -0
- package/web/server/web-server.d.ts +41 -0
- package/web/server/web-server.d.ts.map +1 -0
- package/web/server/web-server.js +807 -0
- package/web/server/web-server.js.map +1 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConversationParser = exports.LogFileManager = exports.WebServer = void 0;
|
|
4
|
+
exports.startWebServer = startWebServer;
|
|
5
|
+
exports.startFromCLI = startFromCLI;
|
|
6
|
+
const { WebServer } = require('./web-server.js');
|
|
7
|
+
exports.WebServer = WebServer;
|
|
8
|
+
const path = require('path');
|
|
9
|
+
var log_file_manager_js_1 = require("./log-file-manager.js");
|
|
10
|
+
Object.defineProperty(exports, "LogFileManager", { enumerable: true, get: function () { return log_file_manager_js_1.LogFileManager; } });
|
|
11
|
+
var conversation_parser_js_1 = require("./conversation-parser.js");
|
|
12
|
+
Object.defineProperty(exports, "ConversationParser", { enumerable: true, get: function () { return conversation_parser_js_1.ConversationParser; } });
|
|
13
|
+
/**
|
|
14
|
+
* 启动Web服务器的便捷函数
|
|
15
|
+
* @param config 服务器配置对象
|
|
16
|
+
* @returns WebServer实例
|
|
17
|
+
*/
|
|
18
|
+
async function startWebServer(config) {
|
|
19
|
+
const server = new WebServer(config);
|
|
20
|
+
await server.start();
|
|
21
|
+
return server;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 命令行启动函数
|
|
25
|
+
*/
|
|
26
|
+
async function startFromCLI() {
|
|
27
|
+
const args = process.argv.slice(2);
|
|
28
|
+
const projectDir = args[0] || '/Users/ligf/Code/claude-code/ccdebug/ccdemo';
|
|
29
|
+
const port = parseInt(args[1]) || 3001;
|
|
30
|
+
const staticDir = args[2] || './dist';
|
|
31
|
+
console.log('启动参数:', { projectDir, port, staticDir });
|
|
32
|
+
try {
|
|
33
|
+
const server = await startWebServer({
|
|
34
|
+
projectDir,
|
|
35
|
+
port,
|
|
36
|
+
staticDir: staticDir ? path.resolve(staticDir) : undefined
|
|
37
|
+
});
|
|
38
|
+
console.log(`Web服务器已启动: ${server.getUrl()}`);
|
|
39
|
+
// 优雅关闭处理
|
|
40
|
+
process.on('SIGINT', async () => {
|
|
41
|
+
console.log('\n正在关闭服务器...');
|
|
42
|
+
await server.stop();
|
|
43
|
+
process.exit(0);
|
|
44
|
+
});
|
|
45
|
+
process.on('SIGTERM', async () => {
|
|
46
|
+
console.log('\n正在关闭服务器...');
|
|
47
|
+
await server.stop();
|
|
48
|
+
process.exit(0);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error('启动服务器失败:', error);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// 如果直接运行此文件,则启动服务器
|
|
57
|
+
if (process.argv[1] === __filename) {
|
|
58
|
+
startFromCLI();
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;AAaA,wCAIC;AAKD,oCAiCC;AAvDD,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAGxC,8BAAS;AAFlB,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAI7B,6DAAuD;AAA9C,qHAAA,cAAc,OAAA;AACvB,mEAA8D;AAArD,4HAAA,kBAAkB,OAAA;AAE3B;;;;GAIG;AACI,KAAK,UAAU,cAAc,CAAC,MAAW;IAC9C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC;IACrC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACrB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,YAAY;IAChC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,6CAA6C,CAAC;IAC5E,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC;IAEtC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAEtD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC;YAClC,UAAU;YACV,IAAI;YACJ,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS;SAC3D,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,cAAc,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAE7C,SAAS;QACT,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;YAC9B,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;YAC5B,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;YAC/B,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;YAC5B,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IAEL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACjC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,mBAAmB;AACnB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,UAAU,EAAE,CAAC;IACnC,YAAY,EAAE,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export declare class LogFileManager {
|
|
2
|
+
private logDir;
|
|
3
|
+
private fileWatcher;
|
|
4
|
+
constructor(logDir?: string);
|
|
5
|
+
private getUserHome;
|
|
6
|
+
private getClaudeProjectsDir;
|
|
7
|
+
/**
|
|
8
|
+
* 根据项目路径解析对应的日志目录
|
|
9
|
+
* @param projectPath 项目路径
|
|
10
|
+
* @returns 日志目录路径
|
|
11
|
+
*/
|
|
12
|
+
resolveLogDirectory(projectPath: string): string;
|
|
13
|
+
private generateProjectId;
|
|
14
|
+
/**
|
|
15
|
+
* 获取指定目录下的所有 JSONL 文件
|
|
16
|
+
* @param logDir 日志目录路径
|
|
17
|
+
* @returns 日志文件信息数组
|
|
18
|
+
*/
|
|
19
|
+
getAvailableLogFiles(logDir: string): Promise<any[]>;
|
|
20
|
+
/**
|
|
21
|
+
* 获取日志目录中的所有日志文件
|
|
22
|
+
*/
|
|
23
|
+
getLogFiles(): any[];
|
|
24
|
+
/**
|
|
25
|
+
* 读取日志文件内容
|
|
26
|
+
*/
|
|
27
|
+
readLogFile(filePath: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* 获取项目信息
|
|
30
|
+
*/
|
|
31
|
+
getProjectInfo(): {
|
|
32
|
+
name: string;
|
|
33
|
+
path: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* 获取系统信息
|
|
37
|
+
*/
|
|
38
|
+
getSystemInfo(): {
|
|
39
|
+
platform: any;
|
|
40
|
+
arch: any;
|
|
41
|
+
nodeVersion: string;
|
|
42
|
+
hostname: any;
|
|
43
|
+
userInfo: any;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* 检查文件是否存在
|
|
47
|
+
*/
|
|
48
|
+
fileExists(filePath: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* 获取文件统计信息
|
|
51
|
+
*/
|
|
52
|
+
getFileStats(filePath: string): any;
|
|
53
|
+
/**
|
|
54
|
+
* 创建目录(如果不存在)
|
|
55
|
+
*/
|
|
56
|
+
ensureDirectory(dirPath: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* 监听日志目录变化
|
|
59
|
+
*/
|
|
60
|
+
watchLogDirectory(logDir: string, callback: (event: string, filename: string, filepath?: string) => void): any | null;
|
|
61
|
+
/**
|
|
62
|
+
* 根据子agent日志文件名在主日志中查找对应的subagent_type
|
|
63
|
+
* @param agentLogPath 子agent日志文件路径
|
|
64
|
+
* @param mainLogPath 主日志文件路径
|
|
65
|
+
* @returns Agent名称和描述
|
|
66
|
+
*/
|
|
67
|
+
resolveAgentName(agentLogPath: string, mainLogPath: string): Promise<{
|
|
68
|
+
name: string;
|
|
69
|
+
description: string;
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* 在主日志行中查找指定 tool_use_id 的 tool_use 块
|
|
73
|
+
* @param mainLogLines 主日志所有行
|
|
74
|
+
* @param toolUseId 工具使用ID
|
|
75
|
+
* @returns tool_use 块或 null
|
|
76
|
+
*/
|
|
77
|
+
private findToolUseById;
|
|
78
|
+
/**
|
|
79
|
+
* 提取日志文件的第一条用户消息作为预览
|
|
80
|
+
* @param logLines 日志文件所有行
|
|
81
|
+
* @returns 预览和完整内容
|
|
82
|
+
*/
|
|
83
|
+
private extractInputPreview;
|
|
84
|
+
/**
|
|
85
|
+
* 获取指定会话的所有子agent日志
|
|
86
|
+
* @param logDir 日志目录
|
|
87
|
+
* @param sessionId 会话ID
|
|
88
|
+
* @returns 子agent日志信息数组
|
|
89
|
+
*/
|
|
90
|
+
getAgentLogsForSession(logDir: string, sessionId: string): Promise<any[]>;
|
|
91
|
+
/**
|
|
92
|
+
* 获取所有主日志的摘要信息
|
|
93
|
+
* @param logDir 日志目录
|
|
94
|
+
* @returns 主日志摘要数组
|
|
95
|
+
*/
|
|
96
|
+
getMainLogSummaries(logDir: string): Promise<any[]>;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=log-file-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-file-manager.d.ts","sourceRoot":"","sources":["log-file-manager.ts"],"names":[],"mappings":"AAKA,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAa;gBAEpB,MAAM,CAAC,EAAE,MAAM;IAI3B,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,oBAAoB;IAI5B;;;;OAIG;IACH,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAUhD,OAAO,CAAC,iBAAiB;IAQzB;;;;OAIG;IACG,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IA4B1D;;OAEG;IACH,WAAW,IAAI,GAAG,EAAE;IAiCpB;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAYrC;;OAEG;IACH,cAAc,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;IAQhD;;OAEG;IACH,aAAa;;;;;;;IAUb;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIrC;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM;IAS7B;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAMtC;;OAEG;IACH,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,GAAG,GAAG,IAAI;IAiBrH;;;;;OAKG;IACG,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAAC;IAwF/G;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAoBvB;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAgC3B;;;;;OAKG;IACG,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAqI/E;;;;OAIG;IACG,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;CA8D1D"}
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LogFileManager = void 0;
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
// import { LogFileInfo } from '../src/types/index.js';
|
|
8
|
+
class LogFileManager {
|
|
9
|
+
constructor(logDir) {
|
|
10
|
+
this.fileWatcher = null;
|
|
11
|
+
this.logDir = logDir || '';
|
|
12
|
+
}
|
|
13
|
+
getUserHome() {
|
|
14
|
+
return os.homedir();
|
|
15
|
+
}
|
|
16
|
+
getClaudeProjectsDir() {
|
|
17
|
+
return path.join(this.getUserHome(), '.claude', 'projects');
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 根据项目路径解析对应的日志目录
|
|
21
|
+
* @param projectPath 项目路径
|
|
22
|
+
* @returns 日志目录路径
|
|
23
|
+
*/
|
|
24
|
+
resolveLogDirectory(projectPath) {
|
|
25
|
+
// 1. 获取项目目录的绝对路径
|
|
26
|
+
const absoluteProjectPath = path.resolve(projectPath);
|
|
27
|
+
// 2. 根据实际规律生成项目目录标识
|
|
28
|
+
const projectId = this.generateProjectId(absoluteProjectPath);
|
|
29
|
+
// 3. 构建对应的日志目录路径
|
|
30
|
+
const logDir = path.join(this.getClaudeProjectsDir(), projectId);
|
|
31
|
+
return logDir;
|
|
32
|
+
}
|
|
33
|
+
generateProjectId(projectPath) {
|
|
34
|
+
// 根据实际规律:将路径中的所有 '/'、'\'、'_' 和 ':' 替换为 '-',同时将非 ASCII 字符(如中文)也替换为 '-'
|
|
35
|
+
// 例如:"/Users/ligf/Code/claude-code/ccdebug/ccdemo" -> "-Users-ligf-Code-claude-code-ccdebug-ccdemo"
|
|
36
|
+
// 例如:"/Users/ligf/Code/项目/测试" -> "-Users-ligf-Code---"
|
|
37
|
+
// 例如:"D:\mysoft\cctest\tests\办公资产_1\work" -> "D--mysoft-cctest-tests------1-work"
|
|
38
|
+
return projectPath.replace(/[\/\\:_]/g, '-').replace(/[^\x00-\x7F]/g, '-');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 获取指定目录下的所有 JSONL 文件
|
|
42
|
+
* @param logDir 日志目录路径
|
|
43
|
+
* @returns 日志文件信息数组
|
|
44
|
+
*/
|
|
45
|
+
async getAvailableLogFiles(logDir) {
|
|
46
|
+
if (!fs.existsSync(logDir)) {
|
|
47
|
+
console.warn(`日志目录不存在: ${logDir}`);
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
const files = await fs.promises.readdir(logDir);
|
|
51
|
+
const logFiles = [];
|
|
52
|
+
for (const file of files) {
|
|
53
|
+
if (file.endsWith('.jsonl')) {
|
|
54
|
+
const filepath = path.join(logDir, file);
|
|
55
|
+
const stats = await fs.promises.stat(filepath);
|
|
56
|
+
logFiles.push({
|
|
57
|
+
id: file.replace('.jsonl', ''),
|
|
58
|
+
name: file,
|
|
59
|
+
path: filepath,
|
|
60
|
+
modifiedAt: stats.mtime,
|
|
61
|
+
size: stats.size
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// 按修改时间降序排序,最新的在前面
|
|
66
|
+
return logFiles.sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime());
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 获取日志目录中的所有日志文件
|
|
70
|
+
*/
|
|
71
|
+
getLogFiles() {
|
|
72
|
+
try {
|
|
73
|
+
if (!fs.existsSync(this.logDir)) {
|
|
74
|
+
console.log(`日志目录不存在: ${this.logDir}`);
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
const files = fs.readdirSync(this.logDir);
|
|
78
|
+
const logFiles = [];
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
if (file.endsWith('.jsonl')) {
|
|
81
|
+
const filepath = path.join(this.logDir, file);
|
|
82
|
+
const stats = fs.statSync(filepath);
|
|
83
|
+
logFiles.push({
|
|
84
|
+
id: file.replace('.jsonl', ''),
|
|
85
|
+
name: file,
|
|
86
|
+
path: filepath,
|
|
87
|
+
modifiedAt: stats.mtime,
|
|
88
|
+
size: stats.size
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// 按修改时间排序,最新的在前
|
|
93
|
+
return logFiles.sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime());
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.error('获取日志文件失败:', error);
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 读取日志文件内容
|
|
102
|
+
*/
|
|
103
|
+
readLogFile(filePath) {
|
|
104
|
+
try {
|
|
105
|
+
if (!fs.existsSync(filePath)) {
|
|
106
|
+
throw new Error(`文件不存在: ${filePath}`);
|
|
107
|
+
}
|
|
108
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.error('读取日志文件失败:', error);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 获取项目信息
|
|
117
|
+
*/
|
|
118
|
+
getProjectInfo() {
|
|
119
|
+
const projectName = path.basename(this.logDir);
|
|
120
|
+
return {
|
|
121
|
+
name: projectName,
|
|
122
|
+
path: this.logDir
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 获取系统信息
|
|
127
|
+
*/
|
|
128
|
+
getSystemInfo() {
|
|
129
|
+
return {
|
|
130
|
+
platform: os.platform(),
|
|
131
|
+
arch: os.arch(),
|
|
132
|
+
nodeVersion: process.version,
|
|
133
|
+
hostname: os.hostname(),
|
|
134
|
+
userInfo: os.userInfo()
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 检查文件是否存在
|
|
139
|
+
*/
|
|
140
|
+
fileExists(filePath) {
|
|
141
|
+
return fs.existsSync(filePath);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 获取文件统计信息
|
|
145
|
+
*/
|
|
146
|
+
getFileStats(filePath) {
|
|
147
|
+
try {
|
|
148
|
+
return fs.statSync(filePath);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error('获取文件统计信息失败:', error);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 创建目录(如果不存在)
|
|
157
|
+
*/
|
|
158
|
+
ensureDirectory(dirPath) {
|
|
159
|
+
if (!fs.existsSync(dirPath)) {
|
|
160
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 监听日志目录变化
|
|
165
|
+
*/
|
|
166
|
+
watchLogDirectory(logDir, callback) {
|
|
167
|
+
if (!fs.existsSync(logDir)) {
|
|
168
|
+
console.warn(`无法监听不存在的目录: ${logDir}`);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const watcher = fs.watch(logDir, { persistent: true }, (eventType, filename) => {
|
|
172
|
+
if (filename && filename.endsWith('.jsonl')) {
|
|
173
|
+
const filepath = path.join(logDir, filename);
|
|
174
|
+
callback(eventType, filename, filepath);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
console.log(`开始监听日志目录: ${logDir}`);
|
|
178
|
+
return watcher;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 根据子agent日志文件名在主日志中查找对应的subagent_type
|
|
182
|
+
* @param agentLogPath 子agent日志文件路径
|
|
183
|
+
* @param mainLogPath 主日志文件路径
|
|
184
|
+
* @returns Agent名称和描述
|
|
185
|
+
*/
|
|
186
|
+
async resolveAgentName(agentLogPath, mainLogPath) {
|
|
187
|
+
try {
|
|
188
|
+
// 1. 从文件名提取 agentId
|
|
189
|
+
const fileName = path.basename(agentLogPath, '.jsonl');
|
|
190
|
+
const agentId = fileName.replace('agent-', '');
|
|
191
|
+
// 2. 读取主日志所有行
|
|
192
|
+
const mainLogContent = await fs.promises.readFile(mainLogPath, 'utf-8');
|
|
193
|
+
const mainLogLines = mainLogContent.split('\n').filter((line) => line.trim());
|
|
194
|
+
// 3. 读取agent日志的第一行,获取用户消息
|
|
195
|
+
const agentContent = await fs.promises.readFile(agentLogPath, 'utf-8');
|
|
196
|
+
const agentLines = agentContent.split('\n').filter((line) => line.trim());
|
|
197
|
+
if (agentLines.length === 0) {
|
|
198
|
+
return { name: agentId, description: '' };
|
|
199
|
+
}
|
|
200
|
+
const firstAgentEntry = JSON.parse(agentLines[0]);
|
|
201
|
+
const agentFirstContent = firstAgentEntry.message?.content;
|
|
202
|
+
// 提取agent的第一条消息文本(用于匹配)
|
|
203
|
+
let agentFirstText = '';
|
|
204
|
+
if (typeof agentFirstContent === 'string') {
|
|
205
|
+
agentFirstText = agentFirstContent.substring(0, 100);
|
|
206
|
+
}
|
|
207
|
+
else if (Array.isArray(agentFirstContent) && agentFirstContent.length > 0) {
|
|
208
|
+
const firstBlock = agentFirstContent[0];
|
|
209
|
+
if (firstBlock.type === 'text' && firstBlock.text) {
|
|
210
|
+
agentFirstText = firstBlock.text.substring(0, 100);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// 4. 获取主日志中所有Task工具调用
|
|
214
|
+
const taskToolUses = [];
|
|
215
|
+
for (const line of mainLogLines) {
|
|
216
|
+
try {
|
|
217
|
+
const entry = JSON.parse(line);
|
|
218
|
+
const content = entry.message?.content;
|
|
219
|
+
if (!Array.isArray(content))
|
|
220
|
+
continue;
|
|
221
|
+
for (const block of content) {
|
|
222
|
+
if (block.type === 'tool_use' && block.name === 'Task' && block.input?.subagent_type) {
|
|
223
|
+
taskToolUses.push({
|
|
224
|
+
id: block.id,
|
|
225
|
+
name: block.input.subagent_type,
|
|
226
|
+
description: block.input.description || '',
|
|
227
|
+
input: block.input
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (parseError) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// 5. 尝试通过prompt匹配来识别agent
|
|
237
|
+
// 检查agent第一条消息是否在Task的prompt中出现
|
|
238
|
+
for (const task of taskToolUses) {
|
|
239
|
+
const taskPrompt = task.input?.prompt || '';
|
|
240
|
+
if (agentFirstText && taskPrompt.includes(agentFirstText.substring(0, 50))) {
|
|
241
|
+
return {
|
|
242
|
+
name: task.name,
|
|
243
|
+
description: task.description
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// 6. 如果只有一个Task还未匹配,则使用它
|
|
248
|
+
if (taskToolUses.length > 0) {
|
|
249
|
+
// 按顺序返回第一个(简单策略)
|
|
250
|
+
return {
|
|
251
|
+
name: taskToolUses[0].name,
|
|
252
|
+
description: taskToolUses[0].description
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
// 回退:返回 agentId
|
|
256
|
+
return { name: agentId, description: '' };
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
console.error(`解析Agent名称失败: ${agentLogPath}`, error);
|
|
260
|
+
// 从文件名提取 agentId 作为回退
|
|
261
|
+
const fileName = path.basename(agentLogPath, '.jsonl');
|
|
262
|
+
const agentId = fileName.replace('agent-', '');
|
|
263
|
+
return { name: agentId, description: '' };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* 在主日志行中查找指定 tool_use_id 的 tool_use 块
|
|
268
|
+
* @param mainLogLines 主日志所有行
|
|
269
|
+
* @param toolUseId 工具使用ID
|
|
270
|
+
* @returns tool_use 块或 null
|
|
271
|
+
*/
|
|
272
|
+
findToolUseById(mainLogLines, toolUseId) {
|
|
273
|
+
for (const line of mainLogLines) {
|
|
274
|
+
try {
|
|
275
|
+
const entry = JSON.parse(line);
|
|
276
|
+
const content = entry.message?.content;
|
|
277
|
+
if (!Array.isArray(content))
|
|
278
|
+
continue;
|
|
279
|
+
for (const block of content) {
|
|
280
|
+
if (block.type === 'tool_use' && block.id === toolUseId) {
|
|
281
|
+
return block;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch (parseError) {
|
|
286
|
+
// 跳过无法解析的行
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* 提取日志文件的第一条用户消息作为预览
|
|
294
|
+
* @param logLines 日志文件所有行
|
|
295
|
+
* @returns 预览和完整内容
|
|
296
|
+
*/
|
|
297
|
+
extractInputPreview(logLines) {
|
|
298
|
+
for (const line of logLines) {
|
|
299
|
+
try {
|
|
300
|
+
const entry = JSON.parse(line);
|
|
301
|
+
if (entry.message?.role === 'user') {
|
|
302
|
+
const content = entry.message.content;
|
|
303
|
+
let fullText = '';
|
|
304
|
+
if (typeof content === 'string') {
|
|
305
|
+
fullText = content;
|
|
306
|
+
}
|
|
307
|
+
else if (Array.isArray(content) && content.length > 0) {
|
|
308
|
+
const firstBlock = content[0];
|
|
309
|
+
if (firstBlock.type === 'text' && firstBlock.text) {
|
|
310
|
+
fullText = firstBlock.text;
|
|
311
|
+
}
|
|
312
|
+
else if (typeof firstBlock === 'string') {
|
|
313
|
+
fullText = firstBlock;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
preview: fullText.slice(0, 50) + (fullText.length > 50 ? '...' : ''),
|
|
318
|
+
full: fullText
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
catch (parseError) {
|
|
323
|
+
// 跳过无法解析的行
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return { preview: '', full: '' };
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* 获取指定会话的所有子agent日志
|
|
331
|
+
* @param logDir 日志目录
|
|
332
|
+
* @param sessionId 会话ID
|
|
333
|
+
* @returns 子agent日志信息数组
|
|
334
|
+
*/
|
|
335
|
+
async getAgentLogsForSession(logDir, sessionId) {
|
|
336
|
+
try {
|
|
337
|
+
if (!fs.existsSync(logDir)) {
|
|
338
|
+
console.warn(`日志目录不存在: ${logDir}`);
|
|
339
|
+
return [];
|
|
340
|
+
}
|
|
341
|
+
const files = await fs.promises.readdir(logDir);
|
|
342
|
+
const mainLogPath = path.join(logDir, `${sessionId}.jsonl`);
|
|
343
|
+
// 检查主日志是否存在
|
|
344
|
+
if (!fs.existsSync(mainLogPath)) {
|
|
345
|
+
console.warn(`主日志不存在: ${mainLogPath}`);
|
|
346
|
+
return [];
|
|
347
|
+
}
|
|
348
|
+
// 1. 获取主日志中所有Task工具调用(按顺序)
|
|
349
|
+
const mainLogContent = await fs.promises.readFile(mainLogPath, 'utf-8');
|
|
350
|
+
const mainLogLines = mainLogContent.split('\n').filter((line) => line.trim());
|
|
351
|
+
const taskToolUses = [];
|
|
352
|
+
const taskToolResult = [];
|
|
353
|
+
for (const line of mainLogLines) {
|
|
354
|
+
try {
|
|
355
|
+
const entry = JSON.parse(line);
|
|
356
|
+
const content = entry.message?.content;
|
|
357
|
+
if (!Array.isArray(content))
|
|
358
|
+
continue;
|
|
359
|
+
for (const block of content) {
|
|
360
|
+
if (block.type === 'tool_use' && block.name === 'Task' && block.input?.subagent_type) {
|
|
361
|
+
taskToolUses.push({
|
|
362
|
+
id: block.id,
|
|
363
|
+
agentId: "",
|
|
364
|
+
agentName: block.input.subagent_type,
|
|
365
|
+
description: block.input.description || ''
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
if (block.type === 'tool_result' && entry.toolUseResult && entry.toolUseResult?.agentId) {
|
|
369
|
+
taskToolResult.push({
|
|
370
|
+
id: block.tool_use_id,
|
|
371
|
+
agentId: entry.toolUseResult.agentId
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
catch (parseError) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
//将taskToolResult的agentId合并到taskToolUses
|
|
381
|
+
for (const toolUse of taskToolUses) {
|
|
382
|
+
const toolUseId = toolUse.id;
|
|
383
|
+
const tResult = taskToolResult.find((toolResult) => { return toolResult.id === toolUseId; });
|
|
384
|
+
//如果没找到工具调用结果,跳过
|
|
385
|
+
if (tResult && tResult?.agentId) {
|
|
386
|
+
toolUse.agentId = tResult.agentId;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
// 2. 获取所有匹配的agent日志文件
|
|
390
|
+
const candidateAgents = [];
|
|
391
|
+
for (const file of files) {
|
|
392
|
+
// 仅处理子agent日志文件
|
|
393
|
+
if (file.startsWith('agent-') && file.endsWith('.jsonl')) {
|
|
394
|
+
const agentLogPath = path.join(logDir, file);
|
|
395
|
+
try {
|
|
396
|
+
// 读取子agent日志的第一行,检查其 sessionId 是否匹配
|
|
397
|
+
const content = await fs.promises.readFile(agentLogPath, 'utf-8');
|
|
398
|
+
const firstLine = content.split('\n').find((line) => line.trim());
|
|
399
|
+
if (firstLine) {
|
|
400
|
+
const firstEntry = JSON.parse(firstLine);
|
|
401
|
+
// 检查 sessionId 是否匹配,且不是 Warmup agent
|
|
402
|
+
if (firstEntry.sessionId === sessionId) {
|
|
403
|
+
const agentFirstContent = firstEntry.message?.content;
|
|
404
|
+
let isWarmup = false;
|
|
405
|
+
// 检查是否为Warmup agent
|
|
406
|
+
if (typeof agentFirstContent === 'string' && agentFirstContent.includes('Warmup')) {
|
|
407
|
+
isWarmup = true;
|
|
408
|
+
}
|
|
409
|
+
if (!isWarmup) {
|
|
410
|
+
const stats = await fs.promises.stat(agentLogPath);
|
|
411
|
+
candidateAgents.push({
|
|
412
|
+
file,
|
|
413
|
+
path: agentLogPath,
|
|
414
|
+
agentId: file.replace('agent-', '').replace('.jsonl', ''),
|
|
415
|
+
mtime: new Date(firstEntry.timestamp)
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
console.error(`处理子agent日志失败: ${file}`, error);
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// 3. 按文件修改时间排序(创建顺序)
|
|
428
|
+
candidateAgents.sort((a, b) => a.mtime.getTime() - b.mtime.getTime());
|
|
429
|
+
// 4. 按agentId关联agent名称及agent日志文件
|
|
430
|
+
const agentLogs = [];
|
|
431
|
+
candidateAgents.forEach((agent, index) => {
|
|
432
|
+
const task = taskToolUses.find((tUse) => { return tUse.agentId === agent.agentId; });
|
|
433
|
+
if (task) {
|
|
434
|
+
agentLogs.push({
|
|
435
|
+
id: agent.file.replace('.jsonl', ''),
|
|
436
|
+
name: agent.file,
|
|
437
|
+
path: agent.path,
|
|
438
|
+
agentId: agent.agentId,
|
|
439
|
+
agentName: task?.agentName ? task.agentName : agent.agentId,
|
|
440
|
+
agentDescription: task?.description ? task.description : ""
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
return agentLogs;
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
console.error('获取子agent日志失败:', error);
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* 获取所有主日志的摘要信息
|
|
453
|
+
* @param logDir 日志目录
|
|
454
|
+
* @returns 主日志摘要数组
|
|
455
|
+
*/
|
|
456
|
+
async getMainLogSummaries(logDir) {
|
|
457
|
+
try {
|
|
458
|
+
if (!fs.existsSync(logDir)) {
|
|
459
|
+
console.warn(`日志目录不存在: ${logDir}`);
|
|
460
|
+
return [];
|
|
461
|
+
}
|
|
462
|
+
const files = await fs.promises.readdir(logDir);
|
|
463
|
+
const mainLogs = [];
|
|
464
|
+
for (const file of files) {
|
|
465
|
+
// 仅处理主日志文件(非 agent-*.jsonl 格式)
|
|
466
|
+
if (file.endsWith('.jsonl') && !file.startsWith('agent-')) {
|
|
467
|
+
const filepath = path.join(logDir, file);
|
|
468
|
+
const sessionId = file.replace('.jsonl', '');
|
|
469
|
+
try {
|
|
470
|
+
const content = await fs.promises.readFile(filepath, 'utf-8');
|
|
471
|
+
const lines = content.split('\n').filter((line) => line.trim());
|
|
472
|
+
if (lines.length === 0) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
// 读取第一行和最后一行获取时间
|
|
476
|
+
const firstEntry = JSON.parse(lines[0]);
|
|
477
|
+
const lastEntry = JSON.parse(lines[lines.length - 1]);
|
|
478
|
+
// 提取时间戳(支持多种格式)
|
|
479
|
+
const startTime = firstEntry.timestamp || firstEntry.snapshot?.timestamp || firstEntry.message?.timestamp;
|
|
480
|
+
const endTime = lastEntry.timestamp || lastEntry.snapshot?.timestamp || lastEntry.message?.timestamp;
|
|
481
|
+
// 提取第一条用户消息
|
|
482
|
+
const { preview, full } = this.extractInputPreview(lines);
|
|
483
|
+
// 获取关联的子agent日志
|
|
484
|
+
const agentLogs = await this.getAgentLogsForSession(logDir, sessionId);
|
|
485
|
+
mainLogs.push({
|
|
486
|
+
id: sessionId,
|
|
487
|
+
name: file,
|
|
488
|
+
path: filepath,
|
|
489
|
+
startTime: startTime,
|
|
490
|
+
endTime: endTime,
|
|
491
|
+
inputPreview: preview,
|
|
492
|
+
inputFull: full,
|
|
493
|
+
agentLogs: agentLogs
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
console.error(`处理主日志失败: ${file}`, error);
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// 按开始时间降序排序,最新的在前面
|
|
503
|
+
return mainLogs.sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime());
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
console.error('获取主日志摘要失败:', error);
|
|
507
|
+
return [];
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
exports.LogFileManager = LogFileManager;
|
|
512
|
+
//# sourceMappingURL=log-file-manager.js.map
|