@jhizzard/termdeck 0.9.0 → 0.10.2
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/package.json +1 -1
- package/packages/client/public/app.js +42 -3
- package/packages/client/public/graph.html +104 -0
- package/packages/client/public/graph.js +683 -0
- package/packages/client/public/index.html +1 -0
- package/packages/client/public/style.css +427 -0
- package/packages/server/src/flashback-diag.js +51 -0
- package/packages/server/src/graph-routes.js +555 -0
- package/packages/server/src/index.js +83 -3
- package/packages/server/src/mnestra-bridge/index.js +63 -9
- package/packages/server/src/preflight.js +82 -0
- package/packages/server/src/rag.js +138 -0
- package/packages/server/src/session.js +95 -5
- package/packages/server/src/setup/mnestra-migrations/009_memory_relationship_metadata.sql +126 -0
- package/packages/server/src/setup/mnestra-migrations/010_memory_recall_graph.sql +147 -0
- package/packages/server/src/setup/mnestra-migrations/011_project_tag_backfill.sql +237 -0
- package/packages/server/src/setup/rumen/migrations/003_graph_inference_schedule.sql +49 -0
|
@@ -3223,3 +3223,430 @@
|
|
|
3223
3223
|
white-space: pre-wrap;
|
|
3224
3224
|
margin: 0 0 12px;
|
|
3225
3225
|
}
|
|
3226
|
+
|
|
3227
|
+
/* ===== Knowledge graph view (Sprint 38 T4) ===== */
|
|
3228
|
+
/* Standalone /graph.html. The body opts in via .graph-page so dashboard
|
|
3229
|
+
styles aren't inadvertently applied (no terminal grid, no panel chrome). */
|
|
3230
|
+
|
|
3231
|
+
body.graph-page {
|
|
3232
|
+
margin: 0;
|
|
3233
|
+
padding: 0;
|
|
3234
|
+
background: var(--tg-bg);
|
|
3235
|
+
color: var(--tg-text);
|
|
3236
|
+
font-family: var(--tg-sans);
|
|
3237
|
+
overflow: hidden;
|
|
3238
|
+
display: flex;
|
|
3239
|
+
flex-direction: column;
|
|
3240
|
+
height: 100vh;
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
.graph-topbar {
|
|
3244
|
+
display: flex;
|
|
3245
|
+
align-items: center;
|
|
3246
|
+
gap: 16px;
|
|
3247
|
+
padding: 8px 16px;
|
|
3248
|
+
background: var(--tg-surface);
|
|
3249
|
+
border-bottom: 1px solid var(--tg-border);
|
|
3250
|
+
flex-shrink: 0;
|
|
3251
|
+
min-height: 44px;
|
|
3252
|
+
}
|
|
3253
|
+
.graph-tb-left {
|
|
3254
|
+
display: flex;
|
|
3255
|
+
align-items: center;
|
|
3256
|
+
gap: 8px;
|
|
3257
|
+
}
|
|
3258
|
+
.graph-tb-back {
|
|
3259
|
+
display: inline-flex;
|
|
3260
|
+
align-items: center;
|
|
3261
|
+
gap: 6px;
|
|
3262
|
+
color: var(--tg-text-bright);
|
|
3263
|
+
text-decoration: none;
|
|
3264
|
+
font-weight: 600;
|
|
3265
|
+
padding: 4px 8px;
|
|
3266
|
+
border-radius: var(--tg-radius-sm);
|
|
3267
|
+
transition: background 120ms;
|
|
3268
|
+
}
|
|
3269
|
+
.graph-tb-back:hover { background: var(--tg-surface-hover); }
|
|
3270
|
+
.graph-tb-divider {
|
|
3271
|
+
color: var(--tg-text-dim);
|
|
3272
|
+
}
|
|
3273
|
+
.graph-tb-title {
|
|
3274
|
+
color: var(--tg-text);
|
|
3275
|
+
font-weight: 500;
|
|
3276
|
+
}
|
|
3277
|
+
.graph-tb-controls {
|
|
3278
|
+
display: flex;
|
|
3279
|
+
align-items: center;
|
|
3280
|
+
gap: 12px;
|
|
3281
|
+
flex: 1;
|
|
3282
|
+
justify-content: center;
|
|
3283
|
+
}
|
|
3284
|
+
.graph-tb-control {
|
|
3285
|
+
display: inline-flex;
|
|
3286
|
+
align-items: center;
|
|
3287
|
+
gap: 6px;
|
|
3288
|
+
font-size: 12px;
|
|
3289
|
+
color: var(--tg-text-dim);
|
|
3290
|
+
}
|
|
3291
|
+
.graph-tb-control select,
|
|
3292
|
+
.graph-tb-control input {
|
|
3293
|
+
background: var(--tg-bg);
|
|
3294
|
+
color: var(--tg-text);
|
|
3295
|
+
border: 1px solid var(--tg-border);
|
|
3296
|
+
border-radius: var(--tg-radius-sm);
|
|
3297
|
+
padding: 5px 8px;
|
|
3298
|
+
font-family: var(--tg-mono);
|
|
3299
|
+
font-size: 12px;
|
|
3300
|
+
min-width: 140px;
|
|
3301
|
+
}
|
|
3302
|
+
.graph-tb-control select:focus,
|
|
3303
|
+
.graph-tb-control input:focus {
|
|
3304
|
+
outline: none;
|
|
3305
|
+
border-color: var(--tg-accent);
|
|
3306
|
+
}
|
|
3307
|
+
.graph-tb-search-wrap input {
|
|
3308
|
+
min-width: 220px;
|
|
3309
|
+
}
|
|
3310
|
+
.graph-tb-btn {
|
|
3311
|
+
background: var(--tg-bg);
|
|
3312
|
+
color: var(--tg-text);
|
|
3313
|
+
border: 1px solid var(--tg-border);
|
|
3314
|
+
border-radius: var(--tg-radius-sm);
|
|
3315
|
+
padding: 5px 10px;
|
|
3316
|
+
font-size: 12px;
|
|
3317
|
+
font-family: var(--tg-mono);
|
|
3318
|
+
cursor: pointer;
|
|
3319
|
+
transition: all 120ms;
|
|
3320
|
+
}
|
|
3321
|
+
.graph-tb-btn:hover {
|
|
3322
|
+
background: var(--tg-surface-hover);
|
|
3323
|
+
border-color: var(--tg-border-active);
|
|
3324
|
+
}
|
|
3325
|
+
.graph-tb-right {
|
|
3326
|
+
display: flex;
|
|
3327
|
+
gap: 12px;
|
|
3328
|
+
align-items: center;
|
|
3329
|
+
}
|
|
3330
|
+
.graph-tb-stat {
|
|
3331
|
+
font-size: 11px;
|
|
3332
|
+
font-family: var(--tg-mono);
|
|
3333
|
+
color: var(--tg-text-dim);
|
|
3334
|
+
padding: 3px 8px;
|
|
3335
|
+
background: var(--tg-bg);
|
|
3336
|
+
border: 1px solid var(--tg-border);
|
|
3337
|
+
border-radius: 999px;
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
.graph-filters {
|
|
3341
|
+
display: flex;
|
|
3342
|
+
gap: 6px;
|
|
3343
|
+
padding: 8px 16px;
|
|
3344
|
+
background: var(--tg-surface);
|
|
3345
|
+
border-bottom: 1px solid var(--tg-border);
|
|
3346
|
+
flex-wrap: wrap;
|
|
3347
|
+
flex-shrink: 0;
|
|
3348
|
+
align-items: center;
|
|
3349
|
+
}
|
|
3350
|
+
.gf-chip {
|
|
3351
|
+
display: inline-flex;
|
|
3352
|
+
align-items: center;
|
|
3353
|
+
gap: 6px;
|
|
3354
|
+
padding: 3px 9px 3px 7px;
|
|
3355
|
+
font-size: 11px;
|
|
3356
|
+
font-family: var(--tg-mono);
|
|
3357
|
+
color: var(--tg-text-dim);
|
|
3358
|
+
background: var(--tg-bg);
|
|
3359
|
+
border: 1px solid var(--tg-border);
|
|
3360
|
+
border-radius: 999px;
|
|
3361
|
+
cursor: pointer;
|
|
3362
|
+
transition: all 150ms;
|
|
3363
|
+
opacity: 0.55;
|
|
3364
|
+
}
|
|
3365
|
+
.gf-chip:hover { opacity: 1; }
|
|
3366
|
+
.gf-chip.active {
|
|
3367
|
+
opacity: 1;
|
|
3368
|
+
color: var(--tg-text-bright);
|
|
3369
|
+
background: var(--tg-surface-hover);
|
|
3370
|
+
}
|
|
3371
|
+
.gf-chip-dot {
|
|
3372
|
+
width: 8px;
|
|
3373
|
+
height: 8px;
|
|
3374
|
+
border-radius: 50%;
|
|
3375
|
+
flex-shrink: 0;
|
|
3376
|
+
}
|
|
3377
|
+
.gf-chip-count {
|
|
3378
|
+
color: var(--tg-text-dim);
|
|
3379
|
+
font-size: 10px;
|
|
3380
|
+
padding: 0 4px;
|
|
3381
|
+
background: rgba(255, 255, 255, 0.04);
|
|
3382
|
+
border-radius: 999px;
|
|
3383
|
+
}
|
|
3384
|
+
.gf-chip.active .gf-chip-count {
|
|
3385
|
+
color: var(--tg-text);
|
|
3386
|
+
background: rgba(255, 255, 255, 0.08);
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
.graph-stage {
|
|
3390
|
+
position: relative;
|
|
3391
|
+
flex: 1;
|
|
3392
|
+
overflow: hidden;
|
|
3393
|
+
background: var(--tg-bg);
|
|
3394
|
+
}
|
|
3395
|
+
.graph-svg {
|
|
3396
|
+
display: block;
|
|
3397
|
+
width: 100%;
|
|
3398
|
+
height: 100%;
|
|
3399
|
+
cursor: grab;
|
|
3400
|
+
}
|
|
3401
|
+
.graph-svg:active {
|
|
3402
|
+
cursor: grabbing;
|
|
3403
|
+
}
|
|
3404
|
+
.graph-zoom-root {
|
|
3405
|
+
will-change: transform;
|
|
3406
|
+
}
|
|
3407
|
+
.graph-nodes circle {
|
|
3408
|
+
transition: stroke 200ms, opacity 200ms;
|
|
3409
|
+
}
|
|
3410
|
+
.graph-nodes circle:hover {
|
|
3411
|
+
stroke: #eef1ff;
|
|
3412
|
+
stroke-width: 2;
|
|
3413
|
+
}
|
|
3414
|
+
.graph-edges line {
|
|
3415
|
+
transition: stroke-opacity 200ms;
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
@keyframes graph-node-pulse {
|
|
3419
|
+
0% { stroke-width: 1.2; }
|
|
3420
|
+
50% { stroke-width: 4; stroke: #eef1ff; }
|
|
3421
|
+
100% { stroke-width: 1.2; }
|
|
3422
|
+
}
|
|
3423
|
+
.graph-node-pulse {
|
|
3424
|
+
animation: graph-node-pulse 1.4s ease-in-out infinite;
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
.graph-loading,
|
|
3428
|
+
.graph-empty {
|
|
3429
|
+
position: absolute;
|
|
3430
|
+
inset: 0;
|
|
3431
|
+
display: flex;
|
|
3432
|
+
flex-direction: column;
|
|
3433
|
+
align-items: center;
|
|
3434
|
+
justify-content: center;
|
|
3435
|
+
gap: 12px;
|
|
3436
|
+
color: var(--tg-text-dim);
|
|
3437
|
+
font-family: var(--tg-mono);
|
|
3438
|
+
pointer-events: none;
|
|
3439
|
+
padding: 24px;
|
|
3440
|
+
text-align: center;
|
|
3441
|
+
}
|
|
3442
|
+
.graph-loading-spinner {
|
|
3443
|
+
width: 28px;
|
|
3444
|
+
height: 28px;
|
|
3445
|
+
border-radius: 50%;
|
|
3446
|
+
border: 2px solid var(--tg-border);
|
|
3447
|
+
border-top-color: var(--tg-accent);
|
|
3448
|
+
animation: graph-spin 720ms linear infinite;
|
|
3449
|
+
}
|
|
3450
|
+
@keyframes graph-spin {
|
|
3451
|
+
to { transform: rotate(360deg); }
|
|
3452
|
+
}
|
|
3453
|
+
.graph-empty h3 {
|
|
3454
|
+
color: var(--tg-text);
|
|
3455
|
+
margin: 0;
|
|
3456
|
+
font-size: 16px;
|
|
3457
|
+
font-family: var(--tg-sans);
|
|
3458
|
+
}
|
|
3459
|
+
.graph-empty p {
|
|
3460
|
+
max-width: 540px;
|
|
3461
|
+
line-height: 1.55;
|
|
3462
|
+
color: var(--tg-text-dim);
|
|
3463
|
+
margin: 0;
|
|
3464
|
+
}
|
|
3465
|
+
.graph-empty code {
|
|
3466
|
+
background: var(--tg-surface);
|
|
3467
|
+
padding: 1px 5px;
|
|
3468
|
+
border-radius: 3px;
|
|
3469
|
+
font-size: 11px;
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
.graph-tooltip {
|
|
3473
|
+
position: fixed;
|
|
3474
|
+
pointer-events: none;
|
|
3475
|
+
background: rgba(15, 17, 23, 0.95);
|
|
3476
|
+
color: var(--tg-text);
|
|
3477
|
+
border: 1px solid var(--tg-border-active);
|
|
3478
|
+
border-radius: var(--tg-radius-sm);
|
|
3479
|
+
padding: 6px 10px;
|
|
3480
|
+
font-family: var(--tg-mono);
|
|
3481
|
+
font-size: 11px;
|
|
3482
|
+
backdrop-filter: blur(6px);
|
|
3483
|
+
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
|
|
3484
|
+
z-index: 50;
|
|
3485
|
+
max-width: 320px;
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
/* Drawer — right-side overlay shown when a node is clicked. */
|
|
3489
|
+
.graph-drawer {
|
|
3490
|
+
position: absolute;
|
|
3491
|
+
top: 0;
|
|
3492
|
+
right: 0;
|
|
3493
|
+
width: 420px;
|
|
3494
|
+
max-width: 90vw;
|
|
3495
|
+
height: 100%;
|
|
3496
|
+
background: var(--tg-surface);
|
|
3497
|
+
border-left: 1px solid var(--tg-border);
|
|
3498
|
+
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.35);
|
|
3499
|
+
display: flex;
|
|
3500
|
+
flex-direction: column;
|
|
3501
|
+
transform: translateX(100%);
|
|
3502
|
+
transition: transform 220ms ease-out;
|
|
3503
|
+
z-index: 30;
|
|
3504
|
+
}
|
|
3505
|
+
.graph-drawer.open {
|
|
3506
|
+
transform: translateX(0);
|
|
3507
|
+
}
|
|
3508
|
+
.graph-drawer-header {
|
|
3509
|
+
display: flex;
|
|
3510
|
+
align-items: center;
|
|
3511
|
+
justify-content: space-between;
|
|
3512
|
+
padding: 12px 14px;
|
|
3513
|
+
border-bottom: 1px solid var(--tg-border);
|
|
3514
|
+
flex-shrink: 0;
|
|
3515
|
+
}
|
|
3516
|
+
.gd-h-meta {
|
|
3517
|
+
display: flex;
|
|
3518
|
+
gap: 8px;
|
|
3519
|
+
align-items: center;
|
|
3520
|
+
font-family: var(--tg-mono);
|
|
3521
|
+
font-size: 11px;
|
|
3522
|
+
color: var(--tg-text-dim);
|
|
3523
|
+
}
|
|
3524
|
+
.gd-project {
|
|
3525
|
+
font-weight: 600;
|
|
3526
|
+
color: var(--tg-text-bright);
|
|
3527
|
+
}
|
|
3528
|
+
.gd-source-type {
|
|
3529
|
+
padding: 1px 6px;
|
|
3530
|
+
background: var(--tg-bg);
|
|
3531
|
+
border-radius: 3px;
|
|
3532
|
+
border: 1px solid var(--tg-border);
|
|
3533
|
+
}
|
|
3534
|
+
.gd-created::before {
|
|
3535
|
+
content: '·';
|
|
3536
|
+
margin-right: 6px;
|
|
3537
|
+
color: var(--tg-border-active);
|
|
3538
|
+
}
|
|
3539
|
+
.graph-drawer-close {
|
|
3540
|
+
background: transparent;
|
|
3541
|
+
border: 0;
|
|
3542
|
+
color: var(--tg-text-dim);
|
|
3543
|
+
font-size: 20px;
|
|
3544
|
+
line-height: 1;
|
|
3545
|
+
cursor: pointer;
|
|
3546
|
+
padding: 4px 8px;
|
|
3547
|
+
border-radius: var(--tg-radius-sm);
|
|
3548
|
+
}
|
|
3549
|
+
.graph-drawer-close:hover {
|
|
3550
|
+
color: var(--tg-text-bright);
|
|
3551
|
+
background: var(--tg-surface-hover);
|
|
3552
|
+
}
|
|
3553
|
+
.graph-drawer-body {
|
|
3554
|
+
flex: 1;
|
|
3555
|
+
overflow: auto;
|
|
3556
|
+
padding: 14px;
|
|
3557
|
+
}
|
|
3558
|
+
.gd-content {
|
|
3559
|
+
background: var(--tg-bg);
|
|
3560
|
+
border: 1px solid var(--tg-border);
|
|
3561
|
+
border-radius: var(--tg-radius-sm);
|
|
3562
|
+
padding: 12px;
|
|
3563
|
+
font-family: var(--tg-mono);
|
|
3564
|
+
font-size: 12px;
|
|
3565
|
+
color: var(--tg-text);
|
|
3566
|
+
white-space: pre-wrap;
|
|
3567
|
+
word-break: break-word;
|
|
3568
|
+
max-height: 40vh;
|
|
3569
|
+
overflow: auto;
|
|
3570
|
+
margin: 0 0 16px;
|
|
3571
|
+
}
|
|
3572
|
+
.gd-section {
|
|
3573
|
+
margin: 0 0 8px;
|
|
3574
|
+
font-size: 11px;
|
|
3575
|
+
text-transform: uppercase;
|
|
3576
|
+
letter-spacing: 0.5px;
|
|
3577
|
+
color: var(--tg-text-dim);
|
|
3578
|
+
font-family: var(--tg-mono);
|
|
3579
|
+
}
|
|
3580
|
+
.gd-neighbors {
|
|
3581
|
+
display: flex;
|
|
3582
|
+
flex-direction: column;
|
|
3583
|
+
gap: 4px;
|
|
3584
|
+
}
|
|
3585
|
+
.gd-neighbor {
|
|
3586
|
+
display: grid;
|
|
3587
|
+
grid-template-columns: 10px 1fr auto;
|
|
3588
|
+
align-items: center;
|
|
3589
|
+
gap: 8px;
|
|
3590
|
+
padding: 7px 8px;
|
|
3591
|
+
background: var(--tg-bg);
|
|
3592
|
+
border: 1px solid var(--tg-border);
|
|
3593
|
+
border-radius: var(--tg-radius-sm);
|
|
3594
|
+
cursor: pointer;
|
|
3595
|
+
text-align: left;
|
|
3596
|
+
transition: border-color 120ms, background 120ms;
|
|
3597
|
+
font-family: var(--tg-sans);
|
|
3598
|
+
color: var(--tg-text);
|
|
3599
|
+
}
|
|
3600
|
+
.gd-neighbor:hover {
|
|
3601
|
+
border-color: var(--tg-border-active);
|
|
3602
|
+
background: var(--tg-surface-hover);
|
|
3603
|
+
}
|
|
3604
|
+
.gd-neighbor-dot {
|
|
3605
|
+
width: 8px;
|
|
3606
|
+
height: 8px;
|
|
3607
|
+
border-radius: 50%;
|
|
3608
|
+
}
|
|
3609
|
+
.gd-neighbor-label {
|
|
3610
|
+
font-size: 12px;
|
|
3611
|
+
overflow: hidden;
|
|
3612
|
+
text-overflow: ellipsis;
|
|
3613
|
+
white-space: nowrap;
|
|
3614
|
+
}
|
|
3615
|
+
.gd-neighbor-meta {
|
|
3616
|
+
font-size: 10px;
|
|
3617
|
+
color: var(--tg-text-dim);
|
|
3618
|
+
font-family: var(--tg-mono);
|
|
3619
|
+
}
|
|
3620
|
+
.gd-empty {
|
|
3621
|
+
padding: 20px 12px;
|
|
3622
|
+
text-align: center;
|
|
3623
|
+
color: var(--tg-text-dim);
|
|
3624
|
+
font-size: 12px;
|
|
3625
|
+
background: var(--tg-bg);
|
|
3626
|
+
border: 1px dashed var(--tg-border);
|
|
3627
|
+
border-radius: var(--tg-radius-sm);
|
|
3628
|
+
}
|
|
3629
|
+
.graph-drawer-footer {
|
|
3630
|
+
display: flex;
|
|
3631
|
+
gap: 8px;
|
|
3632
|
+
padding: 10px 14px;
|
|
3633
|
+
border-top: 1px solid var(--tg-border);
|
|
3634
|
+
flex-shrink: 0;
|
|
3635
|
+
}
|
|
3636
|
+
.graph-drawer-action {
|
|
3637
|
+
flex: 1;
|
|
3638
|
+
background: var(--tg-bg);
|
|
3639
|
+
color: var(--tg-text);
|
|
3640
|
+
border: 1px solid var(--tg-border);
|
|
3641
|
+
border-radius: var(--tg-radius-sm);
|
|
3642
|
+
padding: 6px 10px;
|
|
3643
|
+
font-size: 12px;
|
|
3644
|
+
font-family: var(--tg-mono);
|
|
3645
|
+
cursor: pointer;
|
|
3646
|
+
transition: all 120ms;
|
|
3647
|
+
}
|
|
3648
|
+
.graph-drawer-action:hover {
|
|
3649
|
+
background: var(--tg-surface-hover);
|
|
3650
|
+
border-color: var(--tg-border-active);
|
|
3651
|
+
color: var(--tg-text-bright);
|
|
3652
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Flashback diagnostic ring buffer (Sprint 39 T1).
|
|
2
|
+
//
|
|
3
|
+
// Six decision points along the Flashback pipeline write structured events
|
|
4
|
+
// here so production-flow regressions surface as a readable timeline instead
|
|
5
|
+
// of a silent gate failure. The ring is in-memory and lost on restart by
|
|
6
|
+
// design — persistence is a Sprint-40+ concern. Public surface:
|
|
7
|
+
//
|
|
8
|
+
// log({ sessionId, event, ...fields }) — append one event
|
|
9
|
+
// snapshot({ sessionId?, eventType?, limit? }) — read back filtered tail
|
|
10
|
+
// _resetForTest() — test-only ring clear
|
|
11
|
+
//
|
|
12
|
+
// Event shape (all events): { ts, sessionId, event, ...event-specific fields }.
|
|
13
|
+
//
|
|
14
|
+
// Event types and their producers:
|
|
15
|
+
// pattern_match — session.js _detectErrors (PATTERNS.error /
|
|
16
|
+
// errorLineStart / shellError matched)
|
|
17
|
+
// error_detected — session.js _detectErrors at onErrorDetected
|
|
18
|
+
// entry, before rate-limit check
|
|
19
|
+
// rate_limit_blocked — session.js _detectErrors when 30s limiter rejects
|
|
20
|
+
// bridge_query — mnestra-bridge queryMnestra at call return
|
|
21
|
+
// bridge_result — mnestra-bridge queryMnestra at call return
|
|
22
|
+
// proactive_memory_emit — index.js onErrorDetected WS send block
|
|
23
|
+
//
|
|
24
|
+
// The route GET /api/flashback/diag (registered in index.js) returns
|
|
25
|
+
// snapshot() output as JSON for ad-hoc inspection by Joshua and consumption
|
|
26
|
+
// by T4's production-flow e2e test.
|
|
27
|
+
|
|
28
|
+
const RING_SIZE = 200;
|
|
29
|
+
|
|
30
|
+
let ring = [];
|
|
31
|
+
|
|
32
|
+
function log(event) {
|
|
33
|
+
ring.push({ ts: new Date().toISOString(), ...event });
|
|
34
|
+
if (ring.length > RING_SIZE) {
|
|
35
|
+
ring = ring.slice(-RING_SIZE);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function snapshot({ sessionId, eventType, limit = RING_SIZE } = {}) {
|
|
40
|
+
let out = ring;
|
|
41
|
+
if (sessionId) out = out.filter((e) => e.sessionId === sessionId);
|
|
42
|
+
if (eventType) out = out.filter((e) => e.event === eventType);
|
|
43
|
+
const cap = Math.max(1, Math.min(RING_SIZE, Number(limit) || RING_SIZE));
|
|
44
|
+
return out.slice(-cap);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function _resetForTest() {
|
|
48
|
+
ring = [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { log, snapshot, _resetForTest, RING_SIZE };
|