@gracker/smartperfetto 1.0.19 → 1.0.21
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/dist/agent/config/domainManifest.d.ts +5 -3
- package/dist/agent/config/domainManifest.d.ts.map +1 -1
- package/dist/agent/config/domainManifest.js +120 -5
- package/dist/agent/config/domainManifest.js.map +1 -1
- package/dist/agent/core/conclusionContract.d.ts +1 -0
- package/dist/agent/core/conclusionContract.d.ts.map +1 -1
- package/dist/agent/core/orchestratorTypes.d.ts +5 -0
- package/dist/agent/core/orchestratorTypes.d.ts.map +1 -1
- package/dist/agent/core/orchestratorTypes.js.map +1 -1
- package/dist/agent/scene/buildSmartChatReport.d.ts +13 -0
- package/dist/agent/scene/buildSmartChatReport.d.ts.map +1 -0
- package/dist/agent/scene/buildSmartChatReport.js +333 -0
- package/dist/agent/scene/buildSmartChatReport.js.map +1 -0
- package/dist/agent/scene/sceneAnalysisJobRunner.d.ts +8 -4
- package/dist/agent/scene/sceneAnalysisJobRunner.d.ts.map +1 -1
- package/dist/agent/scene/sceneAnalysisJobRunner.js +78 -3
- package/dist/agent/scene/sceneAnalysisJobRunner.js.map +1 -1
- package/dist/agent/scene/sceneIntervalBuilder.d.ts +14 -5
- package/dist/agent/scene/sceneIntervalBuilder.d.ts.map +1 -1
- package/dist/agent/scene/sceneIntervalBuilder.js +578 -1
- package/dist/agent/scene/sceneIntervalBuilder.js.map +1 -1
- package/dist/agent/scene/sceneStage1Verifier.d.ts +8 -0
- package/dist/agent/scene/sceneStage1Verifier.d.ts.map +1 -0
- package/dist/agent/scene/sceneStage1Verifier.js +245 -0
- package/dist/agent/scene/sceneStage1Verifier.js.map +1 -0
- package/dist/agent/scene/sceneStoryService.d.ts +20 -2
- package/dist/agent/scene/sceneStoryService.d.ts.map +1 -1
- package/dist/agent/scene/sceneStoryService.js +238 -25
- package/dist/agent/scene/sceneStoryService.js.map +1 -1
- package/dist/agent/scene/smartCancelBridge.d.ts +13 -0
- package/dist/agent/scene/smartCancelBridge.d.ts.map +1 -0
- package/dist/agent/scene/smartCancelBridge.js +44 -0
- package/dist/agent/scene/smartCancelBridge.js.map +1 -0
- package/dist/agent/scene/smartDeepDiveDispatch.d.ts +15 -0
- package/dist/agent/scene/smartDeepDiveDispatch.d.ts.map +1 -0
- package/dist/agent/scene/smartDeepDiveDispatch.js +170 -0
- package/dist/agent/scene/smartDeepDiveDispatch.js.map +1 -0
- package/dist/agent/scene/types.d.ts +97 -6
- package/dist/agent/scene/types.d.ts.map +1 -1
- package/dist/agent/tools/frameAnalyzer.d.ts.map +1 -1
- package/dist/agent/tools/frameAnalyzer.js +1 -0
- package/dist/agent/tools/frameAnalyzer.js.map +1 -1
- package/dist/agent/types.d.ts +1 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/agentOpenAI/openAiRuntime.d.ts +24 -0
- package/dist/agentOpenAI/openAiRuntime.d.ts.map +1 -1
- package/dist/agentOpenAI/openAiRuntime.js +159 -191
- package/dist/agentOpenAI/openAiRuntime.js.map +1 -1
- package/dist/agentRuntime/index.d.ts +1 -0
- package/dist/agentRuntime/index.d.ts.map +1 -1
- package/dist/agentRuntime/index.js +16 -1
- package/dist/agentRuntime/index.js.map +1 -1
- package/dist/agentRuntime/runtimeCommon.d.ts +34 -0
- package/dist/agentRuntime/runtimeCommon.d.ts.map +1 -0
- package/dist/agentRuntime/runtimeCommon.js +231 -0
- package/dist/agentRuntime/runtimeCommon.js.map +1 -0
- package/dist/agentv3/claudeConfig.d.ts +6 -2
- package/dist/agentv3/claudeConfig.d.ts.map +1 -1
- package/dist/agentv3/claudeConfig.js +50 -48
- package/dist/agentv3/claudeConfig.js.map +1 -1
- package/dist/agentv3/claudeMcpServer.d.ts.map +1 -1
- package/dist/agentv3/claudeMcpServer.js +74 -66
- package/dist/agentv3/claudeMcpServer.js.map +1 -1
- package/dist/agentv3/claudeRuntime.d.ts +18 -2
- package/dist/agentv3/claudeRuntime.d.ts.map +1 -1
- package/dist/agentv3/claudeRuntime.js +198 -265
- package/dist/agentv3/claudeRuntime.js.map +1 -1
- package/dist/agentv3/claudeSseBridge.js +1 -1
- package/dist/agentv3/claudeSseBridge.js.map +1 -1
- package/dist/agentv3/claudeVerifier.d.ts.map +1 -1
- package/dist/agentv3/claudeVerifier.js +3 -6
- package/dist/agentv3/claudeVerifier.js.map +1 -1
- package/dist/agentv3/strategyLoader.d.ts +3 -0
- package/dist/agentv3/strategyLoader.d.ts.map +1 -1
- package/dist/agentv3/strategyLoader.js +17 -4
- package/dist/agentv3/strategyLoader.js.map +1 -1
- package/dist/cli-user/bin.js +83 -2
- package/dist/cli-user/bin.js.map +1 -1
- package/dist/cli-user/commands/analyze.d.ts +2 -0
- package/dist/cli-user/commands/analyze.d.ts.map +1 -1
- package/dist/cli-user/commands/analyze.js +1 -0
- package/dist/cli-user/commands/analyze.js.map +1 -1
- package/dist/cli-user/commands/capture.d.ts +37 -2
- package/dist/cli-user/commands/capture.d.ts.map +1 -1
- package/dist/cli-user/commands/capture.js +184 -95
- package/dist/cli-user/commands/capture.js.map +1 -1
- package/dist/cli-user/commands/compare.d.ts +2 -0
- package/dist/cli-user/commands/compare.d.ts.map +1 -1
- package/dist/cli-user/commands/compare.js +1 -0
- package/dist/cli-user/commands/compare.js.map +1 -1
- package/dist/cli-user/commands/doctor.js +4 -0
- package/dist/cli-user/commands/doctor.js.map +1 -1
- package/dist/cli-user/services/androidCapture.d.ts +59 -0
- package/dist/cli-user/services/androidCapture.d.ts.map +1 -0
- package/dist/cli-user/services/androidCapture.js +375 -0
- package/dist/cli-user/services/androidCapture.js.map +1 -0
- package/dist/cli-user/services/captureConfig.d.ts +38 -0
- package/dist/cli-user/services/captureConfig.d.ts.map +1 -0
- package/dist/cli-user/services/captureConfig.js +434 -0
- package/dist/cli-user/services/captureConfig.js.map +1 -0
- package/dist/cli-user/services/captureTools.d.ts +11 -0
- package/dist/cli-user/services/captureTools.d.ts.map +1 -0
- package/dist/cli-user/services/captureTools.js +247 -0
- package/dist/cli-user/services/captureTools.js.map +1 -0
- package/dist/cli-user/services/cliAnalyzeService.d.ts +2 -0
- package/dist/cli-user/services/cliAnalyzeService.d.ts.map +1 -1
- package/dist/cli-user/services/cliAnalyzeService.js +1 -0
- package/dist/cli-user/services/cliAnalyzeService.js.map +1 -1
- package/dist/cli-user/services/runtimeGuard.d.ts +10 -0
- package/dist/cli-user/services/runtimeGuard.d.ts.map +1 -1
- package/dist/cli-user/services/runtimeGuard.js +48 -0
- package/dist/cli-user/services/runtimeGuard.js.map +1 -1
- package/dist/cli-user/services/turnRunner.d.ts +3 -0
- package/dist/cli-user/services/turnRunner.d.ts.map +1 -1
- package/dist/cli-user/services/turnRunner.js +4 -0
- package/dist/cli-user/services/turnRunner.js.map +1 -1
- package/dist/cli-user/types.d.ts +57 -0
- package/dist/cli-user/types.d.ts.map +1 -1
- package/dist/config/index.d.ts +12 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -1
- package/dist/perfetto-recording-tools-pin.env +24 -0
- package/dist/routes/agent/finalizeAgentDrivenSession.d.ts +67 -0
- package/dist/routes/agent/finalizeAgentDrivenSession.d.ts.map +1 -0
- package/dist/routes/agent/finalizeAgentDrivenSession.js +103 -0
- package/dist/routes/agent/finalizeAgentDrivenSession.js.map +1 -0
- package/dist/routes/agent/normalizeAnalyzeOptions.d.ts +42 -0
- package/dist/routes/agent/normalizeAnalyzeOptions.d.ts.map +1 -0
- package/dist/routes/agent/normalizeAnalyzeOptions.js +185 -0
- package/dist/routes/agent/normalizeAnalyzeOptions.js.map +1 -0
- package/dist/routes/agentRoutes.d.ts.map +1 -1
- package/dist/routes/agentRoutes.js +285 -125
- package/dist/routes/agentRoutes.js.map +1 -1
- package/dist/scripts/verifyAgentSseScrolling.js +131 -8
- package/dist/scripts/verifyAgentSseScrolling.js.map +1 -1
- package/dist/services/agentResultNormalizer.d.ts.map +1 -1
- package/dist/services/agentResultNormalizer.js +32 -4
- package/dist/services/agentResultNormalizer.js.map +1 -1
- package/dist/services/evidence/evidenceContractBuilder.d.ts.map +1 -1
- package/dist/services/evidence/evidenceContractBuilder.js +17 -3
- package/dist/services/evidence/evidenceContractBuilder.js.map +1 -1
- package/dist/services/perfettoSqlSkill.d.ts.map +1 -1
- package/dist/services/perfettoSqlSkill.js +1 -0
- package/dist/services/perfettoSqlSkill.js.map +1 -1
- package/dist/services/providerManager/connectionTester.d.ts.map +1 -1
- package/dist/services/providerManager/connectionTester.js +4 -68
- package/dist/services/providerManager/connectionTester.js.map +1 -1
- package/dist/services/providerManager/index.d.ts +1 -0
- package/dist/services/providerManager/index.d.ts.map +1 -1
- package/dist/services/providerManager/index.js +8 -1
- package/dist/services/providerManager/index.js.map +1 -1
- package/dist/services/providerManager/providerService.d.ts.map +1 -1
- package/dist/services/providerManager/providerService.js +37 -106
- package/dist/services/providerManager/providerService.js.map +1 -1
- package/dist/services/providerManager/providerSnapshot.d.ts.map +1 -1
- package/dist/services/providerManager/providerSnapshot.js +13 -12
- package/dist/services/providerManager/providerSnapshot.js.map +1 -1
- package/dist/services/providerManager/runtimeCapabilities.d.ts +9 -0
- package/dist/services/providerManager/runtimeCapabilities.d.ts.map +1 -0
- package/dist/services/providerManager/runtimeCapabilities.js +105 -0
- package/dist/services/providerManager/runtimeCapabilities.js.map +1 -0
- package/dist/services/sceneReport/sceneJobArtifactStore.d.ts +22 -0
- package/dist/services/sceneReport/sceneJobArtifactStore.d.ts.map +1 -0
- package/dist/services/sceneReport/sceneJobArtifactStore.js +65 -0
- package/dist/services/sceneReport/sceneJobArtifactStore.js.map +1 -0
- package/dist/services/sceneReport/sceneReportMemoryCache.d.ts +5 -3
- package/dist/services/sceneReport/sceneReportMemoryCache.d.ts.map +1 -1
- package/dist/services/sceneReport/sceneReportMemoryCache.js +15 -10
- package/dist/services/sceneReport/sceneReportMemoryCache.js.map +1 -1
- package/dist/services/sceneReport/sceneReportStore.d.ts +9 -4
- package/dist/services/sceneReport/sceneReportStore.d.ts.map +1 -1
- package/dist/services/sceneReport/sceneReportStore.js +27 -7
- package/dist/services/sceneReport/sceneReportStore.js.map +1 -1
- package/dist/services/skillEngine/skillExecutor.d.ts +1 -0
- package/dist/services/skillEngine/skillExecutor.d.ts.map +1 -1
- package/dist/services/skillEngine/skillExecutor.js +64 -12
- package/dist/services/skillEngine/skillExecutor.js.map +1 -1
- package/dist/services/traceProcessor/sqlSemaphore.d.ts +16 -0
- package/dist/services/traceProcessor/sqlSemaphore.d.ts.map +1 -0
- package/dist/services/traceProcessor/sqlSemaphore.js +63 -0
- package/dist/services/traceProcessor/sqlSemaphore.js.map +1 -0
- package/dist/types/dataContract.d.ts +1 -1
- package/dist/types/dataContract.d.ts.map +1 -1
- package/dist/types/dataContract.js +2 -0
- package/dist/types/dataContract.js.map +1 -1
- package/package.json +3 -3
- package/prebuilts/android-platform-tools/README.md +13 -0
- package/prebuilts/perfetto-recording-tools/README.md +17 -0
- package/skills/README.md +13 -5
- package/skills/atomic/cpu_topology_detection.skill.yaml +105 -159
- package/skills/atomic/cpu_topology_view.skill.yaml +2 -0
- package/skills/composite/device_state_snapshot.skill.yaml +204 -0
- package/skills/composite/scene_reconstruction.skill.yaml +5 -1
- package/strategies/prompt-openai-final-report-continuation-en.template.md +3 -1
- package/strategies/prompt-openai-final-report-continuation-zh.template.md +3 -1
- package/strategies/prompt-output-format.template.md +1 -1
- package/strategies/scene-reconstruction-verifier.template.md +18 -0
- package/strategies/scrolling.strategy.md +5 -1
- package/strategies/smart.strategy.md +53 -0
- package/strategies/startup.strategy.md +4 -4
- package/skills/atomic/device_state_snapshot.skill.yaml +0 -171
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.buildDisplayedScenes = buildDisplayedScenes;
|
|
7
7
|
exports.buildAnalysisIntervals = buildAnalysisIntervals;
|
|
8
|
+
exports.isAnalysisEligibleScene = isAnalysisEligibleScene;
|
|
9
|
+
exports.selectAnalysisEligibleScenes = selectAnalysisEligibleScenes;
|
|
10
|
+
exports.filterDisplayedScenesForSelection = filterDisplayedScenesForSelection;
|
|
8
11
|
exports.computePriority = computePriority;
|
|
9
12
|
const helpers_1 = require("../strategies/helpers");
|
|
10
13
|
const domainManifest_1 = require("../config/domainManifest");
|
|
@@ -37,6 +40,7 @@ const SCENE_DISPLAY_NAMES = {
|
|
|
37
40
|
screen_unlock: '解锁屏幕',
|
|
38
41
|
notification: '通知操作',
|
|
39
42
|
split_screen: '分屏操作',
|
|
43
|
+
pip: '画中画',
|
|
40
44
|
tap: '点击',
|
|
41
45
|
long_press: '长按',
|
|
42
46
|
idle: '空闲',
|
|
@@ -49,6 +53,36 @@ const SCENE_DISPLAY_NAMES = {
|
|
|
49
53
|
ime_hide: '键盘收起',
|
|
50
54
|
window_transition: '窗口转场',
|
|
51
55
|
};
|
|
56
|
+
const SCENE_DEDUPE_TOLERANCE_NS = 150000000n;
|
|
57
|
+
const SCROLL_CHAIN_TOLERANCE_NS = 250000000n;
|
|
58
|
+
const CONTEXT_WINDOW_NS = 750000000n;
|
|
59
|
+
const MAX_CONTEXT_ROWS_PER_GROUP = 6;
|
|
60
|
+
const CLEAN_TIMELINE_SCENE_TYPES = new Set([
|
|
61
|
+
'cold_start',
|
|
62
|
+
'warm_start',
|
|
63
|
+
'hot_start',
|
|
64
|
+
'scroll',
|
|
65
|
+
'inertial_scroll',
|
|
66
|
+
'tap',
|
|
67
|
+
'long_press',
|
|
68
|
+
'idle',
|
|
69
|
+
'app_foreground',
|
|
70
|
+
'home_screen',
|
|
71
|
+
'screen_on',
|
|
72
|
+
'screen_off',
|
|
73
|
+
'screen_sleep',
|
|
74
|
+
'screen_unlock',
|
|
75
|
+
'notification',
|
|
76
|
+
'split_screen',
|
|
77
|
+
'pip',
|
|
78
|
+
'back_key',
|
|
79
|
+
'home_key',
|
|
80
|
+
'recents_key',
|
|
81
|
+
'anr',
|
|
82
|
+
'ime_show',
|
|
83
|
+
'ime_hide',
|
|
84
|
+
'window_transition',
|
|
85
|
+
]);
|
|
52
86
|
/** Known launcher / home-screen package patterns */
|
|
53
87
|
const LAUNCHER_PATTERNS = [
|
|
54
88
|
'miui.home', 'launcher', 'trebuchet', 'lawnchair',
|
|
@@ -61,6 +95,7 @@ function isLauncherPackage(pkg) {
|
|
|
61
95
|
function buildDisplayedScenes(envelopes) {
|
|
62
96
|
const scenes = [];
|
|
63
97
|
const jankRowsForFallback = [];
|
|
98
|
+
const rowsByStep = new Map();
|
|
64
99
|
let hasGestureLikeScene = false;
|
|
65
100
|
let traceDurationSec = 0;
|
|
66
101
|
for (const env of envelopes) {
|
|
@@ -70,6 +105,7 @@ function buildDisplayedScenes(envelopes) {
|
|
|
70
105
|
if (!stepId)
|
|
71
106
|
continue;
|
|
72
107
|
const rows = (0, helpers_1.payloadToObjectRows)(env.data);
|
|
108
|
+
rowsByStep.set(stepId, rows);
|
|
73
109
|
if (stepId === 'trace_time_range') {
|
|
74
110
|
const first = rows[0];
|
|
75
111
|
if (first?.duration_sec)
|
|
@@ -133,6 +169,13 @@ function buildDisplayedScenes(envelopes) {
|
|
|
133
169
|
scenes.push(scene);
|
|
134
170
|
}
|
|
135
171
|
}
|
|
172
|
+
else if (stepId === 'system_events') {
|
|
173
|
+
for (const row of rows) {
|
|
174
|
+
const scene = sceneFromSystemEvent(row, scenes.length);
|
|
175
|
+
if (scene)
|
|
176
|
+
scenes.push(scene);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
136
179
|
else if (stepId === 'navigation_keys' || stepId === 'gesture_navigation') {
|
|
137
180
|
for (const row of rows) {
|
|
138
181
|
const scene = sceneFromNavigationKey(row, scenes.length);
|
|
@@ -189,6 +232,7 @@ function buildDisplayedScenes(envelopes) {
|
|
|
189
232
|
});
|
|
190
233
|
}
|
|
191
234
|
}
|
|
235
|
+
enrichDisplayedScenes(scenes, rowsByStep);
|
|
192
236
|
// Sort by startTs so the timeline rendering and Stage 3 prompt see scenes
|
|
193
237
|
// in chronological order regardless of which skill step produced them
|
|
194
238
|
// (the scene_reconstruction skill's step order is structural, not temporal).
|
|
@@ -209,11 +253,12 @@ function buildDisplayedScenes(envelopes) {
|
|
|
209
253
|
}
|
|
210
254
|
function buildAnalysisIntervals(scenes, options) {
|
|
211
255
|
const manifest = options.manifest ?? domainManifest_1.DEFAULT_DOMAIN_MANIFEST;
|
|
212
|
-
const routes = (0, domainManifest_1.getSceneReconstructionRoutes)(manifest);
|
|
256
|
+
const routes = (0, domainManifest_1.getSceneReconstructionRoutes)(options.routeProfile ?? 'legacy', manifest);
|
|
213
257
|
if (routes.length === 0 || scenes.length === 0)
|
|
214
258
|
return [];
|
|
215
259
|
// Score each scene then pick a matching route in priority order.
|
|
216
260
|
const scored = scenes
|
|
261
|
+
.filter(isAnalysisEligibleScene)
|
|
217
262
|
.map((scene) => ({ scene, priority: computePriority(scene) }))
|
|
218
263
|
.sort((a, b) => b.priority - a.priority);
|
|
219
264
|
const intervals = [];
|
|
@@ -233,6 +278,32 @@ function buildAnalysisIntervals(scenes, options) {
|
|
|
233
278
|
}
|
|
234
279
|
return intervals;
|
|
235
280
|
}
|
|
281
|
+
function isAnalysisEligibleScene(scene) {
|
|
282
|
+
return scene.analysisEligible !== false &&
|
|
283
|
+
scene.sceneRole !== 'marker' &&
|
|
284
|
+
scene.sceneRole !== 'context';
|
|
285
|
+
}
|
|
286
|
+
function selectAnalysisEligibleScenes(scenes, selection) {
|
|
287
|
+
return filterDisplayedScenesForSelection(scenes, selection)
|
|
288
|
+
.filter(isAnalysisEligibleScene);
|
|
289
|
+
}
|
|
290
|
+
function filterDisplayedScenesForSelection(scenes, selection) {
|
|
291
|
+
if (!selection || selection.scope === 'all')
|
|
292
|
+
return scenes;
|
|
293
|
+
if (selection.scope === 'scene_types') {
|
|
294
|
+
const selectedTypes = new Set(selection.sceneTypes ?? []);
|
|
295
|
+
if (selectedTypes.size === 0)
|
|
296
|
+
return [];
|
|
297
|
+
return scenes.filter((scene) => selectedTypes.has(scene.sceneType));
|
|
298
|
+
}
|
|
299
|
+
if (selection.scope === 'scene_ids') {
|
|
300
|
+
const selectedIds = new Set(selection.sceneIds ?? []);
|
|
301
|
+
if (selectedIds.size === 0)
|
|
302
|
+
return [];
|
|
303
|
+
return scenes.filter((scene) => selectedIds.has(scene.id));
|
|
304
|
+
}
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
236
307
|
/**
|
|
237
308
|
* Compute the same numeric priority the legacy strategy used: 90 when the
|
|
238
309
|
* scene exceeds its threshold (a "problem" scene), 50 otherwise.
|
|
@@ -465,6 +536,40 @@ function sceneFromScreenStateChange(row, index) {
|
|
|
465
536
|
analysisState: 'not_planned',
|
|
466
537
|
};
|
|
467
538
|
}
|
|
539
|
+
function sceneFromSystemEvent(row, index) {
|
|
540
|
+
const startTs = String(row.ts ?? '');
|
|
541
|
+
if (!startTs)
|
|
542
|
+
return null;
|
|
543
|
+
const dur = String(row.dur ?? '0');
|
|
544
|
+
const endTs = safeAddNs(startTs, dur) ?? startTs;
|
|
545
|
+
const durationMs = nsToMs(dur);
|
|
546
|
+
const safeDurationMs = Number.isFinite(durationMs) ? durationMs : 0;
|
|
547
|
+
const rawType = String(row.event_type ?? '').trim();
|
|
548
|
+
const sceneType = rawType === 'screen_unlock' ? 'screen_unlock'
|
|
549
|
+
: rawType === 'notification' ? 'notification'
|
|
550
|
+
: rawType === 'split_screen' ? 'split_screen'
|
|
551
|
+
: rawType === 'pip' ? 'pip'
|
|
552
|
+
: null;
|
|
553
|
+
if (!sceneType)
|
|
554
|
+
return null;
|
|
555
|
+
const eventText = String(row.event ?? '').trim();
|
|
556
|
+
return {
|
|
557
|
+
id: `system_events-${index}`,
|
|
558
|
+
sceneType,
|
|
559
|
+
sourceStepId: 'system_events',
|
|
560
|
+
startTs,
|
|
561
|
+
endTs,
|
|
562
|
+
durationMs: safeDurationMs,
|
|
563
|
+
processName: 'system',
|
|
564
|
+
label: `${displayNameOf(sceneType)} (${formatDuration(safeDurationMs)})`,
|
|
565
|
+
metadata: {
|
|
566
|
+
event: eventText,
|
|
567
|
+
eventType: rawType,
|
|
568
|
+
},
|
|
569
|
+
severity: 'good',
|
|
570
|
+
analysisState: 'not_planned',
|
|
571
|
+
};
|
|
572
|
+
}
|
|
468
573
|
// ---------------------------------------------------------------------------
|
|
469
574
|
// New scene factories: navigation keys, ANR, IME, window transitions
|
|
470
575
|
// ---------------------------------------------------------------------------
|
|
@@ -569,6 +674,478 @@ function sceneFromWindowTransition(row, index) {
|
|
|
569
674
|
analysisState: 'not_planned',
|
|
570
675
|
};
|
|
571
676
|
}
|
|
677
|
+
// ---------------------------------------------------------------------------
|
|
678
|
+
// Scene contract enrichment and deterministic fusion
|
|
679
|
+
// ---------------------------------------------------------------------------
|
|
680
|
+
function enrichDisplayedScenes(scenes, rowsByStep) {
|
|
681
|
+
seedSceneContracts(scenes);
|
|
682
|
+
mergeCleanTimelineRows(scenes, getRows(rowsByStep, 'clean_timeline'));
|
|
683
|
+
attachRuntimeContext(scenes, rowsByStep);
|
|
684
|
+
linkScrollMarkersAndFling(scenes);
|
|
685
|
+
finalizeSceneContracts(scenes);
|
|
686
|
+
}
|
|
687
|
+
function seedSceneContracts(scenes) {
|
|
688
|
+
for (const scene of scenes) {
|
|
689
|
+
const primaryRef = evidenceRefForScene(scene, 'primary');
|
|
690
|
+
scene.sceneRole ?? (scene.sceneRole = defaultSceneRole(scene));
|
|
691
|
+
scene.analysisEligible ?? (scene.analysisEligible = scene.sceneRole === 'action');
|
|
692
|
+
scene.evidenceRefs = mergeEvidenceRefs(scene.evidenceRefs, [primaryRef]);
|
|
693
|
+
scene.confidenceReasons = uniqueStrings([
|
|
694
|
+
...(scene.confidenceReasons ?? []),
|
|
695
|
+
`primary:${scene.sourceStepId}`,
|
|
696
|
+
]);
|
|
697
|
+
scene.conflicts ?? (scene.conflicts = []);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
function mergeCleanTimelineRows(scenes, rows) {
|
|
701
|
+
rows.forEach((row, index) => {
|
|
702
|
+
const sceneType = normalizeCleanTimelineSceneType(row.event_type);
|
|
703
|
+
if (!sceneType)
|
|
704
|
+
return;
|
|
705
|
+
const ref = evidenceRefForRow('clean_timeline', row, index, 'supporting');
|
|
706
|
+
const existing = findMatchingSceneForRow(scenes, sceneType, row);
|
|
707
|
+
if (existing) {
|
|
708
|
+
addSupportingEvidence(existing, ref, row, 'clean_timeline');
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
const scene = sceneFromCleanTimeline(row, index);
|
|
712
|
+
if (!scene)
|
|
713
|
+
return;
|
|
714
|
+
scene.evidenceRefs = mergeEvidenceRefs(scene.evidenceRefs, [ref]);
|
|
715
|
+
scene.confidenceReasons = uniqueStrings([
|
|
716
|
+
...(scene.confidenceReasons ?? []),
|
|
717
|
+
'derived:clean_timeline',
|
|
718
|
+
]);
|
|
719
|
+
scenes.push(scene);
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
function sceneFromCleanTimeline(row, index) {
|
|
723
|
+
const sceneType = normalizeCleanTimelineSceneType(row.event_type);
|
|
724
|
+
if (!sceneType)
|
|
725
|
+
return null;
|
|
726
|
+
const startTs = String(row.ts ?? '');
|
|
727
|
+
if (!startTs)
|
|
728
|
+
return null;
|
|
729
|
+
const dur = String(row.dur ?? '0');
|
|
730
|
+
const endTs = safeAddNs(startTs, dur) ?? startTs;
|
|
731
|
+
const durationMs = nsToMs(dur);
|
|
732
|
+
const safeDurationMs = Number.isFinite(durationMs) ? durationMs : 0;
|
|
733
|
+
const eventId = String(row.event_id ?? '').trim();
|
|
734
|
+
const processName = String(row.app_package ?? '').trim() || defaultProcessNameForSceneType(sceneType);
|
|
735
|
+
const sceneRole = defaultSceneRoleForType(sceneType);
|
|
736
|
+
return {
|
|
737
|
+
id: eventId ? `clean_timeline-${eventId}` : `clean_timeline-${sceneType}-${startTs}-${index}`,
|
|
738
|
+
sceneType,
|
|
739
|
+
sourceStepId: 'clean_timeline',
|
|
740
|
+
startTs,
|
|
741
|
+
endTs,
|
|
742
|
+
durationMs: safeDurationMs,
|
|
743
|
+
processName,
|
|
744
|
+
label: `${displayNameOf(sceneType)} (${formatDuration(safeDurationMs)})`,
|
|
745
|
+
metadata: {
|
|
746
|
+
eventId: eventId || undefined,
|
|
747
|
+
event: row.event,
|
|
748
|
+
eventType: row.event_type,
|
|
749
|
+
timeOffset: row.time_offset,
|
|
750
|
+
rating: row.rating,
|
|
751
|
+
},
|
|
752
|
+
severity: severityFromRating(row.rating) ?? severityFor(sceneType, safeDurationMs, row),
|
|
753
|
+
sceneRole,
|
|
754
|
+
analysisEligible: sceneRole === 'action',
|
|
755
|
+
analysisState: 'not_planned',
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
function normalizeCleanTimelineSceneType(value) {
|
|
759
|
+
const sceneType = String(value ?? '').trim();
|
|
760
|
+
if (!sceneType)
|
|
761
|
+
return null;
|
|
762
|
+
if (sceneType === 'system')
|
|
763
|
+
return null;
|
|
764
|
+
return CLEAN_TIMELINE_SCENE_TYPES.has(sceneType) ? sceneType : null;
|
|
765
|
+
}
|
|
766
|
+
function findMatchingSceneForRow(scenes, sceneType, row) {
|
|
767
|
+
const rowStart = safeBigInt(row.ts);
|
|
768
|
+
if (rowStart === null)
|
|
769
|
+
return null;
|
|
770
|
+
const rowEnd = rowStart + (safeBigInt(row.dur) ?? 0n);
|
|
771
|
+
return scenes.find((scene) => {
|
|
772
|
+
if (!areSceneTypesEquivalent(scene.sceneType, sceneType))
|
|
773
|
+
return false;
|
|
774
|
+
const sceneStart = safeBigInt(scene.startTs);
|
|
775
|
+
const sceneEnd = safeBigInt(scene.endTs);
|
|
776
|
+
if (sceneStart === null || sceneEnd === null)
|
|
777
|
+
return false;
|
|
778
|
+
return rangesOverlapOrClose(sceneStart, sceneEnd, rowStart, rowEnd, SCENE_DEDUPE_TOLERANCE_NS);
|
|
779
|
+
}) ?? null;
|
|
780
|
+
}
|
|
781
|
+
function areSceneTypesEquivalent(left, right) {
|
|
782
|
+
if (left === right)
|
|
783
|
+
return true;
|
|
784
|
+
const appContext = new Set(['app_foreground', 'home_screen']);
|
|
785
|
+
if (appContext.has(left) && appContext.has(right))
|
|
786
|
+
return true;
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
function addSupportingEvidence(scene, ref, row, reason) {
|
|
790
|
+
scene.evidenceRefs = mergeEvidenceRefs(scene.evidenceRefs, [ref]);
|
|
791
|
+
scene.confidenceReasons = uniqueStrings([...(scene.confidenceReasons ?? []), `support:${reason}`]);
|
|
792
|
+
scene.context = {
|
|
793
|
+
...(scene.context ?? {}),
|
|
794
|
+
cleanTimeline: appendBoundedContext(scene.context?.cleanTimeline, compactContextRow(row)),
|
|
795
|
+
};
|
|
796
|
+
const supportingApp = String(row.app_package ?? '').trim();
|
|
797
|
+
if (supportingApp && isMeaningfulProcess(scene.processName) && scene.processName !== supportingApp) {
|
|
798
|
+
scene.conflicts = [
|
|
799
|
+
...(scene.conflicts ?? []),
|
|
800
|
+
{
|
|
801
|
+
type: 'app_mismatch',
|
|
802
|
+
severity: 'warning',
|
|
803
|
+
message: `scene app ${scene.processName} differs from ${reason} app ${supportingApp}`,
|
|
804
|
+
evidenceRefs: [evidenceRefForScene(scene, 'primary'), ref],
|
|
805
|
+
},
|
|
806
|
+
];
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
function attachRuntimeContext(scenes, rowsByStep) {
|
|
810
|
+
const operationRows = getRows(rowsByStep, 'operation_chain');
|
|
811
|
+
const activityRows = getRows(rowsByStep, 'activity_lifecycle');
|
|
812
|
+
const appStateRows = [
|
|
813
|
+
...getRows(rowsByStep, 'app_state_tracking'),
|
|
814
|
+
...getRows(rowsByStep, 'app_states'),
|
|
815
|
+
];
|
|
816
|
+
const deviceRows = getRows(rowsByStep, 'device_state');
|
|
817
|
+
for (const scene of scenes) {
|
|
818
|
+
const operationChain = contextRowsNearScene(operationRows, scene, CONTEXT_WINDOW_NS);
|
|
819
|
+
const activityLifecycle = contextRowsNearScene(activityRows, scene, CONTEXT_WINDOW_NS);
|
|
820
|
+
const appState = contextRowsNearScene(appStateRows, scene, CONTEXT_WINDOW_NS, (row) => sameMeaningfulProcess(scene.processName, row.app_package));
|
|
821
|
+
const deviceState = contextRowsNearScene(deviceRows, scene, CONTEXT_WINDOW_NS);
|
|
822
|
+
const context = {
|
|
823
|
+
...(scene.context ?? {}),
|
|
824
|
+
...(operationChain.length > 0 ? { operationChain } : {}),
|
|
825
|
+
...(activityLifecycle.length > 0 ? { activityLifecycle } : {}),
|
|
826
|
+
...(appState.length > 0 ? { appState } : {}),
|
|
827
|
+
...(deviceState.length > 0 ? { deviceState } : {}),
|
|
828
|
+
};
|
|
829
|
+
if (Object.keys(context).length > 0) {
|
|
830
|
+
scene.context = context;
|
|
831
|
+
}
|
|
832
|
+
if (operationChain.length > 0) {
|
|
833
|
+
scene.evidenceRefs = mergeEvidenceRefs(scene.evidenceRefs, [
|
|
834
|
+
evidenceRefForContext('operation_chain', operationChain[0]),
|
|
835
|
+
]);
|
|
836
|
+
scene.confidenceReasons = uniqueStrings([...(scene.confidenceReasons ?? []), 'context:operation_chain']);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
function contextRowsNearScene(rows, scene, windowNs, predicate) {
|
|
841
|
+
const sceneStart = safeBigInt(scene.startTs);
|
|
842
|
+
const sceneEnd = safeBigInt(scene.endTs);
|
|
843
|
+
if (sceneStart === null || sceneEnd === null)
|
|
844
|
+
return [];
|
|
845
|
+
const start = sceneStart - windowNs;
|
|
846
|
+
const end = sceneEnd + windowNs;
|
|
847
|
+
const selected = [];
|
|
848
|
+
for (const row of rows) {
|
|
849
|
+
if (predicate && !predicate(row))
|
|
850
|
+
continue;
|
|
851
|
+
const rowTs = safeBigInt(row.ts);
|
|
852
|
+
if (rowTs === null || rowTs < start || rowTs > end)
|
|
853
|
+
continue;
|
|
854
|
+
selected.push(compactContextRow(row));
|
|
855
|
+
if (selected.length >= MAX_CONTEXT_ROWS_PER_GROUP)
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
return selected;
|
|
859
|
+
}
|
|
860
|
+
function linkScrollMarkersAndFling(scenes) {
|
|
861
|
+
const scrolls = scenes.filter(scene => scene.sceneType === 'scroll');
|
|
862
|
+
const flings = scenes.filter(scene => scene.sceneType === 'inertial_scroll');
|
|
863
|
+
const markers = scenes.filter(scene => scene.sceneType === 'scroll_start');
|
|
864
|
+
for (const marker of markers) {
|
|
865
|
+
marker.sceneRole = 'marker';
|
|
866
|
+
marker.analysisEligible = false;
|
|
867
|
+
const parent = findContainingOrNearestScene(marker, scrolls, SCROLL_CHAIN_TOLERANCE_NS);
|
|
868
|
+
if (!parent)
|
|
869
|
+
continue;
|
|
870
|
+
linkParentChild(parent, marker);
|
|
871
|
+
marker.confidenceReasons = uniqueStrings([...(marker.confidenceReasons ?? []), 'linked:scroll_start']);
|
|
872
|
+
}
|
|
873
|
+
for (const fling of flings) {
|
|
874
|
+
const parent = findContainingOrNearestScene(fling, scrolls, SCROLL_CHAIN_TOLERANCE_NS, true);
|
|
875
|
+
if (!parent)
|
|
876
|
+
continue;
|
|
877
|
+
linkParentChild(parent, fling);
|
|
878
|
+
parent.confidenceReasons = uniqueStrings([...(parent.confidenceReasons ?? []), 'linked:inertial_scroll']);
|
|
879
|
+
fling.confidenceReasons = uniqueStrings([...(fling.confidenceReasons ?? []), 'linked:active_scroll']);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
function findContainingOrNearestScene(target, candidates, toleranceNs, requireSameProcess = false) {
|
|
883
|
+
const targetStart = safeBigInt(target.startTs);
|
|
884
|
+
const targetEnd = safeBigInt(target.endTs);
|
|
885
|
+
if (targetStart === null || targetEnd === null)
|
|
886
|
+
return null;
|
|
887
|
+
let best = null;
|
|
888
|
+
for (const scene of candidates) {
|
|
889
|
+
if (scene.id === target.id)
|
|
890
|
+
continue;
|
|
891
|
+
if (requireSameProcess && !sameMeaningfulProcess(scene.processName, target.processName))
|
|
892
|
+
continue;
|
|
893
|
+
const sceneStart = safeBigInt(scene.startTs);
|
|
894
|
+
const sceneEnd = safeBigInt(scene.endTs);
|
|
895
|
+
if (sceneStart === null || sceneEnd === null)
|
|
896
|
+
continue;
|
|
897
|
+
if (!rangesOverlapOrClose(sceneStart, sceneEnd, targetStart, targetEnd, toleranceNs))
|
|
898
|
+
continue;
|
|
899
|
+
const distance = targetStart >= sceneStart && targetStart <= sceneEnd
|
|
900
|
+
? 0n
|
|
901
|
+
: minBigInt(absBigInt(targetStart - sceneEnd), absBigInt(sceneStart - targetEnd));
|
|
902
|
+
if (!best || distance < best.distance) {
|
|
903
|
+
best = { scene, distance };
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return best?.scene ?? null;
|
|
907
|
+
}
|
|
908
|
+
function linkParentChild(parent, child) {
|
|
909
|
+
child.parentSceneId = parent.id;
|
|
910
|
+
parent.childSceneIds = uniqueStrings([...(parent.childSceneIds ?? []), child.id]);
|
|
911
|
+
}
|
|
912
|
+
function finalizeSceneContracts(scenes) {
|
|
913
|
+
for (const scene of scenes) {
|
|
914
|
+
scene.sceneRole ?? (scene.sceneRole = defaultSceneRole(scene));
|
|
915
|
+
scene.analysisEligible ?? (scene.analysisEligible = scene.sceneRole === 'action');
|
|
916
|
+
scene.evidenceRefs = mergeEvidenceRefs(scene.evidenceRefs, [evidenceRefForScene(scene, 'primary')]);
|
|
917
|
+
scene.conflicts = dedupeConflicts(scene.conflicts ?? []);
|
|
918
|
+
const score = computeConfidenceScore(scene);
|
|
919
|
+
scene.confidenceScore = score;
|
|
920
|
+
scene.confidenceLevel = confidenceLevelForScore(score);
|
|
921
|
+
scene.confidenceReasons = uniqueStrings(scene.confidenceReasons ?? []);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
function computeConfidenceScore(scene) {
|
|
925
|
+
const sourceBase = baseConfidenceForSource(scene.sourceStepId);
|
|
926
|
+
const evidenceCount = scene.evidenceRefs?.length ?? 0;
|
|
927
|
+
const supportBonus = Math.min(0.12, Math.max(0, evidenceCount - 1) * 0.04);
|
|
928
|
+
const metadataScore = confidenceScoreFromMetadata(scene.metadata?.confidence);
|
|
929
|
+
const conflictPenalty = Math.min(0.25, (scene.conflicts ?? []).length * 0.08);
|
|
930
|
+
const rolePenalty = scene.sceneRole === 'marker' ? 0.08 : scene.sceneRole === 'context' ? 0.05 : 0;
|
|
931
|
+
const raw = Math.max(sourceBase, metadataScore ?? 0) + supportBonus - conflictPenalty - rolePenalty;
|
|
932
|
+
return Math.max(0.35, Math.min(0.95, Number(raw.toFixed(2))));
|
|
933
|
+
}
|
|
934
|
+
function baseConfidenceForSource(sourceStepId) {
|
|
935
|
+
if (sourceStepId === 'app_launches')
|
|
936
|
+
return 0.9;
|
|
937
|
+
if (sourceStepId === 'user_gestures')
|
|
938
|
+
return 0.78;
|
|
939
|
+
if (sourceStepId === 'inertial_scrolls')
|
|
940
|
+
return 0.76;
|
|
941
|
+
if (sourceStepId === 'scroll_initiation')
|
|
942
|
+
return 0.64;
|
|
943
|
+
if (sourceStepId === 'system_events')
|
|
944
|
+
return 0.72;
|
|
945
|
+
if (sourceStepId === 'screen_state_changes')
|
|
946
|
+
return 0.78;
|
|
947
|
+
if (sourceStepId === 'top_app_changes')
|
|
948
|
+
return 0.72;
|
|
949
|
+
if (sourceStepId === 'idle_periods')
|
|
950
|
+
return 0.62;
|
|
951
|
+
if (sourceStepId === 'clean_timeline')
|
|
952
|
+
return 0.68;
|
|
953
|
+
if (sourceStepId === 'jank_events')
|
|
954
|
+
return 0.58;
|
|
955
|
+
return 0.7;
|
|
956
|
+
}
|
|
957
|
+
function confidenceScoreFromMetadata(value) {
|
|
958
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
959
|
+
if (value > 1)
|
|
960
|
+
return Math.min(0.95, value / 100);
|
|
961
|
+
return Math.max(0, Math.min(0.95, value));
|
|
962
|
+
}
|
|
963
|
+
const text = String(value ?? '').trim().toLowerCase();
|
|
964
|
+
if (!text)
|
|
965
|
+
return null;
|
|
966
|
+
if (text === '高' || text === 'high')
|
|
967
|
+
return 0.86;
|
|
968
|
+
if (text === '中' || text === 'medium')
|
|
969
|
+
return 0.74;
|
|
970
|
+
if (text === '低' || text === 'low')
|
|
971
|
+
return 0.58;
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
function confidenceLevelForScore(score) {
|
|
975
|
+
if (score >= 0.82)
|
|
976
|
+
return 'high';
|
|
977
|
+
if (score >= 0.65)
|
|
978
|
+
return 'medium';
|
|
979
|
+
return 'low';
|
|
980
|
+
}
|
|
981
|
+
function defaultSceneRole(scene) {
|
|
982
|
+
return defaultSceneRoleForType(scene.sceneType);
|
|
983
|
+
}
|
|
984
|
+
function defaultSceneRoleForType(sceneType) {
|
|
985
|
+
if (sceneType === 'scroll_start')
|
|
986
|
+
return 'marker';
|
|
987
|
+
if (sceneType === 'app_foreground' || sceneType === 'home_screen' || sceneType === 'idle') {
|
|
988
|
+
return 'context';
|
|
989
|
+
}
|
|
990
|
+
return 'action';
|
|
991
|
+
}
|
|
992
|
+
function defaultProcessNameForSceneType(sceneType) {
|
|
993
|
+
if (sceneType === 'screen_unlock'
|
|
994
|
+
|| sceneType === 'notification'
|
|
995
|
+
|| sceneType === 'split_screen'
|
|
996
|
+
|| sceneType === 'pip'
|
|
997
|
+
|| sceneType === 'screen_on'
|
|
998
|
+
|| sceneType === 'screen_off'
|
|
999
|
+
|| sceneType === 'screen_sleep'
|
|
1000
|
+
|| sceneType === 'ime_show'
|
|
1001
|
+
|| sceneType === 'ime_hide'
|
|
1002
|
+
|| sceneType === 'window_transition') {
|
|
1003
|
+
return 'system';
|
|
1004
|
+
}
|
|
1005
|
+
return 'unknown';
|
|
1006
|
+
}
|
|
1007
|
+
function severityFromRating(value) {
|
|
1008
|
+
const text = String(value ?? '');
|
|
1009
|
+
if (!text)
|
|
1010
|
+
return null;
|
|
1011
|
+
if (text.includes('🔴'))
|
|
1012
|
+
return 'bad';
|
|
1013
|
+
if (text.includes('🟡'))
|
|
1014
|
+
return 'warning';
|
|
1015
|
+
if (text.includes('🟢'))
|
|
1016
|
+
return 'good';
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
function evidenceRefForScene(scene, role) {
|
|
1020
|
+
return {
|
|
1021
|
+
sourceStepId: scene.sourceStepId,
|
|
1022
|
+
role,
|
|
1023
|
+
rowSelector: {
|
|
1024
|
+
ts: scene.startTs,
|
|
1025
|
+
sceneType: scene.sceneType,
|
|
1026
|
+
},
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
function evidenceRefForRow(sourceStepId, row, rowIndex, role) {
|
|
1030
|
+
const ref = {
|
|
1031
|
+
sourceStepId,
|
|
1032
|
+
rowIndex,
|
|
1033
|
+
role,
|
|
1034
|
+
};
|
|
1035
|
+
const eventId = String(row.event_id ?? '').trim();
|
|
1036
|
+
if (eventId)
|
|
1037
|
+
ref.eventId = eventId;
|
|
1038
|
+
const selector = {};
|
|
1039
|
+
if (row.ts !== undefined && row.ts !== null)
|
|
1040
|
+
selector.ts = String(row.ts);
|
|
1041
|
+
const eventType = row.event_type ?? row.gesture_type ?? row.key_name ?? row.ime_action;
|
|
1042
|
+
if (eventType !== undefined && eventType !== null)
|
|
1043
|
+
selector.eventType = String(eventType);
|
|
1044
|
+
if (Object.keys(selector).length > 0)
|
|
1045
|
+
ref.rowSelector = selector;
|
|
1046
|
+
return ref;
|
|
1047
|
+
}
|
|
1048
|
+
function evidenceRefForContext(sourceStepId, row) {
|
|
1049
|
+
const selector = {};
|
|
1050
|
+
if (row.ts !== undefined && row.ts !== null)
|
|
1051
|
+
selector.ts = String(row.ts);
|
|
1052
|
+
if (row.event !== undefined && row.event !== null)
|
|
1053
|
+
selector.event = String(row.event);
|
|
1054
|
+
if (row.category !== undefined && row.category !== null)
|
|
1055
|
+
selector.category = String(row.category);
|
|
1056
|
+
return {
|
|
1057
|
+
sourceStepId,
|
|
1058
|
+
role: 'context',
|
|
1059
|
+
...(Object.keys(selector).length > 0 ? { rowSelector: selector } : {}),
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
function mergeEvidenceRefs(existing, next) {
|
|
1063
|
+
const merged = [...(existing ?? [])];
|
|
1064
|
+
for (const ref of next) {
|
|
1065
|
+
const key = evidenceRefKey(ref);
|
|
1066
|
+
if (merged.some(item => evidenceRefKey(item) === key))
|
|
1067
|
+
continue;
|
|
1068
|
+
merged.push(ref);
|
|
1069
|
+
}
|
|
1070
|
+
return merged;
|
|
1071
|
+
}
|
|
1072
|
+
function evidenceRefKey(ref) {
|
|
1073
|
+
const selector = ref.rowSelector ? JSON.stringify(ref.rowSelector) : '';
|
|
1074
|
+
return `${ref.sourceStepId}:${ref.eventId ?? ''}:${ref.rowIndex ?? ''}:${ref.role ?? ''}:${selector}`;
|
|
1075
|
+
}
|
|
1076
|
+
function dedupeConflicts(conflicts) {
|
|
1077
|
+
const seen = new Set();
|
|
1078
|
+
const result = [];
|
|
1079
|
+
for (const conflict of conflicts) {
|
|
1080
|
+
const key = `${conflict.type}:${conflict.severity}:${conflict.message}`;
|
|
1081
|
+
if (seen.has(key))
|
|
1082
|
+
continue;
|
|
1083
|
+
seen.add(key);
|
|
1084
|
+
result.push(conflict);
|
|
1085
|
+
}
|
|
1086
|
+
return result;
|
|
1087
|
+
}
|
|
1088
|
+
function getRows(rowsByStep, stepId) {
|
|
1089
|
+
return rowsByStep.get(stepId) ?? [];
|
|
1090
|
+
}
|
|
1091
|
+
function compactContextRow(row) {
|
|
1092
|
+
const compact = {};
|
|
1093
|
+
for (const key of [
|
|
1094
|
+
'event_id',
|
|
1095
|
+
'time_offset',
|
|
1096
|
+
'ts',
|
|
1097
|
+
'dur',
|
|
1098
|
+
'dur_ms',
|
|
1099
|
+
'event_type',
|
|
1100
|
+
'event',
|
|
1101
|
+
'category',
|
|
1102
|
+
'priority',
|
|
1103
|
+
'app_package',
|
|
1104
|
+
'activity_name',
|
|
1105
|
+
'lifecycle_event',
|
|
1106
|
+
'oom_adj',
|
|
1107
|
+
'state_label',
|
|
1108
|
+
'value',
|
|
1109
|
+
]) {
|
|
1110
|
+
if (row[key] !== undefined && row[key] !== null)
|
|
1111
|
+
compact[key] = row[key];
|
|
1112
|
+
}
|
|
1113
|
+
return compact;
|
|
1114
|
+
}
|
|
1115
|
+
function appendBoundedContext(existing, next) {
|
|
1116
|
+
const rows = [...(existing ?? [])];
|
|
1117
|
+
const key = JSON.stringify(next);
|
|
1118
|
+
if (!rows.some(row => JSON.stringify(row) === key))
|
|
1119
|
+
rows.push(next);
|
|
1120
|
+
return rows.slice(0, MAX_CONTEXT_ROWS_PER_GROUP);
|
|
1121
|
+
}
|
|
1122
|
+
function rangesOverlapOrClose(aStart, aEnd, bStart, bEnd, toleranceNs) {
|
|
1123
|
+
if (aEnd + toleranceNs < bStart)
|
|
1124
|
+
return false;
|
|
1125
|
+
if (bEnd + toleranceNs < aStart)
|
|
1126
|
+
return false;
|
|
1127
|
+
return true;
|
|
1128
|
+
}
|
|
1129
|
+
function sameMeaningfulProcess(left, right) {
|
|
1130
|
+
const l = String(left ?? '').trim();
|
|
1131
|
+
const r = String(right ?? '').trim();
|
|
1132
|
+
if (!isMeaningfulProcess(l) || !isMeaningfulProcess(r))
|
|
1133
|
+
return false;
|
|
1134
|
+
return l === r;
|
|
1135
|
+
}
|
|
1136
|
+
function isMeaningfulProcess(value) {
|
|
1137
|
+
const text = String(value ?? '').trim();
|
|
1138
|
+
return !!text && text !== 'unknown' && text !== 'system';
|
|
1139
|
+
}
|
|
1140
|
+
function uniqueStrings(values) {
|
|
1141
|
+
return Array.from(new Set(values.filter(Boolean)));
|
|
1142
|
+
}
|
|
1143
|
+
function absBigInt(value) {
|
|
1144
|
+
return value < 0n ? -value : value;
|
|
1145
|
+
}
|
|
1146
|
+
function minBigInt(left, right) {
|
|
1147
|
+
return left < right ? left : right;
|
|
1148
|
+
}
|
|
572
1149
|
function aggregateJankFramesToIntervals(rows) {
|
|
573
1150
|
if (rows.length === 0)
|
|
574
1151
|
return [];
|