backtrace-console 0.0.3 → 0.0.5

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.
@@ -0,0 +1,53 @@
1
+ var express = require('express');
2
+ var RepairModule = require('../lib/backtrace/repair');
3
+ var shared = require('./backtrace-shared');
4
+
5
+ var router = express.Router();
6
+
7
+ router.post('/fix-plan/generate', async function(req, res) {
8
+ var body = req.body || {};
9
+ var reportPath = body.reportPath;
10
+ if (!reportPath) {
11
+ return res.status(400).json({ ok: false, error: 'reportPath is required' });
12
+ }
13
+ try {
14
+ var result = await RepairModule.generateRepairPlan({
15
+ reportPath: reportPath,
16
+ }, {
17
+ rootDir: shared.ROOT_DIR,
18
+ fingerprintsRoot: shared.FINGERPRINTS_ROOT,
19
+ workdir: body.workdir || shared.DEFAULT_WORKDIR,
20
+ proxy: shared.resolveCodexProxy(body.proxy),
21
+ });
22
+ return res.json(result);
23
+ } catch (error) {
24
+ return res.status(500).json({ ok: false, error: error instanceof Error ? error.message : String(error) });
25
+ }
26
+ });
27
+
28
+ router.post('/fix-plan/apply', async function(req, res) {
29
+ var body = req.body || {};
30
+ var reportPath = body.reportPath;
31
+ var planText = body.planText;
32
+ if (!reportPath || !planText) {
33
+ return res.status(400).json({ ok: false, error: 'reportPath and planText are required' });
34
+ }
35
+ try {
36
+ var result = await RepairModule.applyRepairPlan({
37
+ reportPath: reportPath,
38
+ planText: planText,
39
+ repairVersion: body.repairVersion,
40
+ repairPlanPath: body.repairPlanPath,
41
+ }, {
42
+ rootDir: shared.ROOT_DIR,
43
+ fingerprintsRoot: shared.FINGERPRINTS_ROOT,
44
+ workdir: body.workdir || shared.DEFAULT_WORKDIR,
45
+ proxy: shared.resolveCodexProxy(body.proxy),
46
+ });
47
+ return res.json(result);
48
+ } catch (error) {
49
+ return res.status(500).json({ ok: false, error: error instanceof Error ? error.message : String(error) });
50
+ }
51
+ });
52
+
53
+ module.exports = router;
@@ -0,0 +1,128 @@
1
+ var express = require('express');
2
+ var fs = require('node:fs/promises');
3
+ var path = require('node:path');
4
+ var BacktraceCodexTool = require('../lib/BacktraceCodexTool').BacktraceCodexTool;
5
+ var { BacktraceSession, downloadItemLogs, fetchAttachmentList } = require('../lib/backtrace/query');
6
+ var { createOptions } = require('../lib/backtrace/options');
7
+ var shared = require('./backtrace-shared');
8
+
9
+ var router = express.Router();
10
+
11
+ router.post('/run', async function(req, res) {
12
+ var body = req.body || {};
13
+ var command = body.command || 'collect-all';
14
+ var tool = new BacktraceCodexTool();
15
+ try {
16
+ var result = await tool.run(command, body);
17
+ return res.json({ ok: true, command: command, result: result });
18
+ } catch (error) {
19
+ return res.status(500).json({ ok: false, command: command, error: error instanceof Error ? error.message : String(error) });
20
+ }
21
+ });
22
+
23
+ router.get('/fingerprint', async function(req, res) {
24
+ var fingerprint = String(req.query.fingerprint || '').trim();
25
+ if (!fingerprint) {
26
+ return res.status(400).json({ ok: false, error: 'fingerprint is required' });
27
+ }
28
+ try {
29
+ var relativeFingerprintPath = shared.normalizeFingerprintPath(fingerprint);
30
+ var result = await shared.readFingerprintMeta(relativeFingerprintPath);
31
+ if (!result) {
32
+ return res.status(404).json({ ok: false, error: 'fingerprint not found' });
33
+ }
34
+ var reports = await shared.listFingerprintReports(relativeFingerprintPath);
35
+ result.reportFiles = reports;
36
+ result.defaultReportPath = reports.length > 0 ? reports[0].path : '';
37
+ return res.json({ ok: true, command: 'fingerprint', result: result });
38
+ } catch (error) {
39
+ return res.status(500).json({ ok: false, command: 'fingerprint', error: error instanceof Error ? error.message : String(error) });
40
+ }
41
+ });
42
+
43
+ router.get('/list', async function(req, res) {
44
+ var tool = new BacktraceCodexTool();
45
+ var overrides = {
46
+ from: req.query.from,
47
+ to: req.query.to,
48
+ limit: req.query.limit,
49
+ offset: req.query.offset,
50
+ select: req.query.select,
51
+ };
52
+ try {
53
+ var result = await tool.list(overrides);
54
+ return res.json({ ok: true, command: 'list', result: result });
55
+ } catch (error) {
56
+ return res.status(500).json({ ok: false, command: 'list', error: error instanceof Error ? error.message : String(error) });
57
+ }
58
+ });
59
+
60
+ router.post('/download-object', async function(req, res) {
61
+ var body = req.body || {};
62
+ var fp = String(body.fingerprint || '').trim();
63
+ var objectIdHex = String(body.objectIdHex || '').trim();
64
+ if (!fp || !objectIdHex) {
65
+ return res.status(400).json({ ok: false, error: 'fingerprint and objectIdHex are required' });
66
+ }
67
+ try {
68
+ var options = createOptions({ command: 'collect-all', fingerprint: fp });
69
+ var session = new BacktraceSession(options);
70
+ await session.login();
71
+ var item = { objectIdHex: objectIdHex, objectIdDecimal: '', fingerprint: fp, values: { fingerprint: fp } };
72
+ var result = await downloadItemLogs(session, item, options);
73
+ return res.json({ ok: true, fingerprint: fp, objectIdHex: objectIdHex, downloadedFiles: result.downloadedFiles, targetDir: result.targetDir });
74
+ } catch (error) {
75
+ return res.status(500).json({ ok: false, error: error instanceof Error ? error.message : String(error) });
76
+ }
77
+ });
78
+
79
+ router.post('/query-object-attachments', async function(req, res) {
80
+ var body = req.body || {};
81
+ var fp = String(body.fingerprint || '').trim();
82
+ var objectIdHex = String(body.objectIdHex || '').trim();
83
+ if (!fp || !objectIdHex) {
84
+ return res.status(400).json({ ok: false, error: 'fingerprint and objectIdHex are required' });
85
+ }
86
+ try {
87
+ var options = createOptions({ command: 'collect-all', fingerprint: fp });
88
+ var session = new BacktraceSession(options);
89
+ await session.login();
90
+
91
+ // 查询远端附件列表
92
+ var attachmentResult = await fetchAttachmentList(session, options.queryUrl, objectIdHex);
93
+ var remoteAttachments = (attachmentResult && attachmentResult.attachments) ? attachmentResult.attachments : [];
94
+
95
+ // 读取本地已下载文件
96
+ var objDir = path.join(shared.FINGERPRINTS_ROOT, fp, 'logs', objectIdHex);
97
+ var localFiles = await fs.readdir(objDir).catch(function() { return []; });
98
+ var localFileSet = new Set(localFiles);
99
+
100
+ // 合并:逐文件检查本地是否已下载
101
+ var attachments = remoteAttachments.map(function(att) {
102
+ return {
103
+ id: att.id || '',
104
+ name: att.name || '',
105
+ size: att.size || 0,
106
+ content_type: att.content_type || '',
107
+ downloaded: localFileSet.has(att.name),
108
+ };
109
+ });
110
+
111
+ // 读取现有 object-meta.json,合并写回
112
+ var metaPath = path.join(objDir, 'object-meta.json');
113
+ var existingRaw = await fs.readFile(metaPath, 'utf8').catch(function() { return ''; });
114
+ var existing = {};
115
+ if (existingRaw) {
116
+ try { existing = JSON.parse(existingRaw); } catch (_) { existing = {}; }
117
+ }
118
+ var updated = Object.assign({}, existing, { attachments: attachments });
119
+ await fs.mkdir(objDir, { recursive: true });
120
+ await fs.writeFile(metaPath, JSON.stringify(updated, null, 2), 'utf8');
121
+
122
+ return res.json({ ok: true, fingerprint: fp, objectIdHex: objectIdHex, attachments: attachments });
123
+ } catch (error) {
124
+ return res.status(500).json({ ok: false, error: error instanceof Error ? error.message : String(error) });
125
+ }
126
+ });
127
+
128
+ module.exports = router;
@@ -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
+ };
@@ -0,0 +1,10 @@
1
+ var express = require('express');
2
+
3
+ var router = express.Router();
4
+
5
+ router.use(require('./backtrace-files'));
6
+ router.use(require('./backtrace-run'));
7
+ router.use(require('./backtrace-fix-plan'));
8
+ router.use(require('./backtrace-chat'));
9
+
10
+ module.exports = router;
@@ -0,0 +1,9 @@
1
+ var express = require('express');
2
+ var router = express.Router();
3
+
4
+ /* GET home page. */
5
+ router.get('/', function(req, res, next) {
6
+ res.render('index', { title: 'Express' });
7
+ });
8
+
9
+ module.exports = router;
@@ -0,0 +1,9 @@
1
+ var express = require('express');
2
+ var router = express.Router();
3
+
4
+ /* GET users listing. */
5
+ router.get('/', function(req, res, next) {
6
+ res.send('respond with a resource');
7
+ });
8
+
9
+ module.exports = router;