@echomem/mcp 1.4.9 → 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/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-canonical-adapter.js +315 -0
- 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/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 +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 +0 -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
package/dist/hud/web.js
CHANGED
|
@@ -114,6 +114,9 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
114
114
|
filter: var(--shadow);
|
|
115
115
|
transition: border-color 0.18s ease, background 0.18s ease, filter 0.18s ease;
|
|
116
116
|
}
|
|
117
|
+
/* Open = the card is the voice. Echo doesn't say the same thing twice, so the speech
|
|
118
|
+
bubble steps aside while the panel is up and comes back when it closes. */
|
|
119
|
+
#hud.open .status-bubble { display: none; }
|
|
117
120
|
.status-bubble::after {
|
|
118
121
|
content: "";
|
|
119
122
|
position: absolute;
|
|
@@ -169,6 +172,9 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
169
172
|
line-height: 1.1;
|
|
170
173
|
color: var(--ink);
|
|
171
174
|
}
|
|
175
|
+
.main-rest { font-size: 10.5px; font-weight: 680; color: var(--muted); }
|
|
176
|
+
.client-line { display: flex; align-items: center; gap: 5px; min-width: 0; }
|
|
177
|
+
.line-icon { width: 11px; height: 11px; flex: 0 0 auto; object-fit: contain; opacity: .85; }
|
|
172
178
|
.client {
|
|
173
179
|
min-width: 0;
|
|
174
180
|
overflow: hidden;
|
|
@@ -211,25 +217,94 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
211
217
|
cursor: grab;
|
|
212
218
|
}
|
|
213
219
|
#hud.open .details { display: block; }
|
|
214
|
-
.
|
|
220
|
+
/* The current session is the one object every number and action belongs to — one card. */
|
|
221
|
+
.now-card {
|
|
222
|
+
border: 1px solid rgba(26, 58, 143, 0.24);
|
|
223
|
+
border-radius: 12px;
|
|
224
|
+
padding: 12px 13px 13px;
|
|
225
|
+
background: #fff;
|
|
226
|
+
box-shadow: 0 1px 5px rgba(26, 58, 143, 0.07);
|
|
227
|
+
}
|
|
228
|
+
#hud.s-amber .now-card { border-color: rgba(233, 185, 73, 0.55); }
|
|
229
|
+
#hud.s-red .now-card { border-color: rgba(199, 55, 47, 0.45); }
|
|
230
|
+
.now-sess {
|
|
215
231
|
display: flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
gap: 8px;
|
|
234
|
+
margin-bottom: 9px;
|
|
235
|
+
}
|
|
236
|
+
.now-icon { width: 18px; height: 18px; flex: 0 0 auto; object-fit: contain; }
|
|
237
|
+
.now-copy { min-width: 0; flex: 1; display: grid; gap: 1px; line-height: 1.15; }
|
|
238
|
+
.now-title, .now-repo { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
239
|
+
.now-title { font-size: 12px; font-weight: 800; color: var(--ink); }
|
|
240
|
+
.now-repo { font-size: 10px; font-weight: 600; color: var(--faint); }
|
|
241
|
+
.verdict {
|
|
242
|
+
overflow: hidden;
|
|
243
|
+
text-overflow: ellipsis;
|
|
244
|
+
white-space: nowrap;
|
|
245
|
+
font-size: 11.5px;
|
|
246
|
+
font-weight: 650;
|
|
247
|
+
color: var(--muted);
|
|
248
|
+
}
|
|
249
|
+
.verdict:empty { display: none; }
|
|
250
|
+
.meta-row {
|
|
251
|
+
display: flex;
|
|
252
|
+
align-items: center;
|
|
216
253
|
justify-content: space-between;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
margin-bottom: 10px;
|
|
254
|
+
gap: 8px;
|
|
255
|
+
margin-top: 10px;
|
|
220
256
|
}
|
|
221
|
-
.
|
|
257
|
+
.meta-line {
|
|
258
|
+
min-width: 0;
|
|
222
259
|
overflow: hidden;
|
|
223
260
|
text-overflow: ellipsis;
|
|
224
261
|
white-space: nowrap;
|
|
225
|
-
font-
|
|
262
|
+
font-family: "JetBrains Mono", ui-monospace, monospace;
|
|
263
|
+
font-size: 9.5px;
|
|
264
|
+
font-weight: 600;
|
|
265
|
+
letter-spacing: 0.02em;
|
|
266
|
+
color: var(--faint);
|
|
267
|
+
}
|
|
268
|
+
.session-actions {
|
|
269
|
+
display: flex;
|
|
270
|
+
align-items: center;
|
|
271
|
+
gap: 6px;
|
|
272
|
+
margin: 0 0 8px 26px;
|
|
273
|
+
}
|
|
274
|
+
.pin-chip, .jump-chip {
|
|
275
|
+
display: none;
|
|
276
|
+
align-items: center;
|
|
277
|
+
justify-content: center;
|
|
278
|
+
min-height: 22px;
|
|
279
|
+
padding: 3px 9px;
|
|
280
|
+
border: 1px solid rgba(26, 58, 143, .18);
|
|
281
|
+
border-radius: 999px;
|
|
282
|
+
background: rgba(255, 255, 255, .78);
|
|
226
283
|
color: var(--faint);
|
|
284
|
+
cursor: pointer;
|
|
285
|
+
font-family: inherit;
|
|
286
|
+
font-size: 10px;
|
|
287
|
+
font-weight: 760;
|
|
288
|
+
letter-spacing: 0;
|
|
289
|
+
line-height: 1;
|
|
290
|
+
}
|
|
291
|
+
.pin-chip:hover, .jump-chip:hover {
|
|
292
|
+
border-color: rgba(26, 58, 143, .34);
|
|
293
|
+
background: #fff;
|
|
294
|
+
color: var(--blue);
|
|
295
|
+
}
|
|
296
|
+
.jump-chip {
|
|
297
|
+
width: 22px;
|
|
298
|
+
min-width: 22px;
|
|
299
|
+
padding: 0;
|
|
300
|
+
border-radius: 50%;
|
|
301
|
+
font-size: 12px;
|
|
227
302
|
}
|
|
228
303
|
.tabs {
|
|
229
304
|
display: grid;
|
|
230
305
|
gap: 6px;
|
|
231
306
|
max-height: 259px;
|
|
232
|
-
margin:
|
|
307
|
+
margin: 10px 0 0;
|
|
233
308
|
padding-right: 3px;
|
|
234
309
|
overflow-y: auto;
|
|
235
310
|
overscroll-behavior: contain;
|
|
@@ -238,24 +313,22 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
238
313
|
.tabs::-webkit-scrollbar { width: 4px; }
|
|
239
314
|
.tabs::-webkit-scrollbar-track { background: transparent; }
|
|
240
315
|
.tabs::-webkit-scrollbar-thumb { border-radius: 999px; background: rgba(26, 58, 143, .2); }
|
|
316
|
+
/* History rows: everything that is NOT the current session. Quiet on purpose. */
|
|
241
317
|
.tab {
|
|
242
|
-
display: grid; grid-template-columns:
|
|
243
|
-
width: 100%; min-height:
|
|
244
|
-
border: 1px solid
|
|
318
|
+
display: grid; grid-template-columns: 16px minmax(0, 1fr) auto auto auto; align-items: center; column-gap: 8px;
|
|
319
|
+
width: 100%; min-height: 30px; font-size: 10.5px; line-height: 1.15; padding: 5px 9px; border-radius: 8px;
|
|
320
|
+
border: 1px solid transparent; background: transparent;
|
|
245
321
|
color: var(--faint); cursor: pointer; text-align: left; font-family: inherit;
|
|
246
322
|
}
|
|
247
|
-
.tab:hover { background: rgba(0, 0, 0, .
|
|
248
|
-
.tab.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.tab-
|
|
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; }
|
|
323
|
+
.tab:hover { border-color: rgba(0, 0, 0, .1); background: rgba(0, 0, 0, .05); }
|
|
324
|
+
.tab-icon { width: 14px; height: 14px; object-fit: contain; opacity: .75; }
|
|
325
|
+
.tab-copy { min-width: 0; display: grid; gap: 1px; }
|
|
326
|
+
.tab-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-weight: 680; }
|
|
327
|
+
.tab-repo { display: flex; align-items: center; gap: 4px; min-width: 0; color: var(--faint); font-size: 9.5px; font-weight: 600; }
|
|
328
|
+
.tab-reponame { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
329
|
+
.tab-gh { width: 10px; height: 10px; flex: 0 0 auto; object-fit: contain; opacity: .7; }
|
|
330
|
+
/* When you last worked here — sits just left of the jump arrow. */
|
|
331
|
+
.tab-time { justify-self: end; font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 9px; font-weight: 600; color: var(--faint); white-space: nowrap; }
|
|
259
332
|
.tab-status {
|
|
260
333
|
width: 10px;
|
|
261
334
|
height: 10px;
|
|
@@ -268,31 +341,24 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
268
341
|
.tab-status.amber { background: var(--amber); }
|
|
269
342
|
.tab-status.red { background: #ff5f57; box-shadow: 0 0 0 1px rgba(199, 55, 47, .28); }
|
|
270
343
|
.tab-status.idle { background: var(--faint); }
|
|
271
|
-
.tab
|
|
272
|
-
|
|
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;
|
|
344
|
+
.tab-health {
|
|
345
|
+
display: inline-flex;
|
|
287
346
|
align-items: center;
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
font-
|
|
293
|
-
|
|
347
|
+
gap: 5px;
|
|
348
|
+
justify-self: end;
|
|
349
|
+
font-family: "JetBrains Mono", ui-monospace, monospace;
|
|
350
|
+
font-size: 9.5px;
|
|
351
|
+
font-weight: 750;
|
|
352
|
+
color: var(--muted);
|
|
294
353
|
}
|
|
295
|
-
.
|
|
354
|
+
.tab-health .tab-status { width: 8px; height: 8px; border-width: 1.5px; }
|
|
355
|
+
/* Jump straight to this conversation in its own app. */
|
|
356
|
+
.tab-jump {
|
|
357
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
358
|
+
width: 18px; height: 18px; justify-self: end; border-radius: 50%;
|
|
359
|
+
color: var(--faint); font-size: 11px; font-weight: 800;
|
|
360
|
+
}
|
|
361
|
+
.tab-jump:hover { background: rgba(26, 58, 143, 0.1); color: var(--blue); }
|
|
296
362
|
.spin {
|
|
297
363
|
display: inline-block; width: 8px; height: 8px; margin-left: 4px; vertical-align: -1px;
|
|
298
364
|
border: 1.5px solid rgba(0, 0, 0, .2); border-top-color: #1a3a8f; border-radius: 50%;
|
|
@@ -301,26 +367,30 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
301
367
|
@keyframes echospin { to { transform: rotate(360deg); } }
|
|
302
368
|
/* Mini mode: a small always-on pill instead of the full mascot+bubble. Opening the panel
|
|
303
369
|
temporarily shows the full layout; closing returns to the pill. */
|
|
370
|
+
/* One line, one reading order: dot · score · agent icon · session · repo icon · repo.
|
|
371
|
+
The session name gets the space; the repo shrinks first. */
|
|
304
372
|
#miniPill {
|
|
305
|
-
display: none; align-items: center; gap:
|
|
306
|
-
margin: 7px 0 0 8px; padding: 5px
|
|
373
|
+
display: none; align-items: center; gap: 7px; width: calc(100% - 16px); min-height: 38px;
|
|
374
|
+
margin: 7px 0 0 8px; padding: 5px 13px; cursor: pointer; user-select: none;
|
|
307
375
|
border: 1.6px solid rgba(26, 58, 143, 0.75); border-radius: 999px; background: #fdfaf2;
|
|
308
376
|
filter: var(--shadow); color: var(--ink); letter-spacing: 0;
|
|
309
377
|
}
|
|
310
378
|
#miniPill .mdot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); flex: 0 0 auto; }
|
|
311
379
|
#miniPill .mdot.amber { background: var(--amber); }
|
|
312
380
|
#miniPill .mdot.idle { background: var(--green); }
|
|
313
|
-
.micon { width:
|
|
381
|
+
.micon { width: 13px; height: 13px; object-fit: contain; flex: 0 0 auto; opacity: .85; }
|
|
314
382
|
#mscore {
|
|
315
383
|
flex: 0 0 auto;
|
|
316
384
|
font-family: "JetBrains Mono", ui-monospace, monospace;
|
|
317
385
|
font-size: 12px;
|
|
318
386
|
font-weight: 850;
|
|
319
387
|
}
|
|
320
|
-
.mcopy { min-width: 0; display: grid; gap:
|
|
388
|
+
.mcopy { min-width: 0; flex: 1; display: grid; gap: 2px; line-height: 1.1; }
|
|
389
|
+
.mrow { display: flex; align-items: center; gap: 5px; min-width: 0; }
|
|
321
390
|
#msession, #mrepo { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
322
391
|
#msession { font-size: 11.5px; font-weight: 760; color: var(--ink); }
|
|
323
392
|
#mrepo { font-size: 10px; font-weight: 650; color: var(--faint); }
|
|
393
|
+
.mrow .micon { width: 11px; height: 11px; }
|
|
324
394
|
#hud.mini:not(.open) .glance { height: 52px; }
|
|
325
395
|
#hud.mini:not(.open) .mascot-stage, #hud.mini:not(.open) .status-bubble, #hud.mini:not(.open) .drag { display: none; }
|
|
326
396
|
#hud.mini:not(.open) #miniPill { display: inline-flex; }
|
|
@@ -331,7 +401,6 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
331
401
|
cursor: pointer; font-family: inherit;
|
|
332
402
|
}
|
|
333
403
|
.cornerbtn:hover { background: #fff; color: #111; }
|
|
334
|
-
.headctl { display: flex; align-items: center; gap: 8px; }
|
|
335
404
|
.switchbtn {
|
|
336
405
|
display: inline-flex; align-items: center; justify-content: center;
|
|
337
406
|
width: 22px; height: 22px; border-radius: 50%; padding: 0; line-height: 1; font-size: 12px;
|
|
@@ -339,12 +408,12 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
339
408
|
cursor: pointer; font-family: inherit;
|
|
340
409
|
}
|
|
341
410
|
.switchbtn:hover { background: rgba(0, 0, 0, .1); color: #111; }
|
|
411
|
+
/* Explicit family switch: a labeled pill, not a mystery icon. */
|
|
342
412
|
.agent-switch {
|
|
343
|
-
width:
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
padding: 0;
|
|
413
|
+
width: auto;
|
|
414
|
+
height: 26px;
|
|
415
|
+
gap: 6px;
|
|
416
|
+
padding: 0 11px 0 8px;
|
|
348
417
|
border-radius: 999px;
|
|
349
418
|
background: rgba(255, 255, 255, .78);
|
|
350
419
|
color: #1f1f1f;
|
|
@@ -352,12 +421,13 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
352
421
|
font-weight: 760;
|
|
353
422
|
letter-spacing: 0;
|
|
354
423
|
white-space: nowrap;
|
|
424
|
+
flex: 0 0 auto;
|
|
355
425
|
}
|
|
356
426
|
.agent-switch:hover { background: #fff; border-color: rgba(26, 58, 143, .34); }
|
|
357
427
|
.agent-switch[data-target-family="claude"] { border-color: rgba(217, 119, 87, .42); }
|
|
358
428
|
.agent-switch[data-target-family="codex"] { border-color: rgba(57, 65, 255, .34); }
|
|
359
|
-
.switch-icon { width:
|
|
360
|
-
.switch-copy { display:
|
|
429
|
+
.switch-icon { width: 14px; height: 14px; flex: 0 0 auto; object-fit: contain; }
|
|
430
|
+
.switch-copy { display: inline; }
|
|
361
431
|
.reveal {
|
|
362
432
|
margin-top: 8px; font-size: 10.5px; padding: 4px 11px; border-radius: 999px;
|
|
363
433
|
border: 1px solid rgba(0, 0, 0, .12); background: rgba(0, 0, 0, .04); color: var(--faint);
|
|
@@ -368,7 +438,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
368
438
|
display: flex;
|
|
369
439
|
align-items: center;
|
|
370
440
|
gap: 9px;
|
|
371
|
-
margin-bottom:
|
|
441
|
+
margin-bottom: 4px;
|
|
372
442
|
}
|
|
373
443
|
.state-dot {
|
|
374
444
|
width: 12px;
|
|
@@ -385,41 +455,28 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
385
455
|
text-overflow: ellipsis;
|
|
386
456
|
white-space: nowrap;
|
|
387
457
|
font-family: "JetBrains Mono", ui-monospace, monospace;
|
|
388
|
-
font-size:
|
|
458
|
+
font-size: 20px;
|
|
389
459
|
font-weight: 850;
|
|
390
460
|
}
|
|
391
461
|
.why-btn {
|
|
392
462
|
margin-left: auto;
|
|
463
|
+
display: inline-flex;
|
|
464
|
+
align-items: center;
|
|
465
|
+
justify-content: center;
|
|
466
|
+
width: 20px;
|
|
467
|
+
height: 20px;
|
|
468
|
+
padding: 0;
|
|
469
|
+
line-height: 1;
|
|
393
470
|
border: 1px solid rgba(26, 58, 143, 0.16);
|
|
394
|
-
border-radius:
|
|
395
|
-
padding: 4px 10px;
|
|
471
|
+
border-radius: 50%;
|
|
396
472
|
background: rgba(26, 58, 143, 0.04);
|
|
397
473
|
color: var(--muted);
|
|
398
474
|
font-family: inherit;
|
|
399
|
-
font-size:
|
|
400
|
-
font-weight:
|
|
475
|
+
font-size: 15px;
|
|
476
|
+
font-weight: 700;
|
|
401
477
|
cursor: pointer;
|
|
402
478
|
}
|
|
403
479
|
.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;
|
|
410
|
-
font-size: 12.5px;
|
|
411
|
-
font-weight: 800;
|
|
412
|
-
color: var(--ink);
|
|
413
|
-
}
|
|
414
|
-
.lead {
|
|
415
|
-
margin-bottom: 0;
|
|
416
|
-
overflow: hidden;
|
|
417
|
-
text-overflow: ellipsis;
|
|
418
|
-
white-space: nowrap;
|
|
419
|
-
font-size: 11px;
|
|
420
|
-
font-weight: 650;
|
|
421
|
-
color: var(--faint);
|
|
422
|
-
}
|
|
423
480
|
.evidence {
|
|
424
481
|
display: none;
|
|
425
482
|
margin: 11px 0 12px;
|
|
@@ -471,8 +528,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
471
528
|
box-shadow: 0 4px 12px rgba(26, 58, 143, 0.22);
|
|
472
529
|
}
|
|
473
530
|
button.renew-btn b { font-weight: 800; }
|
|
474
|
-
button.renew-btn small {
|
|
475
|
-
button.renew-btn.urgent small, button.renew-btn.loading small {
|
|
531
|
+
button.renew-btn small {
|
|
476
532
|
display: block;
|
|
477
533
|
margin-top: 3px;
|
|
478
534
|
font-size: 10.5px;
|
|
@@ -497,20 +553,29 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
497
553
|
font-size: 11.5px;
|
|
498
554
|
font-weight: 750;
|
|
499
555
|
}
|
|
556
|
+
/* Preview of the generated context: a few lines by default, "Read all" opens the whole
|
|
557
|
+
thing right here. */
|
|
500
558
|
.renew-result pre {
|
|
501
|
-
max-height:
|
|
502
|
-
overflow:
|
|
503
|
-
margin: 0;
|
|
559
|
+
max-height: 92px;
|
|
560
|
+
overflow: hidden;
|
|
561
|
+
margin: 7px 0 0;
|
|
504
562
|
white-space: pre-wrap;
|
|
505
563
|
word-break: break-word;
|
|
506
564
|
color: var(--ink);
|
|
507
565
|
font-family: "JetBrains Mono", ui-monospace, monospace;
|
|
508
566
|
font-size: 9.5px;
|
|
567
|
+
line-height: 1.45;
|
|
568
|
+
-webkit-mask-image: linear-gradient(to bottom, #000 62%, transparent 100%);
|
|
509
569
|
}
|
|
570
|
+
.renew-result.expanded pre {
|
|
571
|
+
max-height: 300px;
|
|
572
|
+
overflow: auto;
|
|
573
|
+
-webkit-mask-image: none;
|
|
574
|
+
}
|
|
575
|
+
.renew-actions { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
|
|
510
576
|
.renew-result .copy-btn {
|
|
511
577
|
display: inline-block;
|
|
512
578
|
width: auto;
|
|
513
|
-
margin-top: 6px;
|
|
514
579
|
border: 1px solid rgba(26, 58, 143, 0.22);
|
|
515
580
|
border-radius: 999px;
|
|
516
581
|
padding: 5px 14px;
|
|
@@ -520,7 +585,19 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
520
585
|
font-size: 11px;
|
|
521
586
|
cursor: pointer;
|
|
522
587
|
}
|
|
523
|
-
.
|
|
588
|
+
.ghost-btn {
|
|
589
|
+
border: 1px solid rgba(26, 58, 143, 0.14);
|
|
590
|
+
border-radius: 999px;
|
|
591
|
+
padding: 5px 12px;
|
|
592
|
+
background: transparent;
|
|
593
|
+
color: var(--muted);
|
|
594
|
+
font-family: inherit;
|
|
595
|
+
font-size: 11px;
|
|
596
|
+
font-weight: 750;
|
|
597
|
+
cursor: pointer;
|
|
598
|
+
}
|
|
599
|
+
.ghost-btn:hover { background: rgba(26, 58, 143, 0.06); color: var(--ink); }
|
|
600
|
+
#renewagainbtn { margin-left: auto; }
|
|
524
601
|
.path,
|
|
525
602
|
.missing {
|
|
526
603
|
overflow: hidden;
|
|
@@ -529,7 +606,6 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
529
606
|
color: var(--faint);
|
|
530
607
|
font-size: 11px;
|
|
531
608
|
}
|
|
532
|
-
.meta { display: none; }
|
|
533
609
|
.path { margin-top: 8px; }
|
|
534
610
|
.missing { margin-top: 6px; }
|
|
535
611
|
#hud.s-green .mascot-stage { filter: drop-shadow(0 2px 6px rgba(67, 209, 122, 0.3)); }
|
|
@@ -571,37 +647,51 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
571
647
|
<span class="dot" id="dot"></span>
|
|
572
648
|
<span class="copy">
|
|
573
649
|
<span class="main" id="main">Checking context...</span>
|
|
574
|
-
<span class="client" id="client">EchoMem HUD</span>
|
|
575
|
-
<span class="client-repo" id="clientrepo"></span>
|
|
650
|
+
<span class="client-line"><img class="line-icon" id="bubbleagent" src="/assets/hud/codex.svg" alt="" style="display:none" /><span class="client" id="client">EchoMem HUD</span></span>
|
|
651
|
+
<span class="client-line" id="repoline" style="display:none"><img class="line-icon" src="/assets/hud/github.svg" alt="" /><span class="client-repo" id="clientrepo"></span></span>
|
|
576
652
|
</span>
|
|
577
653
|
<button class="cornerbtn" id="minibtn" title="Shrink to mini pill">–</button>
|
|
578
654
|
</div>
|
|
579
655
|
<div id="miniPill" title="EchoMem — click to expand">
|
|
580
656
|
<span class="mdot" id="mdot"></span>
|
|
581
|
-
<img class="micon" id="micon" src="/assets/hud/codex.svg" alt="" />
|
|
582
657
|
<span id="mscore">on</span>
|
|
583
|
-
<span class="mcopy"
|
|
658
|
+
<span class="mcopy">
|
|
659
|
+
<span class="mrow"><img class="micon" id="micon" src="/assets/hud/codex.svg" alt="" style="display:none" /><span id="msession">EchoMem</span></span>
|
|
660
|
+
<span class="mrow" id="mrepoline" style="display:none"><img class="micon" id="mghicon" src="/assets/hud/github.svg" alt="" /><span id="mrepo"></span></span>
|
|
661
|
+
</span>
|
|
584
662
|
</div>
|
|
585
663
|
</div>
|
|
586
664
|
<div class="details">
|
|
587
|
-
<div class="
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
<
|
|
665
|
+
<div class="now-card" id="nowcard">
|
|
666
|
+
<div class="now-sess">
|
|
667
|
+
<img class="now-icon" id="nowicon" src="/assets/hud/codex.svg" alt="" />
|
|
668
|
+
<span class="now-copy"><span class="now-title" id="nowtitle">EchoMem</span><span class="client-line" id="nowrepoline" style="display:none"><img class="line-icon" src="/assets/hud/github.svg" alt="" /><span class="now-repo" id="nowrepo"></span></span></span>
|
|
669
|
+
</div>
|
|
670
|
+
<div class="session-actions">
|
|
671
|
+
<button class="pin-chip" id="pinchip" type="button" title="Return to auto-follow">pinned x</button>
|
|
672
|
+
<button class="jump-chip" id="jumpchip" type="button" title="Open this conversation">↗</button>
|
|
673
|
+
</div>
|
|
674
|
+
<div class="state-row">
|
|
675
|
+
<span class="state-dot" id="stateDot"></span>
|
|
676
|
+
<span class="state-word" id="stateWord">--%</span>
|
|
677
|
+
<button class="why-btn" id="whybtn" type="button" title="Details" aria-label="Details" style="display:none">+</button>
|
|
678
|
+
</div>
|
|
679
|
+
<div class="verdict" id="verdict"></div>
|
|
680
|
+
<div class="evidence" id="evidence"></div>
|
|
681
|
+
<div class="nudge" id="nudge"></div>
|
|
682
|
+
<button class="renew-btn" id="renewbtn"><b>Start new session with context</b><small>carry 100% signal, drop the junk</small></button>
|
|
683
|
+
<div class="renew-result" id="renewresult">
|
|
684
|
+
<div class="renew-msg" id="renewmsg"></div>
|
|
685
|
+
<pre id="renewpre"></pre>
|
|
686
|
+
<div class="renew-actions">
|
|
687
|
+
<button class="copy-btn" id="copybtn">Copy</button>
|
|
688
|
+
<button class="ghost-btn" id="expandbtn">Read all</button>
|
|
689
|
+
<button class="ghost-btn" id="renewagainbtn" title="Generate a fresh context from the latest turns">Renew</button>
|
|
690
|
+
</div>
|
|
691
|
+
</div>
|
|
593
692
|
</div>
|
|
594
|
-
<div class="
|
|
595
|
-
<div class="
|
|
596
|
-
<div class="evidence" id="evidence"></div>
|
|
597
|
-
<div class="nudge" id="nudge"></div>
|
|
598
|
-
<button class="renew-btn" id="renewbtn">Start new with context</button>
|
|
599
|
-
<div class="renew-result" id="renewresult">
|
|
600
|
-
<div class="renew-msg" id="renewmsg"></div>
|
|
601
|
-
<pre id="renewpre"></pre>
|
|
602
|
-
<button class="copy-btn" id="copybtn">Copy</button>
|
|
603
|
-
</div>
|
|
604
|
-
<div class="meta" id="meta"></div>
|
|
693
|
+
<div class="tabs" id="tabs"></div>
|
|
694
|
+
<div class="meta-row"><span class="meta-line" id="metaline"></span><button class="switchbtn agent-switch" id="switchbtn" title="Switch agent" style="display:none" type="button"></button></div>
|
|
605
695
|
<div class="path" id="path"></div>
|
|
606
696
|
<button class="reveal" id="reveal" style="display:none">↗ Open session in viewer</button>
|
|
607
697
|
<div class="missing" id="missing"></div>
|
|
@@ -613,12 +703,16 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
613
703
|
const main = document.getElementById("main");
|
|
614
704
|
const client = document.getElementById("client");
|
|
615
705
|
const clientRepo = document.getElementById("clientrepo");
|
|
616
|
-
const turnEl = document.getElementById("turn");
|
|
617
706
|
const stateDot = document.getElementById("stateDot");
|
|
618
707
|
const stateWordEl = document.getElementById("stateWord");
|
|
708
|
+
const verdictEl = document.getElementById("verdict");
|
|
709
|
+
const nowIcon = document.getElementById("nowicon");
|
|
710
|
+
const nowTitle = document.getElementById("nowtitle");
|
|
711
|
+
const nowRepo = document.getElementById("nowrepo");
|
|
712
|
+
const nowRepoLine = document.getElementById("nowrepoline");
|
|
713
|
+
const pinChip = document.getElementById("pinchip");
|
|
714
|
+
const jumpChip = document.getElementById("jumpchip");
|
|
619
715
|
const whyBtn = document.getElementById("whybtn");
|
|
620
|
-
const fullness = document.getElementById("fullness");
|
|
621
|
-
const lead = document.getElementById("lead");
|
|
622
716
|
const evidence = document.getElementById("evidence");
|
|
623
717
|
const nudge = document.getElementById("nudge");
|
|
624
718
|
const renewBtn = document.getElementById("renewbtn");
|
|
@@ -626,9 +720,12 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
626
720
|
const renewMsg = document.getElementById("renewmsg");
|
|
627
721
|
const renewPre = document.getElementById("renewpre");
|
|
628
722
|
const copyBtn = document.getElementById("copybtn");
|
|
629
|
-
const
|
|
723
|
+
const expandBtn = document.getElementById("expandbtn");
|
|
724
|
+
const renewAgainBtn = document.getElementById("renewagainbtn");
|
|
725
|
+
const metaLineEl = document.getElementById("metaline");
|
|
726
|
+
const bubbleAgent = document.getElementById("bubbleagent");
|
|
727
|
+
const repoLine = document.getElementById("repoline");
|
|
630
728
|
const pathEl = document.getElementById("path");
|
|
631
|
-
const updatedEl = document.getElementById("updated");
|
|
632
729
|
const tabsEl = document.getElementById("tabs");
|
|
633
730
|
const switchBtn = document.getElementById("switchbtn");
|
|
634
731
|
const revealEl = document.getElementById("reveal");
|
|
@@ -637,6 +734,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
637
734
|
let lastCapsule = "";
|
|
638
735
|
let renewResultSessionKey = "";
|
|
639
736
|
let activityEpoch = null;
|
|
737
|
+
let metaTail = ""; // "turn 2 · 178k tokens" — composed in render(), joined with "updated …" each second
|
|
640
738
|
let lastState = null;
|
|
641
739
|
let currentSrc = null;
|
|
642
740
|
let renewTimer = null;
|
|
@@ -663,6 +761,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
663
761
|
const mscore = document.getElementById("mscore");
|
|
664
762
|
const msession = document.getElementById("msession");
|
|
665
763
|
const mrepo = document.getElementById("mrepo");
|
|
764
|
+
const mrepoLine = document.getElementById("mrepoline");
|
|
666
765
|
let miniOn = false;
|
|
667
766
|
let whyOpen = false;
|
|
668
767
|
try { miniOn = localStorage.getItem("echomemHudMini") === "1"; } catch {}
|
|
@@ -679,7 +778,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
679
778
|
e.stopPropagation();
|
|
680
779
|
whyOpen = !whyOpen;
|
|
681
780
|
hud.classList.toggle("show-why", whyOpen);
|
|
682
|
-
whyBtn.textContent = whyOpen ? "
|
|
781
|
+
whyBtn.textContent = whyOpen ? "−" : "+";
|
|
683
782
|
syncHeight();
|
|
684
783
|
});
|
|
685
784
|
let hudDrag = null;
|
|
@@ -746,17 +845,40 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
746
845
|
.catch(() => {});
|
|
747
846
|
});
|
|
748
847
|
|
|
749
|
-
// Click a
|
|
848
|
+
// Click a history row → pin that session as the current one. Auto-follow is the default;
|
|
849
|
+
// the card's "pinned ×" chip releases the pin. The ↗ corner jumps to the conversation in
|
|
850
|
+
// its own app instead of switching the HUD view.
|
|
750
851
|
if (tabsEl) tabsEl.addEventListener("click", (e) => {
|
|
852
|
+
const jump = e.target && e.target.closest ? e.target.closest("[data-jump]") : null;
|
|
853
|
+
if (jump) {
|
|
854
|
+
e.stopPropagation();
|
|
855
|
+
jumpToSession(jump.getAttribute("data-jump"));
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
751
858
|
const btn = e.target && e.target.closest ? e.target.closest(".tab") : null;
|
|
752
859
|
if (!btn) return;
|
|
753
|
-
|
|
860
|
+
selectSession(btn.getAttribute("data-id"));
|
|
861
|
+
});
|
|
862
|
+
if (jumpChip) jumpChip.addEventListener("click", (e) => {
|
|
863
|
+
e.stopPropagation();
|
|
864
|
+
jumpToSession(jumpChip.getAttribute("data-jump"));
|
|
865
|
+
});
|
|
866
|
+
function jumpToSession(id) {
|
|
867
|
+
if (!id) return;
|
|
868
|
+
fetch("/jump?id=" + encodeURIComponent(id)).catch(() => {});
|
|
869
|
+
}
|
|
870
|
+
if (pinChip) pinChip.addEventListener("click", (e) => {
|
|
871
|
+
e.stopPropagation();
|
|
872
|
+
selectSession("auto");
|
|
873
|
+
});
|
|
874
|
+
function selectSession(id) {
|
|
875
|
+
if (!id) return;
|
|
754
876
|
fetch("/select?id=" + encodeURIComponent(id))
|
|
755
877
|
.then(() => fetch("/state"))
|
|
756
878
|
.then((r) => r.json())
|
|
757
879
|
.then(render)
|
|
758
880
|
.catch(() => {});
|
|
759
|
-
}
|
|
881
|
+
}
|
|
760
882
|
|
|
761
883
|
document.getElementById("toggle").addEventListener("click", (event) => {
|
|
762
884
|
if (suppressNextToggle) {
|
|
@@ -778,7 +900,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
778
900
|
renderRenewLoadingLabel();
|
|
779
901
|
return;
|
|
780
902
|
}
|
|
781
|
-
renewBtn.
|
|
903
|
+
renewBtn.innerHTML = "<b>Start new session with context</b><small>carry 100% signal, drop the junk</small>";
|
|
782
904
|
}
|
|
783
905
|
|
|
784
906
|
if (renewBtn) renewBtn.addEventListener("click", async () => {
|
|
@@ -823,17 +945,18 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
823
945
|
}
|
|
824
946
|
saveCachedCheckpoint(renewSessionKey, prompt, promptKind, meta);
|
|
825
947
|
if (promptKind === "cloud") saveCheckpointStatus(renewSessionKey, statusFromRenewData(renewData, meta));
|
|
948
|
+
const doneSummary = checkpointSuffix(checkpointSummary({ renewedAt: Date.now(), memoryCount: meta.sessionMemoryCount }));
|
|
826
949
|
showRenewResult({
|
|
827
950
|
sessionKey: renewSessionKey,
|
|
828
951
|
prompt,
|
|
829
|
-
message: promptKind === "local" ? "Local capsule ready" : "Decision log ready",
|
|
952
|
+
message: (promptKind === "local" ? "Local capsule ready" : "Decision log ready") + doneSummary,
|
|
830
953
|
canCopy: true,
|
|
831
954
|
});
|
|
832
955
|
try {
|
|
833
956
|
await navigator.clipboard.writeText(prompt);
|
|
834
|
-
if (renewMsg) renewMsg.textContent = promptKind === "local" ? "Local capsule copied" : "Decision log copied";
|
|
957
|
+
if (renewMsg) renewMsg.textContent = (promptKind === "local" ? "Local capsule copied" : "Decision log copied") + doneSummary;
|
|
835
958
|
} catch {
|
|
836
|
-
if (renewMsg) renewMsg.textContent = promptKind === "local" ? "Local capsule ready" : "Decision log ready";
|
|
959
|
+
if (renewMsg) renewMsg.textContent = (promptKind === "local" ? "Local capsule ready" : "Decision log ready") + doneSummary;
|
|
837
960
|
}
|
|
838
961
|
} catch {
|
|
839
962
|
} finally {
|
|
@@ -853,6 +976,25 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
853
976
|
} catch {}
|
|
854
977
|
});
|
|
855
978
|
|
|
979
|
+
if (expandBtn) expandBtn.addEventListener("click", (e) => {
|
|
980
|
+
e.stopPropagation();
|
|
981
|
+
if (!renewResult) return;
|
|
982
|
+
const expanded = renewResult.classList.toggle("expanded");
|
|
983
|
+
expandBtn.textContent = expanded ? "Collapse" : "Read all";
|
|
984
|
+
syncHeight();
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
// Escape hatch: the session moved on and the saved context is stale — clear it and
|
|
988
|
+
// run the renew flow again.
|
|
989
|
+
if (renewAgainBtn) renewAgainBtn.addEventListener("click", (e) => {
|
|
990
|
+
e.stopPropagation();
|
|
991
|
+
clearRenewResult();
|
|
992
|
+
if (renewBtn) {
|
|
993
|
+
renewBtn.style.display = "block";
|
|
994
|
+
renewBtn.click();
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
|
|
856
998
|
function activeRenewScore() {
|
|
857
999
|
const st = lastState || {};
|
|
858
1000
|
return st.active || st;
|
|
@@ -879,9 +1021,15 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
879
1021
|
copyBtn.textContent = "Copy";
|
|
880
1022
|
copyBtn.style.display = "";
|
|
881
1023
|
}
|
|
882
|
-
if (renewResult)
|
|
1024
|
+
if (renewResult) {
|
|
1025
|
+
renewResult.style.display = "none";
|
|
1026
|
+
renewResult.classList.remove("expanded");
|
|
1027
|
+
}
|
|
1028
|
+
if (expandBtn) expandBtn.textContent = "Read all";
|
|
883
1029
|
}
|
|
884
1030
|
|
|
1031
|
+
// The result replaces the action: once a context exists, the big renew button steps aside
|
|
1032
|
+
// and this card carries the preview + timestamp + copy.
|
|
885
1033
|
function showRenewResult(opts) {
|
|
886
1034
|
const prompt = String((opts && opts.prompt) || "");
|
|
887
1035
|
const body = String((opts && opts.body) || prompt || "");
|
|
@@ -890,13 +1038,21 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
890
1038
|
if (renewMsg) renewMsg.textContent = String((opts && opts.message) || "");
|
|
891
1039
|
if (renewPre) {
|
|
892
1040
|
renewPre.textContent = body;
|
|
893
|
-
renewPre.style.display = "none";
|
|
1041
|
+
renewPre.style.display = body ? "block" : "none";
|
|
1042
|
+
}
|
|
1043
|
+
if (expandBtn) {
|
|
1044
|
+
expandBtn.style.display = body ? "inline-block" : "none";
|
|
1045
|
+
expandBtn.textContent = "Read all";
|
|
894
1046
|
}
|
|
895
1047
|
if (copyBtn) {
|
|
896
1048
|
copyBtn.textContent = "Copy";
|
|
897
1049
|
copyBtn.style.display = opts && opts.canCopy ? "inline-block" : "none";
|
|
898
1050
|
}
|
|
899
|
-
if (renewResult)
|
|
1051
|
+
if (renewResult) {
|
|
1052
|
+
renewResult.classList.remove("expanded");
|
|
1053
|
+
renewResult.style.display = "block";
|
|
1054
|
+
}
|
|
1055
|
+
if (renewBtn && !renewStartMs) renewBtn.style.display = "none";
|
|
900
1056
|
syncHeight();
|
|
901
1057
|
}
|
|
902
1058
|
|
|
@@ -982,7 +1138,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
982
1138
|
function checkpointSummary(item) {
|
|
983
1139
|
const parts = [];
|
|
984
1140
|
const renewedAt = timestampMs(item && item.renewedAt);
|
|
985
|
-
if (renewedAt) parts.push("
|
|
1141
|
+
if (renewedAt) parts.push("generated " + relTime(Date.now() - renewedAt));
|
|
986
1142
|
const count = Number(item && (item.memoryCount ?? item.sessionMemoryCount));
|
|
987
1143
|
if (Number.isFinite(count) && count > 0) parts.push(count + " " + (count === 1 ? "memory" : "memories") + " saved");
|
|
988
1144
|
return parts.join(" · ");
|
|
@@ -1187,15 +1343,20 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1187
1343
|
render({
|
|
1188
1344
|
active: sample,
|
|
1189
1345
|
pinnedId: previewPinned ? "p2" : null,
|
|
1346
|
+
sessions: [
|
|
1347
|
+
sample,
|
|
1348
|
+
{ client: "codex", sourcePath: "/preview/b.jsonl", usefulPct: 71, healthScorePct: 71, color: "amber" },
|
|
1349
|
+
{ client: "codex", sourcePath: "/preview/c.jsonl", usefulPct: 94, healthScorePct: 94, color: "green" },
|
|
1350
|
+
],
|
|
1190
1351
|
recentSessions: [
|
|
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: "
|
|
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: "
|
|
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 },
|
|
1352
|
+
{ id: "p1", client: "codex", title: "Simplify HUD session display", label: "EchoMem-Chrome", sourcePath: "/preview/session.jsonl", live: true, lastActiveMs: 4000 },
|
|
1353
|
+
{ id: "p2", client: "codex", title: "修复 HUD 切换和图标", label: "EchoMem-Chrome", sourcePath: "/preview/b.jsonl", live: false, lastActiveMs: 480000 },
|
|
1354
|
+
{ id: "p3", client: "codex", title: "Fix extraction preview", label: "EchoMem-Chrome", sourcePath: "/preview/c.jsonl", live: false, lastActiveMs: 2400000 },
|
|
1355
|
+
{ id: "p4", client: "codex", title: "Port Claude work", label: "EchoForPeople", sourcePath: "/preview/d.jsonl", live: false, lastActiveMs: 9000000 },
|
|
1356
|
+
{ id: "p5", client: "codex", title: "Stamp fixes with Echo mascot", label: "EchoMem-Chrome", sourcePath: "/preview/e.jsonl", live: false, lastActiveMs: 16200000 },
|
|
1357
|
+
{ id: "p6", client: "codex", title: "Review migration checks", label: "EchoForPeople", sourcePath: "/preview/f.jsonl", live: false, lastActiveMs: 21600000 },
|
|
1358
|
+
{ id: "p7", client: "codex", title: "Polish setup story", label: "EchoMem-Chrome", sourcePath: "/preview/g.jsonl", live: false, lastActiveMs: 34200000 },
|
|
1359
|
+
{ id: "p8", client: "claude-code", title: "DG-Web refactor", label: "DG-Web", sourcePath: "/preview/h.jsonl", live: false, lastActiveMs: 39600000 },
|
|
1199
1360
|
],
|
|
1200
1361
|
});
|
|
1201
1362
|
hud.classList.add("open");
|
|
@@ -1213,7 +1374,8 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1213
1374
|
const currentSessionKey = sessionKeyFor(state, score);
|
|
1214
1375
|
const viewColor = score && typeof score.usefulPct === "number" ? (score.color || "green") : "idle";
|
|
1215
1376
|
if (renewResultSessionKey && renewResultSessionKey !== currentSessionKey) clearRenewResult();
|
|
1216
|
-
const
|
|
1377
|
+
const activeId = score && score.sourcePath ? activeIdFor(state, score.sourcePath, activeClient) : null;
|
|
1378
|
+
renderTabs(state, activeId, activeClient);
|
|
1217
1379
|
renderSwitchButton(state, activeClient);
|
|
1218
1380
|
if (!score || typeof score.usefulPct !== "number") {
|
|
1219
1381
|
renderIdle(state);
|
|
@@ -1231,23 +1393,35 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1231
1393
|
stateDot.className = "state-dot " + color;
|
|
1232
1394
|
hud.classList.remove("s-green", "s-amber", "s-red");
|
|
1233
1395
|
hud.classList.add("s-" + color);
|
|
1234
|
-
main.
|
|
1396
|
+
main.innerHTML = health + '%<span class="main-rest"> of tokens are useful</span>';
|
|
1235
1397
|
renderMini(color, health + "%", info);
|
|
1236
1398
|
client.textContent = info.title || info.repo;
|
|
1237
|
-
if (
|
|
1399
|
+
if (bubbleAgent) {
|
|
1400
|
+
bubbleAgent.src = iconForFamily(agentFamily(score.client) || "codex");
|
|
1401
|
+
bubbleAgent.style.display = "inline-block";
|
|
1402
|
+
}
|
|
1403
|
+
const bubbleRepo = info.repo && info.repo !== info.title ? info.repo : "";
|
|
1404
|
+
if (clientRepo) clientRepo.textContent = bubbleRepo;
|
|
1405
|
+
if (repoLine) repoLine.style.display = bubbleRepo ? "flex" : "none";
|
|
1238
1406
|
client.classList.remove("voice");
|
|
1239
1407
|
activityEpoch = typeof score.lastActiveMs === "number" ? Date.now() - score.lastActiveMs : null;
|
|
1240
|
-
|
|
1241
|
-
|
|
1408
|
+
const metaParts = [];
|
|
1409
|
+
if (typeof score.turn === "number" && score.turn > 0) metaParts.push("turn " + score.turn);
|
|
1410
|
+
if (score.ctTokens > 0) metaParts.push(fmt(score.ctTokens) + " tok");
|
|
1411
|
+
metaTail = metaParts.join(" · ");
|
|
1412
|
+
renderMetaLine();
|
|
1242
1413
|
stateWordEl.textContent = health + "%";
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1414
|
+
if (verdictEl) verdictEl.textContent = verdictFor(color, oldPct);
|
|
1415
|
+
if (nowIcon) nowIcon.src = iconForFamily(agentFamily(score.client) || "codex");
|
|
1416
|
+
if (nowTitle) nowTitle.textContent = info.title;
|
|
1417
|
+
if (nowRepo) nowRepo.textContent = bubbleRepo;
|
|
1418
|
+
if (nowRepoLine) nowRepoLine.style.display = bubbleRepo ? "flex" : "none";
|
|
1419
|
+
if (pinChip) pinChip.style.display = state && state.pinnedId ? "inline-block" : "none";
|
|
1420
|
+
if (jumpChip) {
|
|
1421
|
+
if (activeId) jumpChip.setAttribute("data-jump", activeId);
|
|
1422
|
+
jumpChip.style.display = activeId ? "inline-block" : "none";
|
|
1423
|
+
}
|
|
1249
1424
|
renderWhy(score, oldPct, repeatCount, sat, health, color);
|
|
1250
|
-
metaEl.textContent = "";
|
|
1251
1425
|
pathEl.textContent = "";
|
|
1252
1426
|
currentSrc = score.sourcePath || null;
|
|
1253
1427
|
if (revealEl) revealEl.style.display = !DEMO_CLEAN && currentSrc ? "inline-block" : "none";
|
|
@@ -1256,15 +1430,18 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1256
1430
|
nudge.style.display = "none";
|
|
1257
1431
|
|
|
1258
1432
|
renewUrgent = color === "red";
|
|
1259
|
-
if (renewBtn) {
|
|
1260
|
-
renewBtn.style.display = "block";
|
|
1261
|
-
refreshRenewLabel();
|
|
1262
|
-
}
|
|
1263
1433
|
const restoredPrompt = restoreCachedCheckpointFor(score, currentSessionKey);
|
|
1264
1434
|
if (!restoredPrompt) {
|
|
1265
1435
|
const restoredStatus = restoreCheckpointStatusFor(score, currentSessionKey);
|
|
1266
1436
|
if (!restoredStatus) maybeFetchCheckpointStatus(score, currentSessionKey);
|
|
1267
1437
|
}
|
|
1438
|
+
// A generated context replaces the call to generate one: while the result card is up
|
|
1439
|
+
// for this session, the big button stays hidden (Renew in the card re-runs the flow).
|
|
1440
|
+
const resultShowing = renewResult && renewResult.style.display === "block" && renewResultSessionKey === currentSessionKey;
|
|
1441
|
+
if (renewBtn) {
|
|
1442
|
+
renewBtn.style.display = resultShowing && !renewStartMs ? "none" : "block";
|
|
1443
|
+
refreshRenewLabel();
|
|
1444
|
+
}
|
|
1268
1445
|
prevTurnState = {
|
|
1269
1446
|
src: score.sourcePath,
|
|
1270
1447
|
turn: score.turn,
|
|
@@ -1284,19 +1461,22 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1284
1461
|
main.textContent = "MCP on";
|
|
1285
1462
|
renderMini("idle", "on", { client: "EchoMem", title: "MCP", repo: "" });
|
|
1286
1463
|
client.textContent = "Waiting for Codex or Claude";
|
|
1464
|
+
if (bubbleAgent) bubbleAgent.style.display = "none";
|
|
1287
1465
|
if (clientRepo) clientRepo.textContent = "";
|
|
1466
|
+
if (repoLine) repoLine.style.display = "none";
|
|
1288
1467
|
client.classList.remove("voice");
|
|
1289
1468
|
activityEpoch = null;
|
|
1290
|
-
|
|
1469
|
+
metaTail = "";
|
|
1470
|
+
renderMetaLine();
|
|
1291
1471
|
currentSrc = null;
|
|
1292
1472
|
if (revealEl) revealEl.style.display = "none";
|
|
1293
|
-
turnEl.textContent = "";
|
|
1294
1473
|
stateWordEl.textContent = "on";
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1474
|
+
if (verdictEl) verdictEl.textContent = "Waiting for Codex or Claude.";
|
|
1475
|
+
if (nowTitle) nowTitle.textContent = "EchoMem MCP";
|
|
1476
|
+
if (nowRepo) nowRepo.textContent = "";
|
|
1477
|
+
if (nowRepoLine) nowRepoLine.style.display = "none";
|
|
1478
|
+
if (pinChip) pinChip.style.display = "none";
|
|
1479
|
+
if (jumpChip) jumpChip.style.display = "none";
|
|
1300
1480
|
evidence.textContent = "";
|
|
1301
1481
|
if (whyBtn) whyBtn.style.display = "none";
|
|
1302
1482
|
whyOpen = false;
|
|
@@ -1304,7 +1484,6 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1304
1484
|
nudge.style.display = "none";
|
|
1305
1485
|
if (renewBtn) renewBtn.style.display = "none";
|
|
1306
1486
|
clearRenewResult();
|
|
1307
|
-
metaEl.textContent = "";
|
|
1308
1487
|
pathEl.textContent = "";
|
|
1309
1488
|
missing.textContent = formatMissing(state && state.missing);
|
|
1310
1489
|
syncHeight();
|
|
@@ -1312,24 +1491,29 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1312
1491
|
|
|
1313
1492
|
function renderMini(color, scoreText, info) {
|
|
1314
1493
|
if (mdot) mdot.className = "mdot " + color;
|
|
1315
|
-
|
|
1494
|
+
const fam = agentFamily(info && info.clientRaw);
|
|
1495
|
+
if (micon) {
|
|
1496
|
+
micon.src = iconForFamily(fam || "codex");
|
|
1497
|
+
micon.style.display = fam ? "inline-block" : "none";
|
|
1498
|
+
}
|
|
1316
1499
|
if (mscore) mscore.textContent = scoreText;
|
|
1317
1500
|
if (msession) msession.textContent = miniTitle(info);
|
|
1318
|
-
|
|
1501
|
+
const repo = info && info.repo ? info.repo : "";
|
|
1502
|
+
if (mrepo) mrepo.textContent = repo;
|
|
1503
|
+
if (mrepoLine) mrepoLine.style.display = repo ? "flex" : "none";
|
|
1319
1504
|
}
|
|
1320
1505
|
|
|
1321
1506
|
function renderWhy(score, oldPct, repeatCount, sat, health, color) {
|
|
1322
1507
|
const rows = [];
|
|
1323
|
-
rows.push(["
|
|
1324
|
-
if (
|
|
1325
|
-
|
|
1326
|
-
if (repeatCount > 0) rows.push(["repeated reads", String(repeatCount)]);
|
|
1508
|
+
if (sat > 0) rows.push(["context", score.modelContextWindow ? sat + "% full · " + fmt(score.ctTokens) : fmt(score.ctTokens) + " tokens"]);
|
|
1509
|
+
if (score.pollutionTok > 0) rows.push(["junk", fmt(score.pollutionTok) + " (" + Math.round(oldPct) + "% of window)"]);
|
|
1510
|
+
for (const row of junkBreakdown(score)) rows.push(row);
|
|
1327
1511
|
rows.push(["source", ctSourceLabel(score.ctSource)]);
|
|
1328
1512
|
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
1513
|
const useful = color !== "green" || oldPct >= 10 || repeatCount > 0 || sat >= 80;
|
|
1330
1514
|
if (whyBtn) {
|
|
1331
1515
|
whyBtn.style.display = useful ? "inline-flex" : "none";
|
|
1332
|
-
whyBtn.textContent = whyOpen ? "
|
|
1516
|
+
whyBtn.textContent = whyOpen ? "−" : "+";
|
|
1333
1517
|
}
|
|
1334
1518
|
if (!useful) {
|
|
1335
1519
|
whyOpen = false;
|
|
@@ -1339,6 +1523,21 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1339
1523
|
}
|
|
1340
1524
|
}
|
|
1341
1525
|
|
|
1526
|
+
// Top junk categories from the latest local-live waste partition, biggest first.
|
|
1527
|
+
function junkBreakdown(score) {
|
|
1528
|
+
const buckets = score.buckets || {};
|
|
1529
|
+
const tok = (key) => (buckets[key] && buckets[key].tokens) || 0;
|
|
1530
|
+
const copies = buckets.range_redundant || {};
|
|
1531
|
+
const entries = [
|
|
1532
|
+
["stale screenshots", tok("superseded_image"), ""],
|
|
1533
|
+
["superseded copies", tok("range_redundant"), copies.count > 0 ? " · " + copies.count + " re-read" : ""],
|
|
1534
|
+
["carried tool output", tok("stale_tool_output"), ""],
|
|
1535
|
+
["outdated reads", tok("stale_read"), ""],
|
|
1536
|
+
].filter((entry) => entry[1] > 0);
|
|
1537
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
1538
|
+
return entries.map((entry) => ["· " + entry[0], fmt(entry[1]) + entry[2]]);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1342
1541
|
function fullnessPct(score) {
|
|
1343
1542
|
if (typeof score.saturationPct === "number") return score.saturationPct;
|
|
1344
1543
|
if (score.modelContextWindow) return Math.round(100 * (score.ctTokens || 0) / score.modelContextWindow);
|
|
@@ -1408,28 +1607,42 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1408
1607
|
return list.filter((s) => agentFamily(s.client) === targetFamily)[0] || null;
|
|
1409
1608
|
}
|
|
1410
1609
|
|
|
1411
|
-
//
|
|
1412
|
-
//
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1610
|
+
// History rows: recent sessions that are NOT the current one (the current session lives in the
|
|
1611
|
+
// now-card above — every number and action up there belongs to it). Clicking a row pins it as
|
|
1612
|
+
// the new current session; the card's "pinned ×" chip releases back to auto-follow. Scored
|
|
1613
|
+
// sessions carry their own health dot + %, so "Echo is following you across agents" reads off
|
|
1614
|
+
// the list itself. Family-scoped, so Codex never recommends Claude sessions or vice versa.
|
|
1615
|
+
function renderTabs(state, activeId, activeClient) {
|
|
1616
|
+
if (!tabsEl) return;
|
|
1617
|
+
const list = visibleRecentSessions(state, activeClient).filter((s) => s.id !== activeId);
|
|
1618
|
+
if (!list.length) { tabsEl.innerHTML = ""; return; }
|
|
1418
1619
|
const chips = list.map((s) => {
|
|
1419
|
-
const
|
|
1420
|
-
const
|
|
1421
|
-
const
|
|
1422
|
-
|
|
1620
|
+
const activity = s.live ? '<span class="spin"></span>' : "";
|
|
1621
|
+
const hv = sessionHealthFor(state, s);
|
|
1622
|
+
const status = hv
|
|
1623
|
+
? '<span class="tab-health"><span class="tab-status ' + esc(hv.color) + '"></span>' + hv.health + "%</span>"
|
|
1624
|
+
: "";
|
|
1423
1625
|
const title = s.title || label(s.client);
|
|
1424
1626
|
const repo = s.label && s.label !== title ? s.label : "";
|
|
1425
1627
|
const family = agentFamily(s.client);
|
|
1426
|
-
|
|
1628
|
+
const repoCell = repo
|
|
1629
|
+
? '<span class="tab-repo"><img class="tab-gh" src="/assets/hud/github.svg" alt="" /><span class="tab-reponame">' + esc(repo) + "</span></span>"
|
|
1630
|
+
: '<span class="tab-repo"><span class="tab-reponame">' + esc(label(s.client)) + "</span></span>";
|
|
1631
|
+
const time = typeof s.lastActiveMs === "number" ? '<span class="tab-time" title="Last active here">' + esc(relTime(s.lastActiveMs)) + "</span>" : "";
|
|
1632
|
+
return '<button class="tab" data-id="' + esc(s.id) + '" title="' + esc("Switch to · " + label(s.client) + " · " + title + (repo ? " · " + repo : "")) + '">'
|
|
1427
1633
|
+ '<img class="tab-icon" src="' + iconForFamily(family) + '" alt="" />'
|
|
1428
|
-
+ '<span class="tab-copy"><span class="tab-title">' + esc(title) + activity +
|
|
1634
|
+
+ '<span class="tab-copy"><span class="tab-title">' + esc(title) + activity + "</span>" + repoCell + "</span>" + status + time
|
|
1635
|
+
+ '<span class="tab-jump" data-jump="' + esc(s.id) + '" title="Open this conversation in ' + esc(label(s.client)) + '">↗</span></button>';
|
|
1429
1636
|
});
|
|
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>');
|
|
1431
1637
|
tabsEl.innerHTML = chips.join("");
|
|
1432
|
-
|
|
1638
|
+
}
|
|
1639
|
+
// Health for a listed session, joined from the scored views the monitor already computes.
|
|
1640
|
+
// Sessions Echo isn't actively scoring get no dot — honest absence, not a fake green.
|
|
1641
|
+
function sessionHealthFor(state, s) {
|
|
1642
|
+
const views = ((state && state.sessions) || []).concat((state && state.scores) || []);
|
|
1643
|
+
const m = views.filter((v) => v && v.sourcePath === s.sourcePath && v.client === s.client && typeof v.usefulPct === "number")[0];
|
|
1644
|
+
if (!m) return null;
|
|
1645
|
+
return { color: m.color || "green", health: healthScore(m) };
|
|
1433
1646
|
}
|
|
1434
1647
|
function visibleRecentSessions(state, activeClient) {
|
|
1435
1648
|
const list = (state && state.recentSessions) || [];
|
|
@@ -1458,9 +1671,19 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1458
1671
|
return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
|
1459
1672
|
}
|
|
1460
1673
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1674
|
+
// One quiet mono line for all technical metadata: "16s ago · turn 2 · 178k tok".
|
|
1675
|
+
function renderMetaLine() {
|
|
1676
|
+
if (!metaLineEl) return;
|
|
1677
|
+
const upd = activityEpoch === null ? "" : relTime(Date.now() - activityEpoch);
|
|
1678
|
+
metaLineEl.textContent = [upd, metaTail].filter(Boolean).join(" · ");
|
|
1679
|
+
}
|
|
1680
|
+
// One plain sentence saying what the score means for THIS session. Direct, no hedging,
|
|
1681
|
+
// and no second number — the score above is the only number on the card.
|
|
1682
|
+
function verdictFor(color, oldPct) {
|
|
1683
|
+
const usefulPct = 100 - clamp(oldPct || 0, 0, 100);
|
|
1684
|
+
if (color === "red") return usefulPct <= 20 ? "Mostly junk — carry over now." : "Too much junk – consider to carry over.";
|
|
1685
|
+
if (color === "amber") return "Junk is piling up.";
|
|
1686
|
+
return "Context is clean.";
|
|
1464
1687
|
}
|
|
1465
1688
|
function relTime(ms) {
|
|
1466
1689
|
const s = Math.max(0, Math.round(ms / 1000));
|
|
@@ -1470,7 +1693,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1470
1693
|
if (m < 60) return m + "m ago";
|
|
1471
1694
|
return Math.round(m / 60) + "h ago";
|
|
1472
1695
|
}
|
|
1473
|
-
setInterval(
|
|
1696
|
+
setInterval(renderMetaLine, 1000);
|
|
1474
1697
|
|
|
1475
1698
|
function label(clientName) {
|
|
1476
1699
|
if (clientName === "codex") return "Codex";
|