superlocalmemory 4.0.4 → 4.0.6
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 +90 -0
- package/README.md +23 -14
- package/ide/configs/codex-mcp.toml +2 -2
- package/package.json +3 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -0
- 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 +3 -2
- 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 +2 -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 +6 -5
- package/plugin-src/skills/slm-graph/SKILL.md +2 -1
- package/plugin-src/skills/slm-profile/SKILL.md +1 -0
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/rbac.py +106 -0
- package/src/superlocalmemory/brain/__init__.py +5 -0
- package/src/superlocalmemory/brain/truth.py +418 -0
- package/src/superlocalmemory/cli/__main__.py +17 -0
- package/src/superlocalmemory/cli/commands.py +96 -28
- package/src/superlocalmemory/cli/gdpr_cmd.py +779 -0
- package/src/superlocalmemory/cli/gdpr_io.py +109 -0
- package/src/superlocalmemory/cli/main.py +88 -0
- 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/context_cache.py +58 -1
- package/src/superlocalmemory/core/engine_wiring.py +5 -1
- package/src/superlocalmemory/core/maintenance.py +43 -1
- package/src/superlocalmemory/core/mutations.py +155 -25
- package/src/superlocalmemory/core/recall_pipeline.py +6 -10
- package/src/superlocalmemory/core/recall_worker.py +33 -12
- package/src/superlocalmemory/core/remember_runtime.py +271 -2
- package/src/superlocalmemory/core/store_pipeline.py +100 -38
- package/src/superlocalmemory/encoding/consolidator.py +17 -47
- package/src/superlocalmemory/encoding/temporal_validator.py +14 -18
- package/src/superlocalmemory/hooks/user_prompt_hook.py +1 -1
- package/src/superlocalmemory/infra/backup.py +138 -0
- package/src/superlocalmemory/infra/backup_obligations.py +423 -0
- package/src/superlocalmemory/integrations/bounded_loops_mcp.py +4 -3
- package/src/superlocalmemory/learning/engagement.py +165 -0
- package/src/superlocalmemory/mcp/profiles.py +19 -7
- package/src/superlocalmemory/mcp/server.py +4 -2
- package/src/superlocalmemory/mcp/tools_brain.py +54 -10
- package/src/superlocalmemory/mcp/tools_code_graph.py +31 -4
- package/src/superlocalmemory/mcp/tools_core.py +88 -3
- package/src/superlocalmemory/mcp/tools_v3.py +20 -6
- package/src/superlocalmemory/retrieval/engine.py +28 -10
- package/src/superlocalmemory/retrieval/remote_reranker.py +108 -11
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +119 -19
- package/src/superlocalmemory/server/routes/brain.py +297 -14
- package/src/superlocalmemory/server/routes/learning.py +13 -25
- package/src/superlocalmemory/server/routes/memories.py +129 -3
- package/src/superlocalmemory/server/routes/v3_api.py +171 -60
- package/src/superlocalmemory/storage/_migration_internals.py +4 -0
- package/src/superlocalmemory/storage/_schema_version.py +2 -2
- package/src/superlocalmemory/storage/correction_cases.py +670 -0
- package/src/superlocalmemory/storage/database.py +230 -24
- package/src/superlocalmemory/storage/migration_runner.py +7 -0
- package/src/superlocalmemory/storage/migrations/M042_correction_case_ledger.py +245 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +2 -0
- package/src/superlocalmemory/storage/models.py +12 -4
- package/src/superlocalmemory/storage/write_coordinator.py +4 -0
- 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 +28 -11
- package/src/superlocalmemory/ui/js/brain.js +43 -7
- package/src/superlocalmemory/ui/js/od-agents.js +49 -5
- package/src/superlocalmemory/ui/js/od-brain.js +280 -84
- 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,35 +255,53 @@
|
|
|
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;
|
|
229
265
|
var pCount = ((beh.patterns) || []).length;
|
|
230
|
-
|
|
266
|
+
// BrainTruth is the portable V4.0.5 source of truth. Legacy sections
|
|
267
|
+
// remain a rolling-upgrade fallback only.
|
|
268
|
+
var truth = (living && living.brain_truth) || {};
|
|
269
|
+
var memoryActivity = truth.memory_activity || {};
|
|
270
|
+
var feedback = truth.feedback || (living && living.feedback) || {};
|
|
271
|
+
var experience = truth.agent_experience || (living && living.agent_experience) || {};
|
|
272
|
+
var externalEvidence = truth.external_evidence || experience.external_graph_evidence || {};
|
|
273
|
+
var correctionQuality = truth.correction_quality || {};
|
|
231
274
|
var graph = (living && living.graph) || {};
|
|
232
|
-
|
|
233
|
-
|
|
275
|
+
|
|
276
|
+
function truthCount(section, key, unit) {
|
|
277
|
+
if (!section || section.availability === 'unavailable') {
|
|
278
|
+
return 'Unavailable' + (section && section.reason ? ': ' + section.reason : '');
|
|
279
|
+
}
|
|
280
|
+
var value = section[key];
|
|
281
|
+
return value == null ? 'No data yet' : String(value) + (unit ? ' ' + unit : '');
|
|
282
|
+
}
|
|
234
283
|
|
|
235
284
|
// KPI strip
|
|
236
285
|
var strip = EL('div', { className: 'kpi-strip', style: 'margin-bottom:16px' });
|
|
237
286
|
// Ranking phase: text label → isNumeric=false (font-size:24px to match design)
|
|
238
287
|
strip.appendChild(kpiCard('skill', 'Ranking phase', phaseLabel(phase),
|
|
239
288
|
phaseDelta, modelActive, undefined, false));
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
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));
|
|
243
297
|
// Engagement health: text label → isNumeric=false
|
|
244
298
|
strip.appendChild(kpiCard('health', 'Engagement health',
|
|
245
299
|
healthStatus.charAt(0) + healthStatus.slice(1).toLowerCase(),
|
|
246
|
-
(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',
|
|
247
301
|
healthStatus === 'HEALTHY', healthColor, false));
|
|
248
302
|
// Patterns: numeric → isNumeric=true
|
|
249
303
|
strip.appendChild(kpiCard('brain', 'Patterns learned', String(pCount),
|
|
250
|
-
|
|
304
|
+
(beh.cross_project_transfers || 0) + ' used across projects', pCount > 0, undefined, true));
|
|
251
305
|
sec.appendChild(strip);
|
|
252
306
|
|
|
253
307
|
// 2-column grid
|
|
@@ -263,8 +317,17 @@
|
|
|
263
317
|
var pmeta = EL('div', {
|
|
264
318
|
style: 'display:flex;justify-content:space-between;font-size:12px;color:var(--fg-2);margin-bottom:8px',
|
|
265
319
|
});
|
|
266
|
-
|
|
267
|
-
|
|
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
|
+
}
|
|
268
331
|
pb.appendChild(pmeta);
|
|
269
332
|
pb.appendChild(meter(pct));
|
|
270
333
|
var phasesRow = EL('div', {
|
|
@@ -314,13 +377,14 @@
|
|
|
314
377
|
['Models trained', String(stats.models_trained || 0)],
|
|
315
378
|
['Verified active models', String(stats.models_active_verified || 0)],
|
|
316
379
|
['Sources tracked', String(stats.tracked_sources || 0)],
|
|
317
|
-
['
|
|
318
|
-
['
|
|
319
|
-
['Claimed evidence
|
|
320
|
-
['
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
380
|
+
['Memory activity', truthCount(memoryActivity, 'facts_total', 'facts')],
|
|
381
|
+
['Feedback signals', truthCount(feedback, 'signals_total', 'signals')],
|
|
382
|
+
['Claimed evidence', truthCount(experience, 'claimed_experiences_total', '')],
|
|
383
|
+
['Independently verified evidence', truthCount(
|
|
384
|
+
experience, 'independently_verified_experiences_total', '',
|
|
385
|
+
)],
|
|
386
|
+
['External observations', truthCount(externalEvidence, 'receipts_total', '')],
|
|
387
|
+
['Correction quality', truthCount(correctionQuality, 'cases_total', '')],
|
|
324
388
|
['Graph evidence', String(graph.fact_nodes || 0) + ' nodes · ' +
|
|
325
389
|
String(graph.association_edges || 0) + ' edges'],
|
|
326
390
|
].forEach(function (row) {
|
|
@@ -353,24 +417,29 @@
|
|
|
353
417
|
style: 'margin:0 0 14px;font-size:12px;line-height:1.55',
|
|
354
418
|
text: 'SLM records completed work when an integration supplies evidence. These records do not change recall, ranking, or model routing by themselves.',
|
|
355
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);
|
|
356
427
|
var evGrid = EL('div', { className: 'kpi-strip', style: 'margin:0' });
|
|
357
|
-
evGrid.appendChild(kpiCard('fact_check', '
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
: 'connect Bounded Loops to observe terminal runs',
|
|
373
|
-
Number(externalEvidence.total || 0) > 0, undefined, true));
|
|
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',
|
|
437
|
+
truthCount(externalEvidence, 'receipts_total', ''),
|
|
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));
|
|
374
443
|
evb.appendChild(evGrid);
|
|
375
444
|
evc.appendChild(evb);
|
|
376
445
|
sec.appendChild(evc);
|
|
@@ -411,7 +480,7 @@
|
|
|
411
480
|
hmhr.appendChild(EL('h3', { text: 'Reward signal density' }));
|
|
412
481
|
hmhr.appendChild(EL('span', {
|
|
413
482
|
className: 'sub',
|
|
414
|
-
text: '
|
|
483
|
+
text: 'feedback events per day · last ' + Number(reward.window_days || 182) + ' days',
|
|
415
484
|
}));
|
|
416
485
|
hmhr.appendChild(EL('div', { className: 'spacer' }));
|
|
417
486
|
hmhr.appendChild(heatLegend());
|
|
@@ -423,16 +492,26 @@
|
|
|
423
492
|
hmcr.appendChild(hmbr);
|
|
424
493
|
sec.appendChild(hmcr);
|
|
425
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
|
+
|
|
426
505
|
// 2-column: sparkline + outcome mix
|
|
427
506
|
var grid = EL('div', { className: 'grid', style: 'grid-template-columns:1fr 1fr;align-items:start' });
|
|
428
507
|
|
|
429
|
-
//
|
|
508
|
+
// Recall quality card: average reward score + real daily sparkline
|
|
430
509
|
var fbCard = EL('div', { className: 'card' });
|
|
431
510
|
var fbH = EL('div', { className: 'card-head' });
|
|
432
|
-
fbH.appendChild(EL('h3', { text: '
|
|
511
|
+
fbH.appendChild(EL('h3', { text: 'Recall quality' }));
|
|
433
512
|
fbH.appendChild(EL('span', {
|
|
434
513
|
className: 'sub',
|
|
435
|
-
text: fmtNum(reward.count || 0) + '
|
|
514
|
+
text: fmtNum(reward.count || 0) + (isUnmeasuredPrior ? ' interactions · default score' : ' interactions'),
|
|
436
515
|
}));
|
|
437
516
|
fbCard.appendChild(fbH);
|
|
438
517
|
var fbB = EL('div', { className: 'card-pad' });
|
|
@@ -442,6 +521,13 @@
|
|
|
442
521
|
style: 'font-size:30px;margin-bottom:12px',
|
|
443
522
|
text: Number(reward.average).toFixed(3),
|
|
444
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
|
+
}
|
|
445
531
|
}
|
|
446
532
|
var fbSp = EL('div', { id: 'od-brain-sp-fb' });
|
|
447
533
|
var sparkVals = timeline.slice(-30).map(function (point) {
|
|
@@ -455,7 +541,7 @@
|
|
|
455
541
|
fbSp.appendChild(EL('p', {
|
|
456
542
|
className: 'muted',
|
|
457
543
|
style: 'padding:32px;text-align:center;font-size:13px',
|
|
458
|
-
text: 'No
|
|
544
|
+
text: 'No recall history is available yet.',
|
|
459
545
|
}));
|
|
460
546
|
}
|
|
461
547
|
fbB.appendChild(fbSp);
|
|
@@ -466,16 +552,17 @@
|
|
|
466
552
|
var outCard = EL('div', { className: 'card' });
|
|
467
553
|
var outH = EL('div', { className: 'card-head' });
|
|
468
554
|
outH.appendChild(EL('h3', { text: 'Reward distribution' }));
|
|
469
|
-
outH.appendChild(EL('span', { className: 'sub', text: '
|
|
555
|
+
outH.appendChild(EL('span', { className: 'sub', text: 'based on how you engage with recalled results' }));
|
|
470
556
|
outCard.appendChild(outH);
|
|
471
557
|
var outB = EL('div', { className: 'card-pad', id: 'od-brain-outcomes' });
|
|
472
|
-
|
|
473
|
-
var bd = reward.distribution || {};
|
|
474
|
-
if (total === 0) {
|
|
558
|
+
if (total === 0 || isUnmeasuredPrior) {
|
|
475
559
|
outB.appendChild(EL('p', {
|
|
476
560
|
className: 'muted',
|
|
477
561
|
style: 'padding:16px;text-align:center;font-size:13px',
|
|
478
|
-
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.',
|
|
479
566
|
}));
|
|
480
567
|
} else {
|
|
481
568
|
[
|
|
@@ -514,7 +601,7 @@
|
|
|
514
601
|
var tc = EL('div', { className: 'card' });
|
|
515
602
|
var tch = EL('div', { className: 'card-head' });
|
|
516
603
|
tch.appendChild(EL('h3', { text: 'Tech preferences' }));
|
|
517
|
-
tch.appendChild(EL('span', { className: 'sub', text: '
|
|
604
|
+
tch.appendChild(EL('span', { className: 'sub', text: 'your tools and technology preferences' }));
|
|
518
605
|
tc.appendChild(tch);
|
|
519
606
|
var tcb = EL('div', { className: 'card-pad' });
|
|
520
607
|
var techItems = l.tech_preferences || [];
|
|
@@ -555,7 +642,7 @@
|
|
|
555
642
|
var wc = EL('div', { className: 'card' });
|
|
556
643
|
var wch = EL('div', { className: 'card-head' });
|
|
557
644
|
wch.appendChild(EL('h3', { text: 'Workflow patterns' }));
|
|
558
|
-
wch.appendChild(EL('span', { className: 'sub', text: '
|
|
645
|
+
wch.appendChild(EL('span', { className: 'sub', text: 'how you sequence your work over time' }));
|
|
559
646
|
wc.appendChild(wch);
|
|
560
647
|
var wcb = EL('div', { className: 'card-pad' });
|
|
561
648
|
var wfPats = l.workflow_patterns || [];
|
|
@@ -656,11 +743,26 @@
|
|
|
656
743
|
// ======================================================================
|
|
657
744
|
// Tab: CONNECTED CLIENTS
|
|
658
745
|
// ======================================================================
|
|
659
|
-
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) {
|
|
660
758
|
var sec = EL('section', { className: 'tabpane', 'data-p': 'clients' });
|
|
661
|
-
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;
|
|
662
763
|
var configuredData = configured || {};
|
|
663
764
|
|
|
765
|
+
// ── Recent client activity ────────────────────────────────────────────
|
|
664
766
|
// Activity is presence reported by host lifecycle hooks, not the old
|
|
665
767
|
// tool-event proxy. A configured adapter and a recent client are two
|
|
666
768
|
// different truths, rendered as separate cards.
|
|
@@ -670,8 +772,46 @@
|
|
|
670
772
|
evh.appendChild(EL('span', { className: 'sub', text: 'host lifecycle presence · last 5 minutes' }));
|
|
671
773
|
evc.appendChild(evh);
|
|
672
774
|
var evb = EL('div', { className: 'card-pad' });
|
|
673
|
-
|
|
674
|
-
|
|
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',
|
|
675
815
|
text: 'No host activity in the last 5 minutes. This does not mean an integration is uninstalled.',
|
|
676
816
|
}));
|
|
677
817
|
} else {
|
|
@@ -679,7 +819,7 @@
|
|
|
679
819
|
var row = EL('div', { className: 'list-row' });
|
|
680
820
|
row.appendChild(EL('b', { style: 'flex:1', text: String(client.kind || 'other') }));
|
|
681
821
|
row.appendChild(EL('span', { className: 'muted',
|
|
682
|
-
text: 'active ' +
|
|
822
|
+
text: 'active ' + fmtSecondsAgo(client.last_seen_seconds_ago),
|
|
683
823
|
}));
|
|
684
824
|
evb.appendChild(row);
|
|
685
825
|
});
|
|
@@ -687,8 +827,11 @@
|
|
|
687
827
|
evc.appendChild(evb);
|
|
688
828
|
sec.appendChild(evc);
|
|
689
829
|
|
|
690
|
-
// Configured integrations
|
|
691
|
-
|
|
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' });
|
|
692
835
|
var tch = EL('div', { className: 'card-head' });
|
|
693
836
|
tch.appendChild(EL('h3', { text: 'Configured integrations' }));
|
|
694
837
|
tch.appendChild(EL('span', { className: 'sub', text: 'installation and sync availability' }));
|
|
@@ -698,14 +841,50 @@
|
|
|
698
841
|
var state = configuredData[kind] || {};
|
|
699
842
|
var row = EL('div', { className: 'list-row' });
|
|
700
843
|
row.appendChild(EL('span', { style: 'flex:1', text: kind.replace(/_/g, ' ') }));
|
|
701
|
-
|
|
844
|
+
var badgeText = state.active
|
|
845
|
+
? (state.evidence_tier === 'configured' ? 'configured' : 'available')
|
|
846
|
+
: (state.reason || 'not available');
|
|
847
|
+
var badgeEl = EL('span', {
|
|
702
848
|
className: 'badge ' + (state.active ? 'ok' : 'warn'),
|
|
703
|
-
text:
|
|
704
|
-
})
|
|
849
|
+
text: badgeText,
|
|
850
|
+
});
|
|
851
|
+
if (state.evidence) badgeEl.setAttribute('title', String(state.evidence));
|
|
852
|
+
row.appendChild(badgeEl);
|
|
705
853
|
tcb.appendChild(row);
|
|
706
854
|
});
|
|
707
855
|
tc.appendChild(tcb);
|
|
708
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
|
+
|
|
709
888
|
return sec;
|
|
710
889
|
}
|
|
711
890
|
|
|
@@ -721,7 +900,7 @@
|
|
|
721
900
|
var card = EL('div', { className: 'card' });
|
|
722
901
|
var ch = EL('div', { className: 'card-head' });
|
|
723
902
|
ch.appendChild(EL('h3', { text: 'Source quality' }));
|
|
724
|
-
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' }));
|
|
725
904
|
card.appendChild(ch);
|
|
726
905
|
var cb = EL('div', { className: 'card-pad' });
|
|
727
906
|
var aggregate = (living && living.source_quality) || {};
|
|
@@ -736,27 +915,44 @@
|
|
|
736
915
|
cb.appendChild(EL('p', {
|
|
737
916
|
className: 'muted',
|
|
738
917
|
style: 'padding:16px;text-align:center;font-size:13px',
|
|
739
|
-
text: String(observedSources) + ' sources have
|
|
918
|
+
text: String(observedSources) + ' sources have been seen, but individual quality scores are not available yet.',
|
|
740
919
|
}));
|
|
741
920
|
} else {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}));
|
|
748
|
-
entries.forEach(function (k) {
|
|
749
|
-
var v = Number(scores[k]);
|
|
750
|
-
var row = EL('div', { style: 'margin-bottom:14px' });
|
|
751
|
-
var meta = EL('div', {
|
|
752
|
-
style: 'display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px',
|
|
753
|
-
});
|
|
754
|
-
meta.appendChild(EL('span', { className: 'mono', text: k }));
|
|
755
|
-
meta.appendChild(EL('b', { className: 'num', text: v.toFixed(2) }));
|
|
756
|
-
row.appendChild(meta);
|
|
757
|
-
row.appendChild(meter(v * 100));
|
|
758
|
-
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;
|
|
759
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
|
+
}
|
|
760
956
|
}
|
|
761
957
|
card.appendChild(cb); sec.appendChild(card);
|
|
762
958
|
return sec;
|
|
@@ -854,8 +1050,8 @@
|
|
|
854
1050
|
var head = EL('div', { className: 'page-head' });
|
|
855
1051
|
head.appendChild(EL('h2', { text: 'The living brain' }));
|
|
856
1052
|
head.appendChild(EL('p', {
|
|
857
|
-
text: '
|
|
858
|
-
'
|
|
1053
|
+
text: 'A local view of memory activity, feedback, and evidence. Observations are shown separately ' +
|
|
1054
|
+
'from ranking and do not change recall, ranking, or model routing by themselves.',
|
|
859
1055
|
}));
|
|
860
1056
|
|
|
861
1057
|
container.replaceChildren(
|
|
@@ -864,7 +1060,7 @@
|
|
|
864
1060
|
buildOverview(learning, behavioral, dateMap, living),
|
|
865
1061
|
buildReward(behavioral),
|
|
866
1062
|
buildBehaviour(learning, behavioral),
|
|
867
|
-
buildClients(living, brain.cross_platform),
|
|
1063
|
+
buildClients(living, brain.cross_platform, brain.bounded_loops),
|
|
868
1064
|
buildSourceQuality(learning, living)
|
|
869
1065
|
);
|
|
870
1066
|
wireTabs(container);
|