superlocalmemory 4.0.5 → 4.0.7
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/CHANGELOG.md +108 -0
- package/README.md +8 -9
- package/package.json +3 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/rbac.py +106 -0
- package/src/superlocalmemory/brain/truth.py +80 -10
- package/src/superlocalmemory/cli/__main__.py +17 -0
- package/src/superlocalmemory/cli/commands.py +28 -3
- package/src/superlocalmemory/cli/gdpr_cmd.py +779 -0
- package/src/superlocalmemory/cli/gdpr_io.py +109 -0
- package/src/superlocalmemory/cli/main.py +85 -0
- package/src/superlocalmemory/cli/summary_cmd.py +195 -0
- package/src/superlocalmemory/code_graph/bridge/entity_resolver.py +26 -0
- package/src/superlocalmemory/code_graph/bridge/event_listeners.py +14 -3
- package/src/superlocalmemory/code_graph/bridge/maintenance.py +206 -0
- package/src/superlocalmemory/code_graph/config.py +65 -1
- package/src/superlocalmemory/code_graph/extractors/__init__.py +17 -0
- package/src/superlocalmemory/code_graph/graph_store.py +180 -3
- package/src/superlocalmemory/code_graph/parser.py +280 -100
- package/src/superlocalmemory/compliance/gdpr.py +358 -0
- package/src/superlocalmemory/core/config.py +44 -1
- package/src/superlocalmemory/core/engine_wiring.py +5 -1
- package/src/superlocalmemory/core/fact_consolidator.py +24 -1
- package/src/superlocalmemory/core/maintenance.py +93 -1
- package/src/superlocalmemory/core/recall_worker.py +33 -12
- package/src/superlocalmemory/infra/backup.py +138 -0
- package/src/superlocalmemory/infra/backup_obligations.py +423 -0
- package/src/superlocalmemory/learning/engagement.py +165 -0
- package/src/superlocalmemory/mcp/tools_code_graph.py +78 -7
- package/src/superlocalmemory/mcp/tools_v3.py +20 -6
- package/src/superlocalmemory/retrieval/engine.py +21 -0
- package/src/superlocalmemory/retrieval/remote_reranker.py +108 -11
- package/src/superlocalmemory/server/routes/brain.py +283 -15
- package/src/superlocalmemory/server/routes/learning.py +13 -25
- package/src/superlocalmemory/server/routes/memories.py +61 -0
- package/src/superlocalmemory/server/routes/v3_api.py +171 -60
- package/src/superlocalmemory/storage/database.py +36 -0
- package/src/superlocalmemory/storage/models.py +12 -4
- package/src/superlocalmemory/storage/schema_code_graph.py +44 -1
- package/src/superlocalmemory/summaries/__init__.py +37 -0
- package/src/superlocalmemory/summaries/base.py +108 -0
- package/src/superlocalmemory/summaries/daily_reflection.py +293 -0
- package/src/superlocalmemory/summaries/project_work_log.py +424 -0
- package/src/superlocalmemory/summaries/session_summary.py +307 -0
- package/src/superlocalmemory/ui/css/design-system.css +76 -1
- package/src/superlocalmemory/ui/index.html +29 -12
- package/src/superlocalmemory/ui/js/fact-detail.js +61 -0
- package/src/superlocalmemory/ui/js/od-agents.js +49 -5
- package/src/superlocalmemory/ui/js/od-brain.js +257 -77
- package/src/superlocalmemory/ui/js/od-graph.js +147 -6
|
@@ -87,9 +87,16 @@
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
function phaseLabel(raw) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
// Map known phase keys to human labels. Never string-mangle an internal
|
|
91
|
+
// identifier — unknown values fall back to a plain-English generic.
|
|
92
|
+
var LABELS = {
|
|
93
|
+
baseline: 'Baseline',
|
|
94
|
+
rule_based: 'Rule-based',
|
|
95
|
+
ml_model: 'ML model',
|
|
96
|
+
cold_start: 'Starting out',
|
|
97
|
+
};
|
|
98
|
+
var key = (raw || 'cold_start').toLowerCase().replace(/-/g, '_').replace(/\s+/g, '_');
|
|
99
|
+
return LABELS[key] || (raw ? String(raw).replace(/[-_]/g, ' ') : 'Starting out');
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
// ======================================================================
|
|
@@ -153,6 +160,35 @@
|
|
|
153
160
|
return wrap;
|
|
154
161
|
}
|
|
155
162
|
|
|
163
|
+
function humanizeSource(raw) {
|
|
164
|
+
// Map raw internal capability IDs to plain names a non-technical user can read.
|
|
165
|
+
// IDs follow patterns such as http:daemon-capability:<hex> or
|
|
166
|
+
// dashboard:local-capability:http-route:uid:<uid>:<hex>.
|
|
167
|
+
// Strip trailing hex hashes, then map the leading protocol/type prefix.
|
|
168
|
+
var s = String(raw || '')
|
|
169
|
+
.replace(/:?[0-9a-f]{40,}$/i, '')
|
|
170
|
+
.replace(/:?[0-9a-f]{32,}$/i, '')
|
|
171
|
+
.replace(/:$/, '')
|
|
172
|
+
.trim();
|
|
173
|
+
var prefix = (s.split(':')[0] || '').toLowerCase();
|
|
174
|
+
var MAP = {
|
|
175
|
+
http: 'Background service',
|
|
176
|
+
https: 'Background service',
|
|
177
|
+
dashboard: 'Dashboard',
|
|
178
|
+
cli: 'Command line',
|
|
179
|
+
'claude-code': 'Claude Code',
|
|
180
|
+
claude_code: 'Claude Code',
|
|
181
|
+
copilot: 'Copilot',
|
|
182
|
+
cursor: 'Cursor',
|
|
183
|
+
mcp: 'MCP server',
|
|
184
|
+
daemon: 'Background service',
|
|
185
|
+
};
|
|
186
|
+
if (MAP[prefix]) return MAP[prefix];
|
|
187
|
+
// Fall back: take the first segment, replace underscores/dashes, capitalise.
|
|
188
|
+
var label = prefix.replace(/[-_]/g, ' ').trim();
|
|
189
|
+
return label ? label.charAt(0).toUpperCase() + label.slice(1) : String(raw);
|
|
190
|
+
}
|
|
191
|
+
|
|
156
192
|
function heatLegend() {
|
|
157
193
|
var w = EL('div', { className: 'heat-legend' });
|
|
158
194
|
w.appendChild(document.createTextNode('less '));
|
|
@@ -219,10 +255,10 @@
|
|
|
219
255
|
var phaseNumber = Number(ranker.phase || 1);
|
|
220
256
|
var modelActive = Boolean(ranker.model_active);
|
|
221
257
|
var phaseDelta = modelActive
|
|
222
|
-
? '
|
|
258
|
+
? 'Personalised model active'
|
|
223
259
|
: signals < mlGate
|
|
224
|
-
? fmtNum(mlGate - signals) + ' to
|
|
225
|
-
: '
|
|
260
|
+
? fmtNum(mlGate - signals) + ' more interactions to unlock personalisation'
|
|
261
|
+
: 'Ready to personalise — awaiting model verification';
|
|
226
262
|
var healthStatus = (eng.health_status || 'INACTIVE').toUpperCase();
|
|
227
263
|
var healthColor = healthStatus === 'HEALTHY' ? 'var(--ok)'
|
|
228
264
|
: healthStatus === 'ACTIVE' ? 'var(--cyan)' : undefined;
|
|
@@ -250,17 +286,22 @@
|
|
|
250
286
|
// Ranking phase: text label → isNumeric=false (font-size:24px to match design)
|
|
251
287
|
strip.appendChild(kpiCard('skill', 'Ranking phase', phaseLabel(phase),
|
|
252
288
|
phaseDelta, modelActive, undefined, false));
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
-
|
|
289
|
+
// Questions answered: unique queries is the meaningful unit — total signals is context.
|
|
290
|
+
// 5,339 signals across 3 unique queries means one query repeated, not broad learning.
|
|
291
|
+
var uniqueQ = Number(stats.unique_queries || 0);
|
|
292
|
+
strip.appendChild(kpiCard('optimize', 'Questions answered', fmtNum(uniqueQ),
|
|
293
|
+
uniqueQ > 0
|
|
294
|
+
? fmtNum(signals) + ' total interactions'
|
|
295
|
+
: 'no questions asked yet',
|
|
296
|
+
uniqueQ > 0, undefined, true));
|
|
256
297
|
// Engagement health: text label → isNumeric=false
|
|
257
298
|
strip.appendChild(kpiCard('health', 'Engagement health',
|
|
258
299
|
healthStatus.charAt(0) + healthStatus.slice(1).toLowerCase(),
|
|
259
|
-
(eng.days_active || 0) + ' days active · ' + Number(eng.memories_per_day || 0).toFixed(1) + '
|
|
300
|
+
(eng.days_active || 0) + ' days active · ' + Number(eng.memories_per_day || 0).toFixed(1) + ' saves/day',
|
|
260
301
|
healthStatus === 'HEALTHY', healthColor, false));
|
|
261
302
|
// Patterns: numeric → isNumeric=true
|
|
262
303
|
strip.appendChild(kpiCard('brain', 'Patterns learned', String(pCount),
|
|
263
|
-
|
|
304
|
+
(beh.cross_project_transfers || 0) + ' used across projects', pCount > 0, undefined, true));
|
|
264
305
|
sec.appendChild(strip);
|
|
265
306
|
|
|
266
307
|
// 2-column grid
|
|
@@ -276,8 +317,17 @@
|
|
|
276
317
|
var pmeta = EL('div', {
|
|
277
318
|
style: 'display:flex;justify-content:space-between;font-size:12px;color:var(--fg-2);margin-bottom:8px',
|
|
278
319
|
});
|
|
279
|
-
|
|
280
|
-
|
|
320
|
+
if (signals >= mlGate) {
|
|
321
|
+
// Gate already passed — showing the overrun fraction (e.g. "5,339 / 200") is
|
|
322
|
+
// misleading. Show the done state and what comes next instead.
|
|
323
|
+
pmeta.appendChild(EL('span', { text: fmtNum(signals) + ' interactions · gate passed' }));
|
|
324
|
+
var gdBadge = EL('span', { className: 'badge ' + (modelActive ? 'ok' : 'warn') });
|
|
325
|
+
gdBadge.appendChild(document.createTextNode(modelActive ? 'model active' : 'awaiting verification'));
|
|
326
|
+
pmeta.appendChild(gdBadge);
|
|
327
|
+
} else {
|
|
328
|
+
pmeta.appendChild(EL('span', { text: fmtNum(signals) + ' of ' + fmtNum(mlGate) + ' interactions' }));
|
|
329
|
+
pmeta.appendChild(EL('span', { className: 'num', text: pct + '%' }));
|
|
330
|
+
}
|
|
281
331
|
pb.appendChild(pmeta);
|
|
282
332
|
pb.appendChild(meter(pct));
|
|
283
333
|
var phasesRow = EL('div', {
|
|
@@ -329,12 +379,12 @@
|
|
|
329
379
|
['Sources tracked', String(stats.tracked_sources || 0)],
|
|
330
380
|
['Memory activity', truthCount(memoryActivity, 'facts_total', 'facts')],
|
|
331
381
|
['Feedback signals', truthCount(feedback, 'signals_total', 'signals')],
|
|
332
|
-
['Claimed evidence', truthCount(experience, 'claimed_experiences_total', '
|
|
382
|
+
['Claimed evidence', truthCount(experience, 'claimed_experiences_total', '')],
|
|
333
383
|
['Independently verified evidence', truthCount(
|
|
334
|
-
experience, 'independently_verified_experiences_total', '
|
|
384
|
+
experience, 'independently_verified_experiences_total', '',
|
|
335
385
|
)],
|
|
336
|
-
['External observations', truthCount(externalEvidence, 'receipts_total', '
|
|
337
|
-
['Correction quality', truthCount(correctionQuality, 'cases_total', '
|
|
386
|
+
['External observations', truthCount(externalEvidence, 'receipts_total', '')],
|
|
387
|
+
['Correction quality', truthCount(correctionQuality, 'cases_total', '')],
|
|
338
388
|
['Graph evidence', String(graph.fact_nodes || 0) + ' nodes · ' +
|
|
339
389
|
String(graph.association_edges || 0) + ' edges'],
|
|
340
390
|
].forEach(function (row) {
|
|
@@ -367,26 +417,29 @@
|
|
|
367
417
|
style: 'margin:0 0 14px;font-size:12px;line-height:1.55',
|
|
368
418
|
text: 'SLM records completed work when an integration supplies evidence. These records do not change recall, ranking, or model routing by themselves.',
|
|
369
419
|
}));
|
|
420
|
+
// Three plain-language KPI cards.
|
|
421
|
+
// Technical labels (Claimed evidence, Independently verified evidence,
|
|
422
|
+
// External observations, Correction quality) are preserved in the
|
|
423
|
+
// Privacy card rows above for test and audit traceability.
|
|
424
|
+
var claimedCount = Number(experience.claimed_experiences_total || 0);
|
|
425
|
+
var corrCount = Number(correctionQuality.cases_total || 0);
|
|
426
|
+
var obsCount = Number(externalEvidence.receipts_total || 0);
|
|
370
427
|
var evGrid = EL('div', { className: 'kpi-strip', style: 'margin:0' });
|
|
371
|
-
evGrid.appendChild(kpiCard('fact_check', '
|
|
372
|
-
truthCount(experience, 'claimed_experiences_total', ''),
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
evGrid.appendChild(kpiCard('
|
|
380
|
-
truthCount(experience, 'cognitive_turns_total', ''),
|
|
381
|
-
String((experience.cognitive_turns_by_state || {}).open || 0) + ' open · ' +
|
|
382
|
-
String((experience.cognitive_turns_by_state || {}).finalized || 0) + ' finalized',
|
|
383
|
-
Number(experience.cognitive_turns_total || 0) > 0, undefined, true));
|
|
384
|
-
evGrid.appendChild(kpiCard('account_tree', 'External observations',
|
|
428
|
+
evGrid.appendChild(kpiCard('fact_check', 'Tasks completed',
|
|
429
|
+
truthCount(experience, 'claimed_experiences_total', ''),
|
|
430
|
+
claimedCount > 0 ? 'work recorded from connected tools' : 'no tasks recorded yet',
|
|
431
|
+
claimedCount > 0, undefined, true));
|
|
432
|
+
evGrid.appendChild(kpiCard('tune', 'Corrections applied',
|
|
433
|
+
truthCount(correctionQuality, 'cases_total', ''),
|
|
434
|
+
corrCount > 0 ? 'improvements applied to results' : 'no corrections recorded yet',
|
|
435
|
+
corrCount > 0, undefined, true));
|
|
436
|
+
evGrid.appendChild(kpiCard('science', 'Observations logged',
|
|
385
437
|
truthCount(externalEvidence, 'receipts_total', ''),
|
|
386
|
-
externalEvidence.availability === 'available'
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
438
|
+
externalEvidence.availability === 'available' &&
|
|
439
|
+
Number(externalEvidence.demonstrations_total || 0) > 0
|
|
440
|
+
? String(externalEvidence.demonstrations_total) + ' demonstrations'
|
|
441
|
+
: 'no external demonstrations yet',
|
|
442
|
+
obsCount > 0, undefined, true));
|
|
390
443
|
evb.appendChild(evGrid);
|
|
391
444
|
evc.appendChild(evb);
|
|
392
445
|
sec.appendChild(evc);
|
|
@@ -427,7 +480,7 @@
|
|
|
427
480
|
hmhr.appendChild(EL('h3', { text: 'Reward signal density' }));
|
|
428
481
|
hmhr.appendChild(EL('span', {
|
|
429
482
|
className: 'sub',
|
|
430
|
-
text: '
|
|
483
|
+
text: 'feedback events per day · last ' + Number(reward.window_days || 182) + ' days',
|
|
431
484
|
}));
|
|
432
485
|
hmhr.appendChild(EL('div', { className: 'spacer' }));
|
|
433
486
|
hmhr.appendChild(heatLegend());
|
|
@@ -439,16 +492,26 @@
|
|
|
439
492
|
hmcr.appendChild(hmbr);
|
|
440
493
|
sec.appendChild(hmcr);
|
|
441
494
|
|
|
495
|
+
// Compute distribution state once so both cards use the same values.
|
|
496
|
+
var total = Number(reward.count || 0);
|
|
497
|
+
var bd = reward.distribution || {};
|
|
498
|
+
// isUnmeasuredPrior: when positive=0 and negative=0, every label sits on the
|
|
499
|
+
// initialization value (0.5). That is the absence of differentiation, not a
|
|
500
|
+
// finding — render it honestly rather than as a measured result.
|
|
501
|
+
var isUnmeasuredPrior = total > 0
|
|
502
|
+
&& Number(bd.positive || 0) === 0
|
|
503
|
+
&& Number(bd.negative || 0) === 0;
|
|
504
|
+
|
|
442
505
|
// 2-column: sparkline + outcome mix
|
|
443
506
|
var grid = EL('div', { className: 'grid', style: 'grid-template-columns:1fr 1fr;align-items:start' });
|
|
444
507
|
|
|
445
|
-
//
|
|
508
|
+
// Recall quality card: average reward score + real daily sparkline
|
|
446
509
|
var fbCard = EL('div', { className: 'card' });
|
|
447
510
|
var fbH = EL('div', { className: 'card-head' });
|
|
448
|
-
fbH.appendChild(EL('h3', { text: '
|
|
511
|
+
fbH.appendChild(EL('h3', { text: 'Recall quality' }));
|
|
449
512
|
fbH.appendChild(EL('span', {
|
|
450
513
|
className: 'sub',
|
|
451
|
-
text: fmtNum(reward.count || 0) + '
|
|
514
|
+
text: fmtNum(reward.count || 0) + (isUnmeasuredPrior ? ' interactions · default score' : ' interactions'),
|
|
452
515
|
}));
|
|
453
516
|
fbCard.appendChild(fbH);
|
|
454
517
|
var fbB = EL('div', { className: 'card-pad' });
|
|
@@ -458,6 +521,13 @@
|
|
|
458
521
|
style: 'font-size:30px;margin-bottom:12px',
|
|
459
522
|
text: Number(reward.average).toFixed(3),
|
|
460
523
|
}));
|
|
524
|
+
if (isUnmeasuredPrior) {
|
|
525
|
+
fbB.appendChild(EL('p', {
|
|
526
|
+
className: 'muted',
|
|
527
|
+
style: 'font-size:12px;margin-top:4px;margin-bottom:0',
|
|
528
|
+
text: 'Starting value — no differentiated engagement yet.',
|
|
529
|
+
}));
|
|
530
|
+
}
|
|
461
531
|
}
|
|
462
532
|
var fbSp = EL('div', { id: 'od-brain-sp-fb' });
|
|
463
533
|
var sparkVals = timeline.slice(-30).map(function (point) {
|
|
@@ -471,7 +541,7 @@
|
|
|
471
541
|
fbSp.appendChild(EL('p', {
|
|
472
542
|
className: 'muted',
|
|
473
543
|
style: 'padding:32px;text-align:center;font-size:13px',
|
|
474
|
-
text: 'No
|
|
544
|
+
text: 'No recall history is available yet.',
|
|
475
545
|
}));
|
|
476
546
|
}
|
|
477
547
|
fbB.appendChild(fbSp);
|
|
@@ -482,16 +552,17 @@
|
|
|
482
552
|
var outCard = EL('div', { className: 'card' });
|
|
483
553
|
var outH = EL('div', { className: 'card-head' });
|
|
484
554
|
outH.appendChild(EL('h3', { text: 'Reward distribution' }));
|
|
485
|
-
outH.appendChild(EL('span', { className: 'sub', text: '
|
|
555
|
+
outH.appendChild(EL('span', { className: 'sub', text: 'based on how you engage with recalled results' }));
|
|
486
556
|
outCard.appendChild(outH);
|
|
487
557
|
var outB = EL('div', { className: 'card-pad', id: 'od-brain-outcomes' });
|
|
488
|
-
|
|
489
|
-
var bd = reward.distribution || {};
|
|
490
|
-
if (total === 0) {
|
|
558
|
+
if (total === 0 || isUnmeasuredPrior) {
|
|
491
559
|
outB.appendChild(EL('p', {
|
|
492
560
|
className: 'muted',
|
|
493
561
|
style: 'padding:16px;text-align:center;font-size:13px',
|
|
494
|
-
text:
|
|
562
|
+
text: total === 0
|
|
563
|
+
? 'No reward labels yet. Recall engagement will populate this view.'
|
|
564
|
+
: 'All ' + fmtNum(total) + ' labels carry the default score. ' +
|
|
565
|
+
'Differentiated results appear after consistent recall use.',
|
|
495
566
|
}));
|
|
496
567
|
} else {
|
|
497
568
|
[
|
|
@@ -530,7 +601,7 @@
|
|
|
530
601
|
var tc = EL('div', { className: 'card' });
|
|
531
602
|
var tch = EL('div', { className: 'card-head' });
|
|
532
603
|
tch.appendChild(EL('h3', { text: 'Tech preferences' }));
|
|
533
|
-
tch.appendChild(EL('span', { className: 'sub', text: '
|
|
604
|
+
tch.appendChild(EL('span', { className: 'sub', text: 'your tools and technology preferences' }));
|
|
534
605
|
tc.appendChild(tch);
|
|
535
606
|
var tcb = EL('div', { className: 'card-pad' });
|
|
536
607
|
var techItems = l.tech_preferences || [];
|
|
@@ -571,7 +642,7 @@
|
|
|
571
642
|
var wc = EL('div', { className: 'card' });
|
|
572
643
|
var wch = EL('div', { className: 'card-head' });
|
|
573
644
|
wch.appendChild(EL('h3', { text: 'Workflow patterns' }));
|
|
574
|
-
wch.appendChild(EL('span', { className: 'sub', text: '
|
|
645
|
+
wch.appendChild(EL('span', { className: 'sub', text: 'how you sequence your work over time' }));
|
|
575
646
|
wc.appendChild(wch);
|
|
576
647
|
var wcb = EL('div', { className: 'card-pad' });
|
|
577
648
|
var wfPats = l.workflow_patterns || [];
|
|
@@ -672,11 +743,26 @@
|
|
|
672
743
|
// ======================================================================
|
|
673
744
|
// Tab: CONNECTED CLIENTS
|
|
674
745
|
// ======================================================================
|
|
675
|
-
function
|
|
746
|
+
function fmtSecondsAgo(s) {
|
|
747
|
+
if (s == null) return 'unknown';
|
|
748
|
+
var n = Number(s);
|
|
749
|
+
if (n < 120) return n + 's ago';
|
|
750
|
+
if (n < 7200) return Math.round(n / 60) + 'm ago';
|
|
751
|
+
if (n < 172800) return Math.round(n / 3600) + 'h ago';
|
|
752
|
+
return Math.round(n / 86400) + 'd ago';
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// buildClients accepts an optional third argument `boundedLoops` (brain.bounded_loops).
|
|
756
|
+
// Absent / undefined is safe: the BL section is silently omitted.
|
|
757
|
+
function buildClients(living, configured, boundedLoops) {
|
|
676
758
|
var sec = EL('section', { className: 'tabpane', 'data-p': 'clients' });
|
|
677
|
-
var
|
|
759
|
+
var connectedData = (living && living.connected_clients) || {};
|
|
760
|
+
var clients = connectedData.clients || [];
|
|
761
|
+
var registryStatus = String(connectedData.registry_status || '');
|
|
762
|
+
var newestAgo = connectedData.newest_entry_seconds_ago;
|
|
678
763
|
var configuredData = configured || {};
|
|
679
764
|
|
|
765
|
+
// ── Recent client activity ────────────────────────────────────────────
|
|
680
766
|
// Activity is presence reported by host lifecycle hooks, not the old
|
|
681
767
|
// tool-event proxy. A configured adapter and a recent client are two
|
|
682
768
|
// different truths, rendered as separate cards.
|
|
@@ -686,8 +772,46 @@
|
|
|
686
772
|
evh.appendChild(EL('span', { className: 'sub', text: 'host lifecycle presence · last 5 minutes' }));
|
|
687
773
|
evc.appendChild(evh);
|
|
688
774
|
var evb = EL('div', { className: 'card-pad' });
|
|
689
|
-
|
|
690
|
-
|
|
775
|
+
|
|
776
|
+
if (registryStatus === 'error' || registryStatus === 'unknown') {
|
|
777
|
+
// Wave 4 honesty rule: failure and emptiness must not return the same value.
|
|
778
|
+
// 'unknown' belongs HERE, not in the healthy-empty branch below. The
|
|
779
|
+
// registry reader is fail-soft — a corrupt or unreadable file makes
|
|
780
|
+
// _load() return {} and active_client_summary() return [], so the read
|
|
781
|
+
// model reports status 'unknown' with an empty client list and is_real
|
|
782
|
+
// true. Falling through to "No host activity in the last 5 minutes" would
|
|
783
|
+
// render a BROKEN registry exactly like a healthy quiet machine, which is
|
|
784
|
+
// precisely the silent failure this section was rewritten to eliminate.
|
|
785
|
+
evb.appendChild(EL('p', {
|
|
786
|
+
className: 'muted',
|
|
787
|
+
style: 'padding:16px;text-align:center;font-size:13px',
|
|
788
|
+
text: registryStatus === 'unknown'
|
|
789
|
+
? 'Presence records could not be read, so recent activity cannot be '
|
|
790
|
+
+ 'confirmed. This is not the same as no agents being active.'
|
|
791
|
+
: 'Presence registry unavailable. Check daemon logs for details.',
|
|
792
|
+
}));
|
|
793
|
+
} else if (registryStatus === 'stale') {
|
|
794
|
+
// TELEMETRY GAP — entries exist but all are > 10 min old.
|
|
795
|
+
// "No activity" here would be indistinguishable from "hooks not firing".
|
|
796
|
+
evb.appendChild(EL('p', {
|
|
797
|
+
className: 'muted',
|
|
798
|
+
style: 'padding:16px;text-align:center;font-size:13px',
|
|
799
|
+
text: 'Presence recording gap — last hook event was ' + fmtSecondsAgo(newestAgo) + '. ' +
|
|
800
|
+
'Hooks are installed but presence has not been written recently. ' +
|
|
801
|
+
'Recent activity from integrations cannot be confirmed.',
|
|
802
|
+
}));
|
|
803
|
+
} else if (registryStatus === 'absent' || registryStatus === 'empty') {
|
|
804
|
+
// No records at all — first run or hooks have not fired yet.
|
|
805
|
+
evb.appendChild(EL('p', {
|
|
806
|
+
className: 'muted',
|
|
807
|
+
style: 'padding:16px;text-align:center;font-size:13px',
|
|
808
|
+
text: 'No presence records found. Hook events may not have fired yet on this install.',
|
|
809
|
+
}));
|
|
810
|
+
} else if (clients.length === 0) {
|
|
811
|
+
// Registry is live (recent writes) but no clients in the 5-min window.
|
|
812
|
+
evb.appendChild(EL('p', {
|
|
813
|
+
className: 'muted',
|
|
814
|
+
style: 'padding:16px;text-align:center;font-size:13px',
|
|
691
815
|
text: 'No host activity in the last 5 minutes. This does not mean an integration is uninstalled.',
|
|
692
816
|
}));
|
|
693
817
|
} else {
|
|
@@ -695,7 +819,7 @@
|
|
|
695
819
|
var row = EL('div', { className: 'list-row' });
|
|
696
820
|
row.appendChild(EL('b', { style: 'flex:1', text: String(client.kind || 'other') }));
|
|
697
821
|
row.appendChild(EL('span', { className: 'muted',
|
|
698
|
-
text: 'active ' +
|
|
822
|
+
text: 'active ' + fmtSecondsAgo(client.last_seen_seconds_ago),
|
|
699
823
|
}));
|
|
700
824
|
evb.appendChild(row);
|
|
701
825
|
});
|
|
@@ -703,8 +827,11 @@
|
|
|
703
827
|
evc.appendChild(evb);
|
|
704
828
|
sec.appendChild(evc);
|
|
705
829
|
|
|
706
|
-
// Configured integrations
|
|
707
|
-
|
|
830
|
+
// ── Configured integrations ───────────────────────────────────────────
|
|
831
|
+
// Installation / sync state, not client activity.
|
|
832
|
+
// For Codex, evidence_tier='configured' means config-file evidence;
|
|
833
|
+
// badge reads 'configured' rather than 'available' to be explicit.
|
|
834
|
+
var tc = EL('div', { className: 'card', style: 'margin-bottom:16px' });
|
|
708
835
|
var tch = EL('div', { className: 'card-head' });
|
|
709
836
|
tch.appendChild(EL('h3', { text: 'Configured integrations' }));
|
|
710
837
|
tch.appendChild(EL('span', { className: 'sub', text: 'installation and sync availability' }));
|
|
@@ -714,14 +841,50 @@
|
|
|
714
841
|
var state = configuredData[kind] || {};
|
|
715
842
|
var row = EL('div', { className: 'list-row' });
|
|
716
843
|
row.appendChild(EL('span', { style: 'flex:1', text: kind.replace(/_/g, ' ') }));
|
|
717
|
-
|
|
844
|
+
var badgeText = state.active
|
|
845
|
+
? (state.evidence_tier === 'configured' ? 'configured' : 'available')
|
|
846
|
+
: (state.reason || 'not available');
|
|
847
|
+
var badgeEl = EL('span', {
|
|
718
848
|
className: 'badge ' + (state.active ? 'ok' : 'warn'),
|
|
719
|
-
text:
|
|
720
|
-
})
|
|
849
|
+
text: badgeText,
|
|
850
|
+
});
|
|
851
|
+
if (state.evidence) badgeEl.setAttribute('title', String(state.evidence));
|
|
852
|
+
row.appendChild(badgeEl);
|
|
721
853
|
tcb.appendChild(row);
|
|
722
854
|
});
|
|
723
855
|
tc.appendChild(tcb);
|
|
724
856
|
sec.appendChild(tc);
|
|
857
|
+
|
|
858
|
+
// ── Bounded Loops ─────────────────────────────────────────────────────
|
|
859
|
+
// Section is on when bounded_loops.section_enabled === true.
|
|
860
|
+
// When absent (older API or not installed) the section is silently omitted.
|
|
861
|
+
var bl = boundedLoops || {};
|
|
862
|
+
if (bl.section_enabled) {
|
|
863
|
+
var blc = EL('div', { className: 'card' });
|
|
864
|
+
var blh = EL('div', { className: 'card-head' });
|
|
865
|
+
blh.appendChild(EL('h3', { text: 'Bounded Loops' }));
|
|
866
|
+
blh.appendChild(EL('span', {
|
|
867
|
+
className: 'sub',
|
|
868
|
+
text: 'local installation' + (bl.version ? ' · v' + bl.version : ''),
|
|
869
|
+
}));
|
|
870
|
+
blc.appendChild(blh);
|
|
871
|
+
var blb = EL('div', { className: 'card-pad', style: 'display:flex;flex-direction:column;gap:2px' });
|
|
872
|
+
[
|
|
873
|
+
['Status', 'installed'],
|
|
874
|
+
['Version', bl.version || 'unknown'],
|
|
875
|
+
['Bridge contract', bl.bridge_contract || '—'],
|
|
876
|
+
['Evidence', bl.evidence || bl.evidence_tier || '—'],
|
|
877
|
+
['Note', bl.note || '—'],
|
|
878
|
+
].forEach(function (r) {
|
|
879
|
+
var row = EL('div', { className: 'list-row' });
|
|
880
|
+
row.appendChild(EL('span', { className: 'muted', style: 'flex:1', text: r[0] }));
|
|
881
|
+
row.appendChild(EL('b', { text: String(r[1]) }));
|
|
882
|
+
blb.appendChild(row);
|
|
883
|
+
});
|
|
884
|
+
blc.appendChild(blb);
|
|
885
|
+
sec.appendChild(blc);
|
|
886
|
+
}
|
|
887
|
+
|
|
725
888
|
return sec;
|
|
726
889
|
}
|
|
727
890
|
|
|
@@ -737,7 +900,7 @@
|
|
|
737
900
|
var card = EL('div', { className: 'card' });
|
|
738
901
|
var ch = EL('div', { className: 'card-head' });
|
|
739
902
|
ch.appendChild(EL('h3', { text: 'Source quality' }));
|
|
740
|
-
ch.appendChild(EL('span', { className: 'sub', text: '
|
|
903
|
+
ch.appendChild(EL('span', { className: 'sub', text: 'how well each source has performed · 0.0–1.0' }));
|
|
741
904
|
card.appendChild(ch);
|
|
742
905
|
var cb = EL('div', { className: 'card-pad' });
|
|
743
906
|
var aggregate = (living && living.source_quality) || {};
|
|
@@ -752,27 +915,44 @@
|
|
|
752
915
|
cb.appendChild(EL('p', {
|
|
753
916
|
className: 'muted',
|
|
754
917
|
style: 'padding:16px;text-align:center;font-size:13px',
|
|
755
|
-
text: String(observedSources) + ' sources have
|
|
918
|
+
text: String(observedSources) + ' sources have been seen, but individual quality scores are not available yet.',
|
|
756
919
|
}));
|
|
757
920
|
} else {
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
}));
|
|
764
|
-
entries.forEach(function (k) {
|
|
765
|
-
var v = Number(scores[k]);
|
|
766
|
-
var row = EL('div', { style: 'margin-bottom:14px' });
|
|
767
|
-
var meta = EL('div', {
|
|
768
|
-
style: 'display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px',
|
|
769
|
-
});
|
|
770
|
-
meta.appendChild(EL('span', { className: 'mono', text: k }));
|
|
771
|
-
meta.appendChild(EL('b', { className: 'num', text: v.toFixed(2) }));
|
|
772
|
-
row.appendChild(meta);
|
|
773
|
-
row.appendChild(meter(v * 100));
|
|
774
|
-
cb.appendChild(row);
|
|
921
|
+
// allAtPrior: every source at exactly 0.50 means the system has not yet accumulated
|
|
922
|
+
// enough outcome data to shift away from the initialization value. Showing 18 identical
|
|
923
|
+
// rows would mislead a non-technical reader into thinking quality was measured.
|
|
924
|
+
var allAtPrior = entries.every(function (k) {
|
|
925
|
+
return Math.abs(Number(scores[k]) - 0.5) < 0.005;
|
|
775
926
|
});
|
|
927
|
+
if (allAtPrior) {
|
|
928
|
+
cb.appendChild(EL('p', {
|
|
929
|
+
className: 'muted',
|
|
930
|
+
style: 'font-size:13px;margin-bottom:16px',
|
|
931
|
+
text: String(observedSources) + ' source' + (observedSources === 1 ? '' : 's') +
|
|
932
|
+
' observed. Quality scores start at 0.5 and shift once recall shows ' +
|
|
933
|
+
'which sources consistently produce better results. No quality signal has settled yet.',
|
|
934
|
+
}));
|
|
935
|
+
} else {
|
|
936
|
+
cb.appendChild(EL('p', {
|
|
937
|
+
className: 'muted',
|
|
938
|
+
style: 'font-size:13px;margin-bottom:16px',
|
|
939
|
+
text: String(observedSources) + ' source' + (observedSources === 1 ? '' : 's') +
|
|
940
|
+
' · average quality ' + Number(aggregate.mean_quality).toFixed(2) + '.',
|
|
941
|
+
}));
|
|
942
|
+
entries.forEach(function (k) {
|
|
943
|
+
var v = Number(scores[k]);
|
|
944
|
+
var displayName = humanizeSource(k);
|
|
945
|
+
var row = EL('div', { style: 'margin-bottom:14px' });
|
|
946
|
+
var meta = EL('div', {
|
|
947
|
+
style: 'display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px',
|
|
948
|
+
});
|
|
949
|
+
meta.appendChild(EL('span', { text: displayName }));
|
|
950
|
+
meta.appendChild(EL('b', { className: 'num', text: v.toFixed(2) }));
|
|
951
|
+
row.appendChild(meta);
|
|
952
|
+
row.appendChild(meter(v * 100));
|
|
953
|
+
cb.appendChild(row);
|
|
954
|
+
});
|
|
955
|
+
}
|
|
776
956
|
}
|
|
777
957
|
card.appendChild(cb); sec.appendChild(card);
|
|
778
958
|
return sec;
|
|
@@ -880,7 +1060,7 @@
|
|
|
880
1060
|
buildOverview(learning, behavioral, dateMap, living),
|
|
881
1061
|
buildReward(behavioral),
|
|
882
1062
|
buildBehaviour(learning, behavioral),
|
|
883
|
-
buildClients(living, brain.cross_platform),
|
|
1063
|
+
buildClients(living, brain.cross_platform, brain.bounded_loops),
|
|
884
1064
|
buildSourceQuality(learning, living)
|
|
885
1065
|
);
|
|
886
1066
|
wireTabs(container);
|