backtrace-console 0.0.5 → 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.
@@ -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
- rootDir: shared.ROOT_DIR,
43
- fingerprintsRoot: shared.FINGERPRINTS_ROOT,
44
- workdir: body.workdir || shared.DEFAULT_WORKDIR,
45
- proxy: shared.resolveCodexProxy(body.proxy),
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
  };
@@ -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;