@echomem/mcp 1.4.8 → 1.4.9
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/dist/city/chaos-to-clarity-pencil.html +582 -0
- package/dist/city/echo-ai-city-only.html +1104 -105
- package/dist/city/echo-ai-city-only.template.html +1104 -105
- package/dist/city/pencil-pie-generator.html +883 -0
- package/dist/city/pencil-webgl-landscape.html +1239 -0
- package/dist/city/spatial-fan-story.html +479 -0
- 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-native-canonical.js +1193 -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 +56 -0
- package/dist/context-metrics/model-limits.js +26 -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 +8 -4
- package/dist/hud/metric.js +13 -4
- package/dist/hud/monitor.js +135 -16
- package/dist/hud/web.js +344 -298
- 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.js +3290 -196
- package/dist/setup-preview.js +245 -0
- package/dist/setup.js +432 -34
- package/package.json +5 -4
- package/templates/echomem-recall.md +2 -2
package/dist/hud/web.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { MCP_PACKAGE_VERSION } from "../package-metadata.js";
|
|
2
1
|
export const HUD_HTML = String.raw `<!doctype html>
|
|
3
2
|
<html lang="en">
|
|
4
3
|
<head>
|
|
@@ -39,6 +38,11 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
39
38
|
-webkit-font-smoothing: antialiased;
|
|
40
39
|
cursor: grab;
|
|
41
40
|
}
|
|
41
|
+
#hud.capture-preview {
|
|
42
|
+
position: fixed;
|
|
43
|
+
top: 0;
|
|
44
|
+
left: 0;
|
|
45
|
+
}
|
|
42
46
|
#hud.dragging { cursor: grabbing; }
|
|
43
47
|
.glance {
|
|
44
48
|
position: relative;
|
|
@@ -129,24 +133,31 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
129
133
|
min-width: 0;
|
|
130
134
|
display: grid;
|
|
131
135
|
grid-template-columns: minmax(0, 1fr);
|
|
132
|
-
gap:
|
|
133
|
-
padding:
|
|
136
|
+
gap: 2px;
|
|
137
|
+
padding: 15px 38px 11px 30px;
|
|
134
138
|
}
|
|
135
139
|
.dot {
|
|
136
140
|
position: absolute;
|
|
137
|
-
left:
|
|
138
|
-
top:
|
|
141
|
+
left: 13px;
|
|
142
|
+
top: 22px;
|
|
139
143
|
z-index: 3;
|
|
140
|
-
width:
|
|
141
|
-
height:
|
|
144
|
+
width: 12px;
|
|
145
|
+
height: 12px;
|
|
142
146
|
border: 2px solid #fdfaf2;
|
|
143
147
|
border-radius: 999px;
|
|
144
148
|
background: var(--green);
|
|
145
149
|
box-shadow: 0 0 0 1px rgba(26, 58, 143, 0.14);
|
|
146
150
|
}
|
|
147
151
|
.dot.amber { background: var(--amber); }
|
|
148
|
-
.dot.red
|
|
149
|
-
|
|
152
|
+
.dot.red, .state-dot.red, #miniPill .mdot.red {
|
|
153
|
+
background: #ff5f57;
|
|
154
|
+
animation: red-pulse 1.15s ease-in-out infinite;
|
|
155
|
+
}
|
|
156
|
+
.dot.idle { background: var(--green); }
|
|
157
|
+
@keyframes red-pulse {
|
|
158
|
+
0%, 100% { box-shadow: 0 0 0 1px rgba(199, 55, 47, 0.18), 0 0 0 0 rgba(255, 95, 87, 0.34); transform: scale(1); }
|
|
159
|
+
50% { box-shadow: 0 0 0 1px rgba(199, 55, 47, 0.18), 0 0 0 7px rgba(255, 95, 87, 0); transform: scale(1.18); }
|
|
160
|
+
}
|
|
150
161
|
.main {
|
|
151
162
|
min-width: 0;
|
|
152
163
|
overflow: hidden;
|
|
@@ -163,14 +174,22 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
163
174
|
overflow: hidden;
|
|
164
175
|
text-overflow: ellipsis;
|
|
165
176
|
white-space: nowrap;
|
|
166
|
-
font-
|
|
167
|
-
font-
|
|
168
|
-
letter-spacing: 0
|
|
177
|
+
font-size: 10.5px;
|
|
178
|
+
font-weight: 720;
|
|
179
|
+
letter-spacing: 0;
|
|
180
|
+
line-height: 1;
|
|
181
|
+
color: var(--faint);
|
|
182
|
+
}
|
|
183
|
+
.client-repo {
|
|
184
|
+
min-width: 0;
|
|
185
|
+
overflow: hidden;
|
|
186
|
+
text-overflow: ellipsis;
|
|
187
|
+
white-space: nowrap;
|
|
188
|
+
font-size: 9.5px;
|
|
189
|
+
font-weight: 620;
|
|
169
190
|
line-height: 1;
|
|
170
|
-
text-transform: uppercase;
|
|
171
191
|
color: var(--faint);
|
|
172
192
|
}
|
|
173
|
-
/* When drifted, the bubble's second line becomes Echo speaking, not a log line. */
|
|
174
193
|
.client.voice {
|
|
175
194
|
font-family: inherit;
|
|
176
195
|
font-size: 11.5px;
|
|
@@ -182,10 +201,10 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
182
201
|
.details {
|
|
183
202
|
display: none;
|
|
184
203
|
position: relative;
|
|
185
|
-
margin: 2px 8px 0
|
|
204
|
+
margin: 2px 8px 0 8px;
|
|
186
205
|
padding: 16px 17px 15px;
|
|
187
206
|
border: 1px solid var(--line);
|
|
188
|
-
border-radius:
|
|
207
|
+
border-radius: 12px;
|
|
189
208
|
background: var(--card);
|
|
190
209
|
color: var(--ink);
|
|
191
210
|
filter: var(--shadow);
|
|
@@ -206,18 +225,76 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
206
225
|
font-size: 11px;
|
|
207
226
|
color: var(--faint);
|
|
208
227
|
}
|
|
209
|
-
.tabs {
|
|
210
|
-
|
|
228
|
+
.tabs {
|
|
229
|
+
display: grid;
|
|
230
|
+
gap: 6px;
|
|
231
|
+
max-height: 259px;
|
|
232
|
+
margin: 2px 0 12px;
|
|
233
|
+
padding-right: 3px;
|
|
234
|
+
overflow-y: auto;
|
|
235
|
+
overscroll-behavior: contain;
|
|
236
|
+
}
|
|
237
|
+
.tabs:empty { display: none; }
|
|
238
|
+
.tabs::-webkit-scrollbar { width: 4px; }
|
|
239
|
+
.tabs::-webkit-scrollbar-track { background: transparent; }
|
|
240
|
+
.tabs::-webkit-scrollbar-thumb { border-radius: 999px; background: rgba(26, 58, 143, .2); }
|
|
211
241
|
.tab {
|
|
212
|
-
|
|
242
|
+
display: grid; grid-template-columns: 18px minmax(0, 1fr) 12px; align-items: center; column-gap: 8px;
|
|
243
|
+
width: 100%; min-height: 36px; font-size: 11px; line-height: 1.15; padding: 6px 9px; border-radius: 8px;
|
|
213
244
|
border: 1px solid rgba(0, 0, 0, .12); background: rgba(0, 0, 0, .04);
|
|
214
|
-
color: var(--faint); cursor: pointer;
|
|
245
|
+
color: var(--faint); cursor: pointer; text-align: left; font-family: inherit;
|
|
215
246
|
}
|
|
216
247
|
.tab:hover { background: rgba(0, 0, 0, .08); }
|
|
217
|
-
.tab.
|
|
218
|
-
|
|
248
|
+
.tab.viewing {
|
|
249
|
+
border-color: rgba(26, 58, 143, .72);
|
|
250
|
+
background: rgba(26, 58, 143, .11);
|
|
251
|
+
box-shadow: inset 4px 0 0 var(--blue), 0 1px 4px rgba(26, 58, 143, .12);
|
|
252
|
+
color: #111;
|
|
253
|
+
}
|
|
254
|
+
.tab-icon { width: 16px; height: 16px; object-fit: contain; }
|
|
255
|
+
.tab-copy { min-width: 0; display: grid; gap: 2px; }
|
|
256
|
+
.tab-title, .tab-repo { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
257
|
+
.tab-title { color: var(--ink); font-weight: 760; }
|
|
258
|
+
.tab-repo { color: var(--faint); font-size: 10px; font-weight: 600; }
|
|
259
|
+
.tab-status {
|
|
260
|
+
width: 10px;
|
|
261
|
+
height: 10px;
|
|
262
|
+
justify-self: end;
|
|
263
|
+
border: 2px solid #fff;
|
|
264
|
+
border-radius: 50%;
|
|
265
|
+
background: var(--green);
|
|
266
|
+
box-shadow: 0 0 0 1px rgba(26, 58, 143, .18);
|
|
267
|
+
}
|
|
268
|
+
.tab-status.amber { background: var(--amber); }
|
|
269
|
+
.tab-status.red { background: #ff5f57; box-shadow: 0 0 0 1px rgba(199, 55, 47, .28); }
|
|
270
|
+
.tab-status.idle { background: var(--faint); }
|
|
271
|
+
.tab.auto-tab {
|
|
272
|
+
position: sticky;
|
|
273
|
+
top: 0;
|
|
274
|
+
z-index: 1;
|
|
275
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
276
|
+
min-height: 28px;
|
|
277
|
+
background: #f4f2ed;
|
|
278
|
+
}
|
|
279
|
+
.tab.auto-tab.mode-active {
|
|
280
|
+
border-color: rgba(26, 58, 143, .28);
|
|
281
|
+
background: #f8f8f5;
|
|
282
|
+
}
|
|
283
|
+
.auto-check {
|
|
284
|
+
display: none;
|
|
285
|
+
width: 17px;
|
|
286
|
+
height: 17px;
|
|
287
|
+
align-items: center;
|
|
288
|
+
justify-content: center;
|
|
289
|
+
border-radius: 50%;
|
|
290
|
+
background: var(--blue);
|
|
291
|
+
color: #fff;
|
|
292
|
+
font-size: 10px;
|
|
293
|
+
font-weight: 850;
|
|
294
|
+
}
|
|
295
|
+
.auto-tab.mode-active .auto-check { display: inline-flex; }
|
|
219
296
|
.spin {
|
|
220
|
-
display: inline-block; width:
|
|
297
|
+
display: inline-block; width: 8px; height: 8px; margin-left: 4px; vertical-align: -1px;
|
|
221
298
|
border: 1.5px solid rgba(0, 0, 0, .2); border-top-color: #1a3a8f; border-radius: 50%;
|
|
222
299
|
animation: echospin .8s linear infinite;
|
|
223
300
|
}
|
|
@@ -225,18 +302,26 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
225
302
|
/* Mini mode: a small always-on pill instead of the full mascot+bubble. Opening the panel
|
|
226
303
|
temporarily shows the full layout; closing returns to the pill. */
|
|
227
304
|
#miniPill {
|
|
228
|
-
display: none; align-items: center;
|
|
229
|
-
margin: 7px 0 0 8px; padding:
|
|
305
|
+
display: none; align-items: center; gap: 8px; width: calc(100% - 16px); min-height: 38px;
|
|
306
|
+
margin: 7px 0 0 8px; padding: 5px 11px; cursor: pointer; user-select: none;
|
|
230
307
|
border: 1.6px solid rgba(26, 58, 143, 0.75); border-radius: 999px; background: #fdfaf2;
|
|
231
|
-
filter: var(--shadow);
|
|
232
|
-
font-size: 11.5px; font-weight: 700; color: var(--ink); letter-spacing: 0.02em;
|
|
308
|
+
filter: var(--shadow); color: var(--ink); letter-spacing: 0;
|
|
233
309
|
}
|
|
234
310
|
#miniPill .mdot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); flex: 0 0 auto; }
|
|
235
311
|
#miniPill .mdot.amber { background: var(--amber); }
|
|
236
|
-
#miniPill .mdot.
|
|
237
|
-
|
|
238
|
-
#
|
|
239
|
-
|
|
312
|
+
#miniPill .mdot.idle { background: var(--green); }
|
|
313
|
+
.micon { width: 16px; height: 16px; object-fit: contain; flex: 0 0 auto; }
|
|
314
|
+
#mscore {
|
|
315
|
+
flex: 0 0 auto;
|
|
316
|
+
font-family: "JetBrains Mono", ui-monospace, monospace;
|
|
317
|
+
font-size: 12px;
|
|
318
|
+
font-weight: 850;
|
|
319
|
+
}
|
|
320
|
+
.mcopy { min-width: 0; display: grid; gap: 1px; line-height: 1.1; }
|
|
321
|
+
#msession, #mrepo { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
322
|
+
#msession { font-size: 11.5px; font-weight: 760; color: var(--ink); }
|
|
323
|
+
#mrepo { font-size: 10px; font-weight: 650; color: var(--faint); }
|
|
324
|
+
#hud.mini:not(.open) .glance { height: 52px; }
|
|
240
325
|
#hud.mini:not(.open) .mascot-stage, #hud.mini:not(.open) .status-bubble, #hud.mini:not(.open) .drag { display: none; }
|
|
241
326
|
#hud.mini:not(.open) #miniPill { display: inline-flex; }
|
|
242
327
|
.cornerbtn {
|
|
@@ -255,12 +340,11 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
255
340
|
}
|
|
256
341
|
.switchbtn:hover { background: rgba(0, 0, 0, .1); color: #111; }
|
|
257
342
|
.agent-switch {
|
|
258
|
-
width:
|
|
259
|
-
min-width:
|
|
260
|
-
max-width:
|
|
343
|
+
width: 28px;
|
|
344
|
+
min-width: 28px;
|
|
345
|
+
max-width: 28px;
|
|
261
346
|
height: 28px;
|
|
262
|
-
|
|
263
|
-
padding: 0 10px 0 7px;
|
|
347
|
+
padding: 0;
|
|
264
348
|
border-radius: 999px;
|
|
265
349
|
background: rgba(255, 255, 255, .78);
|
|
266
350
|
color: #1f1f1f;
|
|
@@ -273,7 +357,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
273
357
|
.agent-switch[data-target-family="claude"] { border-color: rgba(217, 119, 87, .42); }
|
|
274
358
|
.agent-switch[data-target-family="codex"] { border-color: rgba(57, 65, 255, .34); }
|
|
275
359
|
.switch-icon { width: 16px; height: 16px; flex: 0 0 auto; object-fit: contain; }
|
|
276
|
-
.switch-copy {
|
|
360
|
+
.switch-copy { display: none; }
|
|
277
361
|
.reveal {
|
|
278
362
|
margin-top: 8px; font-size: 10.5px; padding: 4px 11px; border-radius: 999px;
|
|
279
363
|
border: 1px solid rgba(0, 0, 0, .12); background: rgba(0, 0, 0, .04); color: var(--faint);
|
|
@@ -284,87 +368,75 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
284
368
|
display: flex;
|
|
285
369
|
align-items: center;
|
|
286
370
|
gap: 9px;
|
|
287
|
-
margin-bottom:
|
|
371
|
+
margin-bottom: 9px;
|
|
288
372
|
}
|
|
289
373
|
.state-dot {
|
|
290
|
-
width:
|
|
291
|
-
height:
|
|
374
|
+
width: 12px;
|
|
375
|
+
height: 12px;
|
|
292
376
|
flex: none;
|
|
293
377
|
border-radius: 999px;
|
|
294
378
|
background: var(--green);
|
|
295
379
|
box-shadow: 0 0 0 1px rgba(26, 58, 143, 0.12);
|
|
296
380
|
}
|
|
297
381
|
.state-dot.amber { background: var(--amber); }
|
|
298
|
-
.state-dot.red { background: #ff5f57; }
|
|
299
382
|
.state-word {
|
|
300
383
|
min-width: 0;
|
|
301
384
|
overflow: hidden;
|
|
302
385
|
text-overflow: ellipsis;
|
|
303
386
|
white-space: nowrap;
|
|
304
|
-
font-
|
|
305
|
-
font-
|
|
306
|
-
|
|
307
|
-
.full {
|
|
308
|
-
min-height: 17px;
|
|
309
|
-
margin-bottom: 14px;
|
|
310
|
-
font-size: 12px;
|
|
311
|
-
font-weight: 650;
|
|
312
|
-
color: #4a7a3e;
|
|
387
|
+
font-family: "JetBrains Mono", ui-monospace, monospace;
|
|
388
|
+
font-size: 18px;
|
|
389
|
+
font-weight: 850;
|
|
313
390
|
}
|
|
314
|
-
.
|
|
315
|
-
margin-
|
|
316
|
-
|
|
391
|
+
.why-btn {
|
|
392
|
+
margin-left: auto;
|
|
393
|
+
border: 1px solid rgba(26, 58, 143, 0.16);
|
|
394
|
+
border-radius: 999px;
|
|
395
|
+
padding: 4px 10px;
|
|
396
|
+
background: rgba(26, 58, 143, 0.04);
|
|
397
|
+
color: var(--muted);
|
|
398
|
+
font-family: inherit;
|
|
399
|
+
font-size: 11px;
|
|
317
400
|
font-weight: 750;
|
|
318
|
-
|
|
401
|
+
cursor: pointer;
|
|
319
402
|
}
|
|
320
|
-
.
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
403
|
+
.why-btn:hover { background: rgba(26, 58, 143, 0.09); color: var(--ink); }
|
|
404
|
+
.full {
|
|
405
|
+
min-height: 16px;
|
|
406
|
+
margin-bottom: 3px;
|
|
407
|
+
overflow: hidden;
|
|
408
|
+
text-overflow: ellipsis;
|
|
409
|
+
white-space: nowrap;
|
|
324
410
|
font-size: 12.5px;
|
|
325
|
-
|
|
411
|
+
font-weight: 800;
|
|
412
|
+
color: var(--ink);
|
|
326
413
|
}
|
|
327
|
-
.
|
|
328
|
-
|
|
329
|
-
height: 9px;
|
|
330
|
-
margin: 0;
|
|
414
|
+
.lead {
|
|
415
|
+
margin-bottom: 0;
|
|
331
416
|
overflow: hidden;
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
height: 100%;
|
|
338
|
-
min-width: 0;
|
|
339
|
-
transition: width 0.35s ease;
|
|
417
|
+
text-overflow: ellipsis;
|
|
418
|
+
white-space: nowrap;
|
|
419
|
+
font-size: 11px;
|
|
420
|
+
font-weight: 650;
|
|
421
|
+
color: var(--faint);
|
|
340
422
|
}
|
|
341
|
-
.
|
|
342
|
-
.oldbar .old { width: 0; background: var(--brick); }
|
|
343
|
-
.alert {
|
|
423
|
+
.evidence {
|
|
344
424
|
display: none;
|
|
345
|
-
margin
|
|
346
|
-
padding: 9px
|
|
347
|
-
border
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
margin-bottom: 4px;
|
|
351
|
-
color: var(--brick);
|
|
352
|
-
font-size: 12.5px;
|
|
353
|
-
font-weight: 750;
|
|
354
|
-
}
|
|
355
|
-
.alert-detail {
|
|
425
|
+
margin: 11px 0 12px;
|
|
426
|
+
padding: 9px 10px;
|
|
427
|
+
border: 1px solid rgba(26, 58, 143, 0.12);
|
|
428
|
+
border-radius: 8px;
|
|
429
|
+
background: rgba(26, 58, 143, 0.035);
|
|
356
430
|
color: var(--muted);
|
|
357
431
|
font-size: 12px;
|
|
358
|
-
line-height: 1.
|
|
359
|
-
}
|
|
360
|
-
.nudge {
|
|
361
|
-
display: none;
|
|
362
|
-
margin-top: 13px;
|
|
363
|
-
padding: 10px 12px;
|
|
364
|
-
border-radius: 12px;
|
|
365
|
-
font-size: 12.5px;
|
|
366
|
-
line-height: 1.45;
|
|
432
|
+
line-height: 1.5;
|
|
367
433
|
}
|
|
434
|
+
#hud.show-why .evidence { display: block; }
|
|
435
|
+
.why-line { display: flex; justify-content: space-between; gap: 12px; }
|
|
436
|
+
.why-line + .why-line { margin-top: 3px; }
|
|
437
|
+
.why-k { color: var(--faint); }
|
|
438
|
+
.why-v { color: var(--ink); font-weight: 750; text-align: right; }
|
|
439
|
+
.nudge { display: none; }
|
|
368
440
|
.nudge.calm {
|
|
369
441
|
border: 1px solid rgba(26, 58, 143, 0.14);
|
|
370
442
|
background: rgba(26, 58, 143, 0.05);
|
|
@@ -375,30 +447,28 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
375
447
|
background: rgba(199, 55, 47, 0.07);
|
|
376
448
|
color: #7a241e;
|
|
377
449
|
}
|
|
378
|
-
/* Breathing CTA: quiet ghost pill while the session is healthy... */
|
|
379
450
|
button.renew-btn {
|
|
380
451
|
display: none;
|
|
381
|
-
width:
|
|
452
|
+
width: 100%;
|
|
382
453
|
margin-top: 12px;
|
|
383
|
-
border: 1px solid rgba(
|
|
454
|
+
border: 1px solid rgba(26, 58, 143, 0.26);
|
|
384
455
|
border-radius: 999px;
|
|
385
|
-
padding:
|
|
386
|
-
background:
|
|
387
|
-
color: var(--
|
|
456
|
+
padding: 9px 14px;
|
|
457
|
+
background: rgba(26, 58, 143, 0.07);
|
|
458
|
+
color: var(--blue);
|
|
388
459
|
font-weight: 750;
|
|
389
|
-
font-size:
|
|
460
|
+
font-size: 12px;
|
|
390
461
|
cursor: pointer;
|
|
391
462
|
transition: all 0.25s ease;
|
|
392
463
|
}
|
|
393
|
-
/* ...and a full-size primary the moment the session drifts. */
|
|
394
464
|
button.renew-btn.urgent {
|
|
395
|
-
|
|
396
|
-
padding: 11px
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
font-
|
|
401
|
-
box-shadow: 0
|
|
465
|
+
border-color: var(--blue);
|
|
466
|
+
padding: 11px 14px;
|
|
467
|
+
background: var(--blue);
|
|
468
|
+
color: #fff;
|
|
469
|
+
font-size: 13px;
|
|
470
|
+
font-weight: 800;
|
|
471
|
+
box-shadow: 0 4px 12px rgba(26, 58, 143, 0.22);
|
|
402
472
|
}
|
|
403
473
|
button.renew-btn b { font-weight: 800; }
|
|
404
474
|
button.renew-btn small { display: none; }
|
|
@@ -410,19 +480,19 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
410
480
|
opacity: 0.85;
|
|
411
481
|
white-space: normal;
|
|
412
482
|
}
|
|
413
|
-
button.renew-btn:hover { background: rgba(
|
|
414
|
-
button.renew-btn.urgent:hover { background: #
|
|
483
|
+
button.renew-btn:hover { background: rgba(26, 58, 143, 0.12); }
|
|
484
|
+
button.renew-btn.urgent:hover { background: #102d78; }
|
|
415
485
|
button.renew-btn.loading { width: 100%; opacity: 0.75; cursor: wait; }
|
|
416
486
|
.renew-result {
|
|
417
487
|
display: none;
|
|
418
488
|
margin-top: 10px;
|
|
419
489
|
border: 1px solid rgba(26, 58, 143, 0.18);
|
|
420
|
-
border-radius:
|
|
490
|
+
border-radius: 8px;
|
|
421
491
|
background: rgba(26, 58, 143, 0.04);
|
|
422
492
|
padding: 10px 12px;
|
|
423
493
|
}
|
|
424
494
|
.renew-result .renew-msg {
|
|
425
|
-
margin-bottom:
|
|
495
|
+
margin-bottom: 0;
|
|
426
496
|
color: var(--blue);
|
|
427
497
|
font-size: 11.5px;
|
|
428
498
|
font-weight: 750;
|
|
@@ -459,7 +529,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
459
529
|
color: var(--faint);
|
|
460
530
|
font-size: 11px;
|
|
461
531
|
}
|
|
462
|
-
.meta {
|
|
532
|
+
.meta { display: none; }
|
|
463
533
|
.path { margin-top: 8px; }
|
|
464
534
|
.missing { margin-top: 6px; }
|
|
465
535
|
#hud.s-green .mascot-stage { filter: drop-shadow(0 2px 6px rgba(67, 209, 122, 0.3)); }
|
|
@@ -502,35 +572,34 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
502
572
|
<span class="copy">
|
|
503
573
|
<span class="main" id="main">Checking context...</span>
|
|
504
574
|
<span class="client" id="client">EchoMem HUD</span>
|
|
575
|
+
<span class="client-repo" id="clientrepo"></span>
|
|
505
576
|
</span>
|
|
506
577
|
<button class="cornerbtn" id="minibtn" title="Shrink to mini pill">–</button>
|
|
507
578
|
</div>
|
|
508
|
-
<div id="miniPill" title="EchoMem — click to expand"
|
|
579
|
+
<div id="miniPill" title="EchoMem — click to expand">
|
|
580
|
+
<span class="mdot" id="mdot"></span>
|
|
581
|
+
<img class="micon" id="micon" src="/assets/hud/codex.svg" alt="" />
|
|
582
|
+
<span id="mscore">on</span>
|
|
583
|
+
<span class="mcopy"><span id="msession">EchoMem</span><span id="mrepo"></span></span>
|
|
584
|
+
</div>
|
|
509
585
|
</div>
|
|
510
586
|
<div class="details">
|
|
511
587
|
<div class="head"><span class="upd" id="updated"></span><span class="headctl"><span class="turn" id="turn"></span><button class="switchbtn agent-switch" id="switchbtn" title="Switch agent" style="display:none" type="button"></button></span></div>
|
|
512
588
|
<div class="tabs" id="tabs"></div>
|
|
513
589
|
<div class="state-row">
|
|
514
590
|
<span class="state-dot" id="stateDot"></span>
|
|
515
|
-
<span class="state-word" id="stateWord"
|
|
591
|
+
<span class="state-word" id="stateWord">--%</span>
|
|
592
|
+
<button class="why-btn" id="whybtn" type="button" style="display:none">why</button>
|
|
516
593
|
</div>
|
|
517
594
|
<div class="full" id="fullness"></div>
|
|
518
|
-
<div class="lead" id="lead"
|
|
519
|
-
<div class="evidence" id="evidence"
|
|
520
|
-
<div class="oldbar" aria-hidden="true">
|
|
521
|
-
<span class="useful" id="usefulbar"></span>
|
|
522
|
-
<span class="old" id="oldbar"></span>
|
|
523
|
-
</div>
|
|
524
|
-
<div class="alert" id="alert">
|
|
525
|
-
<div class="alert-title" id="alertTitle"></div>
|
|
526
|
-
<div class="alert-detail" id="alertDetail"></div>
|
|
527
|
-
</div>
|
|
595
|
+
<div class="lead" id="lead"></div>
|
|
596
|
+
<div class="evidence" id="evidence"></div>
|
|
528
597
|
<div class="nudge" id="nudge"></div>
|
|
529
|
-
<button class="renew-btn" id="renewbtn">
|
|
598
|
+
<button class="renew-btn" id="renewbtn">Start new with context</button>
|
|
530
599
|
<div class="renew-result" id="renewresult">
|
|
531
600
|
<div class="renew-msg" id="renewmsg"></div>
|
|
532
601
|
<pre id="renewpre"></pre>
|
|
533
|
-
<button class="copy-btn" id="copybtn">Copy
|
|
602
|
+
<button class="copy-btn" id="copybtn">Copy</button>
|
|
534
603
|
</div>
|
|
535
604
|
<div class="meta" id="meta"></div>
|
|
536
605
|
<div class="path" id="path"></div>
|
|
@@ -543,17 +612,14 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
543
612
|
const dot = document.getElementById("dot");
|
|
544
613
|
const main = document.getElementById("main");
|
|
545
614
|
const client = document.getElementById("client");
|
|
615
|
+
const clientRepo = document.getElementById("clientrepo");
|
|
546
616
|
const turnEl = document.getElementById("turn");
|
|
547
617
|
const stateDot = document.getElementById("stateDot");
|
|
548
618
|
const stateWordEl = document.getElementById("stateWord");
|
|
619
|
+
const whyBtn = document.getElementById("whybtn");
|
|
549
620
|
const fullness = document.getElementById("fullness");
|
|
550
621
|
const lead = document.getElementById("lead");
|
|
551
622
|
const evidence = document.getElementById("evidence");
|
|
552
|
-
const usefulbar = document.getElementById("usefulbar");
|
|
553
|
-
const oldbar = document.getElementById("oldbar");
|
|
554
|
-
const alertEl = document.getElementById("alert");
|
|
555
|
-
const alertTitle = document.getElementById("alertTitle");
|
|
556
|
-
const alertDetail = document.getElementById("alertDetail");
|
|
557
623
|
const nudge = document.getElementById("nudge");
|
|
558
624
|
const renewBtn = document.getElementById("renewbtn");
|
|
559
625
|
const renewResult = document.getElementById("renewresult");
|
|
@@ -567,7 +633,6 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
567
633
|
const switchBtn = document.getElementById("switchbtn");
|
|
568
634
|
const revealEl = document.getElementById("reveal");
|
|
569
635
|
const missing = document.getElementById("missing");
|
|
570
|
-
const hudVersion = "v${MCP_PACKAGE_VERSION}";
|
|
571
636
|
let prevTurnState = null;
|
|
572
637
|
let lastCapsule = "";
|
|
573
638
|
let renewResultSessionKey = "";
|
|
@@ -594,8 +659,12 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
594
659
|
// returns to the pill. Preference persists (localStorage here, window size in main's bounds file).
|
|
595
660
|
const miniBtn = document.getElementById("minibtn");
|
|
596
661
|
const mdot = document.getElementById("mdot");
|
|
597
|
-
const
|
|
662
|
+
const micon = document.getElementById("micon");
|
|
663
|
+
const mscore = document.getElementById("mscore");
|
|
664
|
+
const msession = document.getElementById("msession");
|
|
665
|
+
const mrepo = document.getElementById("mrepo");
|
|
598
666
|
let miniOn = false;
|
|
667
|
+
let whyOpen = false;
|
|
599
668
|
try { miniOn = localStorage.getItem("echomemHudMini") === "1"; } catch {}
|
|
600
669
|
if (miniOn) hud.classList.add("mini");
|
|
601
670
|
function applyMini(next) {
|
|
@@ -606,6 +675,13 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
606
675
|
if (window.echomemHud && typeof window.echomemHud.setMini === "function") window.echomemHud.setMini(next);
|
|
607
676
|
}
|
|
608
677
|
if (miniBtn) miniBtn.addEventListener("click", (e) => { e.stopPropagation(); applyMini(true); });
|
|
678
|
+
if (whyBtn) whyBtn.addEventListener("click", (e) => {
|
|
679
|
+
e.stopPropagation();
|
|
680
|
+
whyOpen = !whyOpen;
|
|
681
|
+
hud.classList.toggle("show-why", whyOpen);
|
|
682
|
+
whyBtn.textContent = whyOpen ? "hide" : "why";
|
|
683
|
+
syncHeight();
|
|
684
|
+
});
|
|
609
685
|
let hudDrag = null;
|
|
610
686
|
let suppressNextToggle = false;
|
|
611
687
|
const dragBlockSelector = "button, input, textarea, select, a, pre, .tab, .switchbtn, .reveal, .renew-btn, .copy-btn";
|
|
@@ -694,8 +770,6 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
694
770
|
syncHeight();
|
|
695
771
|
});
|
|
696
772
|
|
|
697
|
-
// Breathing CTA state: quiet while fresh, primary while drifted. Label carries the payoff so
|
|
698
|
-
// the user knows renewing does not lose their trail.
|
|
699
773
|
let renewUrgent = false;
|
|
700
774
|
function refreshRenewLabel() {
|
|
701
775
|
if (!renewBtn) return;
|
|
@@ -704,9 +778,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
704
778
|
renderRenewLoadingLabel();
|
|
705
779
|
return;
|
|
706
780
|
}
|
|
707
|
-
renewBtn.
|
|
708
|
-
? '<b>Renew session</b><small>I pack a checkpoint and carry it into your fresh start</small>'
|
|
709
|
-
: "Renew session";
|
|
781
|
+
renewBtn.textContent = "Start new with context";
|
|
710
782
|
}
|
|
711
783
|
|
|
712
784
|
if (renewBtn) renewBtn.addEventListener("click", async () => {
|
|
@@ -754,20 +826,14 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
754
826
|
showRenewResult({
|
|
755
827
|
sessionKey: renewSessionKey,
|
|
756
828
|
prompt,
|
|
757
|
-
message: promptKind === "local"
|
|
758
|
-
? "Basic local capsule ready. Cloud decision extraction was unavailable; copy it below and paste it into a fresh session."
|
|
759
|
-
: "Decision log ready, copied to your clipboard. Open a fresh session and paste it as the first message.",
|
|
829
|
+
message: promptKind === "local" ? "Local capsule ready" : "Decision log ready",
|
|
760
830
|
canCopy: true,
|
|
761
831
|
});
|
|
762
832
|
try {
|
|
763
833
|
await navigator.clipboard.writeText(prompt);
|
|
764
|
-
if (renewMsg) renewMsg.textContent = promptKind === "local"
|
|
765
|
-
? "Basic local capsule copied. Cloud decision extraction was unavailable; this includes goal, recent instruction, and file anchors, but may miss decisions."
|
|
766
|
-
: "Decision log ready, copied to your clipboard. Open a fresh session and paste it as the first message.";
|
|
834
|
+
if (renewMsg) renewMsg.textContent = promptKind === "local" ? "Local capsule copied" : "Decision log copied";
|
|
767
835
|
} catch {
|
|
768
|
-
if (renewMsg) renewMsg.textContent = promptKind === "local"
|
|
769
|
-
? "Basic local capsule ready. Cloud decision extraction was unavailable; copy it below and paste it into a fresh session."
|
|
770
|
-
: "Decision log ready. Copy it below, then paste it into a fresh session.";
|
|
836
|
+
if (renewMsg) renewMsg.textContent = promptKind === "local" ? "Local capsule ready" : "Decision log ready";
|
|
771
837
|
}
|
|
772
838
|
} catch {
|
|
773
839
|
} finally {
|
|
@@ -783,7 +849,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
783
849
|
try {
|
|
784
850
|
await navigator.clipboard.writeText(lastCapsule);
|
|
785
851
|
copyBtn.textContent = "Copied";
|
|
786
|
-
setTimeout(() => { copyBtn.textContent = "Copy
|
|
852
|
+
setTimeout(() => { copyBtn.textContent = "Copy"; }, 1500);
|
|
787
853
|
} catch {}
|
|
788
854
|
});
|
|
789
855
|
|
|
@@ -806,11 +872,11 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
806
872
|
renewResultSessionKey = "";
|
|
807
873
|
if (renewPre) {
|
|
808
874
|
renewPre.textContent = "";
|
|
809
|
-
renewPre.style.display = "";
|
|
875
|
+
renewPre.style.display = "none";
|
|
810
876
|
}
|
|
811
877
|
if (renewMsg) renewMsg.textContent = "";
|
|
812
878
|
if (copyBtn) {
|
|
813
|
-
copyBtn.textContent = "Copy
|
|
879
|
+
copyBtn.textContent = "Copy";
|
|
814
880
|
copyBtn.style.display = "";
|
|
815
881
|
}
|
|
816
882
|
if (renewResult) renewResult.style.display = "none";
|
|
@@ -824,10 +890,10 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
824
890
|
if (renewMsg) renewMsg.textContent = String((opts && opts.message) || "");
|
|
825
891
|
if (renewPre) {
|
|
826
892
|
renewPre.textContent = body;
|
|
827
|
-
renewPre.style.display =
|
|
893
|
+
renewPre.style.display = "none";
|
|
828
894
|
}
|
|
829
895
|
if (copyBtn) {
|
|
830
|
-
copyBtn.textContent = "Copy
|
|
896
|
+
copyBtn.textContent = "Copy";
|
|
831
897
|
copyBtn.style.display = opts && opts.canCopy ? "inline-block" : "none";
|
|
832
898
|
}
|
|
833
899
|
if (renewResult) renewResult.style.display = "block";
|
|
@@ -895,13 +961,11 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
895
961
|
const stale = savedTurn > 0 && currentTurn > savedTurn;
|
|
896
962
|
const summary = checkpointSummary(cached);
|
|
897
963
|
if (cached && cached.kind === "local") {
|
|
898
|
-
return
|
|
899
|
-
? "Basic local capsule from turn " + savedTurn + ". Renew again for the latest cloud decision log."
|
|
900
|
-
: "Basic local capsule already ready for this session. Cloud decision extraction was unavailable when it was made.";
|
|
964
|
+
return savedTurn > 0 ? "Local capsule from turn " + savedTurn + checkpointSuffix(summary) : "Local capsule ready" + checkpointSuffix(summary);
|
|
901
965
|
}
|
|
902
|
-
return stale
|
|
903
|
-
? "Decision log from turn " + savedTurn +
|
|
904
|
-
: "Decision log
|
|
966
|
+
return savedTurn > 0 && stale
|
|
967
|
+
? "Decision log from turn " + savedTurn + checkpointSuffix(summary)
|
|
968
|
+
: "Decision log ready" + checkpointSuffix(summary);
|
|
905
969
|
}
|
|
906
970
|
|
|
907
971
|
function statusFromRenewData(data, meta) {
|
|
@@ -921,7 +985,11 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
921
985
|
if (renewedAt) parts.push("renewed " + relTime(Date.now() - renewedAt));
|
|
922
986
|
const count = Number(item && (item.memoryCount ?? item.sessionMemoryCount));
|
|
923
987
|
if (Number.isFinite(count) && count > 0) parts.push(count + " " + (count === 1 ? "memory" : "memories") + " saved");
|
|
924
|
-
return parts.
|
|
988
|
+
return parts.join(" · ");
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function checkpointSuffix(summary) {
|
|
992
|
+
return summary ? " · " + summary : "";
|
|
925
993
|
}
|
|
926
994
|
|
|
927
995
|
function timestampMs(value) {
|
|
@@ -981,7 +1049,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
981
1049
|
showRenewResult({
|
|
982
1050
|
sessionKey: key,
|
|
983
1051
|
prompt: body,
|
|
984
|
-
message: "Decision log
|
|
1052
|
+
message: "Decision log ready" + checkpointSuffix(summary),
|
|
985
1053
|
canCopy: true,
|
|
986
1054
|
});
|
|
987
1055
|
}
|
|
@@ -1071,7 +1139,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1071
1139
|
const elapsed = Date.now() - renewStartMs;
|
|
1072
1140
|
const remaining = Math.max(0, Number(renewEta && renewEta.ms) || 20000) - elapsed;
|
|
1073
1141
|
const detail = remaining > 0 ? "ETA " + fmtDuration(remaining) + " left" : "ETA any moment";
|
|
1074
|
-
renewBtn.innerHTML = "<b>
|
|
1142
|
+
renewBtn.innerHTML = "<b>Preparing context…</b><small>" + esc(detail) + "</small>";
|
|
1075
1143
|
}
|
|
1076
1144
|
|
|
1077
1145
|
function fallbackEtaForScore(score) {
|
|
@@ -1094,13 +1162,17 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1094
1162
|
|
|
1095
1163
|
// Dev/design preview: ?preview=green|amber|red renders that health state with sample data and
|
|
1096
1164
|
// skips the live feed — same pattern as the extraction page's ?preview hook.
|
|
1097
|
-
const
|
|
1165
|
+
const previewParams = new URLSearchParams(location.search);
|
|
1166
|
+
const previewColor = previewParams.get("preview");
|
|
1167
|
+
const previewPinned = previewParams.get("pinned") === "1";
|
|
1168
|
+
if (previewParams.get("capture") === "1") hud.classList.add("capture-preview");
|
|
1098
1169
|
if (previewColor === "green" || previewColor === "amber" || previewColor === "red") {
|
|
1099
1170
|
const sample = {
|
|
1100
1171
|
client: "codex",
|
|
1101
1172
|
label: "EchoMem-Chrome",
|
|
1102
|
-
sourcePath: "/preview/session.jsonl",
|
|
1173
|
+
sourcePath: previewPinned ? "/preview/b.jsonl" : "/preview/session.jsonl",
|
|
1103
1174
|
usefulPct: previewColor === "red" ? 55 : previewColor === "amber" ? 75 : 95,
|
|
1175
|
+
healthScorePct: previewColor === "red" ? 48 : previewColor === "amber" ? 72 : 96,
|
|
1104
1176
|
color: previewColor,
|
|
1105
1177
|
ctTokens: previewColor === "red" ? 165000 : previewColor === "amber" ? 110000 : 24000,
|
|
1106
1178
|
modelContextWindow: 258000,
|
|
@@ -1114,11 +1186,16 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1114
1186
|
};
|
|
1115
1187
|
render({
|
|
1116
1188
|
active: sample,
|
|
1189
|
+
pinnedId: previewPinned ? "p2" : null,
|
|
1117
1190
|
recentSessions: [
|
|
1118
|
-
{ id: "p1", client: "codex", title: "EchoMem-Chrome", sourcePath: "/preview/session.jsonl", live: true },
|
|
1119
|
-
{ id: "p2", client: "codex", title: "修复 HUD 切换和图标", sourcePath: "/preview/b.jsonl", live: false },
|
|
1120
|
-
{ id: "p3", client: "codex", title: "Fix extraction preview", sourcePath: "/preview/c.jsonl", live: false },
|
|
1121
|
-
{ id: "p4", client: "
|
|
1191
|
+
{ id: "p1", client: "codex", title: "Simplify HUD session display", label: "EchoMem-Chrome", sourcePath: "/preview/session.jsonl", live: true },
|
|
1192
|
+
{ id: "p2", client: "codex", title: "修复 HUD 切换和图标", label: "EchoMem-Chrome", sourcePath: "/preview/b.jsonl", live: false },
|
|
1193
|
+
{ id: "p3", client: "codex", title: "Fix extraction preview", label: "EchoMem-Chrome", sourcePath: "/preview/c.jsonl", live: false },
|
|
1194
|
+
{ id: "p4", client: "codex", title: "Port Claude work", label: "EchoMem-Chrome", sourcePath: "/preview/d.jsonl", live: false },
|
|
1195
|
+
{ id: "p5", client: "codex", title: "Stamp fixes with Echo mascot", label: "EchoMem-Chrome", sourcePath: "/preview/e.jsonl", live: false },
|
|
1196
|
+
{ id: "p6", client: "codex", title: "Review migration checks", label: "EchoMem-Chrome", sourcePath: "/preview/f.jsonl", live: false },
|
|
1197
|
+
{ id: "p7", client: "codex", title: "Polish setup story", label: "EchoMem-Chrome", sourcePath: "/preview/g.jsonl", live: false },
|
|
1198
|
+
{ id: "p8", client: "claude-code", title: "DG-Web refactor", label: "DG-Web", sourcePath: "/preview/h.jsonl", live: false },
|
|
1122
1199
|
],
|
|
1123
1200
|
});
|
|
1124
1201
|
hud.classList.add("open");
|
|
@@ -1134,8 +1211,9 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1134
1211
|
lastState = state;
|
|
1135
1212
|
const activeClient = score && score.client ? score.client : state && state.active && state.active.client;
|
|
1136
1213
|
const currentSessionKey = sessionKeyFor(state, score);
|
|
1214
|
+
const viewColor = score && typeof score.usefulPct === "number" ? (score.color || "green") : "idle";
|
|
1137
1215
|
if (renewResultSessionKey && renewResultSessionKey !== currentSessionKey) clearRenewResult();
|
|
1138
|
-
renderTabs(state, score && score.sourcePath ? activeIdFor(state, score.sourcePath, activeClient) : null, activeClient);
|
|
1216
|
+
const hasSessionSwitcher = renderTabs(state, score && score.sourcePath ? activeIdFor(state, score.sourcePath, activeClient) : null, activeClient, viewColor);
|
|
1139
1217
|
renderSwitchButton(state, activeClient);
|
|
1140
1218
|
if (!score || typeof score.usefulPct !== "number") {
|
|
1141
1219
|
renderIdle(state);
|
|
@@ -1143,54 +1221,43 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1143
1221
|
}
|
|
1144
1222
|
|
|
1145
1223
|
const color = score.color || "green";
|
|
1146
|
-
const stateName = stateWord(color);
|
|
1147
1224
|
const sat = fullnessPct(score);
|
|
1148
1225
|
const oldPct = clamp(score.pollutionPct || 0, 0, 95);
|
|
1226
|
+
const health = healthScore(score);
|
|
1149
1227
|
const repeatCount = score.buckets && score.buckets.range_redundant ? score.buckets.range_redundant.count || 0 : 0;
|
|
1150
|
-
const
|
|
1228
|
+
const info = activeSessionInfo(score, state);
|
|
1151
1229
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
const stale = typeof score.lastActiveMs === "number" && score.lastActiveMs > 600000;
|
|
1155
|
-
const shownColor = stale ? "idle" : color;
|
|
1156
|
-
dot.className = "dot " + shownColor;
|
|
1157
|
-
stateDot.className = stale ? "state-dot" : "state-dot " + color;
|
|
1230
|
+
dot.className = "dot " + color;
|
|
1231
|
+
stateDot.className = "state-dot " + color;
|
|
1158
1232
|
hud.classList.remove("s-green", "s-amber", "s-red");
|
|
1159
|
-
|
|
1160
|
-
main.textContent =
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
const urgent = !stale && color === "red";
|
|
1166
|
-
client.textContent = urgent ? "let me carry this forward →" : sessionIdentity(score, state);
|
|
1167
|
-
client.classList.toggle("voice", urgent);
|
|
1233
|
+
hud.classList.add("s-" + color);
|
|
1234
|
+
main.textContent = health + "%";
|
|
1235
|
+
renderMini(color, health + "%", info);
|
|
1236
|
+
client.textContent = info.title || info.repo;
|
|
1237
|
+
if (clientRepo) clientRepo.textContent = info.repo && info.repo !== info.title ? info.repo : "";
|
|
1238
|
+
client.classList.remove("voice");
|
|
1168
1239
|
activityEpoch = typeof score.lastActiveMs === "number" ? Date.now() - score.lastActiveMs : null;
|
|
1169
1240
|
renderUpdated();
|
|
1170
1241
|
turnEl.textContent = typeof score.turn === "number" && score.turn > 0 ? "turn " + score.turn : "";
|
|
1171
|
-
stateWordEl.textContent =
|
|
1172
|
-
fullness.textContent =
|
|
1173
|
-
fullness.style.
|
|
1174
|
-
|
|
1175
|
-
lead.
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
metaEl.textContent =
|
|
1242
|
+
stateWordEl.textContent = health + "%";
|
|
1243
|
+
fullness.textContent = hasSessionSwitcher ? "" : info.title;
|
|
1244
|
+
fullness.style.display = hasSessionSwitcher ? "none" : "block";
|
|
1245
|
+
fullness.style.color = "";
|
|
1246
|
+
lead.textContent = hasSessionSwitcher ? "" : (info.repo && info.repo !== info.title ? info.repo : "");
|
|
1247
|
+
lead.style.display = hasSessionSwitcher || !lead.textContent ? "none" : "block";
|
|
1248
|
+
lead.style.color = "";
|
|
1249
|
+
renderWhy(score, oldPct, repeatCount, sat, health, color);
|
|
1250
|
+
metaEl.textContent = "";
|
|
1180
1251
|
pathEl.textContent = "";
|
|
1181
1252
|
currentSrc = score.sourcePath || null;
|
|
1182
1253
|
if (revealEl) revealEl.style.display = !DEMO_CLEAN && currentSrc ? "inline-block" : "none";
|
|
1183
1254
|
missing.textContent = formatMissing(state.missing);
|
|
1184
1255
|
|
|
1185
|
-
renderAlert(score, oldPct, repeatCount);
|
|
1186
|
-
// The old advice box repeated the verdict a third time; its job now lives in leadLine + the
|
|
1187
|
-
// renew button's own subtitle.
|
|
1188
1256
|
nudge.style.display = "none";
|
|
1189
1257
|
|
|
1190
|
-
|
|
1191
|
-
renewUrgent = urgent;
|
|
1258
|
+
renewUrgent = color === "red";
|
|
1192
1259
|
if (renewBtn) {
|
|
1193
|
-
renewBtn.style.display = "block";
|
|
1260
|
+
renewBtn.style.display = "block";
|
|
1194
1261
|
refreshRenewLabel();
|
|
1195
1262
|
}
|
|
1196
1263
|
const restoredPrompt = restoreCachedCheckpointFor(score, currentSessionKey);
|
|
@@ -1214,23 +1281,26 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1214
1281
|
stateDot.className = "state-dot";
|
|
1215
1282
|
hud.classList.remove("s-green", "s-amber", "s-red");
|
|
1216
1283
|
prevTurnState = null;
|
|
1217
|
-
main.textContent = "
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1284
|
+
main.textContent = "MCP on";
|
|
1285
|
+
renderMini("idle", "on", { client: "EchoMem", title: "MCP", repo: "" });
|
|
1286
|
+
client.textContent = "Waiting for Codex or Claude";
|
|
1287
|
+
if (clientRepo) clientRepo.textContent = "";
|
|
1288
|
+
client.classList.remove("voice");
|
|
1221
1289
|
activityEpoch = null;
|
|
1222
1290
|
if (updatedEl) updatedEl.textContent = "";
|
|
1223
1291
|
currentSrc = null;
|
|
1224
1292
|
if (revealEl) revealEl.style.display = "none";
|
|
1225
1293
|
turnEl.textContent = "";
|
|
1226
|
-
stateWordEl.textContent = "
|
|
1227
|
-
fullness.textContent = "";
|
|
1228
|
-
|
|
1294
|
+
stateWordEl.textContent = "on";
|
|
1295
|
+
fullness.textContent = "EchoMem MCP";
|
|
1296
|
+
fullness.style.display = "block";
|
|
1297
|
+
lead.textContent = "Waiting for Codex or Claude";
|
|
1298
|
+
lead.style.display = "block";
|
|
1229
1299
|
lead.style.color = "#6b675d";
|
|
1230
|
-
evidence.textContent = "
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1300
|
+
evidence.textContent = "";
|
|
1301
|
+
if (whyBtn) whyBtn.style.display = "none";
|
|
1302
|
+
whyOpen = false;
|
|
1303
|
+
hud.classList.remove("show-why");
|
|
1234
1304
|
nudge.style.display = "none";
|
|
1235
1305
|
if (renewBtn) renewBtn.style.display = "none";
|
|
1236
1306
|
clearRenewResult();
|
|
@@ -1240,66 +1310,33 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1240
1310
|
syncHeight();
|
|
1241
1311
|
}
|
|
1242
1312
|
|
|
1243
|
-
function
|
|
1244
|
-
if (
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1313
|
+
function renderMini(color, scoreText, info) {
|
|
1314
|
+
if (mdot) mdot.className = "mdot " + color;
|
|
1315
|
+
if (micon) micon.src = iconForFamily(agentFamily(info && info.clientRaw) || "codex");
|
|
1316
|
+
if (mscore) mscore.textContent = scoreText;
|
|
1317
|
+
if (msession) msession.textContent = miniTitle(info);
|
|
1318
|
+
if (mrepo) mrepo.textContent = info && info.repo ? info.repo : "";
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function renderWhy(score, oldPct, repeatCount, sat, health, color) {
|
|
1322
|
+
const rows = [];
|
|
1323
|
+
rows.push(["score", health + "%"]);
|
|
1324
|
+
if (sat > 0) rows.push(["context", score.modelContextWindow ? sat + "% full" : fmt(score.ctTokens) + " tokens"]);
|
|
1325
|
+
if (oldPct > 0 || score.pollutionTok > 0) rows.push(["old context", fmt(score.pollutionTok || 0) + " (" + Math.round(oldPct) + "%)"]);
|
|
1326
|
+
if (repeatCount > 0) rows.push(["repeated reads", String(repeatCount)]);
|
|
1327
|
+
rows.push(["source", ctSourceLabel(score.ctSource)]);
|
|
1328
|
+
evidence.innerHTML = rows.map((row) => '<div class="why-line"><span class="why-k">' + esc(row[0]) + '</span><span class="why-v">' + esc(row[1]) + "</span></div>").join("");
|
|
1329
|
+
const useful = color !== "green" || oldPct >= 10 || repeatCount > 0 || sat >= 80;
|
|
1330
|
+
if (whyBtn) {
|
|
1331
|
+
whyBtn.style.display = useful ? "inline-flex" : "none";
|
|
1332
|
+
whyBtn.textContent = whyOpen ? "hide" : "why";
|
|
1259
1333
|
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
function renderNudge(color, sat) {
|
|
1266
|
-
let text = "";
|
|
1267
|
-
let cls = "nudge";
|
|
1268
|
-
if (color === "red") {
|
|
1269
|
-
text = "A fresh start now beats pushing past this - keep the goal, drop the clutter.";
|
|
1270
|
-
cls += " red";
|
|
1271
|
-
} else if (color === "amber" || sat >= 75) {
|
|
1272
|
-
text = "Still fine - a fresh start soon will keep this snappy.";
|
|
1273
|
-
cls += " calm";
|
|
1334
|
+
if (!useful) {
|
|
1335
|
+
whyOpen = false;
|
|
1336
|
+
hud.classList.remove("show-why");
|
|
1337
|
+
} else {
|
|
1338
|
+
hud.classList.toggle("show-why", whyOpen);
|
|
1274
1339
|
}
|
|
1275
|
-
nudge.textContent = text;
|
|
1276
|
-
nudge.className = cls;
|
|
1277
|
-
nudge.style.display = text ? "block" : "none";
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
function stateWord(color) {
|
|
1281
|
-
if (color === "red") return "Drifted";
|
|
1282
|
-
if (color === "amber") return "Drifting";
|
|
1283
|
-
return "Fresh";
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
// Echo's voice: one line that says how it is AND what to do — replaces the old pile of
|
|
1287
|
-
// metaphor headline + callout + advice box that all repeated the same verdict.
|
|
1288
|
-
function leadLine(color) {
|
|
1289
|
-
if (color === "red") return "Old context is crowding the window. Renew and I carry your work over.";
|
|
1290
|
-
if (color === "amber") return "Getting full. Wrap this thought; a fresh start soon keeps it snappy.";
|
|
1291
|
-
return "All clear. I am taking notes as you go.";
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
function evidenceLine(score, oldPct, repeatCount) {
|
|
1295
|
-
const repeat = repeatCount > 0 ? repeatCount + " repeated read" + (repeatCount === 1 ? "" : "s") : "no re-reads yet";
|
|
1296
|
-
return fmt(score.pollutionTok) + " old context · " + repeat;
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
function fullnessLine(sat, score) {
|
|
1300
|
-
if (!score.modelContextWindow) return fmt(score.ctTokens) + " context tokens";
|
|
1301
|
-
const room = sat >= 85 ? " - compaction near" : sat <= 30 ? " - plenty of room" : "";
|
|
1302
|
-
return sat + "% full - " + fmt(score.ctTokens) + " / " + fmt(score.modelContextWindow) + room;
|
|
1303
1340
|
}
|
|
1304
1341
|
|
|
1305
1342
|
function fullnessPct(score) {
|
|
@@ -1308,10 +1345,10 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1308
1345
|
return 0;
|
|
1309
1346
|
}
|
|
1310
1347
|
|
|
1311
|
-
function
|
|
1312
|
-
if (
|
|
1313
|
-
if (
|
|
1314
|
-
return
|
|
1348
|
+
function healthScore(score) {
|
|
1349
|
+
if (typeof score.healthScorePct === "number" && isFinite(score.healthScorePct)) return clamp(score.healthScorePct, 0, 100);
|
|
1350
|
+
if (typeof score.usefulPct === "number" && isFinite(score.usefulPct)) return clamp(score.usefulPct, 0, 100);
|
|
1351
|
+
return 0;
|
|
1315
1352
|
}
|
|
1316
1353
|
|
|
1317
1354
|
function ctSourceLabel(src) {
|
|
@@ -1324,13 +1361,23 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1324
1361
|
return "Not seeing " + list.map(label).join(", ") + " yet";
|
|
1325
1362
|
}
|
|
1326
1363
|
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1364
|
+
function activeSessionInfo(score, state) {
|
|
1365
|
+
const list = (state && state.recentSessions) || [];
|
|
1366
|
+
const match = list.filter((s) => s.client === score.client && s.sourcePath === score.sourcePath)[0];
|
|
1367
|
+
const repo = String((match && match.label) || score.label || "");
|
|
1368
|
+
const title = String((match && match.title) || repo || label(score.client));
|
|
1369
|
+
return {
|
|
1370
|
+
client: label(score.client),
|
|
1371
|
+
clientRaw: score.client,
|
|
1372
|
+
title,
|
|
1373
|
+
repo,
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
function miniTitle(info) {
|
|
1378
|
+
if (!info) return "EchoMem";
|
|
1379
|
+
const title = info.title && info.title !== info.client ? info.title : info.repo;
|
|
1380
|
+
return title || info.client;
|
|
1334
1381
|
}
|
|
1335
1382
|
|
|
1336
1383
|
function renderSwitchButton(state, activeClient) {
|
|
@@ -1361,21 +1408,28 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1361
1408
|
return list.filter((s) => agentFamily(s.client) === targetFamily)[0] || null;
|
|
1362
1409
|
}
|
|
1363
1410
|
|
|
1364
|
-
// Recent sessions as switchable
|
|
1411
|
+
// Recent sessions as switchable rows. "Auto" clears the pin. Hidden when there's only one
|
|
1365
1412
|
// session for the active agent family, so Codex never recommends Claude sessions or vice versa.
|
|
1366
|
-
function renderTabs(state, activeId, activeClient) {
|
|
1367
|
-
if (!tabsEl) return;
|
|
1413
|
+
function renderTabs(state, activeId, activeClient, viewColor) {
|
|
1414
|
+
if (!tabsEl) return false;
|
|
1368
1415
|
const list = visibleRecentSessions(state, activeClient);
|
|
1369
|
-
if (list.length <= 1) { tabsEl.innerHTML = ""; return; }
|
|
1416
|
+
if (list.length <= 1) { tabsEl.innerHTML = ""; return false; }
|
|
1370
1417
|
const pinnedId = state && state.pinnedId ? state.pinnedId : null;
|
|
1371
1418
|
const chips = list.map((s) => {
|
|
1372
|
-
const
|
|
1373
|
-
const
|
|
1419
|
+
const viewing = s.id === activeId;
|
|
1420
|
+
const cls = "tab" + (viewing ? " viewing" : "") + (s.id === pinnedId ? " pinned" : "");
|
|
1421
|
+
const activity = s.live && !viewing ? '<span class="spin"></span>' : "";
|
|
1422
|
+
const status = viewing ? '<span class="tab-status ' + esc(viewColor || "idle") + '" aria-label="Current session health"></span>' : "";
|
|
1374
1423
|
const title = s.title || label(s.client);
|
|
1375
|
-
|
|
1424
|
+
const repo = s.label && s.label !== title ? s.label : "";
|
|
1425
|
+
const family = agentFamily(s.client);
|
|
1426
|
+
return '<button class="' + cls + '" data-id="' + esc(s.id) + '"' + (viewing ? ' aria-current="true"' : "") + ' title="' + esc((viewing ? "Currently viewing · " : "") + label(s.client) + " · " + title + (repo ? " · " + repo : "")) + '">'
|
|
1427
|
+
+ '<img class="tab-icon" src="' + iconForFamily(family) + '" alt="" />'
|
|
1428
|
+
+ '<span class="tab-copy"><span class="tab-title">' + esc(title) + activity + '</span><span class="tab-repo">' + esc(repo || label(s.client)) + "</span></span>" + status + "</button>";
|
|
1376
1429
|
});
|
|
1377
|
-
chips.unshift('<button class="tab' + (pinnedId ? "" : " active") + '" data-id="auto" title="Auto
|
|
1430
|
+
chips.unshift('<button class="tab auto-tab' + (pinnedId ? "" : " mode-active") + '" data-id="auto" title="' + (pinnedId ? "Use Auto selection" : "Auto selection is on") + '"><span class="tab-title">Auto</span><span class="auto-check" aria-hidden="true">✓</span></button>');
|
|
1378
1431
|
tabsEl.innerHTML = chips.join("");
|
|
1432
|
+
return true;
|
|
1379
1433
|
}
|
|
1380
1434
|
function visibleRecentSessions(state, activeClient) {
|
|
1381
1435
|
const list = (state && state.recentSessions) || [];
|
|
@@ -1432,14 +1486,6 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1432
1486
|
return String(Math.round(n));
|
|
1433
1487
|
}
|
|
1434
1488
|
|
|
1435
|
-
function signedFmt(value) {
|
|
1436
|
-
return (value >= 0 ? "+" : "-") + fmt(Math.abs(value));
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
function joinParts(parts) {
|
|
1440
|
-
return parts.filter(Boolean).join(" - ");
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
1489
|
function clamp(value, min, max) {
|
|
1444
1490
|
return Math.max(min, Math.min(max, Number(value) || 0));
|
|
1445
1491
|
}
|