heaplens-cli 0.1.1 → 0.1.3
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/distribution.json +51 -26
- package/package.json +1 -1
- package/runtime/bin/heaplens +0 -0
- package/runtime/bin/hprof-server +0 -0
- package/runtime/browser-ui/ai-actions.cjs +51 -106
- package/runtime/browser-ui/ai.cjs +29 -7
- package/runtime/browser-ui/build/aiInvestigation.js +425 -0
- package/runtime/browser-ui/build/aiToolProtocol.js +79 -0
- package/runtime/browser-ui/build/diagnosisPolicy.js +21 -0
- package/runtime/browser-ui/build/fix-system-prompt.txt +4 -1
- package/runtime/browser-ui/build/index.html +326 -67
- package/runtime/browser-ui/build/investigationEngine.js +33 -0
- package/runtime/browser-ui/build/prompts.cjs +25 -26
- package/runtime/browser-ui/build/providers.json +10 -1
- package/runtime/browser-ui/build/system-prompt.txt +18 -8
- package/runtime/browser-ui/investigation-actions.cjs +77 -0
- package/runtime/browser-ui/server.cjs +2 -0
- package/runtime/browser-ui/session.cjs +2 -1
- package/runtime/package.json +1 -1
|
@@ -1857,9 +1857,10 @@ body {
|
|
|
1857
1857
|
<span class="tree-shallow" style="font-weight:bold;">Shallow</span>
|
|
1858
1858
|
<span class="tree-size" style="font-weight:bold;">Retained</span>
|
|
1859
1859
|
<span class="tree-bar-wrap"></span>
|
|
1860
|
-
<span class="tree-pct" style="font-weight:bold;"
|
|
1860
|
+
<span class="tree-pct" style="font-weight:bold;" title="Retained size divided by reachable heap. Nested retained sets overlap.">% reachable</span>
|
|
1861
1861
|
<span class="tree-actions"><span class="tree-action-alive"></span><span class="tree-action-icon"></span><span class="tree-action-icon"></span><span class="tree-action-icon"></span></span>
|
|
1862
1862
|
</div>
|
|
1863
|
+
<p class="histogram-percentage-note">Percentages use reachable heap, not the sum of these rows. Nested entries can retain the same objects; percentages need not sum to 100%. N/A means unavailable.</p>
|
|
1863
1864
|
</div>
|
|
1864
1865
|
<p class="dominator-hint">Top retained entry points from analysis; expand for immediately dominated children. Percentages use reachable heap. Nested rows overlap and must not be added together.</p><div id="dominator-status" role="status" aria-live="polite"></div><div id="dominator-tree"><div class="loading">Waiting for analysis...</div></div>
|
|
1865
1866
|
<div id="sunburst-chart"></div>
|
|
@@ -2056,6 +2057,7 @@ body {
|
|
|
2056
2057
|
<div class="chat-input-row">
|
|
2057
2058
|
<textarea class="chat-input" id="chat-input" placeholder="Ask about your heap dump..." rows="1"></textarea>
|
|
2058
2059
|
<button class="chat-send" id="chat-send">Send</button>
|
|
2060
|
+
<button class="chat-clear" id="chat-stop" title="Stop the current AI investigation">Stop</button>
|
|
2059
2061
|
<button class="chat-clear" id="chat-clear" title="Clear chat history">Clear</button>
|
|
2060
2062
|
</div>
|
|
2061
2063
|
<p class="chat-privacy-note">AI sends heap metadata and your messages to the configured provider. Automatic string previews and primitive values are omitted. Names and anything you type may still be sensitive; do not paste secrets.</p>
|
|
@@ -2090,6 +2092,11 @@ body {
|
|
|
2090
2092
|
return request;
|
|
2091
2093
|
}
|
|
2092
2094
|
window.heaplensSend = send;
|
|
2095
|
+
window.addEventListener('pagehide',()=>{
|
|
2096
|
+
// Best effort immediately; a per-view host lease handles lost unload requests.
|
|
2097
|
+
void fetch('/api/command',{method:'POST',keepalive:true,credentials:'omit',headers:{'X-HeapLens-Token':token,'X-HeapLens-View':view,'Content-Type':'application/json'},
|
|
2098
|
+
body:JSON.stringify({session,message:{command:'aiStop'}})}).catch(()=>{});
|
|
2099
|
+
});
|
|
2093
2100
|
document.getElementById('browser-telemetry').onclick=async()=>{
|
|
2094
2101
|
const selected=await choice('Telemetry','Telemetry is enabled by default (usage and errors). Disable it here for this running host, or launch with --telemetry off to keep it disabled from startup. Filtered records go to HeapLens Azure Application Insights. No paths, source/query/dump text, keys or stable IDs. Network services may process IP addresses. Disabling clears pending delivery.',[['Disable telemetry','off'],['Review local diagnostics','review'],['Cancel',null]]);
|
|
2095
2102
|
if(selected)send({command:selected==='off'?'telemetryOff':'reviewDiagnostics'});
|
|
@@ -2450,7 +2457,8 @@ document.querySelectorAll('.tab-btn').forEach(b => {
|
|
|
2450
2457
|
function escapeHtml(str) {
|
|
2451
2458
|
var div = document.createElement('div');
|
|
2452
2459
|
div.textContent = str;
|
|
2453
|
-
|
|
2460
|
+
// Also safe inside double-quoted attributes; heap names are untrusted.
|
|
2461
|
+
return div.innerHTML.replace(/"/g, '"').replace(/'/g, ''');
|
|
2454
2462
|
}
|
|
2455
2463
|
|
|
2456
2464
|
function renderMarkdown(raw) {
|
|
@@ -2847,8 +2855,8 @@ document.querySelectorAll('.tab-btn').forEach(b => {
|
|
|
2847
2855
|
var section = document.getElementById('diagnosis-section');
|
|
2848
2856
|
if (!data.summary || !data.classHistogram) { section.innerHTML = ''; return; }
|
|
2849
2857
|
|
|
2850
|
-
var totalHeap = data.summary.reachable_heap_size
|
|
2851
|
-
if (totalHeap
|
|
2858
|
+
var totalHeap = data.summary.reachable_heap_size;
|
|
2859
|
+
if (!Number.isFinite(totalHeap) || totalHeap <= 0) { section.innerHTML = ''; return; }
|
|
2852
2860
|
|
|
2853
2861
|
var findings = [];
|
|
2854
2862
|
var suspects = data.leakSuspects || [];
|
|
@@ -2862,6 +2870,7 @@ document.querySelectorAll('.tab-btn').forEach(b => {
|
|
|
2862
2870
|
|
|
2863
2871
|
var histogram = data.classHistogram || [];
|
|
2864
2872
|
histogram.forEach(function(entry) {
|
|
2873
|
+
if (!Number.isFinite(entry.retained_size) || entry.retained_size < 0) return;
|
|
2865
2874
|
var pct = (entry.retained_size / totalHeap) * 100;
|
|
2866
2875
|
var cn = entry.class_name;
|
|
2867
2876
|
var cnLower = cn.toLowerCase();
|
|
@@ -3786,7 +3795,7 @@ function buildIncidentReport(data, hprofPath) {
|
|
|
3786
3795
|
lines.push('## Heap Summary');
|
|
3787
3796
|
lines.push('');
|
|
3788
3797
|
lines.push(`- **Total Heap Size:** ${fmt(s.total_heap_size)}`);
|
|
3789
|
-
lines.push(`- **Reachable Heap Size:** ${
|
|
3798
|
+
lines.push(`- **Reachable Heap Size:** ${typeof s.reachable_heap_size === 'number' && Number.isFinite(s.reachable_heap_size) && s.reachable_heap_size >= 0 ? fmt(s.reachable_heap_size) : 'Not calculated'}`);
|
|
3790
3799
|
lines.push(`- **Objects:** ${s.total_instances.toLocaleString()}`);
|
|
3791
3800
|
lines.push(`- **Classes:** ${s.total_classes.toLocaleString()}`);
|
|
3792
3801
|
lines.push(`- **Arrays:** ${s.total_arrays.toLocaleString()}`);
|
|
@@ -3922,6 +3931,9 @@ onMessage=function(command,handler) {
|
|
|
3922
3931
|
});
|
|
3923
3932
|
};
|
|
3924
3933
|
function dispatchHost(message) {
|
|
3934
|
+
// Explicit Stop keeps the request fence until its matching terminal event resets the UI.
|
|
3935
|
+
// Navigation retirement is separate: its old panel must not accept a late reply.
|
|
3936
|
+
if(message.command==='stopAi') {hostSend({command:'cancelAiAssistance'});return;}
|
|
3925
3937
|
if(message.command==='copyReport') {
|
|
3926
3938
|
if(!objectReady || !analysisData) return;
|
|
3927
3939
|
hostSend({command:'copyReportText',text:buildIncidentReport(analysisData,analysisData.displayName || 'Heap dump')});return;
|
|
@@ -3945,7 +3957,8 @@ function dispatchHost(message) {
|
|
|
3945
3957
|
document.querySelectorAll('.suspect-explain-link').forEach(function(link){link.textContent='Explain';});
|
|
3946
3958
|
return;
|
|
3947
3959
|
}
|
|
3948
|
-
|
|
3960
|
+
var exactExplainId = channel==='explain' && typeof message.objectId==='string' && /^[1-9][0-9]{0,19}$/.test(message.objectId) && BigInt(message.objectId)<=18446744073709551615n;
|
|
3961
|
+
if(channel!=='fix' && !exactExplainId && !(message.command==='explainLeakSuspect' && message.objectId===undefined) &&
|
|
3949
3962
|
(!Number.isSafeInteger(message.objectId) || message.objectId<(channel==='flame'?0:1))) {
|
|
3950
3963
|
localStatus('This object ID is unsupported; no request was sent.');return;
|
|
3951
3964
|
}
|
|
@@ -4066,10 +4079,14 @@ document.addEventListener('click',function(e){
|
|
|
4066
4079
|
// Self-contained: owns explain-object streaming buffer.
|
|
4067
4080
|
|
|
4068
4081
|
var _explainObjectBuffer = '';
|
|
4082
|
+
var _explainObjectSelection = null;
|
|
4069
4083
|
|
|
4070
4084
|
function openInspector(objectId, className, shallowSize, retainedSize) {
|
|
4085
|
+
if (_explainObjectSelection !== null) vscode.postMessage({command:'stopAi'});
|
|
4086
|
+
_explainObjectSelection = null; _explainObjectBuffer = '';
|
|
4071
4087
|
var panel = document.getElementById('inspector-panel');
|
|
4072
4088
|
panel.dataset.objectId = objectId;
|
|
4089
|
+
panel.dataset.exactObjectId = (typeof objectId === 'string' && /^[1-9][0-9]{0,19}$/.test(objectId) || typeof objectId === 'number' && Number.isSafeInteger(objectId) && objectId > 0) ? String(objectId) : '';
|
|
4073
4090
|
panel.dataset.className = className;
|
|
4074
4091
|
panel.dataset.shallowSize = shallowSize;
|
|
4075
4092
|
panel.dataset.retainedSize = retainedSize;
|
|
@@ -4082,6 +4099,8 @@ document.addEventListener('click',function(e){
|
|
|
4082
4099
|
}
|
|
4083
4100
|
|
|
4084
4101
|
function closeInspector() {
|
|
4102
|
+
if (_explainObjectSelection !== null) vscode.postMessage({command:'stopAi'});
|
|
4103
|
+
_explainObjectSelection = null; _explainObjectBuffer = '';
|
|
4085
4104
|
var panel = document.getElementById('inspector-panel');
|
|
4086
4105
|
panel.classList.remove('visible');
|
|
4087
4106
|
panel.innerHTML = '';
|
|
@@ -4090,6 +4109,7 @@ document.addEventListener('click',function(e){
|
|
|
4090
4109
|
function renderInspectorFields(objectId, fields) {
|
|
4091
4110
|
var panel = document.getElementById('inspector-panel');
|
|
4092
4111
|
if (!panel.classList.contains('visible')) return;
|
|
4112
|
+
if (String(objectId) !== panel.dataset.objectId) return;
|
|
4093
4113
|
|
|
4094
4114
|
var body = panel.querySelector('.inspector-loading');
|
|
4095
4115
|
if (!body) return;
|
|
@@ -4147,13 +4167,12 @@ document.addEventListener('click',function(e){
|
|
|
4147
4167
|
explainArea.textContent = '';
|
|
4148
4168
|
|
|
4149
4169
|
var panel = document.getElementById('inspector-panel');
|
|
4170
|
+
if (!panel.dataset.exactObjectId) { explainArea.textContent = 'Object identity is not exact. This object cannot be sent to AI.'; explainBtn.disabled = false; return; }
|
|
4171
|
+
_explainObjectSelection = panel.dataset.exactObjectId;
|
|
4150
4172
|
vscode.postMessage({
|
|
4151
4173
|
command: 'explainObject',
|
|
4152
|
-
objectId:
|
|
4153
|
-
className: panel.dataset.className
|
|
4154
|
-
shallowSize: parseInt(panel.dataset.shallowSize),
|
|
4155
|
-
retainedSize: parseInt(panel.dataset.retainedSize),
|
|
4156
|
-
fields: fields
|
|
4174
|
+
objectId: panel.dataset.exactObjectId,
|
|
4175
|
+
className: panel.dataset.className
|
|
4157
4176
|
});
|
|
4158
4177
|
});
|
|
4159
4178
|
}
|
|
@@ -4175,6 +4194,8 @@ document.addEventListener('click',function(e){
|
|
|
4175
4194
|
});
|
|
4176
4195
|
|
|
4177
4196
|
onMessage('explainChunk', function(msg) {
|
|
4197
|
+
var panel = document.getElementById('inspector-panel');
|
|
4198
|
+
if (!_explainObjectSelection || String(msg.objectId) !== _explainObjectSelection || panel.dataset.objectId !== _explainObjectSelection) return;
|
|
4178
4199
|
_explainObjectBuffer += msg.text;
|
|
4179
4200
|
var area = document.getElementById('inspector-explain-area');
|
|
4180
4201
|
if (area) {
|
|
@@ -4183,21 +4204,30 @@ document.addEventListener('click',function(e){
|
|
|
4183
4204
|
}
|
|
4184
4205
|
});
|
|
4185
4206
|
|
|
4186
|
-
onMessage('explainDone', function() {
|
|
4207
|
+
onMessage('explainDone', function(msg) {
|
|
4208
|
+
var panel = document.getElementById('inspector-panel');
|
|
4209
|
+
if (!_explainObjectSelection || String(msg.objectId) !== _explainObjectSelection || panel.dataset.objectId !== _explainObjectSelection) return;
|
|
4187
4210
|
var area = document.getElementById('inspector-explain-area');
|
|
4188
4211
|
if (area) {
|
|
4189
4212
|
area.classList.remove('streaming');
|
|
4190
4213
|
area.classList.add('rendered');
|
|
4191
|
-
|
|
4214
|
+
if (msg.safeEvidenceAnswer) {
|
|
4215
|
+
area.innerHTML = renderMarkdown(_explainObjectBuffer); // Escapes HTML; never executes model output.
|
|
4216
|
+
attachInvestigationEvidence(area, msg.evidence);
|
|
4217
|
+
} else { area.innerHTML = renderMarkdown(_explainObjectBuffer); }
|
|
4192
4218
|
area.scrollTop = 0;
|
|
4193
4219
|
}
|
|
4194
4220
|
_explainObjectBuffer = '';
|
|
4221
|
+
_explainObjectSelection = null;
|
|
4195
4222
|
var btn = document.getElementById('inspector-explain-btn');
|
|
4196
4223
|
if (btn) { btn.textContent = 'Explain this object'; btn.disabled = false; }
|
|
4197
4224
|
});
|
|
4198
4225
|
|
|
4199
4226
|
onMessage('explainError', function(msg) {
|
|
4227
|
+
var panel = document.getElementById('inspector-panel');
|
|
4228
|
+
if (!_explainObjectSelection || String(msg.objectId) !== _explainObjectSelection || panel.dataset.objectId !== _explainObjectSelection) return;
|
|
4200
4229
|
_explainObjectBuffer = '';
|
|
4230
|
+
_explainObjectSelection = null;
|
|
4201
4231
|
var area = document.getElementById('inspector-explain-area');
|
|
4202
4232
|
if (area) {
|
|
4203
4233
|
area.classList.remove('streaming');
|
|
@@ -4742,7 +4772,9 @@ const onTabMessage = (tab, command, handler) => listenTab(tab, command, message
|
|
|
4742
4772
|
row.setAttribute('tabindex', '-1');
|
|
4743
4773
|
if (!leaf) row.setAttribute('aria-expanded', 'false');
|
|
4744
4774
|
|
|
4745
|
-
var pct = _reachableHeap !== null
|
|
4775
|
+
var pct = _reachableHeap !== null && Number.isFinite(obj.retained_size) && obj.retained_size >= 0
|
|
4776
|
+
? ((obj.retained_size / _reachableHeap) * 100) : null;
|
|
4777
|
+
if (!Number.isFinite(pct)) pct = null;
|
|
4746
4778
|
var pctStr = pct === null ? 'N/A' : pct.toFixed(1) + '%';
|
|
4747
4779
|
var barWidth = pct === null ? 0 : Math.max(1, Math.min(100, pct));
|
|
4748
4780
|
var displayName = obj.class_name || obj.node_type;
|
|
@@ -4763,7 +4795,7 @@ const onTabMessage = (tab, command, handler) => listenTab(tab, command, message
|
|
|
4763
4795
|
'<span class="tree-shallow">' + fmt(obj.shallow_size) + '</span>' +
|
|
4764
4796
|
'<span class="tree-size">' + fmt(obj.retained_size) + '</span>' +
|
|
4765
4797
|
'<span class="tree-bar-wrap"><div class="tree-bar" style="width:' + barWidth + '%"></div></span>' +
|
|
4766
|
-
'<span class="tree-pct">' + pctStr + '</span>' +
|
|
4798
|
+
'<span class="tree-pct" title="Retained size as a percentage of reachable heap. Nested entries can overlap.">' + pctStr + '</span>' +
|
|
4767
4799
|
'<span class="tree-actions">' +
|
|
4768
4800
|
'<span class="tree-action-slot tree-action-alive">' + (showPin ? '<button class="why-alive-btn" title="Show GC root path">Why alive?</button>' : '') + '</span>' +
|
|
4769
4801
|
'<span class="tree-action-slot tree-action-icon">' + (showInspect ? '<span class="tree-inspect" role="button" aria-label="Inspect fields for ' + escapeHtml(displayName) + '" title="Inspect fields">\uD83D\uDD0D</span>' : '') + '</span>' +
|
|
@@ -4870,6 +4902,16 @@ const onTabMessage = (tab, command, handler) => listenTab(tab, command, message
|
|
|
4870
4902
|
}
|
|
4871
4903
|
|
|
4872
4904
|
// ---- Self-register ----
|
|
4905
|
+
function retireDominatorPercentage() {
|
|
4906
|
+
_reachableHeap = null;
|
|
4907
|
+
_treeData = [];
|
|
4908
|
+
document.getElementById('dominator-tree').innerHTML = '';
|
|
4909
|
+
document.getElementById('domtree-header').style.display = 'none';
|
|
4910
|
+
document.getElementById('reset-tree-btn').style.display = 'none';
|
|
4911
|
+
}
|
|
4912
|
+
['analysisStarting', 'analysisCancelled', 'analysisFailed', 'serverCrashed'].forEach(function(command) {
|
|
4913
|
+
onMessage(command, retireDominatorPercentage);
|
|
4914
|
+
});
|
|
4873
4915
|
onTabMessage('domtree', 'analysisComplete', function(msg) {
|
|
4874
4916
|
renderDominatorTree(msg.topLayers || [], msg.summary && msg.summary.reachable_heap_size);
|
|
4875
4917
|
});
|
|
@@ -5426,7 +5468,7 @@ const onMessage = (command, handler) => {
|
|
|
5426
5468
|
handler(message);
|
|
5427
5469
|
aiStatus.textContent = command === "chatChunk" ? "Receiving AI response…" : command === "chatDone" ? "Response complete. Verify suggestions before acting." : (message.message || "AI request failed.");
|
|
5428
5470
|
if (command !== "chatChunk") finishAi();
|
|
5429
|
-
}); else if (command === "restoreChatHistory") listen("aiHistory", handler);
|
|
5471
|
+
}); else if (command === "restoreChatHistory") listen("aiHistory", handler); else listen(command, handler);
|
|
5430
5472
|
};
|
|
5431
5473
|
|
|
5432
5474
|
// ---- Tab 9: AI Chat ----
|
|
@@ -5440,6 +5482,105 @@ const onMessage = (command, handler) => {
|
|
|
5440
5482
|
var _isChatStreaming = false;
|
|
5441
5483
|
var _chatStreamBuffer = '';
|
|
5442
5484
|
var _pendingChatQuery = null; // { bubble, codeBlock } when waiting for query result
|
|
5485
|
+
var _chatQuerySequence = 0;
|
|
5486
|
+
var _chatQueryEpoch = 0;
|
|
5487
|
+
var _chatQueryReady = false;
|
|
5488
|
+
var chatQuerySegments = function chatQuerySegments(answer) {
|
|
5489
|
+
if (answer.length > 65536)
|
|
5490
|
+
return [{ text: answer }];
|
|
5491
|
+
const text = answer;
|
|
5492
|
+
const found = [];
|
|
5493
|
+
const fences = [];
|
|
5494
|
+
const candidate = (raw) => {
|
|
5495
|
+
const query = raw.trim();
|
|
5496
|
+
if (!query || query.length > 32000)
|
|
5497
|
+
return;
|
|
5498
|
+
if (/^:(?:info|path|refs|children)\s+(?:0x[0-9a-f]+|[0-9]+)\s*$/i.test(query))
|
|
5499
|
+
return query;
|
|
5500
|
+
if (!/^SELECT\b/i.test(query))
|
|
5501
|
+
return;
|
|
5502
|
+
// Ignore quoted literals when recognizing table names and statement boundaries.
|
|
5503
|
+
let masked = '', quote = '';
|
|
5504
|
+
for (let i = 0; i < query.length; i++) {
|
|
5505
|
+
const ch = query[i];
|
|
5506
|
+
if (quote) {
|
|
5507
|
+
if (ch === '\\') {
|
|
5508
|
+
i++;
|
|
5509
|
+
masked += ' ';
|
|
5510
|
+
continue;
|
|
5511
|
+
}
|
|
5512
|
+
if (ch === quote) {
|
|
5513
|
+
if (query[i + 1] === quote) {
|
|
5514
|
+
i++;
|
|
5515
|
+
masked += ' ';
|
|
5516
|
+
continue;
|
|
5517
|
+
}
|
|
5518
|
+
quote = '';
|
|
5519
|
+
}
|
|
5520
|
+
masked += ' ';
|
|
5521
|
+
}
|
|
5522
|
+
else if (ch === "'" || ch === '"') {
|
|
5523
|
+
quote = ch;
|
|
5524
|
+
masked += ' ';
|
|
5525
|
+
}
|
|
5526
|
+
else
|
|
5527
|
+
masked += ch;
|
|
5528
|
+
}
|
|
5529
|
+
if (quote || /--|\/\*|\*\/|\b(?:INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|TRUNCATE|ATTACH|PRAGMA|EXEC|CALL|INTO)\b/i.test(masked))
|
|
5530
|
+
return;
|
|
5531
|
+
if (masked.replace(/;\s*$/, '').includes(';'))
|
|
5532
|
+
return;
|
|
5533
|
+
const tables = [...masked.matchAll(/\bFROM\s+([A-Za-z_][A-Za-z0-9_]*)/gi)].map(m => m[1].toLowerCase());
|
|
5534
|
+
if (!tables.length || tables.some(t => !['instances', 'class_histogram', 'dominator_tree', 'leak_suspects'].includes(t)))
|
|
5535
|
+
return;
|
|
5536
|
+
// SQL-style terminal delimiters are presentation, not supported HeapQL tokens.
|
|
5537
|
+
return query.endsWith(';') ? query.slice(0, -1).trimEnd() : query;
|
|
5538
|
+
};
|
|
5539
|
+
const add = (start, end, raw) => { const query = candidate(raw); if (query && found.length < 16)
|
|
5540
|
+
found.push({ start, end, query }); };
|
|
5541
|
+
// Exclude every fenced language from prose scanning, including shell/Java/JavaScript.
|
|
5542
|
+
const fence = /^ {0,3}(`{3,}|~{3,})([A-Za-z0-9_-]*)[ \t]*\r?\n([\s\S]*?)^ {0,3}\1[ \t]*\r?(?=\n|$)/gm;
|
|
5543
|
+
for (const match of text.matchAll(fence)) {
|
|
5544
|
+
const start = match.index, end = start + match[0].length;
|
|
5545
|
+
fences.push({ start, end });
|
|
5546
|
+
if (/^(?:heapql|sql)?$/i.test(match[2]))
|
|
5547
|
+
add(start, end, match[3]);
|
|
5548
|
+
}
|
|
5549
|
+
for (const match of text.matchAll(/^ {0,3}(?:`{3,}|~{3,})[^\n]*$/gm)) {
|
|
5550
|
+
if (!fences.some(span => match.index >= span.start && match.index < span.end)) {
|
|
5551
|
+
fences.push({ start: match.index, end: text.length });
|
|
5552
|
+
break;
|
|
5553
|
+
}
|
|
5554
|
+
}
|
|
5555
|
+
fences.sort((a, b) => a.start - b.start);
|
|
5556
|
+
const scan = (start, end) => {
|
|
5557
|
+
const chunk = text.slice(start, end);
|
|
5558
|
+
for (const match of chunk.matchAll(/`([^`\n]+)`/g))
|
|
5559
|
+
add(start + match.index, start + match.index + match[0].length, match[1]);
|
|
5560
|
+
for (const match of chunk.matchAll(/[^\n]+(?:\n(?![ \t\r]*\n)[^\n]+)*/g))
|
|
5561
|
+
add(start + match.index, start + match.index + match[0].length, match[0]);
|
|
5562
|
+
};
|
|
5563
|
+
let offset = 0;
|
|
5564
|
+
for (const span of fences) {
|
|
5565
|
+
scan(offset, span.start);
|
|
5566
|
+
offset = span.end;
|
|
5567
|
+
}
|
|
5568
|
+
scan(offset, text.length);
|
|
5569
|
+
found.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
5570
|
+
const segments = [];
|
|
5571
|
+
offset = 0;
|
|
5572
|
+
for (const span of found) {
|
|
5573
|
+
if (span.start < offset)
|
|
5574
|
+
continue;
|
|
5575
|
+
if (span.start > offset)
|
|
5576
|
+
segments.push({ text: text.slice(offset, span.start) });
|
|
5577
|
+
segments.push({ text: text.slice(span.start, span.end), query: span.query });
|
|
5578
|
+
offset = span.end;
|
|
5579
|
+
}
|
|
5580
|
+
if (offset < text.length || !segments.length)
|
|
5581
|
+
segments.push({ text: text.slice(offset) });
|
|
5582
|
+
return segments;
|
|
5583
|
+
};
|
|
5443
5584
|
|
|
5444
5585
|
function addChatBubble(role, text) {
|
|
5445
5586
|
if (_chatPlaceholder) _chatPlaceholder.style.display = 'none';
|
|
@@ -5482,30 +5623,49 @@ const onMessage = (command, handler) => {
|
|
|
5482
5623
|
}
|
|
5483
5624
|
});
|
|
5484
5625
|
|
|
5485
|
-
function
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5626
|
+
function updateChatQueryButtons() {
|
|
5627
|
+
_chatMessages.querySelectorAll('.chat-run-query-btn').forEach(function(button) {
|
|
5628
|
+
button.disabled = !_chatQueryReady || !!_pendingChatQuery || button.dataset.expired === 'true';
|
|
5629
|
+
});
|
|
5630
|
+
}
|
|
5631
|
+
function renderChatAnswer(bubble, text) {
|
|
5632
|
+
bubble.textContent = '';
|
|
5633
|
+
chatQuerySegments(text).forEach(function(segment) {
|
|
5634
|
+
if (!segment.query) { var prose=document.createElement('div');prose.innerHTML=renderMarkdown(segment.text);bubble.appendChild(prose);return; }
|
|
5635
|
+
var query=segment.query;
|
|
5636
|
+
var pre=document.createElement('pre');pre.className='md-code-block';
|
|
5637
|
+
var code=document.createElement('code');code.className='language-heapql';code.textContent=query;pre.appendChild(code);bubble.appendChild(pre);
|
|
5492
5638
|
var btn = document.createElement('button');
|
|
5493
5639
|
btn.className = 'chat-run-query-btn';
|
|
5494
5640
|
btn.textContent = 'Run Query';
|
|
5641
|
+
var epoch=_chatQueryEpoch;
|
|
5495
5642
|
btn.addEventListener('click', function() {
|
|
5643
|
+
if (!_chatQueryReady || epoch!==_chatQueryEpoch || _pendingChatQuery || !bubble.isConnected) return;
|
|
5496
5644
|
btn.disabled = true;
|
|
5497
5645
|
btn.textContent = 'Running...';
|
|
5498
|
-
|
|
5499
|
-
|
|
5646
|
+
var requestId='chat-query-'+(++_chatQuerySequence);
|
|
5647
|
+
_pendingChatQuery = { bubble: bubble, codeBlock: pre, button: btn, query:query, requestId:requestId };
|
|
5648
|
+
updateChatQueryButtons();
|
|
5649
|
+
vscode.postMessage({ command: 'executeQuery', query: query, requestId:requestId });
|
|
5500
5650
|
});
|
|
5501
|
-
|
|
5651
|
+
bubble.appendChild(btn);
|
|
5502
5652
|
});
|
|
5653
|
+
bubble.classList.add('rendered');
|
|
5654
|
+
updateChatQueryButtons();
|
|
5503
5655
|
}
|
|
5504
5656
|
|
|
5505
|
-
function
|
|
5657
|
+
function retireChatQueries() {
|
|
5658
|
+
_chatQueryReady=false;_chatQueryEpoch++;_pendingChatQuery=null;
|
|
5659
|
+
_chatMessages.querySelectorAll('.chat-run-query-btn').forEach(function(button){button.dataset.expired='true';button.disabled=true;});
|
|
5660
|
+
}
|
|
5661
|
+
['analysisStarting','analysisRetrying','serverCrashed','analysisCancelled'].forEach(function(command){onMessage(command,retireChatQueries);});
|
|
5662
|
+
onMessage('analysisProgress',function(msg){if(msg.stage==='loading')retireChatQueries();});
|
|
5663
|
+
onMessage('analysisComplete',function(){_chatQueryReady=true;updateChatQueryButtons();});
|
|
5664
|
+
|
|
5665
|
+
function renderChatQueryResult(pre, btn, result, query) {
|
|
5506
5666
|
// Remove any existing result for this block
|
|
5507
|
-
var existing =
|
|
5508
|
-
if (existing) existing.remove();
|
|
5667
|
+
var existing = btn.nextElementSibling;
|
|
5668
|
+
if (existing && existing.classList.contains('chat-query-result')) existing.remove();
|
|
5509
5669
|
|
|
5510
5670
|
var container = document.createElement('div');
|
|
5511
5671
|
container.className = 'chat-query-result';
|
|
@@ -5545,7 +5705,7 @@ const onMessage = (command, handler) => {
|
|
|
5545
5705
|
}
|
|
5546
5706
|
|
|
5547
5707
|
var timeMs = result.execution_time_ms !== undefined ? result.execution_time_ms : '?';
|
|
5548
|
-
html += '<div class="chat-query-status">' + rows.length + ' rows (' + timeMs + 'ms)</div>';
|
|
5708
|
+
html += '<div class="chat-query-status">' + rows.length + ' rows (' + escapeHtml(String(timeMs)) + 'ms)</div>';
|
|
5549
5709
|
|
|
5550
5710
|
container.innerHTML = html;
|
|
5551
5711
|
btn.insertAdjacentElement('afterend', container);
|
|
@@ -5558,17 +5718,85 @@ const onMessage = (command, handler) => {
|
|
|
5558
5718
|
link.addEventListener('click', function() {
|
|
5559
5719
|
var queryTab = document.querySelector('.tab-btn[data-tab="query"]');
|
|
5560
5720
|
if (queryTab) queryTab.click();
|
|
5721
|
+
if (typeof query==='string') {
|
|
5722
|
+
var input=document.getElementById('query-input');var run=document.getElementById('query-run-btn');
|
|
5723
|
+
if(input && run){input.value=query;input.dispatchEvent(new Event('input'));run.click();}
|
|
5724
|
+
}
|
|
5561
5725
|
});
|
|
5562
5726
|
}
|
|
5563
5727
|
|
|
5564
5728
|
_chatMessages.scrollTop = _chatMessages.scrollHeight;
|
|
5565
5729
|
}
|
|
5566
5730
|
|
|
5567
|
-
|
|
5731
|
+
function attachInvestigationEvidence(area, evidence) {
|
|
5732
|
+
var answerText = area.textContent || '';
|
|
5733
|
+
var seen = new Set();
|
|
5734
|
+
var cited = (Array.isArray(evidence) ? evidence : []).slice(0, 32).filter(function(record) {
|
|
5735
|
+
if (!record || typeof record.id !== 'string' || !/^E[a-f0-9]{12}_[1-9][0-9]{0,3}$/.test(record.id)
|
|
5736
|
+
|| answerText.indexOf('[' + record.id + ']') < 0 || seen.has(record.id)) return false;
|
|
5737
|
+
seen.add(record.id); return true;
|
|
5738
|
+
});
|
|
5739
|
+
if (cited.length) {
|
|
5740
|
+
var note = document.createElement('small');
|
|
5741
|
+
note.style.display = 'block'; note.style.marginTop = '8px';
|
|
5742
|
+
note.textContent = 'AI interpretation may be wrong. Evidence below is engine metadata, not proof of a cause or repair.';
|
|
5743
|
+
area.appendChild(note);
|
|
5744
|
+
}
|
|
5745
|
+
cited.forEach(function(record) {
|
|
5746
|
+
var button = document.createElement('button');
|
|
5747
|
+
button.className = 'chat-clear';
|
|
5748
|
+
button.textContent = 'View evidence: ' + record.operation;
|
|
5749
|
+
button.addEventListener('click', function() { vscode.postMessage({command:'openAiEvidence', id:record.id}); });
|
|
5750
|
+
area.appendChild(button);
|
|
5751
|
+
});
|
|
5752
|
+
}
|
|
5753
|
+
// Native/browser hosts scope chat in an IIFE; object views share only this safe renderer.
|
|
5754
|
+
window.attachInvestigationEvidence = attachInvestigationEvidence;
|
|
5755
|
+
function retireInvestigationViewer() {
|
|
5756
|
+
var viewer=document.getElementById('heaplens-ai-evidence');
|
|
5757
|
+
if(viewer){if(viewer.open)viewer.close();viewer.remove();}
|
|
5758
|
+
}
|
|
5759
|
+
['analysisStarting','analysisRetrying','serverCrashed','analysisCancelled','aiReset'].forEach(function(command){onMessage(command,retireInvestigationViewer);});
|
|
5760
|
+
onMessage('analysisProgress',function(msg){if(msg.stage==='loading')retireInvestigationViewer();});
|
|
5761
|
+
|
|
5762
|
+
onMessage('aiEvidenceResult', function(msg) {
|
|
5763
|
+
var viewer = document.getElementById('heaplens-ai-evidence');
|
|
5764
|
+
if (!viewer) { viewer = document.createElement('dialog'); viewer.id = 'heaplens-ai-evidence'; document.body.appendChild(viewer); }
|
|
5765
|
+
viewer.textContent = '';
|
|
5766
|
+
var close = document.createElement('button'); close.textContent = 'Close evidence'; close.addEventListener('click',function(){viewer.close();});viewer.appendChild(close);
|
|
5767
|
+
var content = document.createElement('pre'); content.style.whiteSpace = 'pre-wrap';
|
|
5768
|
+
content.textContent = msg.error || (msg.record ? 'Evidence ' + msg.record.id + ' — ' + msg.record.operation + '\n' + JSON.stringify(msg.record.data, null, 2) : 'Evidence unavailable.');
|
|
5769
|
+
viewer.appendChild(content);if (!viewer.open) viewer.showModal();
|
|
5770
|
+
});
|
|
5771
|
+
|
|
5772
|
+
onMessage('aiInvestigationProgress', function(msg) {
|
|
5773
|
+
var areas = [];
|
|
5774
|
+
if (msg.channel === 'chat' && _currentBubble) areas.push(_currentBubble);
|
|
5775
|
+
if (msg.channel === 'explain') { var inspector = document.getElementById('inspector-explain-area'); var panel = document.getElementById('inspector-panel'); if (inspector && panel && panel.dataset.objectId === String(msg.objectId)) areas.push(inspector); }
|
|
5776
|
+
if (msg.channel === 'explainLeak' && typeof msg.className === 'string') {
|
|
5777
|
+
var target = msg.objectId ? 'explain-obj-' + leakEvidenceKey(msg.className,msg.objectId) : 'explain-' + leakEvidenceKey(msg.className);
|
|
5778
|
+
var targetArea = document.getElementById(target); if(targetArea)areas.push(targetArea);
|
|
5779
|
+
}
|
|
5780
|
+
areas.forEach(function(area) {
|
|
5781
|
+
area.textContent = msg.message || 'Investigating…';
|
|
5782
|
+
if (msg.channel !== 'chat') {
|
|
5783
|
+
var stop = document.createElement('button'); stop.textContent = 'Stop investigation';
|
|
5784
|
+
stop.addEventListener('click', function() { vscode.postMessage({command:'stopAi',channel:msg.channel}); }); area.appendChild(stop);
|
|
5785
|
+
}
|
|
5786
|
+
});
|
|
5787
|
+
});
|
|
5788
|
+
|
|
5789
|
+
var _chatStop = document.getElementById('chat-stop');
|
|
5790
|
+
if (_chatStop) _chatStop.addEventListener('click', function() { vscode.postMessage({command:'stopAi'}); });
|
|
5791
|
+
|
|
5792
|
+
onMessage('chatDone', function(msg) {
|
|
5568
5793
|
if (_currentBubble && _chatStreamBuffer) {
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5794
|
+
if (msg.safeEvidenceAnswer) {
|
|
5795
|
+
renderChatAnswer(_currentBubble, _chatStreamBuffer);
|
|
5796
|
+
attachInvestigationEvidence(_currentBubble, msg.evidence);
|
|
5797
|
+
} else {
|
|
5798
|
+
renderChatAnswer(_currentBubble, _chatStreamBuffer);
|
|
5799
|
+
}
|
|
5572
5800
|
}
|
|
5573
5801
|
_isChatStreaming = false;
|
|
5574
5802
|
_chatSend.disabled = false;
|
|
@@ -5577,6 +5805,10 @@ const onMessage = (command, handler) => {
|
|
|
5577
5805
|
});
|
|
5578
5806
|
|
|
5579
5807
|
onMessage('chatError', function(msg) {
|
|
5808
|
+
if (_currentBubble && _chatStreamBuffer) {
|
|
5809
|
+
_currentBubble.textContent = 'Incomplete AI answer — not a completed diagnosis.\n\n' + _chatStreamBuffer;
|
|
5810
|
+
_currentBubble.classList.add('error');
|
|
5811
|
+
}
|
|
5580
5812
|
_isChatStreaming = false;
|
|
5581
5813
|
_chatSend.disabled = false;
|
|
5582
5814
|
_currentBubble = null;
|
|
@@ -5584,27 +5816,37 @@ const onMessage = (command, handler) => {
|
|
|
5584
5816
|
addChatBubble('error', msg.message || 'An error occurred');
|
|
5585
5817
|
});
|
|
5586
5818
|
|
|
5587
|
-
onMessage('
|
|
5588
|
-
if (_pendingChatQuery) {
|
|
5589
|
-
renderChatQueryResult(_pendingChatQuery.codeBlock, _pendingChatQuery.button, msg.result);
|
|
5819
|
+
onMessage('chatQueryResult', function(msg) {
|
|
5820
|
+
if (_pendingChatQuery && msg.requestId===_pendingChatQuery.requestId) {
|
|
5821
|
+
renderChatQueryResult(_pendingChatQuery.codeBlock, _pendingChatQuery.button, msg.result, _pendingChatQuery.query);
|
|
5590
5822
|
_pendingChatQuery = null;
|
|
5823
|
+
updateChatQueryButtons();
|
|
5591
5824
|
return true;
|
|
5592
5825
|
}
|
|
5593
5826
|
return false;
|
|
5594
5827
|
});
|
|
5595
5828
|
|
|
5829
|
+
onMessage('chatQueryError', function(msg) {
|
|
5830
|
+
if (!_pendingChatQuery || msg.requestId!==_pendingChatQuery.requestId) return false;
|
|
5831
|
+
var pending=_pendingChatQuery;_pendingChatQuery=null;
|
|
5832
|
+
pending.button.textContent='Run Query';
|
|
5833
|
+
var previous=pending.button.nextElementSibling;if(previous && previous.classList.contains('chat-query-result'))previous.remove();
|
|
5834
|
+
var error=document.createElement('div');error.className='chat-query-result error';error.textContent=msg.error || 'Query failed. Check its syntax and retry.';
|
|
5835
|
+
pending.button.insertAdjacentElement('afterend',error);updateChatQueryButtons();return true;
|
|
5836
|
+
});
|
|
5837
|
+
|
|
5596
5838
|
onMessage('restoreChatHistory', function(msg) {
|
|
5597
5839
|
var messages = msg.messages || [];
|
|
5598
5840
|
if (messages.length === 0) return;
|
|
5599
5841
|
if (_chatPlaceholder) _chatPlaceholder.style.display = 'none';
|
|
5842
|
+
addChatBubble('system', 'Previous local conversation. Its evidence links have expired and are not reused by this analysis.');
|
|
5600
5843
|
messages.forEach(function(m) {
|
|
5601
5844
|
if (m.role === 'user') {
|
|
5602
5845
|
addChatBubble('user', m.content);
|
|
5603
5846
|
} else if (m.role === 'assistant') {
|
|
5604
5847
|
var bubble = addChatBubble('assistant', '');
|
|
5605
|
-
bubble.
|
|
5606
|
-
bubble.
|
|
5607
|
-
attachHeapqlButtons(bubble);
|
|
5848
|
+
bubble.textContent = m.content;
|
|
5849
|
+
bubble.style.whiteSpace = 'pre-wrap';
|
|
5608
5850
|
}
|
|
5609
5851
|
});
|
|
5610
5852
|
});
|
|
@@ -5612,6 +5854,11 @@ const onMessage = (command, handler) => {
|
|
|
5612
5854
|
var _chatClear = document.getElementById('chat-clear');
|
|
5613
5855
|
if (_chatClear) {
|
|
5614
5856
|
_chatClear.addEventListener('click', function() {
|
|
5857
|
+
_isChatStreaming = false;
|
|
5858
|
+
_chatSend.disabled = false;
|
|
5859
|
+
_currentBubble = null;
|
|
5860
|
+
_chatStreamBuffer = '';
|
|
5861
|
+
_pendingChatQuery = null;
|
|
5615
5862
|
_chatMessages.innerHTML = '';
|
|
5616
5863
|
if (_chatPlaceholder) {
|
|
5617
5864
|
_chatPlaceholder.style.display = 'block';
|
|
@@ -5639,6 +5886,8 @@ function aiControls() {
|
|
|
5639
5886
|
function acceptsAi(message) {return aiReady && aiPending && message.requestId===aiPending;}
|
|
5640
5887
|
function finishAi() {aiPending=null;_isChatStreaming=false;aiControls();}
|
|
5641
5888
|
function requestAi(message) {
|
|
5889
|
+
if(message.command==='openAiEvidence') {send(message);return;}
|
|
5890
|
+
if(message.command==='stopAi') {send({command:message.channel && message.channel!=='chat'?'cancelAiAssistance':'aiStop'});return;}
|
|
5642
5891
|
if(message.command==='executeQuery') {
|
|
5643
5892
|
if(!aiReady || chatQueryId || typeof message.query!=='string' || message.query.length>32000) {
|
|
5644
5893
|
if(_pendingChatQuery) {_pendingChatQuery.button.disabled=false;_pendingChatQuery.button.textContent='Run Query';_pendingChatQuery=chatQueryContext;}
|
|
@@ -5679,13 +5928,7 @@ listen('aiConfiguration',function(message) {
|
|
|
5679
5928
|
listen('aiReset',function(message) {clearAiUi(message.message || 'Chat cleared.');});
|
|
5680
5929
|
listen('aiQueryResult',function(message){
|
|
5681
5930
|
if(!aiReady || message.requestId!==chatQueryId || !_pendingChatQuery)return;
|
|
5682
|
-
renderChatQueryResult(_pendingChatQuery.codeBlock,_pendingChatQuery.button,message.result);
|
|
5683
|
-
var link=_pendingChatQuery.codeBlock.parentElement.querySelector('.chat-query-link');
|
|
5684
|
-
if(link)link.addEventListener('click',function(){
|
|
5685
|
-
document.getElementById('query-input').value=message.query;
|
|
5686
|
-
document.getElementById('query-input').dispatchEvent(new Event('input'));
|
|
5687
|
-
document.getElementById('query-run-btn').click();
|
|
5688
|
-
});
|
|
5931
|
+
renderChatQueryResult(_pendingChatQuery.codeBlock,_pendingChatQuery.button,message.result,message.query);
|
|
5689
5932
|
chatQueryId=null;chatQueryContext=null;_pendingChatQuery=null;
|
|
5690
5933
|
_chatMessages.querySelectorAll('.chat-run-query-btn').forEach(function(button){button.disabled=false;});
|
|
5691
5934
|
});
|
|
@@ -5713,6 +5956,12 @@ const vscode={postMessage:send};const depInfoCache=Object.create(null);const isR
|
|
|
5713
5956
|
// Self-contained: owns explain-leak streaming buffers, rendering, pagination, threshold filtering.
|
|
5714
5957
|
|
|
5715
5958
|
var _explainLeakBuffers = {};
|
|
5959
|
+
function leakEvidenceKey(className, objectId) {
|
|
5960
|
+
var text = JSON.stringify([String(className), objectId === undefined ? '' : String(objectId)]);
|
|
5961
|
+
var key = ''; for(var i=0;i<text.length;i++)key += text.charCodeAt(i).toString(16).padStart(4,'0');
|
|
5962
|
+
return key;
|
|
5963
|
+
}
|
|
5964
|
+
window.leakEvidenceKey = leakEvidenceKey;
|
|
5716
5965
|
var _leakPage = 0;
|
|
5717
5966
|
var LEAK_PAGE_SIZE = 10;
|
|
5718
5967
|
var _leakSuspectsData = [];
|
|
@@ -5800,7 +6049,7 @@ const vscode={postMessage:send};const depInfoCache=Object.create(null);const isR
|
|
|
5800
6049
|
var inspectLink = s.object_id
|
|
5801
6050
|
? ' <button class="why-alive-btn inspect-obj-link" data-object-id="' + s.object_id + '" data-class-name="' + escapeHtml(s.class_name) + '" data-shallow="' + (s.shallow_size || 0) + '" data-retained="' + s.retained_size + '">Inspect</button>'
|
|
5802
6051
|
: '';
|
|
5803
|
-
var sanitizedId = (s.class_name
|
|
6052
|
+
var sanitizedId = leakEvidenceKey(s.class_name,s.object_id);
|
|
5804
6053
|
var explainLink = ' | <a class="suspect-explain-link" data-class="' + escapeHtml(s.class_name) +
|
|
5805
6054
|
'" data-object-id="' + (s.object_id || '') +
|
|
5806
6055
|
'" data-retained="' + s.retained_size +
|
|
@@ -5899,7 +6148,7 @@ const vscode={postMessage:send};const depInfoCache=Object.create(null);const isR
|
|
|
5899
6148
|
: '';
|
|
5900
6149
|
var cachedDep = depInfoCache[s.class_name];
|
|
5901
6150
|
var depBadge = cachedDep ? makeBadgeHtml(cachedDep.tier, cachedDep.dependency) : '';
|
|
5902
|
-
var sanitizedId = s.class_name
|
|
6151
|
+
var sanitizedId = leakEvidenceKey(s.class_name);
|
|
5903
6152
|
var explainLink = ' | <a class="suspect-explain-link" data-class="' + escapeHtml(s.class_name) +
|
|
5904
6153
|
'" data-retained="' + s.retained_size +
|
|
5905
6154
|
'" data-pct="' + s.retained_percentage +
|
|
@@ -5984,7 +6233,7 @@ const vscode={postMessage:send};const depInfoCache=Object.create(null);const isR
|
|
|
5984
6233
|
description: link.dataset.desc
|
|
5985
6234
|
};
|
|
5986
6235
|
if (link.dataset.objectId) {
|
|
5987
|
-
msgPayload.objectId =
|
|
6236
|
+
msgPayload.objectId = link.dataset.objectId;
|
|
5988
6237
|
}
|
|
5989
6238
|
vscode.postMessage(msgPayload);
|
|
5990
6239
|
});
|
|
@@ -6024,16 +6273,16 @@ const vscode={postMessage:send};const depInfoCache=Object.create(null);const isR
|
|
|
6024
6273
|
});
|
|
6025
6274
|
|
|
6026
6275
|
onMessage('explainLeakChunk', function(msg) {
|
|
6027
|
-
var classSanitized = msg.className
|
|
6276
|
+
var classSanitized = leakEvidenceKey(msg.className);
|
|
6028
6277
|
// For object-level suspects, the explain area ID includes the objectId
|
|
6029
|
-
var objSanitized = msg.objectId ? (msg.className
|
|
6278
|
+
var objSanitized = msg.objectId ? leakEvidenceKey(msg.className,msg.objectId) : classSanitized;
|
|
6030
6279
|
var bufferKey = msg.objectId ? objSanitized : classSanitized;
|
|
6031
6280
|
if (!_explainLeakBuffers[bufferKey]) _explainLeakBuffers[bufferKey] = '';
|
|
6032
6281
|
_explainLeakBuffers[bufferKey] += msg.text;
|
|
6033
6282
|
// Try object-view first when objectId is present, then class-view
|
|
6034
6283
|
var area = msg.objectId
|
|
6035
|
-
?
|
|
6036
|
-
:
|
|
6284
|
+
? document.getElementById('explain-obj-' + objSanitized)
|
|
6285
|
+
: document.getElementById('explain-' + classSanitized);
|
|
6037
6286
|
if (area) {
|
|
6038
6287
|
area.textContent = _explainLeakBuffers[bufferKey];
|
|
6039
6288
|
area.scrollTop = area.scrollHeight;
|
|
@@ -6059,53 +6308,61 @@ const vscode={postMessage:send};const depInfoCache=Object.create(null);const isR
|
|
|
6059
6308
|
}
|
|
6060
6309
|
|
|
6061
6310
|
onMessage('explainLeakDone', function(msg) {
|
|
6062
|
-
var classSanitized = msg.className
|
|
6063
|
-
var objSanitized = msg.objectId ? (msg.className
|
|
6311
|
+
var classSanitized = leakEvidenceKey(msg.className);
|
|
6312
|
+
var objSanitized = msg.objectId ? leakEvidenceKey(msg.className,msg.objectId) : classSanitized;
|
|
6064
6313
|
var bufferKey = msg.objectId ? objSanitized : classSanitized;
|
|
6065
6314
|
// Try class-view explain area
|
|
6066
|
-
var classArea = document.getElementById('explain-' + classSanitized);
|
|
6315
|
+
var classArea = !msg.objectId && document.getElementById('explain-' + classSanitized);
|
|
6067
6316
|
if (classArea) {
|
|
6068
6317
|
classArea.classList.remove('streaming');
|
|
6069
6318
|
classArea.classList.add('rendered');
|
|
6070
|
-
|
|
6319
|
+
if (msg.safeEvidenceAnswer) {
|
|
6320
|
+
classArea.innerHTML = renderMarkdown(_explainLeakBuffers[bufferKey] || '');
|
|
6321
|
+
attachInvestigationEvidence(classArea, msg.evidence);
|
|
6322
|
+
} else { classArea.innerHTML = renderMarkdown(_explainLeakBuffers[bufferKey] || ''); }
|
|
6071
6323
|
classArea.scrollTop = 0;
|
|
6072
6324
|
addExplainCloseBtn(classArea);
|
|
6073
6325
|
}
|
|
6074
6326
|
// Try object-view explain area
|
|
6075
|
-
var objArea = document.getElementById('explain-obj-' + objSanitized);
|
|
6327
|
+
var objArea = msg.objectId && document.getElementById('explain-obj-' + objSanitized);
|
|
6076
6328
|
if (objArea) {
|
|
6077
6329
|
objArea.classList.remove('streaming');
|
|
6078
6330
|
objArea.classList.add('rendered');
|
|
6079
|
-
|
|
6331
|
+
if (msg.safeEvidenceAnswer) {
|
|
6332
|
+
objArea.innerHTML = renderMarkdown(_explainLeakBuffers[bufferKey] || '');
|
|
6333
|
+
attachInvestigationEvidence(objArea, msg.evidence);
|
|
6334
|
+
} else { objArea.innerHTML = renderMarkdown(_explainLeakBuffers[bufferKey] || ''); }
|
|
6080
6335
|
objArea.scrollTop = 0;
|
|
6081
6336
|
addExplainCloseBtn(objArea);
|
|
6082
6337
|
}
|
|
6083
6338
|
delete _explainLeakBuffers[bufferKey];
|
|
6084
|
-
document.querySelectorAll('.suspect-explain-link
|
|
6339
|
+
document.querySelectorAll('.suspect-explain-link').forEach(function(link) {
|
|
6340
|
+
if (link.dataset.class !== msg.className) return;
|
|
6085
6341
|
link.textContent = 'Explain';
|
|
6086
6342
|
});
|
|
6087
6343
|
});
|
|
6088
6344
|
|
|
6089
6345
|
onMessage('explainLeakError', function(msg) {
|
|
6090
|
-
var classSanitized = msg.className
|
|
6091
|
-
var objSanitized = msg.objectId ? (msg.className
|
|
6346
|
+
var classSanitized = leakEvidenceKey(msg.className);
|
|
6347
|
+
var objSanitized = msg.objectId ? leakEvidenceKey(msg.className,msg.objectId) : classSanitized;
|
|
6092
6348
|
var bufferKey = msg.objectId ? objSanitized : classSanitized;
|
|
6093
6349
|
delete _explainLeakBuffers[bufferKey];
|
|
6094
6350
|
// Try class-view explain area
|
|
6095
|
-
var classArea = document.getElementById('explain-' + classSanitized);
|
|
6351
|
+
var classArea = !msg.objectId && document.getElementById('explain-' + classSanitized);
|
|
6096
6352
|
if (classArea) {
|
|
6097
6353
|
classArea.classList.remove('streaming');
|
|
6098
6354
|
classArea.classList.add('error', 'visible');
|
|
6099
6355
|
classArea.textContent = msg.message || 'An error occurred';
|
|
6100
6356
|
}
|
|
6101
6357
|
// Try object-view explain area
|
|
6102
|
-
var objArea = document.getElementById('explain-obj-' + objSanitized);
|
|
6358
|
+
var objArea = msg.objectId && document.getElementById('explain-obj-' + objSanitized);
|
|
6103
6359
|
if (objArea) {
|
|
6104
6360
|
objArea.classList.remove('streaming');
|
|
6105
6361
|
objArea.classList.add('error', 'visible');
|
|
6106
6362
|
objArea.textContent = msg.message || 'An error occurred';
|
|
6107
6363
|
}
|
|
6108
|
-
document.querySelectorAll('.suspect-explain-link
|
|
6364
|
+
document.querySelectorAll('.suspect-explain-link').forEach(function(link) {
|
|
6365
|
+
if(link.dataset.class !== msg.className)return;
|
|
6109
6366
|
link.textContent = 'Explain';
|
|
6110
6367
|
});
|
|
6111
6368
|
});
|
|
@@ -6125,7 +6382,9 @@ function leakReply(message,done,error) {
|
|
|
6125
6382
|
if(message.text)leakAnswer+=message.text;
|
|
6126
6383
|
area.classList.add('visible');
|
|
6127
6384
|
if(error){area.textContent=message.message || 'AI request failed.';area.classList.add('error');}
|
|
6128
|
-
else area.innerHTML=renderMarkdown(leakAnswer);
|
|
6385
|
+
else if(done && !message.safeEvidenceAnswer)area.innerHTML=renderMarkdown(leakAnswer);
|
|
6386
|
+
else {area.textContent=leakAnswer;area.style.whiteSpace='pre-wrap';}
|
|
6387
|
+
if(done && message.safeEvidenceAnswer)window.attachInvestigationEvidence(area,message.evidence);
|
|
6129
6388
|
if(done || error){area.classList.remove('streaming');link.textContent='Explain';addExplainCloseBtn(area);leakAnswer='';}
|
|
6130
6389
|
}
|
|
6131
6390
|
listen('explainLeakChunk',function(m){leakReply(m,false,false);});
|