@echomem/mcp 1.4.9 → 1.4.12
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/assets/hud/github.svg +1 -0
- package/dist/city/README.md +9 -0
- package/dist/city/echo-ai-city-only.html +54 -93
- package/dist/context-analysis/claude-native-canonical.js +35 -12
- package/dist/context-metrics/calculate.js +2 -15
- package/dist/context-metrics/estimator.js +45 -0
- package/dist/context-metrics/ledger.js +507 -0
- package/dist/context-metrics/parse-claude.js +227 -0
- package/dist/context-metrics/parse-codex.js +276 -0
- package/dist/hud/adapters.js +69 -198
- package/dist/hud/electron-main.js +3 -2
- package/dist/hud/fs.js +14 -0
- package/dist/hud/metric.js +4 -98
- package/dist/hud/monitor.js +1 -12
- package/dist/hud/render.js +4 -3
- package/dist/hud/server.js +30 -0
- package/dist/hud/web.js +409 -186
- package/dist/index.js +1 -0
- package/dist/setup-page/client-core.js +475 -0
- package/dist/setup-page/client-extraction.js +550 -0
- package/dist/setup-page/client-lifecycle.js +116 -0
- package/dist/setup-page/client-report-audit.js +818 -0
- package/dist/setup-page/client-report-city.js +204 -0
- package/dist/setup-page/client-report.js +6 -0
- package/dist/setup-page/client.js +15 -0
- package/dist/setup-page/document.js +37 -0
- package/dist/setup-page/styles-city-report.js +880 -0
- package/dist/setup-page/styles-context-audit.js +470 -0
- package/dist/setup-page/styles-extraction.js +821 -0
- package/dist/setup-page/styles-foundation.js +231 -0
- package/dist/setup-page/styles.js +11 -0
- package/dist/setup-page.js +6 -5623
- package/dist/setup.js +24 -10
- package/package.json +4 -4
- package/dist/city/10-problems-report.html +0 -649
- package/dist/city/_live.html +0 -37
- package/dist/city/_serve.mjs +0 -45
- package/dist/city/card-data.json +0 -15
- package/dist/city/chaos-to-clarity-pencil.html +0 -582
- package/dist/city/city-data.json +0 -248
- package/dist/city/echo-ai-city-only.template.html +0 -2271
- package/dist/city/generate-echo-city-only.mjs +0 -112
- package/dist/city/pencil-pie-generator.html +0 -883
- package/dist/city/pencil-webgl-landscape.html +0 -1239
- package/dist/city/spatial-fan-story.html +0 -479
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#24292f" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Runtime City Assets
|
|
2
|
+
|
|
3
|
+
Only these files are part of setup runtime:
|
|
4
|
+
|
|
5
|
+
- `echo-ai-city-only.html` - pre-auth Agent Doctor City.
|
|
6
|
+
- `echo-extraction-plate.html` - post-auth extraction progress plate.
|
|
7
|
+
- `vendor/`, `personality_stickers/`, and `echo-face-cutout.png` - assets loaded by those pages.
|
|
8
|
+
|
|
9
|
+
Everything under `archive/` is historical reference. It is excluded from the published package and rejected by the localhost City asset server.
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
+
<!--
|
|
3
|
+
Runtime City source. The setup parent loads this file into an iframe and sends each local scan
|
|
4
|
+
report with postMessage. Do not generate user-specific HTML files: this renderer updates in place.
|
|
5
|
+
The retired static share-card template pipeline lives in artifacts/archive/.
|
|
6
|
+
-->
|
|
2
7
|
<html lang="en">
|
|
3
8
|
<head>
|
|
4
9
|
<meta charset="UTF-8" />
|
|
@@ -1085,6 +1090,7 @@ async function loadSetupReport(){
|
|
|
1085
1090
|
typeof envelope.scanId!=='string' || !envelope.scanId.trim()){
|
|
1086
1091
|
return {kind:'invalid', code:'REPORT_ENVELOPE_INVALID'};
|
|
1087
1092
|
}
|
|
1093
|
+
if(envelope.kind==='scanning') return {kind:'scanning', progress:envelope.progress||{}};
|
|
1088
1094
|
if(envelope.kind==='empty') return {kind:'empty'};
|
|
1089
1095
|
if(envelope.kind==='failed'){
|
|
1090
1096
|
const error=envelope.error && typeof envelope.error==='object' ? envelope.error : {};
|
|
@@ -1102,105 +1108,60 @@ async function loadSetupReport(){
|
|
|
1102
1108
|
setLoadState('failed', 'Your local report could not be shown', 'REPORT_NONCE_MISSING: Rerun Echo setup to open a verified local scan.');
|
|
1103
1109
|
return new Promise(()=>{});
|
|
1104
1110
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
const
|
|
1111
|
+
const renderScanProgress=(progress)=>{
|
|
1112
|
+
const scanned=Number(progress?.scanned||0);
|
|
1113
|
+
const total=Number(progress?.total||0);
|
|
1114
|
+
const elapsed=Number(progress?.stageElapsedMs||0)>0 ? ' · '+Math.round(Number(progress.stageElapsedMs)/1000)+'s' : '';
|
|
1115
|
+
if(total>0){
|
|
1116
|
+
const detail=progress?.stage==='classifying-repeated-context'
|
|
1117
|
+
? (progress.label || 'classifying repeated context')+' '+scanned.toLocaleString()+' / '+total.toLocaleString()+elapsed
|
|
1118
|
+
: (progress?.stage && progress.stage!=='reading-transcripts'
|
|
1119
|
+
? (progress.label || 'building report')+elapsed
|
|
1120
|
+
: 'read '+scanned.toLocaleString()+' / '+total.toLocaleString()+' transcript files');
|
|
1121
|
+
setLoadState('running', 'Reading your local history', detail);
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
// The parent owns /report polling and validation. The city only renders the
|
|
1125
|
+
// parent-provided state, keeping loading, failure, and ready state in one place.
|
|
1126
|
+
return await new Promise((resolve)=>{
|
|
1127
|
+
let closed=false;
|
|
1128
|
+
const close=()=>{
|
|
1129
|
+
if(closed) return;
|
|
1130
|
+
closed=true;
|
|
1131
|
+
window.removeEventListener('message', onMessage);
|
|
1132
|
+
};
|
|
1133
|
+
const stop=(kind, code, message)=>{
|
|
1134
|
+
close();
|
|
1135
|
+
if(kind==='empty'){
|
|
1136
|
+
setLoadState('empty', 'No local workspace data found', 'No demo data was substituted.');
|
|
1137
|
+
}else{
|
|
1138
|
+
setLoadState('failed', 'Your local report could not be shown', String(code||'REPORT_BUILD_FAILED')+': '+(message||'Rerun Echo setup to try again.'));
|
|
1139
|
+
}
|
|
1140
|
+
// Keep the city on its terminal loading/error surface instead of drawing a city without a report.
|
|
1141
|
+
};
|
|
1109
1142
|
const onMessage=(event)=>{
|
|
1110
1143
|
if(event.origin!==location.origin) return;
|
|
1111
1144
|
const data=event.data||{};
|
|
1112
|
-
if(data.type
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
window.addEventListener('message', onMessage);
|
|
1118
|
-
postReady();
|
|
1119
|
-
readyTimer=window.setInterval(postReady, 500);
|
|
1120
|
-
});
|
|
1121
|
-
const sleep=(ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
1122
|
-
const fetchOnce=async()=>{
|
|
1123
|
-
// A design-preview iframe may consume only its explicit parent envelope. It must
|
|
1124
|
-
// never race the production /report endpoint and accidentally display local data.
|
|
1125
|
-
if(previewMode) return null;
|
|
1126
|
-
const controller=typeof AbortController!=='undefined' ? new AbortController() : null;
|
|
1127
|
-
const timer=controller ? window.setTimeout(()=>controller.abort(), 3500) : null;
|
|
1128
|
-
try{
|
|
1129
|
-
const res=await fetch(`/report?nonce=${encodeURIComponent(nonce)}`, {credentials:'omit', cache:'no-store', signal:controller?.signal});
|
|
1130
|
-
if(res.status===200){
|
|
1131
|
-
try{ return {source:'server', envelope:await res.json()}; }
|
|
1132
|
-
catch(_){ return {source:'failure', code:'REPORT_RESPONSE_INVALID', message:'The local scanner returned an unreadable report.'}; }
|
|
1145
|
+
if(data.type!=='echomem:setup-report' || data.nonce!==nonce || !Object.prototype.hasOwnProperty.call(data,'envelope')) return;
|
|
1146
|
+
const classified=classifyEnvelope(data.envelope);
|
|
1147
|
+
if(classified.kind==='scanning'){
|
|
1148
|
+
renderScanProgress(classified.progress);
|
|
1149
|
+
return;
|
|
1133
1150
|
}
|
|
1134
|
-
if(res.status!==202){
|
|
1135
|
-
let payload=null;
|
|
1136
|
-
try{ payload=await res.json(); }catch(_){}
|
|
1137
|
-
if(payload && payload.kind==='failed') return {source:'server', envelope:payload};
|
|
1138
|
-
const error=payload?.error && typeof payload.error==='object' ? payload.error : {};
|
|
1139
|
-
return {
|
|
1140
|
-
source:'failure',
|
|
1141
|
-
code:String(error.code||`REPORT_HTTP_${res.status}`),
|
|
1142
|
-
message:String(error.message||'The local scanner could not provide this report.'),
|
|
1143
|
-
};
|
|
1144
|
-
}
|
|
1145
|
-
// 202 = still scanning. This is a file-processing counter; final session totals are
|
|
1146
|
-
// deduplicated separately once the report has finished parsing.
|
|
1147
|
-
try{
|
|
1148
|
-
const p=await res.json();
|
|
1149
|
-
if(p && p.kind==='failed') return {source:'server', envelope:p};
|
|
1150
|
-
const progress=p?.progress || p;
|
|
1151
|
-
if(countEl && progress && Number(progress.total)>0){
|
|
1152
|
-
const scanned=Number(progress.scanned||0);
|
|
1153
|
-
const total=Number(progress.total||0);
|
|
1154
|
-
const elapsed=Number(progress.stageElapsedMs||0)>0 ? ` · ${Math.round(Number(progress.stageElapsedMs)/1000)}s` : '';
|
|
1155
|
-
if(progress.stage && progress.stage!=='reading-transcripts'){
|
|
1156
|
-
countEl.textContent=progress.stage==='classifying-repeated-context'
|
|
1157
|
-
? `${progress.label || 'classifying repeated context'} ${scanned.toLocaleString()} / ${total.toLocaleString()}${elapsed}`
|
|
1158
|
-
: `${progress.label || 'building report'}${elapsed}`;
|
|
1159
|
-
}else{
|
|
1160
|
-
countEl.textContent=`read ${scanned.toLocaleString()} / ${total.toLocaleString()} transcript files`;
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
}catch(_){ return {source:'failure', code:'REPORT_PROGRESS_INVALID', message:'The local scanner returned an unreadable progress update.'}; }
|
|
1164
|
-
return null;
|
|
1165
|
-
}catch(error){
|
|
1166
|
-
if(error?.name!=='AbortError') setLoadState('running', 'Reading your local history', 'Local scanner connection interrupted · retrying');
|
|
1167
|
-
return null;
|
|
1168
|
-
}finally{
|
|
1169
|
-
if(timer) window.clearTimeout(timer);
|
|
1170
|
-
}
|
|
1171
|
-
};
|
|
1172
|
-
const stopClosed=(kind, code, message)=>{
|
|
1173
|
-
cleanupParentListener();
|
|
1174
|
-
if(kind==='empty'){
|
|
1175
|
-
setLoadState('empty', 'No local workspace data found', 'No demo data was substituted.');
|
|
1176
|
-
}else{
|
|
1177
|
-
setLoadState('failed', 'Your local report could not be shown', `${code}: ${message||'Rerun Echo setup to try again.'}`);
|
|
1178
|
-
}
|
|
1179
|
-
return new Promise(()=>{});
|
|
1180
|
-
};
|
|
1181
|
-
while(true){
|
|
1182
|
-
const result=await Promise.race([parentEnvelope, fetchOnce()]);
|
|
1183
|
-
if(result){
|
|
1184
|
-
if(result.source==='failure') return stopClosed('failed', result.code, result.message);
|
|
1185
|
-
const classified=classifyEnvelope(result.envelope);
|
|
1186
1151
|
if(classified.kind==='ready'){
|
|
1187
|
-
|
|
1188
|
-
|
|
1152
|
+
close();
|
|
1153
|
+
resolve(data.envelope);
|
|
1154
|
+
return;
|
|
1189
1155
|
}
|
|
1190
|
-
if(classified.kind==='empty')
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
const duringSleep=await Promise.race([parentEnvelope, sleep(500).then(()=>null)]);
|
|
1194
|
-
if(duringSleep){
|
|
1195
|
-
const classified=classifyEnvelope(duringSleep.envelope);
|
|
1196
|
-
if(classified.kind==='ready'){
|
|
1197
|
-
cleanupParentListener();
|
|
1198
|
-
return duringSleep.envelope;
|
|
1156
|
+
if(classified.kind==='empty'){
|
|
1157
|
+
stop('empty');
|
|
1158
|
+
return;
|
|
1199
1159
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1160
|
+
stop('failed', classified.code, classified.message || 'The report failed provenance or structure validation.');
|
|
1161
|
+
};
|
|
1162
|
+
window.addEventListener('message', onMessage);
|
|
1163
|
+
try{ window.parent?.postMessage({type:'echomem:city-ready', nonce}, location.origin); }catch(_){}
|
|
1164
|
+
});
|
|
1204
1165
|
}
|
|
1205
1166
|
let signpostTokens=setupMode?'—':'7.49B';
|
|
1206
1167
|
let signpostHours=setupMode?'—':'191.1h';
|
|
@@ -1339,7 +1300,7 @@ const POSTER_REPOS=[
|
|
|
1339
1300
|
{name:'Daimler Engineering', x:-168,z:160, tool:'codex', tokens:4439902, cost:15, days:0, hours:0.2, sessions:2},
|
|
1340
1301
|
];
|
|
1341
1302
|
let REPOS=setupMode ? [] : POSTER_REPOS;
|
|
1342
|
-
//
|
|
1303
|
+
// Per-repo Codex/Claude split for the retired standalone poster fallback.
|
|
1343
1304
|
const REPO_SPLIT={'WebPageReactVersion':[31,69],'EchoMem-Chrome':[19,81],'echo-design-system':[44,56],'sleepy-merkle-3d0470':[0,100],'DG-Web':[0,100],'Kobe Life':[100,0],'public':[0,100],'EchoMemory_log':[63,37],'production-prompt':[0,100],'brands':[0,100],'components':[0,100],'dist':[0,100],'Daimler Engineering':[100,0]};
|
|
1344
1305
|
for(const o of REPOS){ const s=REPO_SPLIT[o.name]; if(s){ o.codexPct=s[0]; o.claudePct=s[1]; } else { o.codexPct=o.tool==='codex'?100:0; o.claudePct=100-o.codexPct; } }
|
|
1345
1306
|
if(setupMode){
|
|
@@ -61,7 +61,7 @@ function parseClaudeNativeSession(file) {
|
|
|
61
61
|
const requests = new Map();
|
|
62
62
|
const pendingTools = new Map();
|
|
63
63
|
for (const row of rows) {
|
|
64
|
-
if (typeof row.sessionId === "string")
|
|
64
|
+
if (!session && typeof row.sessionId === "string")
|
|
65
65
|
session = row.sessionId;
|
|
66
66
|
if (!cwd && typeof row.cwd === "string")
|
|
67
67
|
cwd = row.cwd;
|
|
@@ -832,6 +832,7 @@ function isVerificationCommand(command) {
|
|
|
832
832
|
return /\b(?:test|build|tsc|lint|typecheck|check)\b/.test(normalizeCommand(command));
|
|
833
833
|
}
|
|
834
834
|
function aggregateClaudeNative(sessions) {
|
|
835
|
+
const uniqueSessionIds = new Set(sessions.map((session) => session.session));
|
|
835
836
|
const summary = sessions.reduce((sum, session) => {
|
|
836
837
|
sum.input += session.officialInputTokens;
|
|
837
838
|
sum.useful += session.usefulTokens;
|
|
@@ -845,26 +846,43 @@ function aggregateClaudeNative(sessions) {
|
|
|
845
846
|
const buckets = { duplicate: 0, refind: 0, dead: 0, unattributed: 0 };
|
|
846
847
|
const rawBuckets = { keep_oh: 0, keep_prod: 0, opt_dup: 0, opt_refind: 0, opt_dead: 0 };
|
|
847
848
|
const repos = new Map();
|
|
849
|
+
const sessionRows = new Map();
|
|
848
850
|
const problemRows = new Map();
|
|
849
851
|
for (const id of PROBLEM_IDS)
|
|
850
|
-
problemRows.set(id, { tokens: 0, count: 0, pressure: 0,
|
|
852
|
+
problemRows.set(id, { tokens: 0, count: 0, pressure: 0, sessionIds: new Set(), examples: [] });
|
|
851
853
|
for (const session of sessions) {
|
|
852
854
|
for (const key of Object.keys(buckets))
|
|
853
855
|
buckets[key] += session.buckets[key] || 0;
|
|
854
856
|
for (const key of Object.keys(rawBuckets))
|
|
855
857
|
rawBuckets[key] += session.rawBuckets[key] || 0;
|
|
856
|
-
const repo = repos.get(session.repo) || {
|
|
857
|
-
repo.
|
|
858
|
+
const repo = repos.get(session.repo) || { sessionIds: new Set(), officialInputTokens: 0, wasteTokens: 0 };
|
|
859
|
+
repo.sessionIds.add(session.session);
|
|
858
860
|
repo.officialInputTokens += session.officialInputTokens;
|
|
859
861
|
repo.wasteTokens += session.wasteTokens;
|
|
860
862
|
repos.set(session.repo, repo);
|
|
863
|
+
const sessionRow = sessionRows.get(session.session) || {
|
|
864
|
+
session: session.session,
|
|
865
|
+
repo: session.repo,
|
|
866
|
+
turns: 0,
|
|
867
|
+
officialInputTokens: 0,
|
|
868
|
+
wasteTokens: 0,
|
|
869
|
+
problems: new Map(),
|
|
870
|
+
};
|
|
871
|
+
sessionRow.turns += session.turns;
|
|
872
|
+
sessionRow.officialInputTokens += session.officialInputTokens;
|
|
873
|
+
sessionRow.wasteTokens += session.wasteTokens;
|
|
874
|
+
if (sessionRow.repo === "home" && session.repo !== "home")
|
|
875
|
+
sessionRow.repo = session.repo;
|
|
876
|
+
sessionRows.set(session.session, sessionRow);
|
|
861
877
|
for (const [id, stats] of session.problems) {
|
|
862
878
|
const row = problemRows.get(id);
|
|
863
879
|
row.tokens += stats.tokens;
|
|
864
880
|
row.count += stats.count;
|
|
865
881
|
row.pressure += stats.pressure;
|
|
866
|
-
if (stats.tokens > 0)
|
|
867
|
-
row.
|
|
882
|
+
if (stats.tokens > 0) {
|
|
883
|
+
row.sessionIds.add(session.session);
|
|
884
|
+
sessionRow.problems.set(id, (sessionRow.problems.get(id) || 0) + stats.tokens);
|
|
885
|
+
}
|
|
868
886
|
row.examples.push(...stats.examples);
|
|
869
887
|
row.examples.sort((left, right) => (right.tokens || 0) - (left.tokens || 0));
|
|
870
888
|
row.examples.splice(1);
|
|
@@ -880,7 +898,7 @@ function aggregateClaudeNative(sessions) {
|
|
|
880
898
|
category: meta.category,
|
|
881
899
|
confidence: meta.confidence,
|
|
882
900
|
count: row.count,
|
|
883
|
-
sessions: row.
|
|
901
|
+
sessions: row.sessionIds.size,
|
|
884
902
|
allocatedWasteTokens: Math.round(row.tokens),
|
|
885
903
|
qualifiedTokenPressure: Math.round(row.pressure),
|
|
886
904
|
severity: summary.waste ? (row.tokens / summary.waste) * 100 : 0,
|
|
@@ -897,7 +915,7 @@ function aggregateClaudeNative(sessions) {
|
|
|
897
915
|
note: "Claude Code sessions are scored natively from JSONL requestIds and toolUseResult objects. For each human turn, the terminal unique provider request by request sequence is selected as the retained context-window proxy; episode-aware evidence determines raw useful/residue for each selected turn request. Raw residue is counted as waste, while P01-P13 explain the attributable portion and remaining unlabeled residue stays included in headline waste.",
|
|
898
916
|
},
|
|
899
917
|
summary: {
|
|
900
|
-
sessionsAnalyzed:
|
|
918
|
+
sessionsAnalyzed: uniqueSessionIds.size,
|
|
901
919
|
reposAnalyzed: repos.size,
|
|
902
920
|
turnsAnalyzed: summary.turns,
|
|
903
921
|
officialInputTokens: Math.round(summary.input),
|
|
@@ -914,18 +932,23 @@ function aggregateClaudeNative(sessions) {
|
|
|
914
932
|
buckets: mapBucketValues(buckets),
|
|
915
933
|
rawBuckets: mapBucketValues(rawBuckets),
|
|
916
934
|
problems,
|
|
917
|
-
sessions:
|
|
918
|
-
const top = [...session.problems.entries()].sort((a, b) => b[1]
|
|
935
|
+
sessions: [...sessionRows.values()].map((session) => {
|
|
936
|
+
const top = [...session.problems.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
919
937
|
return {
|
|
920
938
|
session: session.session,
|
|
921
939
|
repo: session.repo,
|
|
922
940
|
turns: session.turns,
|
|
923
941
|
officialInputTokens: Math.round(session.officialInputTokens),
|
|
924
942
|
wasteTokens: Math.round(session.wasteTokens),
|
|
925
|
-
topProblemId: top && top[1]
|
|
943
|
+
topProblemId: top && top[1] > 0 ? top[0] : null,
|
|
926
944
|
};
|
|
927
945
|
}).sort((a, b) => b.wasteTokens - a.wasteTokens).slice(0, 8),
|
|
928
|
-
repos: [...repos.entries()].map(([repo, value]) => ({
|
|
946
|
+
repos: [...repos.entries()].map(([repo, value]) => ({
|
|
947
|
+
repo,
|
|
948
|
+
sessions: value.sessionIds.size,
|
|
949
|
+
officialInputTokens: Math.round(value.officialInputTokens),
|
|
950
|
+
wasteTokens: Math.round(value.wasteTokens),
|
|
951
|
+
}))
|
|
929
952
|
.sort((a, b) => b.wasteTokens - a.wasteTokens).slice(0, 8),
|
|
930
953
|
episodes: sessions.flatMap((session) => session.episodes).sort((a, b) => b.wasteTokens - a.wasteTokens).slice(0, 8),
|
|
931
954
|
};
|
|
@@ -11,11 +11,7 @@ export function calculateContextMetrics(input) {
|
|
|
11
11
|
const projectedNextFullnessPct = modelContextLimitTokens && newOutputThisTurnTokens !== undefined
|
|
12
12
|
? (latestInputTokens + newOutputThisTurnTokens) / modelContextLimitTokens
|
|
13
13
|
: undefined;
|
|
14
|
-
const healthScorePct = calculateHealthScorePct({
|
|
15
|
-
noisePct,
|
|
16
|
-
contextFullnessPct,
|
|
17
|
-
projectedNextFullnessPct,
|
|
18
|
-
});
|
|
14
|
+
const healthScorePct = calculateHealthScorePct({ noisePct });
|
|
19
15
|
return {
|
|
20
16
|
latestInputTokens,
|
|
21
17
|
modelContextLimitTokens,
|
|
@@ -32,13 +28,7 @@ export function calculateContextMetrics(input) {
|
|
|
32
28
|
};
|
|
33
29
|
}
|
|
34
30
|
function calculateHealthScorePct(input) {
|
|
35
|
-
|
|
36
|
-
// Noise is a direct quality loss. Fullness becomes a separate pressure penalty after 70% because
|
|
37
|
-
// clean-but-nearly-full sessions still become slow, brittle, or compaction-prone.
|
|
38
|
-
const fullnessPenaltyPct = pressurePct > 0.7
|
|
39
|
-
? clamp01((pressurePct - 0.7) / 0.3) * 35
|
|
40
|
-
: 0;
|
|
41
|
-
return Math.max(0, Math.min(100, Math.round(100 - input.noisePct * 100 - fullnessPenaltyPct)));
|
|
31
|
+
return Math.max(0, Math.min(100, Math.round(100 - input.noisePct * 100)));
|
|
42
32
|
}
|
|
43
33
|
function nonNegative(value) {
|
|
44
34
|
return Number.isFinite(value) ? Math.max(0, Math.round(value)) : 0;
|
|
@@ -51,6 +41,3 @@ function optionalPositive(value) {
|
|
|
51
41
|
return null;
|
|
52
42
|
return Math.round(value);
|
|
53
43
|
}
|
|
54
|
-
function clamp01(value) {
|
|
55
|
-
return Math.max(0, Math.min(1, value));
|
|
56
|
-
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Live resident-waste estimator for the HUD and echo_context_health.
|
|
2
|
+
//
|
|
3
|
+
// HUD score follows the same definition as the local-live golden scorer: use the
|
|
4
|
+
// latest turn's causal partition directly, then score usefulness as 100 - waste%.
|
|
5
|
+
// Keep the fitted rates available for evaluation/calibration experiments, but do
|
|
6
|
+
// not apply them to the user-facing HUD score.
|
|
7
|
+
import { classifySession } from "./ledger.js";
|
|
8
|
+
export const WASTE_RATES = {
|
|
9
|
+
codex: { stReads: 0.686, cReads: 1.0, stTransients: 1.0, stAgent: 0.894, stReasoning: 0, deadReads: 0, imgRefs: 0, imgRefsCalm: 0.035, imgShots: 1.0 },
|
|
10
|
+
claude: { stReads: 0.917, cReads: 0.978, stTransients: 1.0, stAgent: 1.0, stReasoning: 0, deadReads: 0, imgRefs: 0.587, imgRefsCalm: 0.023, imgShots: 0.998 },
|
|
11
|
+
};
|
|
12
|
+
export function applyRates(row, rates) {
|
|
13
|
+
const base = row.opt_dup + row.opt_refind + row.opt_dead;
|
|
14
|
+
const un = row.uncertain;
|
|
15
|
+
if (!un)
|
|
16
|
+
return base;
|
|
17
|
+
let est = base;
|
|
18
|
+
for (const key of Object.keys(rates)) {
|
|
19
|
+
const sign = key === "deadReads" ? -1 : 1;
|
|
20
|
+
est += sign * rates[key] * un[key];
|
|
21
|
+
}
|
|
22
|
+
return Math.min(row.C_t, Math.max(0, Math.round(est)));
|
|
23
|
+
}
|
|
24
|
+
// Latest-turn estimate for the HUD hot path: one classification pass over the ledger.
|
|
25
|
+
export function estimateResidentWaste(ledger) {
|
|
26
|
+
const result = classifySession(ledger, "live-causal", true);
|
|
27
|
+
const row = result.series[result.series.length - 1];
|
|
28
|
+
if (!row)
|
|
29
|
+
return null;
|
|
30
|
+
const wasteTokens = row.opt_dup + row.opt_refind + row.opt_dead;
|
|
31
|
+
return {
|
|
32
|
+
turn: row.turn,
|
|
33
|
+
latestInputTokens: row.C_t,
|
|
34
|
+
wasteTokens,
|
|
35
|
+
usefulTokens: Math.max(0, row.C_t - wasteTokens),
|
|
36
|
+
trackedWasteTokens: row.opt_dup + row.opt_refind + row.opt_dead,
|
|
37
|
+
dupTokens: row.opt_dup,
|
|
38
|
+
refindTokens: row.opt_refind,
|
|
39
|
+
deadReadTokens: row.deadReadTokens,
|
|
40
|
+
deadImageTokens: row.deadImageTokens,
|
|
41
|
+
dupCount: row.dupCount,
|
|
42
|
+
turnsSeen: row.turn,
|
|
43
|
+
anchorSource: result.anchorSource,
|
|
44
|
+
};
|
|
45
|
+
}
|