@gracker/smartperfetto 1.0.19 → 1.0.20

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.
Files changed (115) hide show
  1. package/dist/agent/core/conclusionContract.d.ts +1 -0
  2. package/dist/agent/core/conclusionContract.d.ts.map +1 -1
  3. package/dist/agent/tools/frameAnalyzer.d.ts.map +1 -1
  4. package/dist/agent/tools/frameAnalyzer.js +1 -0
  5. package/dist/agent/tools/frameAnalyzer.js.map +1 -1
  6. package/dist/agentOpenAI/openAiRuntime.d.ts +24 -0
  7. package/dist/agentOpenAI/openAiRuntime.d.ts.map +1 -1
  8. package/dist/agentOpenAI/openAiRuntime.js +159 -191
  9. package/dist/agentOpenAI/openAiRuntime.js.map +1 -1
  10. package/dist/agentRuntime/index.d.ts +1 -0
  11. package/dist/agentRuntime/index.d.ts.map +1 -1
  12. package/dist/agentRuntime/index.js +16 -1
  13. package/dist/agentRuntime/index.js.map +1 -1
  14. package/dist/agentRuntime/runtimeCommon.d.ts +34 -0
  15. package/dist/agentRuntime/runtimeCommon.d.ts.map +1 -0
  16. package/dist/agentRuntime/runtimeCommon.js +231 -0
  17. package/dist/agentRuntime/runtimeCommon.js.map +1 -0
  18. package/dist/agentv3/claudeConfig.d.ts +6 -2
  19. package/dist/agentv3/claudeConfig.d.ts.map +1 -1
  20. package/dist/agentv3/claudeConfig.js +50 -48
  21. package/dist/agentv3/claudeConfig.js.map +1 -1
  22. package/dist/agentv3/claudeMcpServer.d.ts.map +1 -1
  23. package/dist/agentv3/claudeMcpServer.js +74 -66
  24. package/dist/agentv3/claudeMcpServer.js.map +1 -1
  25. package/dist/agentv3/claudeRuntime.d.ts +18 -2
  26. package/dist/agentv3/claudeRuntime.d.ts.map +1 -1
  27. package/dist/agentv3/claudeRuntime.js +198 -265
  28. package/dist/agentv3/claudeRuntime.js.map +1 -1
  29. package/dist/agentv3/claudeSseBridge.js +1 -1
  30. package/dist/agentv3/claudeSseBridge.js.map +1 -1
  31. package/dist/agentv3/claudeVerifier.d.ts.map +1 -1
  32. package/dist/agentv3/claudeVerifier.js +3 -6
  33. package/dist/agentv3/claudeVerifier.js.map +1 -1
  34. package/dist/cli-user/bin.js +83 -2
  35. package/dist/cli-user/bin.js.map +1 -1
  36. package/dist/cli-user/commands/analyze.d.ts +2 -0
  37. package/dist/cli-user/commands/analyze.d.ts.map +1 -1
  38. package/dist/cli-user/commands/analyze.js +1 -0
  39. package/dist/cli-user/commands/analyze.js.map +1 -1
  40. package/dist/cli-user/commands/capture.d.ts +37 -2
  41. package/dist/cli-user/commands/capture.d.ts.map +1 -1
  42. package/dist/cli-user/commands/capture.js +184 -95
  43. package/dist/cli-user/commands/capture.js.map +1 -1
  44. package/dist/cli-user/commands/compare.d.ts +2 -0
  45. package/dist/cli-user/commands/compare.d.ts.map +1 -1
  46. package/dist/cli-user/commands/compare.js +1 -0
  47. package/dist/cli-user/commands/compare.js.map +1 -1
  48. package/dist/cli-user/commands/doctor.js +4 -0
  49. package/dist/cli-user/commands/doctor.js.map +1 -1
  50. package/dist/cli-user/services/androidCapture.d.ts +59 -0
  51. package/dist/cli-user/services/androidCapture.d.ts.map +1 -0
  52. package/dist/cli-user/services/androidCapture.js +375 -0
  53. package/dist/cli-user/services/androidCapture.js.map +1 -0
  54. package/dist/cli-user/services/captureConfig.d.ts +38 -0
  55. package/dist/cli-user/services/captureConfig.d.ts.map +1 -0
  56. package/dist/cli-user/services/captureConfig.js +434 -0
  57. package/dist/cli-user/services/captureConfig.js.map +1 -0
  58. package/dist/cli-user/services/captureTools.d.ts +11 -0
  59. package/dist/cli-user/services/captureTools.d.ts.map +1 -0
  60. package/dist/cli-user/services/captureTools.js +247 -0
  61. package/dist/cli-user/services/captureTools.js.map +1 -0
  62. package/dist/cli-user/services/cliAnalyzeService.d.ts +2 -0
  63. package/dist/cli-user/services/cliAnalyzeService.d.ts.map +1 -1
  64. package/dist/cli-user/services/cliAnalyzeService.js +1 -0
  65. package/dist/cli-user/services/cliAnalyzeService.js.map +1 -1
  66. package/dist/cli-user/services/runtimeGuard.d.ts +10 -0
  67. package/dist/cli-user/services/runtimeGuard.d.ts.map +1 -1
  68. package/dist/cli-user/services/runtimeGuard.js +48 -0
  69. package/dist/cli-user/services/runtimeGuard.js.map +1 -1
  70. package/dist/cli-user/services/turnRunner.d.ts +3 -0
  71. package/dist/cli-user/services/turnRunner.d.ts.map +1 -1
  72. package/dist/cli-user/services/turnRunner.js +4 -0
  73. package/dist/cli-user/services/turnRunner.js.map +1 -1
  74. package/dist/cli-user/types.d.ts +57 -0
  75. package/dist/cli-user/types.d.ts.map +1 -1
  76. package/dist/perfetto-recording-tools-pin.env +24 -0
  77. package/dist/services/agentResultNormalizer.d.ts.map +1 -1
  78. package/dist/services/agentResultNormalizer.js +32 -4
  79. package/dist/services/agentResultNormalizer.js.map +1 -1
  80. package/dist/services/evidence/evidenceContractBuilder.d.ts.map +1 -1
  81. package/dist/services/evidence/evidenceContractBuilder.js +17 -3
  82. package/dist/services/evidence/evidenceContractBuilder.js.map +1 -1
  83. package/dist/services/perfettoSqlSkill.d.ts.map +1 -1
  84. package/dist/services/perfettoSqlSkill.js +1 -0
  85. package/dist/services/perfettoSqlSkill.js.map +1 -1
  86. package/dist/services/providerManager/connectionTester.d.ts.map +1 -1
  87. package/dist/services/providerManager/connectionTester.js +4 -68
  88. package/dist/services/providerManager/connectionTester.js.map +1 -1
  89. package/dist/services/providerManager/index.d.ts +1 -0
  90. package/dist/services/providerManager/index.d.ts.map +1 -1
  91. package/dist/services/providerManager/index.js +8 -1
  92. package/dist/services/providerManager/index.js.map +1 -1
  93. package/dist/services/providerManager/providerService.d.ts.map +1 -1
  94. package/dist/services/providerManager/providerService.js +37 -106
  95. package/dist/services/providerManager/providerService.js.map +1 -1
  96. package/dist/services/providerManager/providerSnapshot.d.ts.map +1 -1
  97. package/dist/services/providerManager/providerSnapshot.js +13 -12
  98. package/dist/services/providerManager/providerSnapshot.js.map +1 -1
  99. package/dist/services/providerManager/runtimeCapabilities.d.ts +9 -0
  100. package/dist/services/providerManager/runtimeCapabilities.d.ts.map +1 -0
  101. package/dist/services/providerManager/runtimeCapabilities.js +105 -0
  102. package/dist/services/providerManager/runtimeCapabilities.js.map +1 -0
  103. package/dist/services/skillEngine/skillExecutor.d.ts +1 -0
  104. package/dist/services/skillEngine/skillExecutor.d.ts.map +1 -1
  105. package/dist/services/skillEngine/skillExecutor.js +64 -12
  106. package/dist/services/skillEngine/skillExecutor.js.map +1 -1
  107. package/package.json +3 -3
  108. package/prebuilts/android-platform-tools/README.md +13 -0
  109. package/prebuilts/perfetto-recording-tools/README.md +17 -0
  110. package/skills/atomic/cpu_topology_detection.skill.yaml +105 -159
  111. package/skills/atomic/cpu_topology_view.skill.yaml +2 -0
  112. package/strategies/prompt-openai-final-report-continuation-en.template.md +3 -1
  113. package/strategies/prompt-openai-final-report-continuation-zh.template.md +3 -1
  114. package/strategies/prompt-output-format.template.md +1 -1
  115. package/strategies/startup.strategy.md +4 -4
@@ -65,18 +65,10 @@ const contextTokenMeter_1 = require("./contextTokenMeter");
65
65
  const agentMetrics_1 = require("./agentMetrics");
66
66
  const analysisPatternMemory_1 = require("./analysisPatternMemory");
67
67
  const codeAwareOutputRegistry_1 = require("../services/security/codeAwareOutputRegistry");
68
- const skillNotesInjector_1 = require("./selfImprove/skillNotesInjector");
69
68
  const strategyFingerprint_1 = require("./selfImprove/strategyFingerprint");
70
69
  const claudeVerifier_1 = require("./claudeVerifier");
71
70
  const runtimePaths_1 = require("../runtimePaths");
72
71
  const finalResultQualityGate_1 = require("../services/finalResultQualityGate");
73
- function parseQuickBudgetEnv() {
74
- const v = process.env.SELF_IMPROVE_QUICK_NOTES_BUDGET;
75
- if (!v)
76
- return undefined;
77
- const n = Number.parseInt(v, 10);
78
- return Number.isFinite(n) && n >= 0 ? n : undefined;
79
- }
80
72
  function parseLeadingJsonObject(text) {
81
73
  let depth = 0;
82
74
  let inString = false;
@@ -142,7 +134,7 @@ function extractPlanPhaseIdFromToolResult(resultStr) {
142
134
  return undefined;
143
135
  }
144
136
  function looksLikeProcessNarration(text) {
145
- return /(?:我需要|我将|接下来|先重新|重新读取|继续调用|工具|tool|let me|i need to|i will|next i)/i
137
+ return /(?:我来|我需要|我将|接下来|先重新|重新读取|继续调用|首先.*提交|计划已提交|工具|tool|let me|i need to|i will|next i)/i
146
138
  .test(text.slice(0, 500));
147
139
  }
148
140
  function correctionResultLooksUsable(text) {
@@ -156,16 +148,116 @@ function correctionResultLooksUsable(text) {
156
148
  return false;
157
149
  return hasFinalReportMarker || !(0, claudeVerifier_1.isConclusionIncomplete)(trimmed);
158
150
  }
151
+ function findDeliverableReportHeadingIndex(text) {
152
+ const match = text.match(/(?:^|\n)\s{0,3}(?:#{1,3}\s*)?(?:(?:[^\n#]{0,40})?分析报告|综合结论|关键结论|最终结论|最终报告|根因分析|Final Conclusion|Final Report|Analysis Report|Root Cause)(?=\s|[::。.!!?\n]|$)/i);
153
+ return match?.index ?? -1;
154
+ }
155
+ function stripLeadingProcessNarrationBeforeSection(text) {
156
+ const trimmed = text.trim();
157
+ if (!trimmed)
158
+ return '';
159
+ const reportHeadingMatch = trimmed.match(/^\s{0,3}#{1,3}\s*[^\n]*(?:分析报告|最终报告|Final Report|Analysis Report)[^\n]*\n+/i);
160
+ const heading = reportHeadingMatch ? reportHeadingMatch[0].trimEnd() : '';
161
+ const body = reportHeadingMatch
162
+ ? trimmed.slice(reportHeadingMatch[0].length).trimStart()
163
+ : trimmed;
164
+ const sectionMatch = /(?:^|\n)\s{0,3}#{1,4}\s+\S/.exec(body);
165
+ if (!sectionMatch || sectionMatch.index === undefined || sectionMatch.index <= 0) {
166
+ return trimmed;
167
+ }
168
+ const prefix = body.slice(0, sectionMatch.index).trim();
169
+ const prefixIsProcessNarration = looksLikeProcessNarration(prefix) ||
170
+ (0, finalResultQualityGate_1.looksLikeProcessNarrationConclusion)(prefix) ||
171
+ /(?:我来分析|计划已提交|开始\s*Phase|进入\s*Phase|Phase\s*\d+|所有假设已解决|完整结构化报告|修正重试|验证发现|update_plan_phase|resolve_hypothesis)/i.test(prefix);
172
+ if (!prefixIsProcessNarration)
173
+ return trimmed;
174
+ const reportBody = body.slice(sectionMatch.index).trimStart();
175
+ return heading ? `${heading}\n\n${reportBody}` : reportBody;
176
+ }
177
+ function sanitizeClaudeConclusionText(text) {
178
+ const trimmed = text.trim();
179
+ if (!trimmed)
180
+ return '';
181
+ const singleLineCleaned = trimmed
182
+ .replace(/^(?:完成综合结论输出|完整结构化报告已(?:输出|生成))[。::\s]*/i, '')
183
+ .replace(/^所有假设已解决[。;;,\s]*(?:现在)?输出完整结构化报告[。::\s-]*/i, '')
184
+ .replace(/^所有(?:深钻)?数据已收集完毕[。;;,\s]*(?:现在)?输出(?:综合结论|最终报告|完整结构化报告)[。::\s-]*/i, '')
185
+ .trim();
186
+ const processIntroCleaned = stripLeadingProcessNarrationBeforeSection(singleLineCleaned);
187
+ if (processIntroCleaned !== trimmed)
188
+ return processIntroCleaned;
189
+ const headingIndex = findDeliverableReportHeadingIndex(trimmed);
190
+ if (headingIndex <= 0 || !(0, finalResultQualityGate_1.hasDeliverableFinalReportHeading)(trimmed.slice(headingIndex))) {
191
+ return trimmed;
192
+ }
193
+ const prefix = trimmed.slice(0, headingIndex).trim();
194
+ const prefixIsProcessNarration = looksLikeProcessNarration(prefix) ||
195
+ (0, finalResultQualityGate_1.looksLikeProcessNarrationConclusion)(prefix) ||
196
+ /(?:我来分析|计划已提交|开始\s*Phase|进入\s*Phase|Phase\s*\d+|所有假设已解决|完整结构化报告|修正重试|验证发现|update_plan_phase|resolve_hypothesis)/i.test(prefix);
197
+ if (!prefixIsProcessNarration)
198
+ return trimmed;
199
+ return trimmed.slice(headingIndex).trim();
200
+ }
201
+ function reportHeadingForScene(sceneType, outputLanguage) {
202
+ const zh = outputLanguage !== 'en';
203
+ switch (sceneType) {
204
+ case 'startup':
205
+ return zh ? '# 启动性能分析报告' : '# Startup Performance Analysis Report';
206
+ case 'scrolling':
207
+ return zh ? '# 滑动性能分析报告' : '# Scrolling Performance Analysis Report';
208
+ case 'anr':
209
+ return zh ? '# ANR 分析报告' : '# ANR Analysis Report';
210
+ default:
211
+ return zh ? '# 性能分析报告' : '# Performance Analysis Report';
212
+ }
213
+ }
214
+ function looksLikeStructuredDeliverableReport(text) {
215
+ const trimmed = text.trim();
216
+ const headingCount = (trimmed.match(/(^|\n)\s{0,3}#{1,3}\s+\S/g) || []).length;
217
+ if (headingCount < 2)
218
+ return false;
219
+ return /(?:evidence_ref_id|source_ref|art-\d+|data:art-\d+|data:skill:|##?\s*(?:概览|关键发现|根因|优化建议|Recommendations|Evidence))/i.test(trimmed);
220
+ }
221
+ function ensureClaudeFinalReportHeading(text, sceneType, outputLanguage) {
222
+ const trimmed = sanitizeClaudeConclusionText(text);
223
+ if (!trimmed || (0, finalResultQualityGate_1.hasDeliverableFinalReportHeading)(trimmed))
224
+ return trimmed;
225
+ if (!looksLikeStructuredDeliverableReport(trimmed))
226
+ return trimmed;
227
+ return `${reportHeadingForScene(sceneType, outputLanguage)}\n\n${trimmed}`;
228
+ }
229
+ function shouldMarkCorrectionTimeoutPartial(input) {
230
+ if (correctionResultLooksUsable(sanitizeClaudeConclusionText(input.correctedResult))) {
231
+ return false;
232
+ }
233
+ return !correctionResultLooksUsable(sanitizeClaudeConclusionText(input.existingConclusion));
234
+ }
235
+ function chooseClaudeConclusionText(input) {
236
+ const finalResult = sanitizeClaudeConclusionText(input.finalResult);
237
+ const accumulatedAnswer = sanitizeClaudeConclusionText(input.accumulatedAnswer);
238
+ if (!finalResult)
239
+ return accumulatedAnswer;
240
+ if (!accumulatedAnswer)
241
+ return finalResult;
242
+ if ((0, claudeVerifier_1.isConclusionIncomplete)(finalResult) &&
243
+ accumulatedAnswer.length > finalResult.length &&
244
+ (0, finalResultQualityGate_1.hasDeliverableFinalReportHeading)(accumulatedAnswer)) {
245
+ return accumulatedAnswer;
246
+ }
247
+ if (!(0, finalResultQualityGate_1.hasDeliverableFinalReportHeading)(finalResult) &&
248
+ (0, finalResultQualityGate_1.hasDeliverableFinalReportHeading)(accumulatedAnswer)) {
249
+ return accumulatedAnswer;
250
+ }
251
+ return finalResult;
252
+ }
159
253
  const traceCompletenessProber_1 = require("./traceCompletenessProber");
160
- const entityCapture_1 = require("../agent/core/entityCapture");
161
254
  const outputLanguage_1 = require("./outputLanguage");
162
255
  const runtimeSnapshotStore_1 = require("../services/runtimeSnapshotStore");
163
256
  const enterpriseMigration_1 = require("../services/enterpriseMigration");
257
+ const runtimeCommon_1 = require("../agentRuntime/runtimeCommon");
164
258
  const SESSION_MAP_FILE = (0, runtimePaths_1.backendLogPath)('claude_session_map.json');
165
259
  /** Max age for session map entries before pruning (24 hours). */
166
260
  const SESSION_MAP_MAX_AGE_MS = 24 * 60 * 60 * 1000;
167
- /** Claude SDK sessions expire server-side after roughly 4 hours. */
168
- const SDK_SESSION_FRESHNESS_MS = 4 * 60 * 60 * 1000;
169
261
  function enterpriseSessionMapDbWritesEnabled() {
170
262
  return (0, enterpriseMigration_1.enterpriseDbWritesEnabled)();
171
263
  }
@@ -213,25 +305,6 @@ function loadSessionMapForCurrentMode() {
213
305
  }
214
306
  return new Map();
215
307
  }
216
- function providerScopeFromOptions(options) {
217
- if (!options.tenantId || !options.workspaceId)
218
- return undefined;
219
- return {
220
- tenantId: options.tenantId,
221
- workspaceId: options.workspaceId,
222
- userId: options.userId,
223
- };
224
- }
225
- function knowledgeScopeFromOptions(options) {
226
- if (!options.tenantId || !options.workspaceId)
227
- return undefined;
228
- return {
229
- tenantId: options.tenantId,
230
- workspaceId: options.workspaceId,
231
- userId: options.userId,
232
- sourceRunId: options.runId,
233
- };
234
- }
235
308
  /**
236
309
  * Debounce timer for session map persistence — avoids blocking event loop on every SDK message.
237
310
  * P2-1: Use a Map keyed by the Map reference to support multiple ClaudeRuntime instances.
@@ -272,24 +345,6 @@ function savePersistedSessionMapSync(map) {
272
345
  // The old logs/session_notes/ directory is no longer written to.
273
346
  // P2-G1: ALLOWED_TOOLS is now auto-derived from createClaudeMcpServer() return value.
274
347
  // No longer hardcoded — adding a new MCP tool automatically includes it.
275
- /**
276
- * Format pre-queried trace datasets as Markdown tables to prepend to the AI prompt.
277
- * Mirrors smartperfetto's approach: data is ready upfront so the AI skips basic SQL turns.
278
- */
279
- function formatTraceContext(datasets, outputLanguage = outputLanguage_1.DEFAULT_OUTPUT_LANGUAGE) {
280
- if (!datasets || datasets.length === 0)
281
- return '';
282
- const parts = datasets.map((d) => {
283
- const header = `| ${d.columns.join(' | ')} |`;
284
- const sep = `| ${d.columns.map(() => '---').join(' | ')} |`;
285
- const rows = d.rows.slice(0, 100).map((r) => `| ${r.map((v) => String(v ?? '—')).join(' | ')} |`);
286
- const truncNote = d.rows.length > 100
287
- ? (0, outputLanguage_1.localize)(outputLanguage, `\n*(前 100 行,共 ${d.rows.length} 行)*`, `\n*(first 100 rows out of ${d.rows.length})*`)
288
- : '';
289
- return `### ${d.label}\n${header}\n${sep}\n${rows.join('\n')}${truncNote}`;
290
- });
291
- return (0, outputLanguage_1.localize)(outputLanguage, `## 前端预查询 Trace 数据\n\n以下数据已由前端查询完毕,直接使用,无需重复 SQL 查询:\n\n${parts.join('\n\n')}`, `## Frontend Pre-queried Trace Data\n\nThe frontend has already queried the following data. Use it directly; do not repeat the same SQL query.\n\n${parts.join('\n\n')}`);
292
- }
293
348
  /** Check if an error is retryable (API overload/server errors). */
294
349
  function isRetryableError(err) {
295
350
  const msg = err.message || '';
@@ -331,44 +386,18 @@ function isMissingSdkConversationError(message) {
331
386
  function isFreshFullSdkSessionEntry(entry, now = Date.now()) {
332
387
  return !!entry
333
388
  && entry.mode === 'full'
334
- && now - (entry.updatedAt || 0) < SDK_SESSION_FRESHNESS_MS;
335
- }
336
- function compactForPrompt(value, maxChars) {
337
- const text = String(value ?? '').replace(/\s+/g, ' ').trim();
338
- if (text.length <= maxChars)
339
- return text;
340
- return `${text.slice(0, Math.max(0, maxChars - 1))}...`;
341
- }
342
- function buildQuickConversationContext(previousTurns, outputLanguage = outputLanguage_1.DEFAULT_OUTPUT_LANGUAGE) {
343
- const turns = previousTurns.filter(turn => turn.completed).slice(-3);
344
- if (turns.length === 0)
345
- return undefined;
346
- const lines = [
347
- (0, outputLanguage_1.localize)(outputLanguage, '## 最近对话上下文\n\n以下是 SmartPerfetto 本地保存的最近问答,用于理解“继续/刚才/这个”等指代;不要把它当作当前问题的新证据。', '## Recent Conversation Context\n\nThe following recent SmartPerfetto turns are local context for references like "continue", "earlier", or "this"; do not treat them as new evidence for the current question.'),
348
- ];
349
- for (const turn of turns) {
350
- const query = compactForPrompt(turn.query, 220);
351
- const answer = compactForPrompt(turn.result?.message || '', 700);
352
- const findings = turn.findings
353
- .slice(0, 3)
354
- .map(f => `[${f.severity}] ${compactForPrompt(f.title, 160)}`)
355
- .filter(Boolean);
356
- lines.push(`### Turn ${turn.turnIndex + 1}`);
357
- lines.push(`- ${(0, outputLanguage_1.localize)(outputLanguage, '用户', 'User')}: ${query}`);
358
- if (answer) {
359
- lines.push(`- ${(0, outputLanguage_1.localize)(outputLanguage, '上轮回答', 'Previous answer')}: ${answer}`);
360
- }
361
- if (findings.length > 0) {
362
- lines.push(`- ${(0, outputLanguage_1.localize)(outputLanguage, '上轮发现', 'Previous findings')}: ${findings.join('; ')}`);
363
- }
364
- }
365
- return lines.join('\n');
389
+ && (0, runtimeCommon_1.isFreshRuntimeEntry)(entry, runtimeCommon_1.SDK_SESSION_FRESHNESS_MS, now);
366
390
  }
367
391
  exports.__testing = {
368
392
  getSdkResultErrorMessage,
369
393
  isMissingSdkConversationError,
370
394
  isFreshFullSdkSessionEntry,
371
- buildQuickConversationContext,
395
+ buildQuickConversationContext: runtimeCommon_1.buildQuickConversationContext,
396
+ correctionResultLooksUsable,
397
+ chooseClaudeConclusionText,
398
+ ensureClaudeFinalReportHeading,
399
+ sanitizeClaudeConclusionText,
400
+ shouldMarkCorrectionTimeoutPartial,
372
401
  };
373
402
  /** Sleep for the given milliseconds. */
374
403
  function sleep(ms) {
@@ -491,7 +520,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
491
520
  }
492
521
  /** Restore a cached architecture detection result (e.g., from session persistence). */
493
522
  restoreArchitectureCache(traceId, architecture) {
494
- this.architectureCache.set(traceId, architecture);
523
+ (0, runtimeCommon_1.setLruCacheEntry)(this.architectureCache, traceId, architecture);
495
524
  }
496
525
  /** Get cached architecture for a traceId (used for persistence). */
497
526
  getCachedArchitecture(traceId) {
@@ -503,7 +532,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
503
532
  return isFreshFullSdkSessionEntry(entry) ? entry.sdkSessionId : undefined;
504
533
  }
505
534
  buildSessionMapKey(sessionId, referenceTraceId) {
506
- return referenceTraceId ? `${sessionId}:ref:${referenceTraceId}` : sessionId;
535
+ return (0, runtimeCommon_1.buildRuntimeSessionMapKey)(sessionId, referenceTraceId);
507
536
  }
508
537
  persistSessionMapEntry(sessionId, traceId, sessionMapKey, entry, options) {
509
538
  if (legacySessionMapWritesEnabled()) {
@@ -541,7 +570,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
541
570
  }
542
571
  if (enterpriseSessionMapDbWritesEnabled()) {
543
572
  try {
544
- (0, runtimeSnapshotStore_1.deleteClaudeSessionMapRuntimeSnapshot)(sessionId, sessionMapKey, providerScopeFromOptions(options));
573
+ (0, runtimeSnapshotStore_1.deleteClaudeSessionMapRuntimeSnapshot)(sessionId, sessionMapKey, (0, runtimeCommon_1.providerScopeFromAnalysisOptions)(options));
545
574
  }
546
575
  catch (err) {
547
576
  console.warn('[ClaudeRuntime] Failed to delete stale SDK session map from runtime_snapshots:', err.message);
@@ -607,7 +636,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
607
636
  // Distinguish: only full analysis turns trigger multi-turn continuity (not prior quick turns)
608
637
  hasPriorFullAnalysis: previousTurns.some(t => t.intent?.complexity !== 'simple'),
609
638
  };
610
- const cachedArch = this.architectureCache.get(traceId);
639
+ const cachedArch = (0, runtimeCommon_1.getLruCacheEntry)(this.architectureCache, traceId);
611
640
  // Focus detection runs for every path — kick it off once and let the classifier
612
641
  // (if needed) share the wait. Explicit 'fast'/'full' skips the classifier entirely.
613
642
  const explicitMode = options.analysisMode;
@@ -615,7 +644,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
615
644
  console.warn('[ClaudeRuntime] Focus app detection failed (graceful):', err.message);
616
645
  return { apps: [], primaryApp: undefined, method: 'none' };
617
646
  });
618
- const providerScope = providerScopeFromOptions(options);
647
+ const providerScope = (0, runtimeCommon_1.providerScopeFromAnalysisOptions)(options);
619
648
  const runtimeConfig = (0, claudeConfig_1.resolveRuntimeConfig)(this.config, options.providerId, providerScope);
620
649
  let queryComplexity;
621
650
  let classifierSource;
@@ -654,6 +683,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
654
683
  sessionContext,
655
684
  previousTurns,
656
685
  sceneType,
686
+ runtimeConfig,
657
687
  });
658
688
  const { handleMessage: bridge, getAccumulatedAnswer } = (0, claudeSseBridge_1.createSseBridge)((update) => {
659
689
  this.emitUpdate(update);
@@ -702,7 +732,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
702
732
  }
703
733
  // Prepend pre-queried trace data so the AI has all context without spending turns on SQL
704
734
  if (options.traceContext && options.traceContext.length > 0) {
705
- const traceSection = formatTraceContext(options.traceContext, this.config.outputLanguage);
735
+ const traceSection = (0, runtimeCommon_1.formatTraceContext)(options.traceContext, this.config.outputLanguage);
706
736
  effectivePrompt = `${traceSection}\n\n${effectivePrompt}`;
707
737
  }
708
738
  const sdkEnv = (0, claudeConfig_1.createSdkEnv)(options.providerId, providerScope);
@@ -1091,8 +1121,8 @@ class ClaudeRuntime extends events_1.EventEmitter {
1091
1121
  terminationMessage = (0, analysisTermination_1.buildMaxTurnsTerminationMessage)({
1092
1122
  mode: 'full',
1093
1123
  turns: rounds,
1094
- maxTurns: this.config.maxTurns,
1095
- outputLanguage: this.config.outputLanguage,
1124
+ maxTurns: runtimeConfig.maxTurns,
1125
+ outputLanguage: runtimeConfig.outputLanguage,
1096
1126
  });
1097
1127
  }
1098
1128
  // Record SDK token usage and prompt cache metrics
@@ -1181,13 +1211,24 @@ class ClaudeRuntime extends events_1.EventEmitter {
1181
1211
  mode: 'full',
1182
1212
  });
1183
1213
  }
1184
- // Use SDK terminal result if available; fall back to accumulated streamed answer tokens.
1185
- // On timeout, the SDK result message may never arrive, but answer_token events
1186
- // were already streamed to the frontend use that text to populate the report.
1187
- conclusionText = finalResult || getAccumulatedAnswer() || '';
1214
+ // Prefer a deliverable streamed report over a short SDK terminal summary.
1215
+ // Some compatible providers put the full report in answer_token chunks but
1216
+ // return only a terse summary in the terminal result.
1217
+ const accumulatedAnswerBeforeVerification = getAccumulatedAnswer();
1218
+ conclusionText = chooseClaudeConclusionText({
1219
+ finalResult: finalResult || '',
1220
+ accumulatedAnswer: accumulatedAnswerBeforeVerification,
1221
+ });
1222
+ conclusionText = ensureClaudeFinalReportHeading(conclusionText, ctx.sceneType, runtimeConfig.outputLanguage);
1188
1223
  if (!finalResult && conclusionText) {
1189
1224
  console.warn(`[ClaudeRuntime] Session ${sessionId}: SDK result was empty, recovered ${conclusionText.length} chars from streamed answer tokens`);
1190
1225
  }
1226
+ else if (finalResult &&
1227
+ conclusionText === sanitizeClaudeConclusionText(accumulatedAnswerBeforeVerification) &&
1228
+ conclusionText !== sanitizeClaudeConclusionText(finalResult)) {
1229
+ console.warn(`[ClaudeRuntime] Session ${sessionId}: SDK result was a short terminal summary ` +
1230
+ `(${finalResult.length} chars), using streamed report (${conclusionText.length} chars) before verification`);
1231
+ }
1191
1232
  allFindings.push((0, claudeFindingExtractor_1.extractFindingsFromText)(conclusionText));
1192
1233
  let mergedFindings = (0, claudeFindingExtractor_1.mergeFindings)(allFindings);
1193
1234
  if (conclusionText.trim() && ctx.analysisPlan.current) {
@@ -1284,7 +1325,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
1284
1325
  type: 'progress',
1285
1326
  content: {
1286
1327
  phase: 'concluding',
1287
- message: (0, outputLanguage_1.localize)(this.config.outputLanguage, `发现 ${errorIssues.length} 个 ERROR 级问题,启动修正重试 (${attempt + 1}/${MAX_CORRECTION_ATTEMPTS})...`, `Found ${errorIssues.length} ERROR-level issue(s). Starting correction retry (${attempt + 1}/${MAX_CORRECTION_ATTEMPTS})...`),
1328
+ message: (0, outputLanguage_1.localize)(this.config.outputLanguage, `最终报告仍需补齐,正在自动修正 (${attempt + 1}/${MAX_CORRECTION_ATTEMPTS})...`, `The final report still needs completion; applying automatic correction (${attempt + 1}/${MAX_CORRECTION_ATTEMPTS})...`),
1288
1329
  },
1289
1330
  timestamp: Date.now(),
1290
1331
  });
@@ -1378,11 +1419,15 @@ class ClaudeRuntime extends events_1.EventEmitter {
1378
1419
  if (!correctedResult && !correctionTimedOut) {
1379
1420
  correctedResult = correctionAnswerBridge.getAccumulatedAnswer();
1380
1421
  }
1422
+ correctedResult = ensureClaudeFinalReportHeading(correctedResult, ctx.sceneType, runtimeConfig.outputLanguage);
1381
1423
  if (correctionTimedOut) {
1382
1424
  console.warn(`[ClaudeRuntime] Correction attempt ${attempt + 1} timed out, using partial result (${correctedResult.length} chars)`);
1383
- if (!correctionResultLooksUsable(correctedResult)) {
1425
+ if (shouldMarkCorrectionTimeoutPartial({ correctedResult, existingConclusion: conclusionText })) {
1426
+ correctedResult = '';
1427
+ verificationDegradedMessage = (0, outputLanguage_1.localize)(this.config.outputLanguage, '修正重试超时,且当前结论仍不可独立交付;已保留原结论并标记为 partial。', 'Correction retry timed out and the current conclusion is still not independently deliverable; keeping the previous conclusion and marking the result partial.');
1428
+ }
1429
+ else if (!correctionResultLooksUsable(correctedResult)) {
1384
1430
  correctedResult = '';
1385
- verificationDegradedMessage = (0, outputLanguage_1.localize)(this.config.outputLanguage, 'Claude 修正重试超时,且没有产出可独立交付的修正报告;已保留原结论并标记为 partial。', 'Claude correction retry timed out without a deliverable corrected report; keeping the previous conclusion and marking the result partial.');
1386
1431
  }
1387
1432
  }
1388
1433
  // P2-G13: Compare correction quality by finding count and coverage, not text length.
@@ -1424,27 +1469,28 @@ class ClaudeRuntime extends events_1.EventEmitter {
1424
1469
  allFindings.push((0, claudeFindingExtractor_1.extractFindingsFromText)(conclusionText));
1425
1470
  mergedFindings = (0, claudeFindingExtractor_1.mergeFindings)(allFindings);
1426
1471
  }
1472
+ conclusionText = ensureClaudeFinalReportHeading(conclusionText, ctx.sceneType, runtimeConfig.outputLanguage);
1427
1473
  const isPartialResult = terminationReason === analysisTermination_1.MAX_TURNS_TERMINATION_REASON;
1428
1474
  if (isPartialResult) {
1429
1475
  terminationMessage || (terminationMessage = (0, analysisTermination_1.buildMaxTurnsTerminationMessage)({
1430
1476
  mode: 'full',
1431
1477
  turns: rounds,
1432
- maxTurns: this.config.maxTurns,
1433
- outputLanguage: this.config.outputLanguage,
1478
+ maxTurns: runtimeConfig.maxTurns,
1479
+ outputLanguage: runtimeConfig.outputLanguage,
1434
1480
  }));
1435
1481
  conclusionText = conclusionText.trim()
1436
- ? (0, analysisTermination_1.prependPartialNotice)(conclusionText, terminationMessage, this.config.outputLanguage)
1482
+ ? (0, analysisTermination_1.prependPartialNotice)(conclusionText, terminationMessage, runtimeConfig.outputLanguage)
1437
1483
  : (0, analysisTermination_1.buildMaxTurnsFallbackConclusion)({
1438
1484
  mode: 'full',
1439
1485
  turns: rounds,
1440
- maxTurns: this.config.maxTurns,
1441
- outputLanguage: this.config.outputLanguage,
1486
+ maxTurns: runtimeConfig.maxTurns,
1487
+ outputLanguage: runtimeConfig.outputLanguage,
1442
1488
  });
1443
1489
  allFindings.push((0, claudeFindingExtractor_1.extractFindingsFromText)(conclusionText));
1444
1490
  mergedFindings = (0, claudeFindingExtractor_1.mergeFindings)(allFindings);
1445
1491
  failedApproaches.push({
1446
1492
  type: 'strategy_failure',
1447
- approach: `analysis reached ${this.config.maxTurns} full-mode turns`,
1493
+ approach: `analysis reached ${runtimeConfig.maxTurns} full-mode turns`,
1448
1494
  reason: 'SDK returned error_max_turns before a normal success result',
1449
1495
  });
1450
1496
  this.emitUpdate({
@@ -1457,7 +1503,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
1457
1503
  partial: true,
1458
1504
  terminationReason,
1459
1505
  turns: rounds,
1460
- maxTurns: this.config.maxTurns,
1506
+ maxTurns: runtimeConfig.maxTurns,
1461
1507
  },
1462
1508
  timestamp: Date.now(),
1463
1509
  });
@@ -1469,12 +1515,12 @@ class ClaudeRuntime extends events_1.EventEmitter {
1469
1515
  ? `${terminationMessage}\n\n${verificationDegradedMessage}`
1470
1516
  : verificationDegradedMessage;
1471
1517
  conclusionText = conclusionText.trim()
1472
- ? (0, analysisTermination_1.prependPartialNotice)(conclusionText, verificationDegradedMessage, this.config.outputLanguage)
1518
+ ? (0, analysisTermination_1.prependPartialNotice)(conclusionText, verificationDegradedMessage, runtimeConfig.outputLanguage)
1473
1519
  : (0, analysisTermination_1.buildMaxTurnsFallbackConclusion)({
1474
1520
  mode: 'full',
1475
1521
  turns: rounds,
1476
- maxTurns: this.config.maxTurns,
1477
- outputLanguage: this.config.outputLanguage,
1522
+ maxTurns: runtimeConfig.maxTurns,
1523
+ outputLanguage: runtimeConfig.outputLanguage,
1478
1524
  });
1479
1525
  mergedFindings = (0, claudeFindingExtractor_1.mergeFindings)([(0, claudeFindingExtractor_1.extractFindingsFromText)(conclusionText)]);
1480
1526
  this.emitUpdate({
@@ -1566,7 +1612,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
1566
1612
  sessionId,
1567
1613
  turnIndex: ctx.previousTurns.length,
1568
1614
  },
1569
- knowledgeScope: knowledgeScopeFromOptions(options),
1615
+ knowledgeScope: (0, runtimeCommon_1.knowledgeScopeFromAnalysisOptions)(options),
1570
1616
  };
1571
1617
  (0, analysisPatternMemory_1.saveAnalysisPattern)(fullFeatures, insights, sceneType, ctx.architecture?.type, finalAnalysisResult.confidence, patternExtras)
1572
1618
  .catch(err => console.warn('[ClaudeRuntime] Pattern save failed:', err.message));
@@ -1578,7 +1624,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
1578
1624
  sceneType,
1579
1625
  architectureType: ctx.architecture?.type,
1580
1626
  verifierPassed: true,
1581
- knowledgeScope: knowledgeScopeFromOptions(options),
1627
+ knowledgeScope: (0, runtimeCommon_1.knowledgeScopeFromAnalysisOptions)(options),
1582
1628
  }).catch(err => console.warn('[ClaudeRuntime] Quick→full promote failed:', err.message));
1583
1629
  }
1584
1630
  // Derive sql_error FailedApproach entries from persistent SQL errors
@@ -1594,7 +1640,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
1594
1640
  // P1: Save negative patterns to long-term memory (fire-and-forget)
1595
1641
  if (failedApproaches.length > 0 && fullFeatures.length > 0) {
1596
1642
  (0, analysisPatternMemory_1.saveNegativePattern)(fullFeatures, failedApproaches, sceneType, ctx.architecture?.type, {
1597
- knowledgeScope: knowledgeScopeFromOptions(options),
1643
+ knowledgeScope: (0, runtimeCommon_1.knowledgeScopeFromAnalysisOptions)(options),
1598
1644
  })
1599
1645
  .catch(err => console.warn('[ClaudeRuntime] Negative pattern save failed:', err.message));
1600
1646
  }
@@ -1602,7 +1648,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
1602
1648
  }
1603
1649
  catch (error) {
1604
1650
  const rawErrorMessage = error.message || 'Unknown error';
1605
- const errMsg = (0, claudeConfig_1.explainClaudeRuntimeError)(rawErrorMessage, this.config.outputLanguage, (0, claudeConfig_1.getCredentialSourceHint)(options.providerId, providerScopeFromOptions(options)));
1651
+ const errMsg = (0, claudeConfig_1.explainClaudeRuntimeError)(rawErrorMessage, this.config.outputLanguage, (0, claudeConfig_1.getCredentialSourceHint)(options.providerId, (0, runtimeCommon_1.providerScopeFromAnalysisOptions)(options)));
1606
1652
  const quotaExceeded = (0, claudeConfig_1.isClaudeQuotaError)(rawErrorMessage);
1607
1653
  console.error('[ClaudeRuntime] Analysis failed:', errMsg);
1608
1654
  // P1-3: Preserve partial findings and generate partial conclusion on mid-stream errors
@@ -1696,13 +1742,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
1696
1742
  packageName: effectivePackageName,
1697
1743
  });
1698
1744
  if (arch) {
1699
- this.architectureCache.set(traceId, arch);
1700
- // LRU eviction: match full path's 50-entry cap
1701
- if (this.architectureCache.size > 50) {
1702
- const firstKey = this.architectureCache.keys().next().value;
1703
- if (firstKey)
1704
- this.architectureCache.delete(firstKey);
1705
- }
1745
+ (0, runtimeCommon_1.setLruCacheEntry)(this.architectureCache, traceId, arch);
1706
1746
  }
1707
1747
  return arch;
1708
1748
  }
@@ -1719,12 +1759,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
1719
1759
  const watchdogWarning = { current: null };
1720
1760
  // Quick path defaults to no skill-notes injection per §8 of the
1721
1761
  // self-improving design. Operators can opt-in via the env override.
1722
- const quickNotesBudget = process.env.SELF_IMPROVE_NOTES_INJECT_ENABLED === '1'
1723
- ? new skillNotesInjector_1.SkillNotesBudget({
1724
- mode: 'quick',
1725
- quickOverrideTotal: parseQuickBudgetEnv(),
1726
- })
1727
- : undefined;
1762
+ const quickNotesBudget = (0, runtimeCommon_1.createRuntimeSkillNotesBudget)(true);
1728
1763
  const { server: mcpServer, allowedTools } = (0, claudeMcpServer_1.createClaudeMcpServer)({
1729
1764
  sessionId,
1730
1765
  traceId,
@@ -1737,7 +1772,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
1737
1772
  lightweight: true,
1738
1773
  skillNotesBudget: quickNotesBudget,
1739
1774
  outputLanguage: this.config.outputLanguage,
1740
- knowledgeScope: knowledgeScopeFromOptions(options),
1775
+ knowledgeScope: (0, runtimeCommon_1.knowledgeScopeFromAnalysisOptions)(options),
1741
1776
  codeAwareMode: options.codeAwareMode,
1742
1777
  codebaseIds: options.codebaseIds,
1743
1778
  });
@@ -1749,8 +1784,9 @@ class ClaudeRuntime extends events_1.EventEmitter {
1749
1784
  selectionContext: options.selectionContext,
1750
1785
  outputLanguage: this.config.outputLanguage,
1751
1786
  });
1752
- const providerScope = providerScopeFromOptions(options);
1753
- const quickConfig = (0, claudeConfig_1.createQuickConfig)((0, claudeConfig_1.resolveRuntimeConfig)(this.config, options.providerId, providerScope));
1787
+ const providerScope = (0, runtimeCommon_1.providerScopeFromAnalysisOptions)(options);
1788
+ const sdkEnv = (0, claudeConfig_1.createSdkEnv)(options.providerId, providerScope);
1789
+ const quickConfig = (0, claudeConfig_1.createQuickConfig)((0, claudeConfig_1.resolveRuntimeConfig)(this.config, options.providerId, providerScope), sdkEnv);
1754
1790
  const { handleMessage: bridge, getAccumulatedAnswer } = (0, claudeSseBridge_1.createSseBridge)((update) => {
1755
1791
  this.emitUpdate(update);
1756
1792
  }, this.config.outputLanguage);
@@ -1762,20 +1798,19 @@ class ClaudeRuntime extends events_1.EventEmitter {
1762
1798
  },
1763
1799
  timestamp: Date.now(),
1764
1800
  });
1765
- const sdkEnv = (0, claudeConfig_1.createSdkEnv)(options.providerId, providerScope);
1766
1801
  // Quick calls intentionally do not resume or persist Claude SDK sessions.
1767
1802
  // The SDK's maxTurns budget is tied to the resumed conversation, while
1768
1803
  // SmartPerfetto's fast mode budget is a per-question latency guard. Keep
1769
1804
  // cross-turn context local and compact so quick cannot exhaust or overwrite
1770
1805
  // the full-mode SDK conversation.
1771
1806
  let quickPrompt = query;
1772
- const quickConversationContext = buildQuickConversationContext(previousTurns, this.config.outputLanguage);
1807
+ const quickConversationContext = (0, runtimeCommon_1.buildQuickConversationContext)(previousTurns, this.config.outputLanguage);
1773
1808
  if (quickConversationContext) {
1774
1809
  quickPrompt = `${quickConversationContext}\n\n${quickPrompt}`;
1775
1810
  }
1776
1811
  // Prepend pre-queried trace data so the AI skips basic SQL turns in fast mode.
1777
1812
  if (options.traceContext && options.traceContext.length > 0) {
1778
- quickPrompt = `${formatTraceContext(options.traceContext, this.config.outputLanguage)}\n\n${quickPrompt}`;
1813
+ quickPrompt = `${(0, runtimeCommon_1.formatTraceContext)(options.traceContext, this.config.outputLanguage)}\n\n${quickPrompt}`;
1779
1814
  }
1780
1815
  const { stream, close: closeSdk } = sdkQueryWithRetry({
1781
1816
  prompt: quickPrompt,
@@ -1966,14 +2001,14 @@ class ClaudeRuntime extends events_1.EventEmitter {
1966
2001
  (0, analysisPatternMemory_1.saveQuickPathPattern)(quickFeatures, insights, sceneType, cachedArch?.type, {
1967
2002
  status: 'provisional',
1968
2003
  provenance: { sessionId, turnIndex: previousTurns.length },
1969
- knowledgeScope: knowledgeScopeFromOptions(options),
2004
+ knowledgeScope: (0, runtimeCommon_1.knowledgeScopeFromAnalysisOptions)(options),
1970
2005
  }).catch(err => console.warn('[ClaudeRuntime] Quick pattern save failed:', err.message));
1971
2006
  }
1972
2007
  return quickResult;
1973
2008
  }
1974
2009
  catch (error) {
1975
2010
  const rawErrorMessage = error.message || 'Unknown error';
1976
- const errMsg = (0, claudeConfig_1.explainClaudeRuntimeError)(rawErrorMessage, this.config.outputLanguage, (0, claudeConfig_1.getCredentialSourceHint)(options.providerId, providerScopeFromOptions(options)));
2011
+ const errMsg = (0, claudeConfig_1.explainClaudeRuntimeError)(rawErrorMessage, this.config.outputLanguage, (0, claudeConfig_1.getCredentialSourceHint)(options.providerId, (0, runtimeCommon_1.providerScopeFromAnalysisOptions)(options)));
1977
2012
  const quotaExceeded = (0, claudeConfig_1.isClaudeQuotaError)(rawErrorMessage);
1978
2013
  console.error('[ClaudeRuntime] Quick analysis failed:', errMsg);
1979
2014
  this.emitUpdate({
@@ -2135,7 +2170,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
2135
2170
  this.artifactStores.set(sessionId, artifactStore_1.ArtifactStore.fromSnapshot(snapshot.artifacts));
2136
2171
  }
2137
2172
  if (snapshot.architecture) {
2138
- this.architectureCache.set(traceId, snapshot.architecture);
2173
+ (0, runtimeCommon_1.setLruCacheEntry)(this.architectureCache, traceId, snapshot.architecture);
2139
2174
  }
2140
2175
  if (snapshot.sdkSessionId && snapshot.sdkSessionMode === 'full') {
2141
2176
  this.sessionMap.set(this.buildSessionMapKey(sessionId, snapshot.referenceTraceId), {
@@ -2147,24 +2182,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
2147
2182
  }
2148
2183
  /** P0-1: Convert agentv3 Hypothesis to agentProtocol Hypothesis format for AnalysisResult. */
2149
2184
  toProtocolHypothesis(h) {
2150
- const statusMap = {
2151
- formed: 'proposed',
2152
- confirmed: 'confirmed',
2153
- rejected: 'rejected',
2154
- };
2155
- const confidenceMap = { formed: 0.5, confirmed: 0.85, rejected: 0.1 };
2156
- return {
2157
- id: h.id,
2158
- description: h.statement,
2159
- status: statusMap[h.status] || 'proposed',
2160
- confidence: confidenceMap[h.status] ?? 0.5,
2161
- supportingEvidence: h.evidence && h.status === 'confirmed' ? [{ id: `${h.id}-ev`, type: 'observation', description: h.evidence, source: 'claude', strength: 0.8 }] : [],
2162
- contradictingEvidence: h.evidence && h.status === 'rejected' ? [{ id: `${h.id}-ev`, type: 'observation', description: h.evidence, source: 'claude', strength: 0.8 }] : [],
2163
- proposedBy: 'claude',
2164
- relevantAgents: ['claude'],
2165
- createdAt: h.formedAt,
2166
- updatedAt: h.resolvedAt || h.formedAt,
2167
- };
2185
+ return (0, runtimeCommon_1.toProtocolHypothesis)(h, 'claude');
2168
2186
  }
2169
2187
  reset() {
2170
2188
  this.architectureCache.clear();
@@ -2187,64 +2205,14 @@ class ClaudeRuntime extends events_1.EventEmitter {
2187
2205
  * Caps at 5 findings to prevent unbounded prompt growth.
2188
2206
  */
2189
2207
  collectPreviousFindings(sessionContext, maxTurns) {
2190
- try {
2191
- let turns = sessionContext.getAllTurns?.() || [];
2192
- if (maxTurns && maxTurns > 0) {
2193
- turns = turns.slice(-maxTurns);
2194
- }
2195
- return turns.flatMap((turn) => turn.findings || []).slice(-5);
2196
- }
2197
- catch {
2198
- return [];
2199
- }
2208
+ return (0, runtimeCommon_1.collectRecentFindings)(sessionContext, { maxTurns, maxFindings: 5 });
2200
2209
  }
2201
2210
  /**
2202
2211
  * Build a compact entity context string for the system prompt.
2203
2212
  * Gives Claude awareness of known frames/sessions for drill-down resolution.
2204
2213
  */
2205
2214
  buildEntityContext(entityStore) {
2206
- try {
2207
- const stats = entityStore.getStats();
2208
- if (stats.totalEntityCount === 0)
2209
- return undefined;
2210
- const lines = [];
2211
- const frames = entityStore.getAllFrames?.() || [];
2212
- if (frames.length > 0) {
2213
- lines.push(`**帧 (${frames.length})**:`);
2214
- for (const f of frames.slice(0, 15)) {
2215
- const parts = [`frame_id=${f.frame_id}`];
2216
- if (f.start_ts)
2217
- parts.push(`ts=${f.start_ts}`);
2218
- if (f.jank_type)
2219
- parts.push(`jank=${f.jank_type}`);
2220
- if (f.dur_ms)
2221
- parts.push(`dur=${f.dur_ms}ms`);
2222
- if (f.process_name)
2223
- parts.push(`proc=${f.process_name}`);
2224
- lines.push(`- ${parts.join(', ')}`);
2225
- }
2226
- if (frames.length > 15)
2227
- lines.push(`- ...及其他 ${frames.length - 15} 帧`);
2228
- }
2229
- const sessions = entityStore.getAllSessions?.() || [];
2230
- if (sessions.length > 0) {
2231
- lines.push(`**滑动会话 (${sessions.length})**:`);
2232
- for (const s of sessions.slice(0, 8)) {
2233
- const parts = [`session_id=${s.session_id}`];
2234
- if (s.start_ts)
2235
- parts.push(`ts=${s.start_ts}`);
2236
- if (s.jank_count)
2237
- parts.push(`janks=${s.jank_count}`);
2238
- if (s.process_name)
2239
- parts.push(`proc=${s.process_name}`);
2240
- lines.push(`- ${parts.join(', ')}`);
2241
- }
2242
- }
2243
- return lines.length > 0 ? lines.join('\n') : undefined;
2244
- }
2245
- catch {
2246
- return undefined;
2247
- }
2215
+ return (0, runtimeCommon_1.buildEntityContext)(entityStore);
2248
2216
  }
2249
2217
  /**
2250
2218
  * Prepare all context needed for a Claude analysis run.
@@ -2253,6 +2221,9 @@ class ClaudeRuntime extends events_1.EventEmitter {
2253
2221
  * into a single cohesive preparation phase.
2254
2222
  */
2255
2223
  async prepareAnalysisContext(query, sessionId, traceId, options, precomputed) {
2224
+ const providerScope = (0, runtimeCommon_1.providerScopeFromAnalysisOptions)(options);
2225
+ const runtimeConfig = precomputed?.runtimeConfig
2226
+ ?? (0, claudeConfig_1.resolveRuntimeConfig)(this.config, options.providerId, providerScope);
2256
2227
  // Phase 0: Selection context logging
2257
2228
  if (options.selectionContext) {
2258
2229
  const sc = options.selectionContext;
@@ -2276,7 +2247,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
2276
2247
  type: 'progress',
2277
2248
  content: {
2278
2249
  phase: 'starting',
2279
- message: (0, outputLanguage_1.localize)(this.config.outputLanguage, `检测到焦点应用: ${focusResult.primaryApp} (${focusResult.method})`, `Detected focus app: ${focusResult.primaryApp} (${focusResult.method})`),
2250
+ message: (0, outputLanguage_1.localize)(runtimeConfig.outputLanguage, `检测到焦点应用: ${focusResult.primaryApp} (${focusResult.method})`, `Detected focus app: ${focusResult.primaryApp} (${focusResult.method})`),
2280
2251
  },
2281
2252
  timestamp: Date.now(),
2282
2253
  });
@@ -2287,13 +2258,8 @@ class ClaudeRuntime extends events_1.EventEmitter {
2287
2258
  skillExecutor.registerSkills(skillLoader_1.skillRegistry.getAllSkills());
2288
2259
  skillExecutor.setFragmentRegistry(skillLoader_1.skillRegistry.getFragmentCache());
2289
2260
  // Phase 2: Architecture detection (LRU cached per traceId)
2290
- let architecture = this.architectureCache.get(traceId);
2291
- if (architecture) {
2292
- // LRU touch: delete and re-insert to move to end of Map iteration order
2293
- this.architectureCache.delete(traceId);
2294
- this.architectureCache.set(traceId, architecture);
2295
- }
2296
- else {
2261
+ let architecture = (0, runtimeCommon_1.getLruCacheEntry)(this.architectureCache, traceId);
2262
+ if (!architecture) {
2297
2263
  try {
2298
2264
  const detector = (0, architectureDetector_1.createArchitectureDetector)();
2299
2265
  architecture = await detector.detect({
@@ -2302,13 +2268,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
2302
2268
  packageName: effectivePackageName,
2303
2269
  });
2304
2270
  if (architecture) {
2305
- this.architectureCache.set(traceId, architecture);
2306
- // LRU eviction: remove oldest entry (first key in Map)
2307
- if (this.architectureCache.size > 50) {
2308
- const firstKey = this.architectureCache.keys().next().value;
2309
- if (firstKey)
2310
- this.architectureCache.delete(firstKey);
2311
- }
2271
+ (0, runtimeCommon_1.setLruCacheEntry)(this.architectureCache, traceId, architecture);
2312
2272
  }
2313
2273
  this.emitUpdate({ type: 'architecture_detected', content: { architecture }, timestamp: Date.now() });
2314
2274
  }
@@ -2317,7 +2277,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
2317
2277
  }
2318
2278
  }
2319
2279
  // Phase 2.5: Vendor detection (LRU cached per traceId, reuses SkillAnalysisAdapter.detectVendor)
2320
- let detectedVendor = this.vendorCache.get(traceId) ?? null;
2280
+ let detectedVendor = (0, runtimeCommon_1.getLruCacheEntry)(this.vendorCache, traceId) ?? null;
2321
2281
  if (!detectedVendor) {
2322
2282
  try {
2323
2283
  const adapter = (0, skillAnalysisAdapter_1.getSkillAnalysisAdapter)(this.traceProcessorService);
@@ -2325,13 +2285,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
2325
2285
  const vendorResult = await adapter.detectVendor(traceId);
2326
2286
  detectedVendor = vendorResult.vendor;
2327
2287
  if (detectedVendor && detectedVendor !== 'aosp') {
2328
- this.vendorCache.set(traceId, detectedVendor);
2329
- // LRU eviction: match architectureCache limit
2330
- if (this.vendorCache.size > 50) {
2331
- const firstKey = this.vendorCache.keys().next().value;
2332
- if (firstKey)
2333
- this.vendorCache.delete(firstKey);
2334
- }
2288
+ (0, runtimeCommon_1.setLruCacheEntry)(this.vendorCache, traceId, detectedVendor);
2335
2289
  }
2336
2290
  }
2337
2291
  catch (err) {
@@ -2347,7 +2301,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
2347
2301
  type: 'progress',
2348
2302
  content: {
2349
2303
  phase: 'starting',
2350
- message: (0, outputLanguage_1.localize)(this.config.outputLanguage, '对比模式:正在检测参考 Trace...', 'Comparison mode: detecting the reference trace...'),
2304
+ message: (0, outputLanguage_1.localize)(runtimeConfig.outputLanguage, '对比模式:正在检测参考 Trace...', 'Comparison mode: detecting the reference trace...'),
2351
2305
  },
2352
2306
  timestamp: Date.now(),
2353
2307
  });
@@ -2356,7 +2310,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
2356
2310
  const [refFocusResult, refArchitecture, currentTables, refTables] = await Promise.all([
2357
2311
  (0, focusAppDetector_1.detectFocusApps)(this.traceProcessorService, referenceTraceId).catch(() => ({ apps: [], method: 'none', primaryApp: undefined })),
2358
2312
  (async () => {
2359
- let refArch = this.architectureCache.get(referenceTraceId);
2313
+ let refArch = (0, runtimeCommon_1.getLruCacheEntry)(this.architectureCache, referenceTraceId);
2360
2314
  if (!refArch) {
2361
2315
  try {
2362
2316
  const detector = (0, architectureDetector_1.createArchitectureDetector)();
@@ -2366,7 +2320,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
2366
2320
  packageName: undefined,
2367
2321
  }) ?? undefined;
2368
2322
  if (refArch)
2369
- this.architectureCache.set(referenceTraceId, refArch);
2323
+ (0, runtimeCommon_1.setLruCacheEntry)(this.architectureCache, referenceTraceId, refArch);
2370
2324
  }
2371
2325
  catch { /* non-fatal */ }
2372
2326
  }
@@ -2405,16 +2359,11 @@ class ClaudeRuntime extends events_1.EventEmitter {
2405
2359
  `capDiff=${capabilityDiff ? `cur=${capabilityDiff.currentOnly.length}/ref=${capabilityDiff.referenceOnly.length}` : 'none'}`);
2406
2360
  }
2407
2361
  // Phase 2.9: Trace data completeness probe (cached per traceId, ~50ms first run)
2408
- let traceCompleteness = this.completenessCache.get(traceId);
2362
+ let traceCompleteness = (0, runtimeCommon_1.getLruCacheEntry)(this.completenessCache, traceId);
2409
2363
  if (!traceCompleteness) {
2410
2364
  try {
2411
2365
  traceCompleteness = await (0, traceCompletenessProber_1.probeTraceCompleteness)(this.traceProcessorService, traceId, architecture?.type);
2412
- this.completenessCache.set(traceId, traceCompleteness);
2413
- if (this.completenessCache.size > 50) {
2414
- const firstKey = this.completenessCache.keys().next().value;
2415
- if (firstKey)
2416
- this.completenessCache.delete(firstKey);
2417
- }
2366
+ (0, runtimeCommon_1.setLruCacheEntry)(this.completenessCache, traceId, traceCompleteness);
2418
2367
  }
2419
2368
  catch (err) {
2420
2369
  console.warn('[ClaudeRuntime] Trace completeness probe failed (non-fatal):', err.message);
@@ -2445,15 +2394,17 @@ class ClaudeRuntime extends events_1.EventEmitter {
2445
2394
  const entityContext = this.buildEntityContext(entityStore);
2446
2395
  // Phase 5: Scene classification + effort resolution (reuse precomputed if available)
2447
2396
  const sceneType = precomputed?.sceneType ?? (0, sceneClassifier_1.classifyScene)(query);
2448
- const effectiveEffort = (0, claudeConfig_1.resolveEffort)(this.config, sceneType);
2397
+ const effectiveEffort = (0, claudeConfig_1.resolveEffort)(runtimeConfig, sceneType, {
2398
+ configuredEffortOverridesScene: (0, claudeConfig_1.hasConfiguredClaudeEffortOverride)(options.providerId, providerScope),
2399
+ });
2449
2400
  // Phase 5.5: Pattern memory — match similar historical traces (P2-2)
2450
2401
  const traceFeatures = (0, analysisPatternMemory_1.extractTraceFeatures)({
2451
2402
  architectureType: architecture?.type,
2452
2403
  sceneType,
2453
2404
  packageName: effectivePackageName,
2454
2405
  });
2455
- const patternContext = (0, analysisPatternMemory_1.buildPatternContextSection)(traceFeatures, knowledgeScopeFromOptions(options));
2456
- const negativePatternContext = (0, analysisPatternMemory_1.buildNegativePatternSection)(traceFeatures, knowledgeScopeFromOptions(options));
2406
+ const patternContext = (0, analysisPatternMemory_1.buildPatternContextSection)(traceFeatures, (0, runtimeCommon_1.knowledgeScopeFromAnalysisOptions)(options));
2407
+ const negativePatternContext = (0, analysisPatternMemory_1.buildNegativePatternSection)(traceFeatures, (0, runtimeCommon_1.knowledgeScopeFromAnalysisOptions)(options));
2457
2408
  // Phase 6: Session-scoped artifact store + analysis notes
2458
2409
  if (!this.artifactStores.has(sessionId)) {
2459
2410
  this.artifactStores.set(sessionId, new artifactStore_1.ArtifactStore());
@@ -2497,14 +2448,12 @@ class ClaudeRuntime extends events_1.EventEmitter {
2497
2448
  // Seed new sessions with previously learned fix pairs from disk (cross-session learning)
2498
2449
  let sqlErrors = this.sessionSqlErrors.get(sessionId);
2499
2450
  if (!sqlErrors) {
2500
- sqlErrors = (0, claudeMcpServer_1.loadLearnedSqlFixPairs)(5, knowledgeScopeFromOptions(options));
2451
+ sqlErrors = (0, claudeMcpServer_1.loadLearnedSqlFixPairs)(5, (0, runtimeCommon_1.knowledgeScopeFromAnalysisOptions)(options));
2501
2452
  this.sessionSqlErrors.set(sessionId, sqlErrors);
2502
2453
  }
2503
2454
  // Phase 8: MCP server with all session-scoped state
2504
2455
  // P2-G1: Destructure to get both server and auto-derived allowedTools
2505
- const fullNotesBudget = process.env.SELF_IMPROVE_NOTES_INJECT_ENABLED === '1'
2506
- ? new skillNotesInjector_1.SkillNotesBudget({ mode: 'full' })
2507
- : undefined;
2456
+ const fullNotesBudget = (0, runtimeCommon_1.createRuntimeSkillNotesBudget)(false);
2508
2457
  const { server: mcpServer, allowedTools } = (0, claudeMcpServer_1.createClaudeMcpServer)({
2509
2458
  sessionId,
2510
2459
  traceId,
@@ -2530,8 +2479,8 @@ class ClaudeRuntime extends events_1.EventEmitter {
2530
2479
  referenceTraceId,
2531
2480
  comparisonContext,
2532
2481
  skillNotesBudget: fullNotesBudget,
2533
- outputLanguage: this.config.outputLanguage,
2534
- knowledgeScope: knowledgeScopeFromOptions(options),
2482
+ outputLanguage: runtimeConfig.outputLanguage,
2483
+ knowledgeScope: (0, runtimeCommon_1.knowledgeScopeFromAnalysisOptions)(options),
2535
2484
  codeAwareMode: options.codeAwareMode,
2536
2485
  codebaseIds: options.codebaseIds,
2537
2486
  });
@@ -2548,12 +2497,12 @@ class ClaudeRuntime extends events_1.EventEmitter {
2548
2497
  }
2549
2498
  // Phase 11: Sub-agent definitions (feature-gated)
2550
2499
  let agents;
2551
- if (this.config.enableSubAgents && sceneType !== 'anr') {
2500
+ if (runtimeConfig.enableSubAgents && sceneType !== 'anr') {
2552
2501
  agents = (0, claudeAgentDefinitions_1.buildAgentDefinitions)(sceneType, {
2553
2502
  architecture,
2554
2503
  packageName: effectivePackageName,
2555
2504
  allowedTools,
2556
- subAgentModel: this.config.subAgentModel,
2505
+ subAgentModel: runtimeConfig.subAgentModel,
2557
2506
  });
2558
2507
  }
2559
2508
  // Phase 12: SQL error-fix pairs for prompt injection
@@ -2586,7 +2535,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
2586
2535
  traceCompleteness,
2587
2536
  traceOs: traceInfo?.traceOs,
2588
2537
  traceFormat: traceInfo?.traceFormat,
2589
- outputLanguage: this.config.outputLanguage,
2538
+ outputLanguage: runtimeConfig.outputLanguage,
2590
2539
  codeAwareMode: options.codeAwareMode,
2591
2540
  codebaseIds: options.codebaseIds,
2592
2541
  };
@@ -2617,23 +2566,7 @@ class ClaudeRuntime extends events_1.EventEmitter {
2617
2566
  }
2618
2567
  /** Capture entities from skill displayResults into EntityStore for multi-turn drill-down. */
2619
2568
  captureEntitiesFromSkillDisplayResults(displayResults, entityStore) {
2620
- try {
2621
- const data = {};
2622
- for (const dr of displayResults) {
2623
- if (dr.stepId && dr.data) {
2624
- data[dr.stepId] = dr.data;
2625
- }
2626
- }
2627
- const captured = (0, entityCapture_1.captureEntitiesFromResponses)([{
2628
- agentId: 'claude-agent',
2629
- success: true,
2630
- toolResults: [{ toolName: 'invoke_skill', data }],
2631
- }]);
2632
- (0, entityCapture_1.applyCapturedEntities)(entityStore, captured);
2633
- }
2634
- catch (err) {
2635
- console.warn('[ClaudeRuntime] Entity capture failed:', err.message);
2636
- }
2569
+ (0, runtimeCommon_1.captureSkillDisplayEntities)(displayResults, entityStore, 'claude-agent');
2637
2570
  }
2638
2571
  }
2639
2572
  exports.ClaudeRuntime = ClaudeRuntime;