backtrace-console 0.0.4 → 0.0.6
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/lib/feishu.js +5 -2
- package/lib/p4ops.js +72 -0
- package/lib/p4sync.js +35 -0
- package/lib/scheduler.js +101 -55
- package/package.json +2 -1
- package/public/chat-claude-core.js +650 -0
- package/public/chat-claude-send.js +241 -0
- package/public/chat-claude.html +84 -0
- package/public/chat-components.css +105 -0
- package/public/chat-core.js +27 -12
- package/public/chat-p4.js +113 -0
- package/public/chat-prompt.js +29 -0
- package/public/chat-send.js +3 -3
- package/public/chat.html +16 -1
- package/public/index-page.js +94 -67
- package/public/index.html +3 -0
- package/public/stylesheets/style.css +4 -3
- package/routes/backtrace-chat-claude.js +477 -0
- package/routes/backtrace-chat.js +88 -20
- package/routes/backtrace-fix-plan.js +65 -6
- package/routes/backtrace-p4.js +104 -0
- package/routes/backtrace-shared.js +32 -0
- package/routes/backtrace.js +2 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
var express = require('express');
|
|
2
|
+
var path = require('node:path');
|
|
3
|
+
var fs = require('node:fs/promises');
|
|
2
4
|
var RepairModule = require('../lib/backtrace/repair');
|
|
5
|
+
var p4ops = require('../lib/p4ops');
|
|
3
6
|
var shared = require('./backtrace-shared');
|
|
4
7
|
|
|
5
8
|
var router = express.Router();
|
|
@@ -32,18 +35,74 @@ router.post('/fix-plan/apply', async function(req, res) {
|
|
|
32
35
|
if (!reportPath || !planText) {
|
|
33
36
|
return res.status(400).json({ ok: false, error: 'reportPath and planText are required' });
|
|
34
37
|
}
|
|
38
|
+
|
|
39
|
+
var overrides = {
|
|
40
|
+
rootDir: shared.ROOT_DIR,
|
|
41
|
+
fingerprintsRoot: shared.FINGERPRINTS_ROOT,
|
|
42
|
+
workdir: body.workdir || shared.DEFAULT_WORKDIR,
|
|
43
|
+
proxy: shared.resolveCodexProxy(body.proxy),
|
|
44
|
+
};
|
|
45
|
+
|
|
35
46
|
try {
|
|
36
47
|
var result = await RepairModule.applyRepairPlan({
|
|
37
48
|
reportPath: reportPath,
|
|
38
49
|
planText: planText,
|
|
39
50
|
repairVersion: body.repairVersion,
|
|
40
51
|
repairPlanPath: body.repairPlanPath,
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
}, overrides);
|
|
53
|
+
|
|
54
|
+
if (result.ok && result.repairVersion) {
|
|
55
|
+
// archivedSources 中保存了 Codex 修改过的源文件相对路径
|
|
56
|
+
var openedFiles = (result.archivedSources || []).map(function(p) {
|
|
57
|
+
// 路径格式:<fp>/repair/<ver>/sources/<rel> → 转为绝对路径
|
|
58
|
+
return require('node:path').join(shared.FINGERPRINTS_ROOT, p);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Extract fingerprint id from repairResultPath: "<fp>/repair/<ver>/apply-result.md"
|
|
62
|
+
var repairRelPath = String(result.repairResultPath || '').replace(/\\/g, '/');
|
|
63
|
+
var repairIdx = repairRelPath.indexOf('/repair/');
|
|
64
|
+
var fingerprintId = repairIdx > 0 ? repairRelPath.slice(0, repairIdx) : null;
|
|
65
|
+
|
|
66
|
+
if (fingerprintId) {
|
|
67
|
+
// Use Codex to generate a concise changelist description
|
|
68
|
+
var autoDescription = '';
|
|
69
|
+
try {
|
|
70
|
+
var descPrompt = [
|
|
71
|
+
'根据以下修复结果,用一行英文写出 Perforce changelist 说明,',
|
|
72
|
+
'格式:fix: <描述>,只输出这一行,不要其他内容。\n\n',
|
|
73
|
+
String(result.resultText || '').slice(0, 1500)
|
|
74
|
+
].join('');
|
|
75
|
+
var descResult = await RepairModule.runChatTurn(
|
|
76
|
+
{ prompt: descPrompt },
|
|
77
|
+
overrides
|
|
78
|
+
);
|
|
79
|
+
if (descResult.ok) autoDescription = String(descResult.reply || '').trim();
|
|
80
|
+
} catch (e) {
|
|
81
|
+
// description generation is best-effort
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
var changesPath = path.join(
|
|
85
|
+
shared.FINGERPRINTS_ROOT,
|
|
86
|
+
fingerprintId,
|
|
87
|
+
'repair', result.repairVersion,
|
|
88
|
+
'p4-changes.json'
|
|
89
|
+
);
|
|
90
|
+
await fs.writeFile(changesPath, JSON.stringify({
|
|
91
|
+
fingerprint: fingerprintId,
|
|
92
|
+
repairVersion: result.repairVersion,
|
|
93
|
+
description: autoDescription,
|
|
94
|
+
files: openedFiles,
|
|
95
|
+
recordedAt: new Date().toISOString(),
|
|
96
|
+
submittedAt: null
|
|
97
|
+
}, null, 2)).catch(function(e) {
|
|
98
|
+
console.error('[fix-plan] 写入 p4-changes.json 失败:', e.message);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
result.p4OpenedFiles = openedFiles;
|
|
102
|
+
result.p4Description = autoDescription;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
47
106
|
return res.json(result);
|
|
48
107
|
} catch (error) {
|
|
49
108
|
return res.status(500).json({ ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
var express = require('express');
|
|
2
|
+
var path = require('node:path');
|
|
3
|
+
var fs = require('node:fs/promises');
|
|
4
|
+
var p4ops = require('../lib/p4ops');
|
|
5
|
+
var shared = require('./backtrace-shared');
|
|
6
|
+
|
|
7
|
+
var router = express.Router();
|
|
8
|
+
|
|
9
|
+
router.get('/p4/pending', async function(req, res) {
|
|
10
|
+
var fingerprint = String(req.query.fingerprint || '').trim();
|
|
11
|
+
var sessionId = String(req.query.sessionId || '').trim();
|
|
12
|
+
try {
|
|
13
|
+
var items = await scanPendingChanges(fingerprint || null, sessionId || null);
|
|
14
|
+
return res.json({ ok: true, items: items });
|
|
15
|
+
} catch (err) {
|
|
16
|
+
return res.status(500).json({ ok: false, error: err.message });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
router.post('/p4/submit', async function(req, res) {
|
|
21
|
+
var body = req.body || {};
|
|
22
|
+
var description = String(body.description || '').trim();
|
|
23
|
+
if (!description) return res.status(400).json({ ok: false, error: 'description is required' });
|
|
24
|
+
if (!p4ops.checkConfig()) return res.status(400).json({ ok: false, error: '未配置 P4PORT/P4USER/P4CLIENT' });
|
|
25
|
+
var fingerprint = String(body.fingerprint || '').trim();
|
|
26
|
+
var sessionId = String(body.sessionId || '').trim();
|
|
27
|
+
try {
|
|
28
|
+
// 先对待提交文件做 reconcile,确保它们在 P4 中标记为 opened for edit
|
|
29
|
+
var pendingItems = await scanPendingChanges(fingerprint || null, sessionId || null).catch(function() { return []; });
|
|
30
|
+
var allFiles = [];
|
|
31
|
+
pendingItems.forEach(function(item) {
|
|
32
|
+
if (item.files && item.files.length) allFiles = allFiles.concat(item.files);
|
|
33
|
+
});
|
|
34
|
+
if (allFiles.length > 0) {
|
|
35
|
+
await p4ops.p4ReconcileFiles(allFiles).catch(function(e) {
|
|
36
|
+
console.error('[p4/submit] reconcile 失败:', e.message);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
var result = await p4ops.p4Submit(description, allFiles);
|
|
41
|
+
if (result.ok) {
|
|
42
|
+
var items = pendingItems;
|
|
43
|
+
var submittedAt = new Date().toISOString();
|
|
44
|
+
var changelistId = result.changelistId || null;
|
|
45
|
+
for (var i = 0; i < items.length; i++) {
|
|
46
|
+
var item = items[i];
|
|
47
|
+
if (item._filePath) {
|
|
48
|
+
var updated = Object.assign({}, item, { submittedAt: submittedAt, changelistId: changelistId, description: description });
|
|
49
|
+
delete updated._filePath;
|
|
50
|
+
await fs.writeFile(item._filePath, JSON.stringify(updated, null, 2)).catch(function() {});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return res.json({ ok: result.ok, output: result.output || '', changelistId: result.changelistId || null, error: result.error });
|
|
55
|
+
} catch (err) {
|
|
56
|
+
return res.status(500).json({ ok: false, error: err.message });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
async function scanPendingChanges(filterFingerprint, filterSessionId) {
|
|
61
|
+
var items = [];
|
|
62
|
+
var fpNames;
|
|
63
|
+
if (filterFingerprint) {
|
|
64
|
+
fpNames = [shared.normalizeFingerprintPath(filterFingerprint)];
|
|
65
|
+
} else {
|
|
66
|
+
try {
|
|
67
|
+
var entries = await fs.readdir(shared.FINGERPRINTS_ROOT, { withFileTypes: true });
|
|
68
|
+
fpNames = entries
|
|
69
|
+
.filter(function(e) { return e.isDirectory() && !e.name.startsWith('.'); })
|
|
70
|
+
.map(function(e) { return e.name; });
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return items;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (var i = 0; i < fpNames.length; i++) {
|
|
76
|
+
var repairRoot = path.join(shared.FINGERPRINTS_ROOT, fpNames[i], 'repair');
|
|
77
|
+
var repairDirs;
|
|
78
|
+
try {
|
|
79
|
+
repairDirs = await fs.readdir(repairRoot, { withFileTypes: true });
|
|
80
|
+
} catch (e) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
for (var j = 0; j < repairDirs.length; j++) {
|
|
84
|
+
var repairEntry = repairDirs[j];
|
|
85
|
+
if (!repairEntry.isDirectory()) continue;
|
|
86
|
+
if (filterSessionId && repairEntry.name !== 'chat-' + filterSessionId) continue;
|
|
87
|
+
var changesPath = path.join(repairRoot, repairEntry.name, 'p4-changes.json');
|
|
88
|
+
try {
|
|
89
|
+
var raw = await fs.readFile(changesPath, 'utf8');
|
|
90
|
+
var data = JSON.parse(raw);
|
|
91
|
+
// 指定了 sessionId 时无论是否已提交都返回(前端负责展示状态)
|
|
92
|
+
if (!data.submittedAt || filterSessionId) {
|
|
93
|
+
data._filePath = changesPath;
|
|
94
|
+
items.push(data);
|
|
95
|
+
}
|
|
96
|
+
} catch (e) {
|
|
97
|
+
// no p4-changes.json or invalid, skip
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return items;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = router;
|
|
@@ -181,11 +181,41 @@ function resolveCodexProxy(proxyValue) {
|
|
|
181
181
|
return RepairModule.resolveCodexProxy(proxyValue || DEFAULT_PROXY);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
// ── 模型配置(统一从 .env 读取) ─────────────────────────────────────────
|
|
185
|
+
var CLAUDE_MODEL = process.env.CLAUDE_MODEL || 'claude-sonnet-4-5';
|
|
186
|
+
var CODEX_MODEL = process.env.CODEX_MODEL || '';
|
|
187
|
+
var CODEX_REASONING_EFFORT = process.env.CODEX_REASONING_EFFORT || 'high';
|
|
188
|
+
|
|
189
|
+
// ── 崩溃上下文 prompt(前后端共享同一份内容) ────────────────────────────
|
|
190
|
+
function buildCrashContextPrompt(meta) {
|
|
191
|
+
var errorMessage = meta && meta.errorMessage ? meta.errorMessage : '-';
|
|
192
|
+
var classifiers = meta && meta.classifiers ? meta.classifiers : '-';
|
|
193
|
+
return [
|
|
194
|
+
'你是一名资深 C++ 开发工程师与 Unreal Engine 引擎专家,正在协助分析虚幻引擎客户端的一次崩溃。',
|
|
195
|
+
'',
|
|
196
|
+
'## 崩溃上下文',
|
|
197
|
+
'- Error Message: ' + errorMessage,
|
|
198
|
+
'- Classifiers: ' + classifiers,
|
|
199
|
+
'',
|
|
200
|
+
'## 工作准则',
|
|
201
|
+
'- 优先从 Error Message 推断崩溃位置与触发条件,必要时使用工具读取项目代码与日志文件以定位根因',
|
|
202
|
+
'- 给出根因分析与最小化的修复建议;修改代码时保持改动可审查、影响范围明确',
|
|
203
|
+
'- 回答使用简体中文,代码块使用合适的语言标注',
|
|
204
|
+
].join('\n');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function buildCrashContextFirstTurnPrompt(userText, meta) {
|
|
208
|
+
return buildCrashContextPrompt(meta) + '\n\n----\n我的问题:\n' + String(userText || '');
|
|
209
|
+
}
|
|
210
|
+
|
|
184
211
|
module.exports = {
|
|
185
212
|
ROOT_DIR: ROOT_DIR,
|
|
186
213
|
FINGERPRINTS_ROOT: FINGERPRINTS_ROOT,
|
|
187
214
|
DEFAULT_WORKDIR: DEFAULT_WORKDIR,
|
|
188
215
|
UE_ENGINE_PATH: UE_ENGINE_PATH,
|
|
216
|
+
CLAUDE_MODEL: CLAUDE_MODEL,
|
|
217
|
+
CODEX_MODEL: CODEX_MODEL,
|
|
218
|
+
CODEX_REASONING_EFFORT: CODEX_REASONING_EFFORT,
|
|
189
219
|
toSafeAbsolute: toSafeAbsolute,
|
|
190
220
|
normalizeFingerprintPath: normalizeFingerprintPath,
|
|
191
221
|
formatChatSessionId: formatChatSessionId,
|
|
@@ -199,4 +229,6 @@ module.exports = {
|
|
|
199
229
|
listFingerprintReports: listFingerprintReports,
|
|
200
230
|
buildCodexChatPrompt: buildCodexChatPrompt,
|
|
201
231
|
resolveCodexProxy: resolveCodexProxy,
|
|
232
|
+
buildCrashContextPrompt: buildCrashContextPrompt,
|
|
233
|
+
buildCrashContextFirstTurnPrompt: buildCrashContextFirstTurnPrompt,
|
|
202
234
|
};
|
package/routes/backtrace.js
CHANGED
|
@@ -6,5 +6,7 @@ router.use(require('./backtrace-files'));
|
|
|
6
6
|
router.use(require('./backtrace-run'));
|
|
7
7
|
router.use(require('./backtrace-fix-plan'));
|
|
8
8
|
router.use(require('./backtrace-chat'));
|
|
9
|
+
router.use(require('./backtrace-chat-claude'));
|
|
10
|
+
router.use(require('./backtrace-p4'));
|
|
9
11
|
|
|
10
12
|
module.exports = router;
|