trimprompt 1.0.28 → 1.0.30
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/api-proxy.js +1 -1
- package/cache-manager.js +1 -0
- package/cache.js +1 -1
- package/ccr.js +1 -1
- package/cli.js +1 -1
- package/dashboard.js +1 -1
- package/executor.js +1 -1
- package/file-watcher.js +1 -0
- package/filters/devops.js +1 -1
- package/filters/generic.js +1 -1
- package/filters/git.js +1 -1
- package/filters/go.js +1 -1
- package/filters/index.js +1 -1
- package/filters/js.js +1 -1
- package/filters/python.js +1 -1
- package/filters/rust.js +1 -1
- package/filters/shell.js +1 -1
- package/hooks/claude-hook.js +1 -0
- package/index.html +340 -121
- package/mcp.js +1 -1
- package/package.json +1 -1
- package/proxy-conv.js +1 -1
- package/proxy-daemon.js +1 -1
- package/proxy-resp.js +1 -1
- package/redactor.js +1 -1
- package/seed.js +1 -1
- package/shims.js +1 -1
- package/simulate.js +1 -1
- package/sync.js +1 -1
- package/tracker.js +1 -1
- package/traffic-interceptor.js +1 -1
package/index.html
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
flex-direction: column;
|
|
42
42
|
position: relative;
|
|
43
43
|
overflow-x: hidden;
|
|
44
|
+
zoom: 80%;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/* Ambient radial glow background */
|
|
@@ -235,14 +236,16 @@
|
|
|
235
236
|
font-family: 'Outfit', sans-serif;
|
|
236
237
|
font-weight: 700;
|
|
237
238
|
color: var(--text-muted);
|
|
238
|
-
padding: 1rem;
|
|
239
|
+
padding: 1.1rem 1.2rem;
|
|
239
240
|
border-bottom: 1px solid var(--border);
|
|
241
|
+
vertical-align: middle;
|
|
240
242
|
}
|
|
241
243
|
|
|
242
244
|
td {
|
|
243
|
-
padding: 1rem;
|
|
245
|
+
padding: 1.1rem 1.2rem;
|
|
244
246
|
border-bottom: 1px solid var(--border);
|
|
245
247
|
color: var(--text);
|
|
248
|
+
vertical-align: middle;
|
|
246
249
|
}
|
|
247
250
|
|
|
248
251
|
tr:last-child td {
|
|
@@ -678,7 +681,13 @@
|
|
|
678
681
|
</h2>
|
|
679
682
|
</div>
|
|
680
683
|
<div id="csa-body" style="display:none;">
|
|
681
|
-
<div id="csa-list"
|
|
684
|
+
<div id="csa-list">
|
|
685
|
+
<div style="padding:40px 20px;text-align:center;color:#818CF8;font-size:13px;display:flex;align-items:center;justify-content:center;gap:12px;">
|
|
686
|
+
<style>@keyframes csaSpin { to { transform: rotate(360deg); } }</style>
|
|
687
|
+
<span style="width:18px;height:18px;border:2px solid #818CF8;border-top-color:transparent;border-radius:50%;display:inline-block;animation:csaSpin 0.8s linear infinite;"></span>
|
|
688
|
+
<span style="font-weight:500;">Loading Live Session Context Controls...</span>
|
|
689
|
+
</div>
|
|
690
|
+
</div>
|
|
682
691
|
<div class="csa-summary" id="csa-summary"></div>
|
|
683
692
|
</div>
|
|
684
693
|
</div>
|
|
@@ -740,6 +749,7 @@
|
|
|
740
749
|
<script>
|
|
741
750
|
let savingsChart = null;
|
|
742
751
|
let currentFilter = 'ai';
|
|
752
|
+
const expandedGroupKeys = new Set();
|
|
743
753
|
|
|
744
754
|
const FEATURE_META = {
|
|
745
755
|
filter: { label: 'Filter', color: '#6366F1', bg: 'rgba(99,102,241,0.12)' },
|
|
@@ -771,17 +781,22 @@
|
|
|
771
781
|
{ key: 'lock', title: 'Lock file detection', desc: 'package-lock.json, yarn.lock, Cargo.lock, go.sum → summary line', badges: ['Claude','Cursor','Copilot','Gemini','Antigravity'], statSub: 'on lock files', status: 'active' },
|
|
772
782
|
{ key: 'binary', title: 'Binary/minified detection', desc: '.png, .min.js, .wasm, .pdf, 40+ extensions → placeholder', badges: ['Claude','Cursor','Copilot','Gemini','Antigravity'], statSub: 'on binaries', status: 'active' },
|
|
773
783
|
{ key: 'json_crusher', title: 'SmartCrusher (JSON arrays → tables)', desc: 'Arrays of objects compressed to tabular format. Removes resolved/integrity fields', badges: ['Claude','Cursor','Copilot','Gemini','Antigravity'], statSub: 'on JSON arrays', status: 'active' },
|
|
774
|
-
{ key: 'skeleton', title: 'Code skeleton (imports + signatures only)', desc: 'Files over 100 lines → show structure only, collapse bodies', badges: ['Claude','Cursor','Copilot','Gemini','Antigravity'], statSub: 'on large files', status: 'active' }
|
|
775
|
-
{ key: 'ccr', title: 'CCR — Reversible compression', desc: 'Store original in cache, send compressed. Agent can retrieve full via trim retrieve', badges: ['Claude','Cursor','Copilot','Gemini','Antigravity'], statSub: 'deeper compression', status: 'off' },
|
|
784
|
+
{ key: 'skeleton', title: 'Code skeleton (imports + signatures only)', desc: 'Files over 100 lines → show structure only, collapse bodies', badges: ['Claude','Cursor','Copilot','Gemini','Antigravity'], statSub: 'on large files', status: 'active' }
|
|
776
785
|
]},
|
|
777
786
|
{ category: 'S — Security', features: [
|
|
778
787
|
{ key: 'redact', title: 'Secret redaction (20 patterns)', desc: 'AWS keys, API tokens, JWTs, DB strings auto-blocked', badges: ['Claude','Cursor','Copilot','Gemini','Antigravity'], statKey: 'secrets', statSub: 'secrets blocked', status: 'active' },
|
|
779
788
|
]},
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
789
|
+
{ category: 'V — CONVERSATION COMPRESSION (LIVE CONTEXT)', features: [
|
|
790
|
+
{ key: 'conv_claude', title: 'Claude Code Live Compression', desc: 'Compresses active sessions in ~/.claude/projects/ via side-cache', badges: ['Claude Code'], agentKey: 'claude', statSub: 'on conversation', status: 'active' },
|
|
791
|
+
{ key: 'conv_gemini', title: 'Google Gemini Code Assistant', desc: 'Compresses Gemini CLI & extension session logs in ~/.config/gemini/', badges: ['Gemini'], agentKey: 'gemini', statSub: 'on gemini logs', status: 'active' },
|
|
792
|
+
{ key: 'conv_codex', title: 'Codex Shared Memory Compression', desc: 'Compresses shared memory session logs with Claude', badges: ['Codex'], agentKey: 'codex', statSub: 'on shared memory', status: 'active' },
|
|
793
|
+
{ key: 'conv_cursor', title: 'Cursor Workspace State Compression', desc: 'Compresses workspaceStorage JSON/vscdb files in real-time', badges: ['Cursor'], agentKey: 'cursor', statSub: 'on workspace state', status: 'active' },
|
|
794
|
+
{ key: 'conv_aider', title: 'Aider Chat History Optimizer', desc: 'Compresses .aider.chat.history.md files automatically on launch', badges: ['Aider'], agentKey: 'aider', statSub: 'on markdown logs', status: 'active' },
|
|
795
|
+
{ key: 'conv_copilot', title: 'Copilot CLI Session Trimmer', desc: 'Compresses github-copilot-cli session state logs', badges: ['Copilot CLI'], agentKey: 'copilot', statSub: 'on CLI sessions', status: 'active' },
|
|
796
|
+
{ key: 'conv_openclaw', title: 'OpenClaw ContextEngine Plugin', desc: 'Runs as an internal ContextEngine plugin for OpenClaw pipeline', badges: ['OpenClaw'], agentKey: 'openclaw', statSub: 'on plugin context', status: 'active' },
|
|
797
|
+
{ key: 'conv_opencode', title: 'OpenCode Config & Proxy Optimizer', desc: 'Injects proxy config and compresses .opencode/sessions payloads', badges: ['OpenCode'], agentKey: 'opencode', statSub: 'on open sessions', status: 'active' },
|
|
798
|
+
{ key: 'conv_cortex', title: 'Cortex Code SDK Library Mode', desc: 'SDK wrapper library mode providing 60-65% token savings', badges: ['Cortex Code'], agentKey: 'cortex', statSub: 'on SDK memory', status: 'active' },
|
|
799
|
+
]},
|
|
785
800
|
];
|
|
786
801
|
|
|
787
802
|
let proxyRunning = false;
|
|
@@ -875,30 +890,52 @@
|
|
|
875
890
|
try { await fetch('/api/config/features', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(csaToggles) }); } catch {}
|
|
876
891
|
}
|
|
877
892
|
|
|
878
|
-
function renderCsaSection(logs) {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
+
async function renderCsaSection(logs) {
|
|
894
|
+
try {
|
|
895
|
+
const modelSelect = document.getElementById('model-select');
|
|
896
|
+
const modelVal = modelSelect ? modelSelect.value : 'claude-opus-4-8';
|
|
897
|
+
const pricePerM = PRICING_TABLE[modelVal] || 3.00;
|
|
898
|
+
const totalRaw = logs.reduce((s, l) => s + (l.estimated_raw_tokens || 0), 0);
|
|
899
|
+
const totalComp = logs.reduce((s, l) => s + (l.estimated_compressed_tokens || 0), 0);
|
|
900
|
+
const totalSaved = logs.reduce((s, l) => s + (l.tokens_saved || 0), 0);
|
|
901
|
+
const totalSecrets = logs.reduce((s, l) => s + (l.secrets_found || 0), 0);
|
|
902
|
+
const savedUsd = (totalSaved * pricePerM) / 1000000;
|
|
903
|
+
const reductionPct = totalRaw > 0 ? ((totalSaved / totalRaw) * 100).toFixed(1) : '0.0';
|
|
904
|
+
|
|
905
|
+
const featureCounts = {};
|
|
906
|
+
const featureSavings = {};
|
|
907
|
+
logs.forEach(l => {
|
|
908
|
+
(l.features || []).forEach(f => {
|
|
909
|
+
featureCounts[f] = (featureCounts[f] || 0) + 1;
|
|
910
|
+
featureSavings[f] = (featureSavings[f] || 0) + (l.tokens_saved || 0);
|
|
911
|
+
});
|
|
912
|
+
if (l.command) {
|
|
913
|
+
const matched = CSA_FEATURES.flatMap(c => c.features).find(feat => feat.agentKey && (l.command.includes(`conv:${feat.agentKey}`) || l.command.includes(feat.agentKey)));
|
|
914
|
+
if (matched) {
|
|
915
|
+
featureCounts[matched.key] = (featureCounts[matched.key] || 0) + 1;
|
|
916
|
+
featureSavings[matched.key] = (featureSavings[matched.key] || 0) + (l.tokens_saved || 0);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
893
919
|
});
|
|
894
|
-
});
|
|
895
920
|
|
|
896
|
-
|
|
897
|
-
|
|
921
|
+
const list = document.getElementById('csa-list');
|
|
922
|
+
if (!list) return;
|
|
923
|
+
let html = '';
|
|
924
|
+
|
|
925
|
+
let globalConfigData = {};
|
|
926
|
+
try {
|
|
927
|
+
const cfgResp = await fetch('/api/config');
|
|
928
|
+
globalConfigData = await cfgResp.json();
|
|
929
|
+
} catch(e){}
|
|
898
930
|
|
|
899
931
|
CSA_FEATURES.forEach(cat => {
|
|
900
932
|
html += `<div class="csa-category">${cat.category}</div>`;
|
|
901
|
-
cat.features.
|
|
933
|
+
const sortedFeatures = [...cat.features].sort((a, b) => {
|
|
934
|
+
const savA = featureSavings[a.key] || 0;
|
|
935
|
+
const savB = featureSavings[b.key] || 0;
|
|
936
|
+
return savB - savA;
|
|
937
|
+
});
|
|
938
|
+
sortedFeatures.forEach(f => {
|
|
902
939
|
const isSoon = f.status === 'soon';
|
|
903
940
|
const isOff = f.status === 'off';
|
|
904
941
|
const defaultOn = f.status === 'active';
|
|
@@ -906,6 +943,7 @@
|
|
|
906
943
|
const count = featureCounts[f.key] || 0;
|
|
907
944
|
const statusLabels = { active: 'Active', off: 'Off by default', soon: 'Coming soon' };
|
|
908
945
|
let statVal, statColor;
|
|
946
|
+
let statSubText = f.statSub;
|
|
909
947
|
if (f.statKey === 'secrets') {
|
|
910
948
|
statVal = totalSecrets.toLocaleString();
|
|
911
949
|
statColor = '#EF4444';
|
|
@@ -917,21 +955,69 @@
|
|
|
917
955
|
const featureSaved = (featureSavedTokens * pricePerM) / 1000000;
|
|
918
956
|
statVal = '$' + featureSaved.toFixed(2);
|
|
919
957
|
statColor = '#10B981';
|
|
958
|
+
if (featureSavedTokens > 0) {
|
|
959
|
+
statSubText = `<span style="color:#10B981;font-weight:600;">⚡ ${featureSavedTokens.toLocaleString()} tokens saved</span>`;
|
|
960
|
+
}
|
|
920
961
|
}
|
|
921
962
|
const rowClass = isSoon ? 'csa-row is-soon' : isOff ? 'csa-row is-off' : 'csa-row';
|
|
963
|
+
|
|
964
|
+
let liveBadge = `<span class="csa-status ${f.status}">${statusLabels[f.status]}</span>`;
|
|
965
|
+
let latestFileHtml = '';
|
|
966
|
+
let claudeCompactHtml = '';
|
|
967
|
+
if (f.agentKey) {
|
|
968
|
+
const sess = globalConfigData.latestSessions && globalConfigData.latestSessions[f.agentKey];
|
|
969
|
+
let isToday = false;
|
|
970
|
+
if (sess && sess.lastModified) {
|
|
971
|
+
const fileDate = new Date(sess.lastModified).toDateString();
|
|
972
|
+
const todayDate = new Date().toDateString();
|
|
973
|
+
if (fileDate === todayDate) isToday = true;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
if (isToday) {
|
|
977
|
+
liveBadge = `<span style="background:rgba(16,185,129,0.25);color:#10B981;border:1px solid #10B981;padding:2px 10px;border-radius:12px;font-size:10px;font-weight:700;display:inline-flex;align-items:center;gap:5px;margin-left:8px;box-shadow:0 0 10px rgba(16,185,129,0.2);"><span style="width:7px;height:7px;border-radius:50%;background:#10B981;box-shadow:0 0 6px #10B981;"></span>● LIVE SESSION TODAY</span>`;
|
|
978
|
+
} else {
|
|
979
|
+
liveBadge = `<span style="background:rgba(148,163,184,0.1);color:#94A3B8;border:1px solid rgba(148,163,184,0.2);padding:2px 8px;border-radius:12px;font-size:10px;font-weight:500;display:inline-flex;align-items:center;gap:4px;margin-left:8px;"><span style="width:6px;height:6px;border-radius:50%;background:#64748B;"></span>Idle</span>`;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if (f.agentKey) {
|
|
983
|
+
const agentConvLogs = logs.filter(l => l.command && l.command.includes(`conv:${f.agentKey}`));
|
|
984
|
+
const lastLog = agentConvLogs.length > 0 ? agentConvLogs[agentConvLogs.length - 1] : null;
|
|
985
|
+
const savedTokens = (sess && sess.activeSavedTokens > 0) ? sess.activeSavedTokens : (lastLog ? (lastLog.tokens_saved || lastLog.estimated_raw_tokens || 0) : 0);
|
|
986
|
+
if (savedTokens > 0 || (lastLog && lastLog.timestamp)) {
|
|
987
|
+
const timeStr = lastLog && lastLog.timestamp ? new Date(lastLog.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : (sess && sess.lastModified ? new Date(sess.lastModified).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : 'Today');
|
|
988
|
+
claudeCompactHtml = `<div style="font-size:11px;color:#F59E0B;margin-top:4px;font-family:sans-serif;display:inline-flex;align-items:center;gap:6px;font-weight:600;background:rgba(245,158,11,0.12);border:1px solid rgba(245,158,11,0.3);padding:2px 8px;border-radius:6px;"><span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:#F59E0B;box-shadow:0 0 6px #F59E0B;"></span>🟡 Last Compacted: ${timeStr} (${savedTokens.toLocaleString()} tokens)</div>`;
|
|
989
|
+
} else {
|
|
990
|
+
claudeCompactHtml = `<div style="font-size:11px;color:#F59E0B;margin-top:4px;font-family:sans-serif;display:inline-flex;align-items:center;gap:6px;font-weight:600;background:rgba(245,158,11,0.12);border:1px solid rgba(245,158,11,0.3);padding:2px 8px;border-radius:6px;"><span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:#F59E0B;box-shadow:0 0 6px #F59E0B;"></span>🟡 Last Compacted: Active Monitoring</div>`;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (sess && sess.fileName) {
|
|
995
|
+
latestFileHtml = `<div style="font-size:11px;color:#10B981;margin-top:3px;font-family:monospace;display:flex;align-items:center;gap:4px;"><span>📄 Latest Session:</span> <strong style="color:#F1F5F9;">${sess.fileName}</strong></div>`;
|
|
996
|
+
} else {
|
|
997
|
+
latestFileHtml = `<div style="font-size:11px;color:#64748B;margin-top:3px;font-family:monospace;"><span>📄 Waiting for active session file...</span></div>`;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
922
1001
|
html += `<div class="${rowClass}">
|
|
923
1002
|
<label class="csa-toggle">
|
|
924
1003
|
<input type="checkbox" ${checked ? 'checked' : ''} ${isSoon ? 'disabled' : ''} onchange="saveCsaToggle('${f.key}', this.checked)">
|
|
925
1004
|
<span class="slider"></span>
|
|
926
1005
|
</label>
|
|
927
1006
|
<div class="csa-info">
|
|
928
|
-
<h4>${f.title}
|
|
1007
|
+
<h4>${f.title} ${liveBadge}</h4>
|
|
929
1008
|
<p>${f.desc}</p>
|
|
930
|
-
|
|
1009
|
+
${latestFileHtml}
|
|
1010
|
+
${claudeCompactHtml}
|
|
1011
|
+
<div class="csa-badges" style="margin-top:6px;">
|
|
1012
|
+
${f.badges.map(b => `<span class="csa-badge">${b}</span>`).join('')}
|
|
1013
|
+
${f.agentKey ? `<button type="button" style="background:rgba(245,158,11,0.15);border:1px solid rgba(245,158,11,0.4);color:#F59E0B;font-size:10px;padding:2px 8px;border-radius:4px;cursor:pointer;margin-left:6px;font-weight:600;" onclick="event.preventDefault(); runManualCompact('${f.agentKey}', this)">⚡ Compact Now</button>` : ''}
|
|
1014
|
+
${f.agentKey ? `<button style="background:rgba(16,185,129,0.12);border:1px solid rgba(16,185,129,0.3);color:#10B981;font-size:10px;padding:2px 8px;border-radius:4px;cursor:pointer;margin-left:4px;font-weight:600;" onclick="openAgentFolder('${f.agentKey}')">📁 Open Directory</button>` : ''}
|
|
1015
|
+
${f.agentKey ? `<button style="background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);color:#94A3B8;font-size:10px;padding:2px 8px;border-radius:4px;cursor:pointer;margin-left:4px;" onclick="openLocateModal('${f.agentKey}', '${f.title}')">⚙️ Change Path</button>` : ''}
|
|
1016
|
+
</div>
|
|
931
1017
|
</div>
|
|
932
1018
|
<div class="csa-stat">
|
|
933
1019
|
<div class="csa-val" style="color:${statColor};">${statVal}</div>
|
|
934
|
-
<div class="csa-sub">${
|
|
1020
|
+
<div class="csa-sub">${statSubText}</div>
|
|
935
1021
|
</div>
|
|
936
1022
|
</div>`;
|
|
937
1023
|
});
|
|
@@ -963,14 +1049,87 @@
|
|
|
963
1049
|
<div class="csa-sum-label">Secrets Blocked</div>
|
|
964
1050
|
<div class="csa-sum-val" style="color:var(--red);">${totalSecrets.toLocaleString()}</div>
|
|
965
1051
|
</div>`;
|
|
1052
|
+
} catch(err) {
|
|
1053
|
+
console.error('Error rendering CSA section:', err);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
async function openLocateModal(agentKey, title) {
|
|
1058
|
+
let currentPath = 'Default system path';
|
|
1059
|
+
try {
|
|
1060
|
+
const resp = await fetch('/api/config');
|
|
1061
|
+
const cfg = await resp.json();
|
|
1062
|
+
if (cfg.resolvedPaths && cfg.resolvedPaths[agentKey]) {
|
|
1063
|
+
currentPath = cfg.resolvedPaths[agentKey].join(' ; ');
|
|
1064
|
+
}
|
|
1065
|
+
if (cfg.custom_storage_paths && cfg.custom_storage_paths[agentKey]) {
|
|
1066
|
+
currentPath = cfg.custom_storage_paths[agentKey];
|
|
1067
|
+
}
|
|
1068
|
+
} catch(e){}
|
|
1069
|
+
|
|
1070
|
+
const customPath = prompt(`⚙️ Configure Session Storage Directory for ${title}:\n\nCurrently Monitored Path:\n${currentPath}\n\nEnter custom directory path (or leave empty to reset to default):`, currentPath.includes(' ; ') ? '' : currentPath);
|
|
1071
|
+
if (customPath !== null) {
|
|
1072
|
+
try {
|
|
1073
|
+
await fetch('/api/config/custom-path', {
|
|
1074
|
+
method: 'POST',
|
|
1075
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1076
|
+
body: JSON.stringify({ agentKey, path: customPath.trim() || null })
|
|
1077
|
+
});
|
|
1078
|
+
alert(`Storage path updated for ${title}!\nMonitored directory is now active and saved permanently.`);
|
|
1079
|
+
renderCsa();
|
|
1080
|
+
} catch (e) {
|
|
1081
|
+
alert('Failed to save path.');
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
async function openAgentFolder(agentKey) {
|
|
1087
|
+
try {
|
|
1088
|
+
const resp = await fetch('/api/config/open-dir', {
|
|
1089
|
+
method: 'POST',
|
|
1090
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1091
|
+
body: JSON.stringify({ agentKey })
|
|
1092
|
+
});
|
|
1093
|
+
const res = await resp.json();
|
|
1094
|
+
if (!res.success) {
|
|
1095
|
+
alert('Could not open directory.');
|
|
1096
|
+
}
|
|
1097
|
+
} catch (e) {
|
|
1098
|
+
alert('Error opening directory.');
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
async function runManualCompact(agentKey, btn) {
|
|
1103
|
+
const origText = btn.innerText;
|
|
1104
|
+
btn.innerText = '⏳ Compacting...';
|
|
1105
|
+
btn.disabled = true;
|
|
1106
|
+
try {
|
|
1107
|
+
const resp = await fetch('/api/agent/compact', {
|
|
1108
|
+
method: 'POST',
|
|
1109
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1110
|
+
body: JSON.stringify({ agentKey })
|
|
1111
|
+
});
|
|
1112
|
+
const res = await resp.json();
|
|
1113
|
+
if (res.ok) {
|
|
1114
|
+
btn.innerText = '✅ Compacted!';
|
|
1115
|
+
setTimeout(() => { btn.innerText = origText; btn.disabled = false; }, 2000);
|
|
1116
|
+
loadStats();
|
|
1117
|
+
} else {
|
|
1118
|
+
alert(res.message || 'Compaction finished');
|
|
1119
|
+
btn.innerText = origText;
|
|
1120
|
+
btn.disabled = false;
|
|
1121
|
+
}
|
|
1122
|
+
} catch(e) {
|
|
1123
|
+
btn.innerText = origText;
|
|
1124
|
+
btn.disabled = false;
|
|
1125
|
+
}
|
|
966
1126
|
}
|
|
967
1127
|
|
|
968
1128
|
function isAiCommand(log) {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
return true;
|
|
1129
|
+
if (!log) return false;
|
|
1130
|
+
const cmd = log.command || '';
|
|
1131
|
+
const feats = log.features || [];
|
|
1132
|
+
return cmd.startsWith('conv:') || feats.includes('live-conv') || feats.some(f => typeof f === 'string' && f.startsWith('conv_'));
|
|
974
1133
|
}
|
|
975
1134
|
|
|
976
1135
|
function setFilter(mode) {
|
|
@@ -1123,39 +1282,21 @@
|
|
|
1123
1282
|
|
|
1124
1283
|
async function loadStats() {
|
|
1125
1284
|
const select = document.getElementById('model-select');
|
|
1126
|
-
const model = select.value;
|
|
1285
|
+
const model = select ? select.value : 'auto';
|
|
1127
1286
|
|
|
1128
1287
|
try {
|
|
1129
|
-
// Fetch full history and compute AI-only stats client-side
|
|
1130
|
-
const histResp = await fetch('/api/stats/history');
|
|
1131
|
-
const allLogs = await histResp.json();
|
|
1132
|
-
const aiLogs = allLogs.filter(l => isAiCommand(l));
|
|
1133
|
-
|
|
1134
1288
|
const summaryResp = await fetch(`/api/stats/summary?model=${model}`);
|
|
1289
|
+
if (!summaryResp.ok) return;
|
|
1135
1290
|
const data = await summaryResp.json();
|
|
1136
1291
|
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1145
|
-
const pct = totalRaw > 0 ? ((totalSaved / totalRaw) * 100).toFixed(1) : '0.0';
|
|
1146
|
-
const rawCostUsd = (totalRaw * pricePerM) / 1000000;
|
|
1147
|
-
const compCostUsd = (totalComp * pricePerM) / 1000000;
|
|
1148
|
-
const savedUsd = (totalSaved * pricePerM) / 1000000;
|
|
1149
|
-
|
|
1150
|
-
document.getElementById('val-savings').innerText = `$${savedUsd.toFixed(4)}`;
|
|
1151
|
-
document.getElementById('val-savings-desc').innerText = `based on ${data.model_name} pricing`;
|
|
1152
|
-
document.getElementById('val-orig-cost').innerText = `$${rawCostUsd.toFixed(4)}`;
|
|
1153
|
-
document.getElementById('val-comp-cost').innerText = `$${compCostUsd.toFixed(4)}`;
|
|
1154
|
-
document.getElementById('val-reduction').innerText = `${pct}%`;
|
|
1155
|
-
document.getElementById('val-tokens-desc').innerText = `raw: ${totalRaw.toLocaleString()} → compressed: ${totalComp.toLocaleString()}`;
|
|
1156
|
-
|
|
1157
|
-
// Return for chart model resolution
|
|
1158
|
-
data.model_key = data.model_key || model;
|
|
1292
|
+
if (data && data.raw_cost_usd !== undefined) {
|
|
1293
|
+
document.getElementById('val-savings').innerText = `$${data.money_saved_usd.toFixed(4)}`;
|
|
1294
|
+
document.getElementById('val-savings-desc').innerText = `based on ${data.model_name} pricing`;
|
|
1295
|
+
document.getElementById('val-orig-cost').innerText = `$${data.raw_cost_usd.toFixed(4)}`;
|
|
1296
|
+
document.getElementById('val-comp-cost').innerText = `$${data.compressed_cost_usd.toFixed(4)}`;
|
|
1297
|
+
document.getElementById('val-reduction').innerText = `${data.savings_percentage}%`;
|
|
1298
|
+
document.getElementById('val-tokens-desc').innerText = `raw: ${data.total_raw_tokens.toLocaleString()} → compressed: ${data.total_compressed_tokens.toLocaleString()}`;
|
|
1299
|
+
}
|
|
1159
1300
|
return data;
|
|
1160
1301
|
} catch (err) {
|
|
1161
1302
|
console.error('Failed to fetch summary stats', err);
|
|
@@ -1164,27 +1305,25 @@
|
|
|
1164
1305
|
|
|
1165
1306
|
async function loadHistory() {
|
|
1166
1307
|
const select = document.getElementById('model-select');
|
|
1167
|
-
const model = select.value;
|
|
1308
|
+
const model = select ? select.value : 'auto';
|
|
1309
|
+
|
|
1310
|
+
loadStats();
|
|
1168
1311
|
|
|
1169
1312
|
try {
|
|
1170
1313
|
const response = await fetch('/api/stats/history');
|
|
1314
|
+
if (!response.ok) return;
|
|
1171
1315
|
const logs = await response.json();
|
|
1316
|
+
if (!Array.isArray(logs)) return;
|
|
1172
1317
|
|
|
1173
|
-
|
|
1174
|
-
if (model === 'auto') {
|
|
1175
|
-
const stats = await loadStats();
|
|
1176
|
-
if (stats && stats.model_key) {
|
|
1177
|
-
resolvedModel = stats.model_key;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1318
|
+
const resolvedModel = model === 'auto' ? 'claude-opus-4-8' : model;
|
|
1180
1319
|
|
|
1181
|
-
// Filter logs: AI-only for chart
|
|
1320
|
+
// Filter logs: AI-only for chart
|
|
1182
1321
|
const aiLogs = logs.filter(l => isAiCommand(l));
|
|
1183
1322
|
|
|
1184
|
-
// Chart
|
|
1323
|
+
// Chart always uses AI-only logs
|
|
1185
1324
|
renderChart(aiLogs, resolvedModel);
|
|
1186
1325
|
|
|
1187
|
-
// CSA Feature Controls
|
|
1326
|
+
// CSA Feature Controls (Run asynchronously in background)
|
|
1188
1327
|
renderCsaSection(logs);
|
|
1189
1328
|
|
|
1190
1329
|
// Security banner — aggregate across all logs
|
|
@@ -1223,53 +1362,118 @@
|
|
|
1223
1362
|
return;
|
|
1224
1363
|
}
|
|
1225
1364
|
|
|
1226
|
-
|
|
1227
|
-
const
|
|
1365
|
+
// Group logs by command name
|
|
1366
|
+
const groups = {};
|
|
1367
|
+
displayLogs.forEach(l => {
|
|
1368
|
+
const key = l.command || 'unknown';
|
|
1369
|
+
if (!groups[key]) groups[key] = [];
|
|
1370
|
+
groups[key].push(l);
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
// Sort groups by most recent log timestamp descending
|
|
1374
|
+
const sortedGroupKeys = Object.keys(groups).sort((a, b) => {
|
|
1375
|
+
const lastA = new Date(groups[a][groups[a].length - 1].timestamp).getTime();
|
|
1376
|
+
const lastB = new Date(groups[b][groups[b].length - 1].timestamp).getTime();
|
|
1377
|
+
return lastB - lastA;
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1228
1380
|
tbody.innerHTML = '';
|
|
1229
1381
|
|
|
1230
|
-
|
|
1231
|
-
const
|
|
1232
|
-
const
|
|
1233
|
-
const
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1382
|
+
sortedGroupKeys.forEach((cmdKey, gIdx) => {
|
|
1383
|
+
const groupLogs = groups[cmdKey].reverse(); // Most recent first
|
|
1384
|
+
const latestLog = groupLogs[0];
|
|
1385
|
+
const latestTime = new Date(latestLog.timestamp).toLocaleTimeString();
|
|
1386
|
+
|
|
1387
|
+
let grpRaw = 0, grpComp = 0, grpSaved = 0, grpSecrets = 0;
|
|
1388
|
+
const isLiveConvGroup = cmdKey.startsWith('conv:') || (latestLog.features && latestLog.features.includes('live-conv'));
|
|
1389
|
+
if (isLiveConvGroup) {
|
|
1390
|
+
grpRaw = latestLog.estimated_raw_tokens || 0;
|
|
1391
|
+
grpComp = latestLog.estimated_compressed_tokens || 0;
|
|
1392
|
+
grpSaved = grpRaw - grpComp;
|
|
1393
|
+
groupLogs.forEach(l => { grpSecrets += (l.secrets_found || 0); });
|
|
1394
|
+
} else {
|
|
1395
|
+
groupLogs.forEach(l => {
|
|
1396
|
+
grpRaw += (l.estimated_raw_tokens || 0);
|
|
1397
|
+
grpComp += (l.estimated_compressed_tokens || 0);
|
|
1398
|
+
grpSaved += (l.tokens_saved || 0);
|
|
1399
|
+
grpSecrets += (l.secrets_found || 0);
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
const grpSavingsPct = grpRaw > 0 ? ((grpSaved / grpRaw) * 100).toFixed(1) : '0.0';
|
|
1403
|
+
const groupId = `grp-${gIdx}`;
|
|
1404
|
+
const isGroupExpanded = expandedGroupKeys.has(cmdKey);
|
|
1405
|
+
|
|
1406
|
+
const trHead = document.createElement('tr');
|
|
1407
|
+
trHead.style.background = 'rgba(255,255,255,0.03)';
|
|
1408
|
+
trHead.style.cursor = 'pointer';
|
|
1409
|
+
trHead.style.borderBottom = '1px solid rgba(255,255,255,0.06)';
|
|
1410
|
+
trHead.onclick = () => {
|
|
1411
|
+
const el = document.querySelectorAll(`.${groupId}`);
|
|
1412
|
+
const chev = document.getElementById(`chev-${groupId}`);
|
|
1413
|
+
const isHidden = el[0] && el[0].style.display === 'none';
|
|
1414
|
+
if (isHidden) {
|
|
1415
|
+
expandedGroupKeys.add(cmdKey);
|
|
1416
|
+
} else {
|
|
1417
|
+
expandedGroupKeys.delete(cmdKey);
|
|
1418
|
+
}
|
|
1419
|
+
el.forEach(row => row.style.display = isHidden ? '' : 'none');
|
|
1420
|
+
if (chev) chev.style.transform = isHidden ? 'rotate(90deg)' : 'rotate(0deg)';
|
|
1421
|
+
};
|
|
1422
|
+
|
|
1423
|
+
const displayCmd = formatCommandName(latestLog.command);
|
|
1424
|
+
|
|
1425
|
+
trHead.innerHTML = `
|
|
1426
|
+
<td style="font-size:12px;color:#94A3B8;white-space:nowrap;"><span id="chev-${groupId}" style="display:inline-block;transition:transform 0.2s;margin-right:6px;font-size:10px;transform:${isGroupExpanded ? 'rotate(90deg)' : 'rotate(0deg)'}">▶</span>${latestTime}</td>
|
|
1427
|
+
<td style="font-weight: 600;color:#F1F5F9;text-transform:capitalize;">${displayCmd} <span style="font-size:11px;color:#818CF8;font-weight:400;margin-left:6px;">(${groupLogs.length} runs)</span>${grpSecrets ? ' <span style="color:#F09595;font-size:11px;" title="' + grpSecrets + ' secrets redacted">🛡' + grpSecrets + '</span>' : ''}</td>
|
|
1428
|
+
<td>${featureTagHtml(latestLog.features)}</td>
|
|
1429
|
+
<td style="font-weight:500;">${grpRaw.toLocaleString()}</td>
|
|
1430
|
+
<td style="font-weight:500;">${grpComp.toLocaleString()}</td>
|
|
1431
|
+
<td style="color: var(--emerald); font-weight: 700;">${grpSavingsPct}%</td>
|
|
1432
|
+
<td><span class="status-badge success">Active</span></td>
|
|
1433
|
+
<td><button class="btn-inspect" style="background:rgba(129,140,248,0.15);color:#818CF8;border:1px solid rgba(129,140,248,0.3);" onclick="event.stopPropagation(); this.closest('tr').click();">Details (${groupLogs.length})</button></td>
|
|
1251
1434
|
`;
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1435
|
+
tbody.appendChild(trHead);
|
|
1436
|
+
|
|
1437
|
+
// Append child rows
|
|
1438
|
+
groupLogs.forEach(log => {
|
|
1439
|
+
const tr = document.createElement('tr');
|
|
1440
|
+
tr.className = groupId;
|
|
1441
|
+
tr.style.display = isGroupExpanded ? '' : 'none';
|
|
1442
|
+
tr.style.background = 'rgba(0,0,0,0.2)';
|
|
1443
|
+
const time = new Date(log.timestamp).toLocaleTimeString();
|
|
1444
|
+
const savingsPct = log.raw_chars > 0
|
|
1445
|
+
? ((log.estimated_raw_tokens - log.estimated_compressed_tokens) / log.estimated_raw_tokens * 100).toFixed(1)
|
|
1446
|
+
: '0.0';
|
|
1447
|
+
const isConvLog = log.command && log.command.includes('conv:');
|
|
1448
|
+
let convBadgeHtml = '';
|
|
1449
|
+
if (log.features && log.features.includes('trim_offload')) {
|
|
1450
|
+
convBadgeHtml = `<div style="font-size:10px;color:#10B981;margin-top:4px;padding:2px 8px;border-radius:4px;background:rgba(16,185,129,0.1);display:inline-block;font-weight:600;">⚡ TrimPrompt Side-Cache Offloaded ${log.estimated_raw_tokens.toLocaleString()} Tokens</div>`;
|
|
1451
|
+
} else if (log.features && log.features.includes('official_compact')) {
|
|
1452
|
+
convBadgeHtml = `<div style="font-size:10px;color:#818CF8;margin-top:4px;padding:2px 8px;border-radius:4px;background:rgba(129,140,248,0.1);display:inline-block;font-weight:600;">📦 Official Agent Compact Saved ${log.estimated_raw_tokens.toLocaleString()} Tokens</div>`;
|
|
1453
|
+
} else if (isConvLog) {
|
|
1454
|
+
convBadgeHtml = `<div style="font-size:10px;color:#F59E0B;margin-top:4px;padding:2px 8px;border-radius:4px;background:rgba(245,158,11,0.1);display:inline-block;font-weight:500;">🔄 Active Session Context Monitored</div>`;
|
|
1455
|
+
}
|
|
1456
|
+
const childCmd = formatCommandName(log.command);
|
|
1457
|
+
|
|
1458
|
+
tr.innerHTML = `
|
|
1459
|
+
<td style="padding-left:24px;font-size:11px;color:#64748B;white-space:nowrap;">↳ ${time}</td>
|
|
1460
|
+
<td style="font-size:12px;color:#CBD5E1;text-transform:capitalize;">${childCmd}${convBadgeHtml}</td>
|
|
1461
|
+
<td>${featureTagHtml(log.features)}</td>
|
|
1462
|
+
<td style="font-size:12px;">${log.estimated_raw_tokens.toLocaleString()}</td>
|
|
1463
|
+
<td style="font-size:12px;">${log.estimated_compressed_tokens.toLocaleString()}</td>
|
|
1464
|
+
<td style="color: var(--emerald); font-size:12px;">${savingsPct}%</td>
|
|
1465
|
+
<td>
|
|
1466
|
+
<span class="status-badge ${log.exit_code === 0 ? 'success' : 'fail'}">
|
|
1467
|
+
${log.exit_code}
|
|
1468
|
+
</span>
|
|
1469
|
+
</td>
|
|
1470
|
+
<td>
|
|
1471
|
+
<button class="btn-inspect" data-id="${log.id}" data-cmd="${log.command}" data-sec="${log.secrets_found || 0}" data-types="${(log.secret_types || []).join(',')}" onclick="inspectFromBtn(this)">Inspect</button>
|
|
1472
|
+
</td>
|
|
1473
|
+
`;
|
|
1270
1474
|
tbody.appendChild(tr);
|
|
1271
1475
|
});
|
|
1272
|
-
}
|
|
1476
|
+
});
|
|
1273
1477
|
} catch (err) {
|
|
1274
1478
|
console.error('Failed to fetch history logs', err);
|
|
1275
1479
|
}
|
|
@@ -1279,6 +1483,15 @@
|
|
|
1279
1483
|
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1280
1484
|
}
|
|
1281
1485
|
|
|
1486
|
+
function formatCommandName(cmd) {
|
|
1487
|
+
if (!cmd) return 'unknown';
|
|
1488
|
+
if (cmd.includes('conv:')) {
|
|
1489
|
+
const parts = cmd.split('conv:');
|
|
1490
|
+
return parts[1] || cmd;
|
|
1491
|
+
}
|
|
1492
|
+
return cmd;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1282
1495
|
// Human-friendly labels for redactor rule types (see redactor.ts)
|
|
1283
1496
|
const SECRET_TYPE_LABELS = {
|
|
1284
1497
|
aws_key: 'AWS access key',
|
|
@@ -1355,7 +1568,8 @@
|
|
|
1355
1568
|
|
|
1356
1569
|
async function inspectDiff(id, command, secretsFound, secretTypes) {
|
|
1357
1570
|
try {
|
|
1358
|
-
|
|
1571
|
+
const cleanCmd = formatCommandName(command);
|
|
1572
|
+
document.getElementById('modal-command-title').innerText = `Inspector: trim ${cleanCmd}`;
|
|
1359
1573
|
document.getElementById('diff-raw').innerText = 'Loading...';
|
|
1360
1574
|
document.getElementById('diff-comp').innerText = 'Loading...';
|
|
1361
1575
|
document.getElementById('diff-modal').style.display = 'flex';
|
|
@@ -1363,8 +1577,13 @@
|
|
|
1363
1577
|
const response = await fetch(`/api/stats/inspect/${id}`);
|
|
1364
1578
|
const data = await response.json();
|
|
1365
1579
|
|
|
1366
|
-
|
|
1367
|
-
|
|
1580
|
+
if (command && command.includes('conv:')) {
|
|
1581
|
+
document.getElementById('diff-raw').innerHTML = `<div style="color:#F59E0B;font-size:13px;padding:24px;font-weight:600;line-height:1.6;">⚡ Live Conversation Context Monitor Active<br><span style="font-size:12px;color:#94A3B8;font-weight:400;">Savings achieved via conversation compaction (Native Hook & Side-Cache active)</span></div>`;
|
|
1582
|
+
document.getElementById('diff-comp').innerHTML = `<div style="color:#10B981;font-size:13px;padding:24px;font-weight:600;line-height:1.6;">✅ Tokens Trimmed & Offloaded Live<br><span style="font-size:12px;color:#94A3B8;font-weight:400;">Active memory trimming and real-time token savings within conversation</span></div>`;
|
|
1583
|
+
} else {
|
|
1584
|
+
document.getElementById('diff-raw').innerHTML = highlightRedacted(escapeHtml(data.raw), 'red');
|
|
1585
|
+
document.getElementById('diff-comp').innerHTML = highlightRedacted(escapeHtml(data.compressed), 'green');
|
|
1586
|
+
}
|
|
1368
1587
|
|
|
1369
1588
|
const info = detectFilterType(data.raw, data.compressed, command);
|
|
1370
1589
|
const bar = document.getElementById('inspect-info-bar');
|