backtrace-console 0.0.1 → 0.0.2
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/app.js +3 -2
- package/bin/backtrace-server.js +101 -0
- package/bin/www +3 -0
- package/lib/backtrace/constants.js +4 -0
- package/lib/backtrace/query-download.js +117 -0
- package/lib/backtrace/query-session.js +229 -0
- package/lib/backtrace/query.js +11 -445
- package/lib/backtrace/repair.js +35 -0
- package/lib/backtrace/tool.js +34 -3
- package/lib/feishu.js +66 -0
- package/lib/scheduler.js +126 -0
- package/package.json +10 -4
- package/public/chat-components.css +569 -0
- package/public/chat-core.js +635 -0
- package/public/chat-layout.css +290 -0
- package/public/chat-render.js +308 -0
- package/public/chat-send.js +230 -0
- package/public/chat.html +69 -0
- package/public/{__inline_check__.js → index-page.js} +107 -54
- package/public/index.html +1 -505
- package/routes/backtrace-chat.js +389 -0
- package/routes/backtrace-files.js +88 -0
- package/routes/backtrace-fix-plan.js +53 -0
- package/routes/backtrace-run.js +128 -0
- package/routes/backtrace-shared.js +202 -0
- package/routes/backtrace.js +7 -861
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
var fs = require('node:fs/promises');
|
|
2
|
+
var path = require('node:path');
|
|
3
|
+
var BacktraceCodexToolModule = require('../lib/BacktraceCodexTool');
|
|
4
|
+
var RepairModule = require('../lib/backtrace/repair');
|
|
5
|
+
|
|
6
|
+
var ROOT_DIR = process.cwd();
|
|
7
|
+
var FINGERPRINTS_ROOT = path.join(ROOT_DIR, 'fingerprints');
|
|
8
|
+
var DEFAULT_WORKDIR = BacktraceCodexToolModule.DEFAULT_WORKDIR;
|
|
9
|
+
var DEFAULT_PROXY = BacktraceCodexToolModule.DEFAULT_PROXY;
|
|
10
|
+
var UE_ENGINE_PATH = process.env.UE_ENGINE_PATH || process.env.UNREAL_ENGINE_PATH || '';
|
|
11
|
+
var REPAIR_STATUS_FILE = '.repair-status.json';
|
|
12
|
+
|
|
13
|
+
function toSafeAbsolute(rootDir, relativePath) {
|
|
14
|
+
var safeRelative = String(relativePath || '').replace(/[\\/]+/g, path.sep);
|
|
15
|
+
var targetPath = path.resolve(rootDir, safeRelative);
|
|
16
|
+
if (targetPath !== rootDir && !targetPath.startsWith(rootDir + path.sep)) {
|
|
17
|
+
throw new Error('Invalid path');
|
|
18
|
+
}
|
|
19
|
+
return targetPath;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function pathExists(targetPath) {
|
|
23
|
+
try {
|
|
24
|
+
await fs.access(targetPath);
|
|
25
|
+
return true;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeFingerprintPath(fingerprint) {
|
|
32
|
+
var normalized = String(fingerprint || '').trim();
|
|
33
|
+
if (!normalized) {
|
|
34
|
+
throw new Error('fingerprint is required');
|
|
35
|
+
}
|
|
36
|
+
return normalized;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function formatChatSessionId(date) {
|
|
40
|
+
return [
|
|
41
|
+
date.getFullYear(),
|
|
42
|
+
String(date.getMonth() + 1).padStart(2, '0'),
|
|
43
|
+
String(date.getDate()).padStart(2, '0'),
|
|
44
|
+
].join('') + '-' + [
|
|
45
|
+
String(date.getHours()).padStart(2, '0'),
|
|
46
|
+
String(date.getMinutes()).padStart(2, '0'),
|
|
47
|
+
String(date.getSeconds()).padStart(2, '0'),
|
|
48
|
+
].join('');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function listImmediateDirectories(rootDir, relativeDir) {
|
|
52
|
+
var absoluteDir = toSafeAbsolute(rootDir, relativeDir || '');
|
|
53
|
+
if (!(await pathExists(absoluteDir))) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
var entries = await fs.readdir(absoluteDir, { withFileTypes: true });
|
|
57
|
+
return entries.filter(function(entry) { return entry.isDirectory(); }).map(function(entry) {
|
|
58
|
+
return { name: entry.name, path: relativeDir ? path.join(relativeDir, entry.name) : entry.name };
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function listImmediateFiles(rootDir, relativeDir) {
|
|
63
|
+
var absoluteDir = toSafeAbsolute(rootDir, relativeDir || '');
|
|
64
|
+
if (!(await pathExists(absoluteDir))) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
var entries = await fs.readdir(absoluteDir, { withFileTypes: true });
|
|
68
|
+
return entries.filter(function(entry) { return entry.isFile(); }).map(function(entry) {
|
|
69
|
+
var childRelative = relativeDir ? path.join(relativeDir, entry.name) : entry.name;
|
|
70
|
+
return { name: entry.name, path: childRelative, ext: path.extname(entry.name).toLowerCase() };
|
|
71
|
+
}).sort(function(a, b) { return a.name.localeCompare(b.name); });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function buildFingerprintStatus(hasReports, hasCompletedRepair) {
|
|
75
|
+
if (!hasReports) {
|
|
76
|
+
return '未生成报告';
|
|
77
|
+
}
|
|
78
|
+
return hasCompletedRepair ? '已完成' : '未修复状态';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function readRepairStatus(relativeFingerprintPath) {
|
|
82
|
+
var statusPath = toSafeAbsolute(FINGERPRINTS_ROOT, path.join(relativeFingerprintPath, 'reports', REPAIR_STATUS_FILE));
|
|
83
|
+
var raw = await fs.readFile(statusPath, 'utf8').catch(function() { return ''; });
|
|
84
|
+
if (!raw) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(raw);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function readFingerprintFetchedMeta(relativeFingerprintPath) {
|
|
95
|
+
var absolutePath = toSafeAbsolute(FINGERPRINTS_ROOT, relativeFingerprintPath);
|
|
96
|
+
var stats = await fs.stat(absolutePath);
|
|
97
|
+
var fetchedAtDate = stats.birthtimeMs ? new Date(stats.birthtimeMs) : new Date(stats.ctimeMs || stats.mtimeMs);
|
|
98
|
+
return { fetchedAt: fetchedAtDate.toISOString(), fetchedAtTs: fetchedAtDate.getTime() };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function listTopLevelMergedDirectories() {
|
|
102
|
+
var fingerprintDirs = await listImmediateDirectories(FINGERPRINTS_ROOT, '');
|
|
103
|
+
var items = await Promise.all(fingerprintDirs.map(async function(item) {
|
|
104
|
+
var logDirs = await listImmediateDirectories(FINGERPRINTS_ROOT, path.join(item.path, 'logs'));
|
|
105
|
+
var reportFiles = await listImmediateFiles(FINGERPRINTS_ROOT, path.join(item.path, 'reports'));
|
|
106
|
+
var repairStatus = await readRepairStatus(item.path);
|
|
107
|
+
var fetchedMeta = await readFingerprintFetchedMeta(item.path);
|
|
108
|
+
var hasReports = reportFiles.length > 0;
|
|
109
|
+
var hasCompletedRepair = !!(repairStatus && repairStatus.completed);
|
|
110
|
+
return {
|
|
111
|
+
name: item.name,
|
|
112
|
+
path: item.path,
|
|
113
|
+
hasLogs: logDirs.length > 0,
|
|
114
|
+
hasReports: hasReports,
|
|
115
|
+
hasCompletedRepair: hasCompletedRepair,
|
|
116
|
+
status: buildFingerprintStatus(hasReports, hasCompletedRepair),
|
|
117
|
+
fetchedAt: fetchedMeta.fetchedAt,
|
|
118
|
+
fetchedAtTs: fetchedMeta.fetchedAtTs,
|
|
119
|
+
};
|
|
120
|
+
}));
|
|
121
|
+
return items.sort(function(a, b) {
|
|
122
|
+
var diff = Number(b.fetchedAtTs || 0) - Number(a.fetchedAtTs || 0);
|
|
123
|
+
return diff || b.name.localeCompare(a.name);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function readFingerprintMeta(relativeFingerprintPath) {
|
|
128
|
+
var targetPath = toSafeAbsolute(FINGERPRINTS_ROOT, path.join(relativeFingerprintPath, 'meta.json'));
|
|
129
|
+
var raw = await fs.readFile(targetPath, 'utf8').catch(function(error) {
|
|
130
|
+
if (error && error.code === 'ENOENT') {
|
|
131
|
+
return '';
|
|
132
|
+
}
|
|
133
|
+
throw error;
|
|
134
|
+
});
|
|
135
|
+
if (!raw) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return JSON.parse(raw);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function writeFingerprintMeta(relativeFingerprintPath, payload) {
|
|
142
|
+
var fingerprintDir = toSafeAbsolute(FINGERPRINTS_ROOT, relativeFingerprintPath);
|
|
143
|
+
var targetPath = toSafeAbsolute(FINGERPRINTS_ROOT, path.join(relativeFingerprintPath, 'meta.json'));
|
|
144
|
+
await fs.mkdir(fingerprintDir, { recursive: true });
|
|
145
|
+
await fs.writeFile(targetPath, JSON.stringify(payload, null, 2), 'utf8');
|
|
146
|
+
return targetPath;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getReportPriority(name) {
|
|
150
|
+
var lowerName = String(name || '').toLowerCase();
|
|
151
|
+
if (lowerName === 'error-line-summary.md') return 0;
|
|
152
|
+
if (lowerName.endsWith('.md')) return 1;
|
|
153
|
+
if (lowerName.endsWith('.json')) return 2;
|
|
154
|
+
return 3;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function listFingerprintReports(relativeFingerprintPath) {
|
|
158
|
+
var files = await listImmediateFiles(FINGERPRINTS_ROOT, path.join(relativeFingerprintPath, 'reports'));
|
|
159
|
+
return files.filter(function(file) {
|
|
160
|
+
return ['.md', '.txt', '.json'].indexOf(file.ext) >= 0;
|
|
161
|
+
}).sort(function(a, b) {
|
|
162
|
+
return getReportPriority(a.name) - getReportPriority(b.name) || a.name.localeCompare(b.name);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function buildCodexChatPrompt(userPrompt, options) {
|
|
167
|
+
var workdir = options && options.workdir ? options.workdir : DEFAULT_WORKDIR;
|
|
168
|
+
var enginePath = options && options.enginePath ? options.enginePath : UE_ENGINE_PATH;
|
|
169
|
+
return [
|
|
170
|
+
'当前工程上下文:',
|
|
171
|
+
'项目工作目录:' + (workdir || '-'),
|
|
172
|
+
'UE 引擎目录:' + (enginePath || '-'),
|
|
173
|
+
'',
|
|
174
|
+
'请在分析和修改代码时优先使用上述路径。用户消息如下:',
|
|
175
|
+
'',
|
|
176
|
+
String(userPrompt || ''),
|
|
177
|
+
].join('\n');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function resolveCodexProxy(proxyValue) {
|
|
181
|
+
return RepairModule.resolveCodexProxy(proxyValue || DEFAULT_PROXY);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
ROOT_DIR: ROOT_DIR,
|
|
186
|
+
FINGERPRINTS_ROOT: FINGERPRINTS_ROOT,
|
|
187
|
+
DEFAULT_WORKDIR: DEFAULT_WORKDIR,
|
|
188
|
+
UE_ENGINE_PATH: UE_ENGINE_PATH,
|
|
189
|
+
toSafeAbsolute: toSafeAbsolute,
|
|
190
|
+
normalizeFingerprintPath: normalizeFingerprintPath,
|
|
191
|
+
formatChatSessionId: formatChatSessionId,
|
|
192
|
+
listImmediateDirectories: listImmediateDirectories,
|
|
193
|
+
listImmediateFiles: listImmediateFiles,
|
|
194
|
+
listTopLevelMergedDirectories: listTopLevelMergedDirectories,
|
|
195
|
+
buildFingerprintStatus: buildFingerprintStatus,
|
|
196
|
+
readRepairStatus: readRepairStatus,
|
|
197
|
+
readFingerprintMeta: readFingerprintMeta,
|
|
198
|
+
writeFingerprintMeta: writeFingerprintMeta,
|
|
199
|
+
listFingerprintReports: listFingerprintReports,
|
|
200
|
+
buildCodexChatPrompt: buildCodexChatPrompt,
|
|
201
|
+
resolveCodexProxy: resolveCodexProxy,
|
|
202
|
+
};
|