@echomem/mcp 1.4.8 → 1.4.10
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/README.md +23 -3
- package/assets/canonical-scorer/README.md +18 -0
- package/assets/canonical-scorer/analyze-10-problems.mjs +857 -0
- package/assets/canonical-scorer/build-session-waste-dashboard.mjs +1628 -0
- package/assets/canonical-scorer/golden_anchors.mjs +83 -0
- package/assets/canonical-scorer/optimizable_detail.mjs +633 -0
- package/assets/hud/github.svg +1 -0
- package/dist/city/README.md +9 -0
- package/dist/city/echo-ai-city-only.html +1067 -107
- package/dist/codex-session-files.js +283 -0
- package/dist/codex-sync.js +7 -2
- package/dist/context-analysis/canonical-golden.js +47 -0
- package/dist/context-analysis/claude-canonical-adapter.js +315 -0
- package/dist/context-analysis/claude-native-canonical.js +1216 -0
- package/dist/context-analysis/vendored-canonical.js +793 -0
- package/dist/context-analysis/workspace-report.js +1838 -0
- package/dist/context-metrics/calculate.js +43 -0
- package/dist/context-metrics/estimator.js +45 -0
- package/dist/context-metrics/ledger.js +507 -0
- package/dist/context-metrics/model-limits.js +26 -0
- package/dist/context-metrics/parse-claude.js +227 -0
- package/dist/context-metrics/parse-codex.js +276 -0
- package/dist/context-metrics/types.js +1 -0
- package/dist/forensics-10-problems.js +7 -6
- package/dist/forensics.js +863 -132
- package/dist/hud/adapters.js +77 -202
- package/dist/hud/cli.js +0 -0
- package/dist/hud/efficiency.js +447 -0
- package/dist/hud/electron-main.js +3 -2
- package/dist/hud/fs.js +14 -0
- package/dist/hud/metric.js +17 -102
- package/dist/hud/monitor.js +136 -28
- package/dist/hud/render.js +4 -3
- package/dist/hud/server.js +30 -0
- package/dist/hud/web.js +622 -353
- package/dist/index.js +7 -3
- package/dist/local-data-paths.js +87 -0
- package/dist/migrate.js +37 -29
- package/dist/report.js +101 -40
- 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 +15 -2538
- package/dist/setup-preview.js +245 -0
- package/dist/setup.js +456 -44
- package/package.json +8 -7
- package/templates/echomem-recall.md +2 -2
- 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/city-data.json +0 -248
- package/dist/city/echo-ai-city-only.template.html +0 -1272
- package/dist/city/generate-echo-city-only.mjs +0 -112
package/dist/hud/adapters.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { calculateContextMetrics } from "../context-metrics/calculate.js";
|
|
4
|
+
import { estimateResidentWaste } from "../context-metrics/estimator.js";
|
|
5
|
+
import { parseCodexSession } from "../context-metrics/parse-codex.js";
|
|
6
|
+
import { parseClaudeSession } from "../context-metrics/parse-claude.js";
|
|
7
|
+
import { resolveClaudeProjectsDir, resolveCodexSessionsDir } from "../local-data-paths.js";
|
|
8
|
+
import { homePath, newestFile, resolveClaudeTranscript, walkFiles } from "./fs.js";
|
|
9
|
+
import { BUCKETS, newMetricState, qualityColor, scoreMetric } from "./metric.js";
|
|
5
10
|
export const adapters = {
|
|
6
11
|
codex: {
|
|
7
12
|
client: "codex",
|
|
@@ -31,106 +36,35 @@ export function adapterList(mode) {
|
|
|
31
36
|
return [adapters[mode]];
|
|
32
37
|
}
|
|
33
38
|
function listCodex() {
|
|
34
|
-
const root =
|
|
35
|
-
return walkFiles(root, (file) => /^rollout-.*\.jsonl$/.test(path.basename(file)));
|
|
39
|
+
const root = resolveCodexSessionsDir();
|
|
40
|
+
return root ? walkFiles(root, (file) => /^rollout-.*\.jsonl$/.test(path.basename(file))) : [];
|
|
36
41
|
}
|
|
37
42
|
function findActiveCodex() {
|
|
38
43
|
return newestFile(listCodex());
|
|
39
44
|
}
|
|
40
45
|
function scoreCodex(file) {
|
|
41
|
-
|
|
42
|
-
let ctTokens = 0;
|
|
43
|
-
let modelContextWindow = 0;
|
|
44
|
-
let compactMarkers = 0;
|
|
45
|
-
let patchEdits = 0;
|
|
46
|
-
let functionOutputs = 0;
|
|
47
|
-
let largeFunctionOutputs = 0;
|
|
48
|
-
let lastTool = "";
|
|
49
|
-
const largeByTool = {};
|
|
50
|
-
const outputTokensByTool = {};
|
|
51
|
-
const filesByTool = {};
|
|
52
|
-
let updatedAt = new Date().toISOString();
|
|
53
|
-
for (const record of readJsonl(file)) {
|
|
54
|
-
const top = isRecord(record) ? record : {};
|
|
55
|
-
const payload = isRecord(top.payload) ? top.payload : {};
|
|
56
|
-
const payloadType = typeof payload.type === "string" ? payload.type : "";
|
|
57
|
-
if (typeof top.timestamp === "string")
|
|
58
|
-
updatedAt = top.timestamp;
|
|
59
|
-
if (payloadType === "task_started")
|
|
60
|
-
bumpTurn(state);
|
|
61
|
-
else if (payloadType === "token_count") {
|
|
62
|
-
const info = isRecord(payload.info) ? payload.info : {};
|
|
63
|
-
const last = isRecord(info.last_token_usage) ? info.last_token_usage : {};
|
|
64
|
-
ctTokens = readNumber(last.input_tokens) || ctTokens;
|
|
65
|
-
modelContextWindow = readNumber(info.model_context_window) || modelContextWindow;
|
|
66
|
-
}
|
|
67
|
-
else if (payloadType === "function_call") {
|
|
68
|
-
const name = typeof payload.name === "string" ? payload.name : "";
|
|
69
|
-
recordTool(state, name);
|
|
70
|
-
if (name)
|
|
71
|
-
lastTool = name;
|
|
72
|
-
const args = parseArguments(payload.arguments);
|
|
73
|
-
const cmd = isRecord(args) && typeof args.cmd === "string" ? args.cmd : "";
|
|
74
|
-
if ((name === "exec_command" || name === "shell") && cmd) {
|
|
75
|
-
const read = shellRead(cmd);
|
|
76
|
-
if (read) {
|
|
77
|
-
recordRead(state, read.file, read.start, read.end);
|
|
78
|
-
addToolFile(filesByTool, name, read.file);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
else if (payloadType === "function_call_output") {
|
|
83
|
-
functionOutputs += 1;
|
|
84
|
-
const outTool = lastTool || "output";
|
|
85
|
-
const osize = outputSize(payload.output);
|
|
86
|
-
// Image outputs (base64 PNGs) cost ~IMG_TOK, not their byte length — Kobe's calibration.
|
|
87
|
-
const outTok = /image/i.test(outTool) ? 4000 : Math.round(osize / 4);
|
|
88
|
-
outputTokensByTool[outTool] = (outputTokensByTool[outTool] || 0) + outTok;
|
|
89
|
-
if (osize > 12_000) {
|
|
90
|
-
largeFunctionOutputs += 1;
|
|
91
|
-
largeByTool[outTool] = (largeByTool[outTool] || 0) + 1;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
else if (payloadType === "patch_apply_end") {
|
|
95
|
-
const changes = isRecord(payload.changes) ? payload.changes : {};
|
|
96
|
-
for (const changed of Object.keys(changes)) {
|
|
97
|
-
recordEdit(state, changed);
|
|
98
|
-
patchEdits += 1;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
else if (payloadType === "context_compacted" || top.type === "compacted") {
|
|
102
|
-
compactMarkers += 1;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
const score = scoreMetric({
|
|
106
|
-
client: "codex",
|
|
107
|
-
sourcePath: file,
|
|
108
|
-
state,
|
|
109
|
-
ctTokens,
|
|
110
|
-
ctSource: "token_count",
|
|
111
|
-
modelContextWindow,
|
|
112
|
-
updatedAt,
|
|
113
|
-
stats: { compactMarkers, patchEdits, functionOutputs, largeFunctionOutputs },
|
|
114
|
-
});
|
|
115
|
-
if (Object.keys(largeByTool).length)
|
|
116
|
-
score.largeOutputsByTool = largeByTool;
|
|
117
|
-
if (Object.keys(outputTokensByTool).length)
|
|
118
|
-
score.outputTokensByTool = outputTokensByTool;
|
|
119
|
-
if (Object.keys(filesByTool).length)
|
|
120
|
-
score.filesByTool = topFilesByTool(filesByTool);
|
|
121
|
-
return score;
|
|
46
|
+
return scoreFromParsed("codex", file, parseCodexSession(fs.readFileSync(file, "utf8")), "token_count");
|
|
122
47
|
}
|
|
123
48
|
function listClaudeCode() {
|
|
124
|
-
|
|
49
|
+
const root = resolveClaudeProjectsDir();
|
|
50
|
+
return root ? walkFiles(root, (file) => file.endsWith(".jsonl")) : [];
|
|
125
51
|
}
|
|
126
52
|
function findActiveClaudeCode() {
|
|
127
|
-
const
|
|
53
|
+
const projects = resolveClaudeProjectsDir();
|
|
54
|
+
const cacheRoot = projects ? path.join(path.dirname(projects), "echo-ctx") : null;
|
|
55
|
+
const cache = cacheRoot ? newestFile(walkFiles(cacheRoot, (file) => file.endsWith(".json"))) : null;
|
|
128
56
|
if (cache)
|
|
129
57
|
return cache;
|
|
130
58
|
return newestFile(listClaudeCode());
|
|
131
59
|
}
|
|
132
60
|
function scoreClaudeCode(file) {
|
|
133
61
|
if (file.includes(`${path.sep}.claude${path.sep}echo-ctx${path.sep}`) || file.endsWith(".json")) {
|
|
62
|
+
// statusline cache: the transcript is the real data source; the cache only locates it
|
|
63
|
+
const transcript = resolveClaudeTranscript(file);
|
|
64
|
+
if (transcript) {
|
|
65
|
+
const score = scoreClaudeTranscript(transcript, "claude-code", "usage_estimate");
|
|
66
|
+
return { ...score, sourcePath: file };
|
|
67
|
+
}
|
|
134
68
|
const raw = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
135
69
|
return normalizeCachedClaudeScore(raw, file, "claude-code", "statusline");
|
|
136
70
|
}
|
|
@@ -147,101 +81,64 @@ function scoreClaudeDesktop(file) {
|
|
|
147
81
|
return scoreClaudeTranscript(file, "claude-desktop", "usage_estimate");
|
|
148
82
|
}
|
|
149
83
|
function scoreClaudeTranscript(file, client, ctSource) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const score = scoreMetric({ client, sourcePath: file, state, ctTokens, ctSource, updatedAt });
|
|
207
|
-
if (Object.keys(outputTokensByTool).length)
|
|
208
|
-
score.outputTokensByTool = outputTokensByTool;
|
|
209
|
-
if (Object.keys(filesByTool).length)
|
|
210
|
-
score.filesByTool = topFilesByTool(filesByTool);
|
|
84
|
+
return scoreFromParsed(client, file, parseClaudeSession(fs.readFileSync(file, "utf8")), ctSource);
|
|
85
|
+
}
|
|
86
|
+
// Assemble the HUD Score from a parsed ledger: the latest local-live partition supplies resident
|
|
87
|
+
// waste, the shared calculator turns it into pollution/useful/health, and the ledger extras carry
|
|
88
|
+
// the session facts (tools, files, stats) the capsule and panels display.
|
|
89
|
+
function scoreFromParsed(client, file, parsed, ctSource) {
|
|
90
|
+
const estimate = estimateResidentWaste(parsed.ledger);
|
|
91
|
+
const empty = {
|
|
92
|
+
turn: 0, latestInputTokens: 0, wasteTokens: 0, usefulTokens: 0, trackedWasteTokens: 0,
|
|
93
|
+
dupTokens: 0, refindTokens: 0, deadReadTokens: 0, deadImageTokens: 0, dupCount: 0,
|
|
94
|
+
turnsSeen: 0, anchorSource: "none",
|
|
95
|
+
};
|
|
96
|
+
const e = estimate ?? empty;
|
|
97
|
+
const modelWindow = parsed.extras.modelContextWindow;
|
|
98
|
+
const metrics = calculateContextMetrics({
|
|
99
|
+
latestInputTokens: e.latestInputTokens,
|
|
100
|
+
modelContextLimitTokens: modelWindow,
|
|
101
|
+
currentResidentWasteTokens: e.wasteTokens,
|
|
102
|
+
});
|
|
103
|
+
const pollution = Math.min(0.95, metrics.noisePct);
|
|
104
|
+
const pollutionPct = Math.round(pollution * 100);
|
|
105
|
+
const saturationPct = metrics.contextFullnessPct !== undefined ? Math.round(metrics.contextFullnessPct * 100) : null;
|
|
106
|
+
const otherDead = Math.max(0, e.wasteTokens - e.dupTokens - e.refindTokens - e.deadReadTokens - e.deadImageTokens);
|
|
107
|
+
const buckets = {
|
|
108
|
+
[BUCKETS.rangeRedundant]: { tokens: e.dupTokens, count: e.dupCount },
|
|
109
|
+
[BUCKETS.staleRead]: { tokens: e.deadReadTokens + otherDead, count: 0 },
|
|
110
|
+
[BUCKETS.supersededImage]: { tokens: e.deadImageTokens, count: 0 },
|
|
111
|
+
[BUCKETS.staleToolOutput]: { tokens: e.refindTokens, count: 0 },
|
|
112
|
+
[BUCKETS.compactionRecoverable]: { tokens: 0, count: 0 },
|
|
113
|
+
};
|
|
114
|
+
const reads = parsed.ledger.items.reduce((n, it) => n + (it.kind === "read" ? 1 : 0), 0);
|
|
115
|
+
const score = {
|
|
116
|
+
client,
|
|
117
|
+
sourcePath: file,
|
|
118
|
+
turn: e.turnsSeen,
|
|
119
|
+
reads,
|
|
120
|
+
redundantCount: e.dupCount,
|
|
121
|
+
usefulPct: Math.round(Math.max(0, Math.min(1, metrics.usefulPct)) * 100),
|
|
122
|
+
healthScorePct: metrics.healthScorePct,
|
|
123
|
+
pollutionPct,
|
|
124
|
+
pollutionTok: e.wasteTokens,
|
|
125
|
+
ctTokens: e.latestInputTokens,
|
|
126
|
+
ctSource,
|
|
127
|
+
modelContextWindow: modelWindow,
|
|
128
|
+
saturationPct,
|
|
129
|
+
color: qualityColor(pollutionPct, saturationPct),
|
|
130
|
+
buckets,
|
|
131
|
+
honesty: "tracked lower bound",
|
|
132
|
+
updatedAt: parsed.extras.updatedAt,
|
|
133
|
+
stats: { ...parsed.extras.stats, trackedWasteTokens: e.trackedWasteTokens, anchorSource: e.anchorSource },
|
|
134
|
+
tools: parsed.extras.toolCounts,
|
|
135
|
+
};
|
|
136
|
+
if (Object.keys(parsed.extras.outputTokensByTool).length)
|
|
137
|
+
score.outputTokensByTool = parsed.extras.outputTokensByTool;
|
|
138
|
+
if (Object.keys(parsed.extras.filesByTool).length)
|
|
139
|
+
score.filesByTool = parsed.extras.filesByTool;
|
|
211
140
|
return score;
|
|
212
141
|
}
|
|
213
|
-
function addToolFile(map, tool, file) {
|
|
214
|
-
if (!tool || !file)
|
|
215
|
-
return;
|
|
216
|
-
const inner = map[tool] || (map[tool] = {});
|
|
217
|
-
inner[file] = (inner[file] || 0) + 1;
|
|
218
|
-
}
|
|
219
|
-
function topFilesByTool(map) {
|
|
220
|
-
const out = {};
|
|
221
|
-
for (const tool of Object.keys(map)) {
|
|
222
|
-
out[tool] = Object.entries(map[tool]).sort((a, b) => b[1] - a[1]).slice(0, 12).map((entry) => entry[0]);
|
|
223
|
-
}
|
|
224
|
-
return out;
|
|
225
|
-
}
|
|
226
|
-
function claudeResultTokens(content) {
|
|
227
|
-
if (typeof content === "string")
|
|
228
|
-
return Math.round(outputSize(content) / 4);
|
|
229
|
-
if (Array.isArray(content)) {
|
|
230
|
-
let total = 0;
|
|
231
|
-
for (const block of content) {
|
|
232
|
-
if (!isRecord(block))
|
|
233
|
-
continue;
|
|
234
|
-
if (block.type === "image")
|
|
235
|
-
total += 4000; // IMG_TOK, not base64 byte-length
|
|
236
|
-
else if (typeof block.text === "string")
|
|
237
|
-
total += Math.round(outputSize(block.text) / 4);
|
|
238
|
-
else
|
|
239
|
-
total += Math.round(outputSize(block) / 4);
|
|
240
|
-
}
|
|
241
|
-
return total;
|
|
242
|
-
}
|
|
243
|
-
return Math.round(outputSize(content) / 4);
|
|
244
|
-
}
|
|
245
142
|
function normalizeCachedClaudeScore(raw, file, client, ctSource) {
|
|
246
143
|
const ctTokens = readNumber(raw.ctTokens) || readNumber(raw.size) || readNumber(raw.contextTokens) || 0;
|
|
247
144
|
const pollutionTok = readNumber(raw.pollutionTok) || readNumber(raw.redundantTok) || readNumber(raw.pollution_tokens) || 0;
|
|
@@ -261,28 +158,6 @@ function normalizeCachedClaudeScore(raw, file, client, ctSource) {
|
|
|
261
158
|
updatedAt: new Date().toISOString(),
|
|
262
159
|
});
|
|
263
160
|
}
|
|
264
|
-
function parseArguments(value) {
|
|
265
|
-
if (!value)
|
|
266
|
-
return {};
|
|
267
|
-
if (isRecord(value))
|
|
268
|
-
return value;
|
|
269
|
-
if (typeof value !== "string")
|
|
270
|
-
return {};
|
|
271
|
-
try {
|
|
272
|
-
return JSON.parse(value);
|
|
273
|
-
}
|
|
274
|
-
catch {
|
|
275
|
-
return {};
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
function outputSize(value) {
|
|
279
|
-
if (typeof value === "string")
|
|
280
|
-
return Buffer.byteLength(value);
|
|
281
|
-
return Buffer.byteLength(JSON.stringify(value || ""));
|
|
282
|
-
}
|
|
283
161
|
function readNumber(value) {
|
|
284
162
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
285
163
|
}
|
|
286
|
-
function isRecord(value) {
|
|
287
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
288
|
-
}
|
package/dist/hud/cli.js
CHANGED
|
File without changes
|