superlocalmemory 2.8.0 → 2.8.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/ATTRIBUTION.md +50 -0
- package/CHANGELOG.md +8 -0
- package/README.md +24 -15
- package/api_server.py +23 -0
- package/bin/aider-smart +2 -2
- package/bin/slm +35 -15
- package/configs/continue-skills.yaml +4 -4
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/CLI-COMMANDS-REFERENCE.md +18 -18
- package/docs/FRAMEWORK-INTEGRATIONS.md +4 -4
- package/docs/UNIVERSAL-INTEGRATION.md +15 -15
- package/install.sh +19 -4
- package/mcp_server.py +39 -4
- package/package.json +4 -2
- package/skills/slm-list-recent/SKILL.md +1 -1
- package/skills/slm-remember/SKILL.md +1 -1
- package/skills/slm-status/SKILL.md +1 -1
- package/skills/slm-switch-profile/SKILL.md +3 -3
- package/src/graph/graph_core.py +3 -3
- package/src/hnsw_index.py +10 -4
- package/src/lifecycle/lifecycle_engine.py +61 -8
- package/src/lifecycle/lifecycle_evaluator.py +38 -6
- package/src/mcp_tools_v28.py +4 -3
- package/src/memory-profiles.py +1 -0
- package/src/memory_store_v2.py +6 -1
- package/src/qualixar_attribution.py +139 -0
- package/src/qualixar_watermark.py +78 -0
- package/src/setup_validator.py +2 -2
- package/ui/index.html +152 -4
- package/ui/js/behavioral.js +276 -0
- package/ui/js/compliance.js +252 -0
- package/ui/js/init.js +10 -0
- package/ui/js/lifecycle.js +298 -0
- package/ui/js/profiles.js +4 -0
- package/ui_server.py +19 -0
- /package/bin/{superlocalmemoryv2:learning → superlocalmemoryv2-learning} +0 -0
- /package/bin/{superlocalmemoryv2:list → superlocalmemoryv2-list} +0 -0
- /package/bin/{superlocalmemoryv2:patterns → superlocalmemoryv2-patterns} +0 -0
- /package/bin/{superlocalmemoryv2:profile → superlocalmemoryv2-profile} +0 -0
- /package/bin/{superlocalmemoryv2:recall → superlocalmemoryv2-recall} +0 -0
- /package/bin/{superlocalmemoryv2:remember → superlocalmemoryv2-remember} +0 -0
- /package/bin/{superlocalmemoryv2:reset → superlocalmemoryv2-reset} +0 -0
- /package/bin/{superlocalmemoryv2:status → superlocalmemoryv2-status} +0 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
+
// Compliance tab — audit trail, retention policies, ABAC (v2.8)
|
|
4
|
+
// NOTE: All dynamic values use textContent or escapeHtml() from core.js before DOM insertion.
|
|
5
|
+
|
|
6
|
+
var _complianceData = null;
|
|
7
|
+
|
|
8
|
+
async function loadCompliance() {
|
|
9
|
+
var filterEl = document.getElementById('cp-audit-filter');
|
|
10
|
+
var filterValue = filterEl ? filterEl.value : '';
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
var url = '/api/compliance/status';
|
|
14
|
+
if (filterValue) url += '?event_type=' + encodeURIComponent(filterValue);
|
|
15
|
+
var response = await fetch(url);
|
|
16
|
+
var data = await response.json();
|
|
17
|
+
_complianceData = data;
|
|
18
|
+
|
|
19
|
+
if (!data.available) {
|
|
20
|
+
showEmpty('compliance-audit-content', 'shield-lock', 'Compliance engine not available. Upgrade to v2.8.');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
renderComplianceStats(data);
|
|
25
|
+
renderCompliancePolicies(data);
|
|
26
|
+
renderComplianceAudit(data);
|
|
27
|
+
|
|
28
|
+
var badge = document.getElementById('compliance-profile-badge');
|
|
29
|
+
if (badge) badge.textContent = data.active_profile || 'default';
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('Error loading compliance:', error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function renderComplianceStats(data) {
|
|
36
|
+
var stats = data.stats || {};
|
|
37
|
+
animateCounter('cp-audit-count', stats.audit_count || 0);
|
|
38
|
+
animateCounter('cp-retention-count', stats.retention_count || 0);
|
|
39
|
+
animateCounter('cp-abac-count', stats.abac_count || 0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function renderCompliancePolicies(data) {
|
|
43
|
+
var container = document.getElementById('compliance-policies-content');
|
|
44
|
+
if (!container) return;
|
|
45
|
+
var policies = data.retention_policies || [];
|
|
46
|
+
container.textContent = '';
|
|
47
|
+
|
|
48
|
+
if (policies.length === 0) {
|
|
49
|
+
var empty = document.createElement('div');
|
|
50
|
+
empty.className = 'text-center text-muted py-3';
|
|
51
|
+
empty.textContent = 'No retention policies configured. Create one above or use the set_retention_policy MCP tool.';
|
|
52
|
+
container.appendChild(empty);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
var table = document.createElement('table');
|
|
57
|
+
table.className = 'table table-sm table-hover mb-0';
|
|
58
|
+
var thead = document.createElement('thead');
|
|
59
|
+
var headRow = document.createElement('tr');
|
|
60
|
+
['Policy Name', 'Retention (days)', 'Category', 'Action', 'Created'].forEach(function(h) {
|
|
61
|
+
var th = document.createElement('th');
|
|
62
|
+
th.textContent = h;
|
|
63
|
+
headRow.appendChild(th);
|
|
64
|
+
});
|
|
65
|
+
thead.appendChild(headRow);
|
|
66
|
+
table.appendChild(thead);
|
|
67
|
+
|
|
68
|
+
var tbody = document.createElement('tbody');
|
|
69
|
+
for (var i = 0; i < policies.length; i++) {
|
|
70
|
+
var pol = policies[i];
|
|
71
|
+
var row = document.createElement('tr');
|
|
72
|
+
|
|
73
|
+
var nameCell = document.createElement('td');
|
|
74
|
+
var nameIcon = document.createElement('i');
|
|
75
|
+
nameIcon.className = 'bi bi-shield-check text-success me-1';
|
|
76
|
+
nameCell.appendChild(nameIcon);
|
|
77
|
+
nameCell.appendChild(document.createTextNode(pol.name || ''));
|
|
78
|
+
row.appendChild(nameCell);
|
|
79
|
+
|
|
80
|
+
var daysCell = document.createElement('td');
|
|
81
|
+
daysCell.textContent = (pol.retention_days || 0) + ' days';
|
|
82
|
+
row.appendChild(daysCell);
|
|
83
|
+
|
|
84
|
+
var catCell = document.createElement('td');
|
|
85
|
+
if (pol.category) {
|
|
86
|
+
var catBadge = document.createElement('span');
|
|
87
|
+
catBadge.className = 'badge bg-info';
|
|
88
|
+
catBadge.textContent = pol.category;
|
|
89
|
+
catCell.appendChild(catBadge);
|
|
90
|
+
} else {
|
|
91
|
+
catCell.textContent = 'All';
|
|
92
|
+
}
|
|
93
|
+
row.appendChild(catCell);
|
|
94
|
+
|
|
95
|
+
var actionCell = document.createElement('td');
|
|
96
|
+
var actionColors = { archive: 'bg-secondary', tombstone: 'bg-danger', notify: 'bg-warning' };
|
|
97
|
+
var actionBadge = document.createElement('span');
|
|
98
|
+
actionBadge.className = 'badge ' + (actionColors[pol.action] || 'bg-secondary');
|
|
99
|
+
actionBadge.textContent = pol.action || '';
|
|
100
|
+
actionCell.appendChild(actionBadge);
|
|
101
|
+
row.appendChild(actionCell);
|
|
102
|
+
|
|
103
|
+
var dateCell = document.createElement('td');
|
|
104
|
+
dateCell.className = 'small text-muted';
|
|
105
|
+
dateCell.textContent = formatDate(pol.created_at || '');
|
|
106
|
+
row.appendChild(dateCell);
|
|
107
|
+
|
|
108
|
+
tbody.appendChild(row);
|
|
109
|
+
}
|
|
110
|
+
table.appendChild(tbody);
|
|
111
|
+
container.appendChild(table);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function renderComplianceAudit(data) {
|
|
115
|
+
var container = document.getElementById('compliance-audit-content');
|
|
116
|
+
if (!container) return;
|
|
117
|
+
var events = data.audit_events || [];
|
|
118
|
+
container.textContent = '';
|
|
119
|
+
|
|
120
|
+
if (events.length === 0) {
|
|
121
|
+
var empty = document.createElement('div');
|
|
122
|
+
empty.className = 'text-center text-muted py-3';
|
|
123
|
+
empty.textContent = 'No audit events recorded yet.';
|
|
124
|
+
container.appendChild(empty);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
var wrapper = document.createElement('div');
|
|
129
|
+
wrapper.style.maxHeight = '400px';
|
|
130
|
+
wrapper.style.overflowY = 'auto';
|
|
131
|
+
|
|
132
|
+
var table = document.createElement('table');
|
|
133
|
+
table.className = 'table table-sm table-hover mb-0';
|
|
134
|
+
var thead = document.createElement('thead');
|
|
135
|
+
thead.style.position = 'sticky';
|
|
136
|
+
thead.style.top = '0';
|
|
137
|
+
thead.style.backgroundColor = 'var(--bs-body-bg)';
|
|
138
|
+
var headRow = document.createElement('tr');
|
|
139
|
+
['Timestamp', 'Event', 'Actor', 'Action', 'Target', 'Result'].forEach(function(h) {
|
|
140
|
+
var th = document.createElement('th');
|
|
141
|
+
th.textContent = h;
|
|
142
|
+
headRow.appendChild(th);
|
|
143
|
+
});
|
|
144
|
+
thead.appendChild(headRow);
|
|
145
|
+
table.appendChild(thead);
|
|
146
|
+
|
|
147
|
+
var eventBadgeColors = {
|
|
148
|
+
recall: 'bg-primary',
|
|
149
|
+
remember: 'bg-success',
|
|
150
|
+
delete: 'bg-danger',
|
|
151
|
+
lifecycle_transition: 'bg-warning',
|
|
152
|
+
access_denied: 'bg-danger',
|
|
153
|
+
retention_enforced: 'bg-warning'
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
var tbody = document.createElement('tbody');
|
|
157
|
+
for (var i = 0; i < events.length; i++) {
|
|
158
|
+
var ev = events[i];
|
|
159
|
+
var row = document.createElement('tr');
|
|
160
|
+
|
|
161
|
+
var tsCell = document.createElement('td');
|
|
162
|
+
tsCell.className = 'small text-muted';
|
|
163
|
+
tsCell.textContent = formatDateFull(ev.timestamp || '');
|
|
164
|
+
row.appendChild(tsCell);
|
|
165
|
+
|
|
166
|
+
var typeCell = document.createElement('td');
|
|
167
|
+
var typeBadge = document.createElement('span');
|
|
168
|
+
typeBadge.className = 'badge ' + (eventBadgeColors[ev.event_type] || 'bg-secondary');
|
|
169
|
+
typeBadge.textContent = ev.event_type || '';
|
|
170
|
+
typeCell.appendChild(typeBadge);
|
|
171
|
+
row.appendChild(typeCell);
|
|
172
|
+
|
|
173
|
+
var actorCell = document.createElement('td');
|
|
174
|
+
actorCell.textContent = ev.actor || '';
|
|
175
|
+
row.appendChild(actorCell);
|
|
176
|
+
|
|
177
|
+
var actionCell = document.createElement('td');
|
|
178
|
+
actionCell.textContent = ev.action || '';
|
|
179
|
+
row.appendChild(actionCell);
|
|
180
|
+
|
|
181
|
+
var targetCell = document.createElement('td');
|
|
182
|
+
targetCell.className = 'small';
|
|
183
|
+
targetCell.textContent = ev.target || '';
|
|
184
|
+
row.appendChild(targetCell);
|
|
185
|
+
|
|
186
|
+
var resultCell = document.createElement('td');
|
|
187
|
+
if (ev.result === 'success' || ev.result === 'allowed') {
|
|
188
|
+
var okBadge = document.createElement('span');
|
|
189
|
+
okBadge.className = 'badge bg-success';
|
|
190
|
+
okBadge.textContent = ev.result;
|
|
191
|
+
resultCell.appendChild(okBadge);
|
|
192
|
+
} else if (ev.result === 'denied' || ev.result === 'error') {
|
|
193
|
+
var failBadge = document.createElement('span');
|
|
194
|
+
failBadge.className = 'badge bg-danger';
|
|
195
|
+
failBadge.textContent = ev.result;
|
|
196
|
+
resultCell.appendChild(failBadge);
|
|
197
|
+
} else {
|
|
198
|
+
resultCell.textContent = ev.result || '';
|
|
199
|
+
}
|
|
200
|
+
row.appendChild(resultCell);
|
|
201
|
+
|
|
202
|
+
tbody.appendChild(row);
|
|
203
|
+
}
|
|
204
|
+
table.appendChild(tbody);
|
|
205
|
+
wrapper.appendChild(table);
|
|
206
|
+
container.appendChild(wrapper);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function createRetentionPolicy() {
|
|
210
|
+
var nameInput = document.getElementById('cp-policy-name');
|
|
211
|
+
var daysInput = document.getElementById('cp-retention-days');
|
|
212
|
+
var categoryInput = document.getElementById('cp-category');
|
|
213
|
+
var actionSelect = document.getElementById('cp-action');
|
|
214
|
+
|
|
215
|
+
var policyName = (nameInput.value || '').trim();
|
|
216
|
+
if (!policyName) {
|
|
217
|
+
showToast('Enter a policy name.');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
var days = parseInt(daysInput.value, 10);
|
|
222
|
+
if (isNaN(days) || days <= 0) {
|
|
223
|
+
showToast('Enter a valid number of days.');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
var response = await fetch('/api/compliance/retention-policy', {
|
|
229
|
+
method: 'POST',
|
|
230
|
+
headers: { 'Content-Type': 'application/json' },
|
|
231
|
+
body: JSON.stringify({
|
|
232
|
+
name: policyName,
|
|
233
|
+
retention_days: days,
|
|
234
|
+
category: categoryInput.value.trim() || undefined,
|
|
235
|
+
action: actionSelect.value
|
|
236
|
+
})
|
|
237
|
+
});
|
|
238
|
+
var data = await response.json();
|
|
239
|
+
if (response.ok) {
|
|
240
|
+
showToast('Retention policy created.');
|
|
241
|
+
nameInput.value = '';
|
|
242
|
+
daysInput.value = '90';
|
|
243
|
+
categoryInput.value = '';
|
|
244
|
+
loadCompliance(); // Refresh
|
|
245
|
+
} else {
|
|
246
|
+
showToast(data.detail || 'Failed to create policy.');
|
|
247
|
+
}
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error('Error creating retention policy:', error);
|
|
250
|
+
showToast('Error creating retention policy.');
|
|
251
|
+
}
|
|
252
|
+
}
|
package/ui/js/init.js
CHANGED
|
@@ -33,3 +33,13 @@ if (agentsTab) agentsTab.addEventListener('shown.bs.tab', loadAgents);
|
|
|
33
33
|
// v2.7 learning tab (graceful)
|
|
34
34
|
var learningTab = document.getElementById('learning-tab');
|
|
35
35
|
if (learningTab) learningTab.addEventListener('shown.bs.tab', loadLearning);
|
|
36
|
+
|
|
37
|
+
// v2.8 tabs (graceful — elements may not exist on older installs)
|
|
38
|
+
var lifecycleTab = document.getElementById('lifecycle-tab');
|
|
39
|
+
if (lifecycleTab) lifecycleTab.addEventListener('shown.bs.tab', loadLifecycle);
|
|
40
|
+
|
|
41
|
+
var behavioralTab = document.getElementById('behavioral-tab');
|
|
42
|
+
if (behavioralTab) behavioralTab.addEventListener('shown.bs.tab', loadBehavioral);
|
|
43
|
+
|
|
44
|
+
var complianceTab = document.getElementById('compliance-tab');
|
|
45
|
+
if (complianceTab) complianceTab.addEventListener('shown.bs.tab', loadCompliance);
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
+
// Lifecycle tab — state distribution, compaction, transitions (v2.8)
|
|
4
|
+
// NOTE: All dynamic values pass through escapeHtml() or textContent for DOM insertion.
|
|
5
|
+
|
|
6
|
+
var _lifecycleData = null;
|
|
7
|
+
|
|
8
|
+
async function loadLifecycle() {
|
|
9
|
+
try {
|
|
10
|
+
var response = await fetch('/api/lifecycle/status');
|
|
11
|
+
var data = await response.json();
|
|
12
|
+
_lifecycleData = data;
|
|
13
|
+
|
|
14
|
+
if (!data.available) {
|
|
15
|
+
showEmpty('lifecycle-states-row', 'hourglass-split', 'Lifecycle engine not available. Upgrade to v2.8.');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
renderLifecycleStates(data);
|
|
20
|
+
renderLifecycleProgress(data);
|
|
21
|
+
renderLifecycleAgeStats(data);
|
|
22
|
+
renderLifecycleTransitions(data);
|
|
23
|
+
|
|
24
|
+
var badge = document.getElementById('lifecycle-profile-badge');
|
|
25
|
+
if (badge) badge.textContent = data.active_profile || 'default';
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Error loading lifecycle:', error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function renderLifecycleStates(data) {
|
|
32
|
+
var states = data.states || {};
|
|
33
|
+
var mapping = {
|
|
34
|
+
active: 'lc-active-count',
|
|
35
|
+
warm: 'lc-warm-count',
|
|
36
|
+
cold: 'lc-cold-count',
|
|
37
|
+
archived: 'lc-archived-count',
|
|
38
|
+
tombstoned: 'lc-tombstoned-count'
|
|
39
|
+
};
|
|
40
|
+
for (var state in mapping) {
|
|
41
|
+
animateCounter(mapping[state], states[state] || 0);
|
|
42
|
+
}
|
|
43
|
+
animateCounter('lc-total-count', data.total_memories || 0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function renderLifecycleProgress(data) {
|
|
47
|
+
var bar = document.getElementById('lifecycle-progress-bar');
|
|
48
|
+
if (!bar) return;
|
|
49
|
+
var states = data.states || {};
|
|
50
|
+
var total = data.total_memories || 1;
|
|
51
|
+
var colors = {
|
|
52
|
+
active: '#198754',
|
|
53
|
+
warm: '#ffc107',
|
|
54
|
+
cold: '#0dcaf0',
|
|
55
|
+
archived: '#6c757d',
|
|
56
|
+
tombstoned: '#dc3545'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
bar.textContent = '';
|
|
60
|
+
var hasSegments = false;
|
|
61
|
+
|
|
62
|
+
var stateKeys = ['active', 'warm', 'cold', 'archived', 'tombstoned'];
|
|
63
|
+
for (var i = 0; i < stateKeys.length; i++) {
|
|
64
|
+
var state = stateKeys[i];
|
|
65
|
+
var count = states[state] || 0;
|
|
66
|
+
if (count > 0) {
|
|
67
|
+
hasSegments = true;
|
|
68
|
+
var pct = ((count / total) * 100).toFixed(1);
|
|
69
|
+
var segment = document.createElement('div');
|
|
70
|
+
segment.className = 'progress-bar';
|
|
71
|
+
segment.setAttribute('role', 'progressbar');
|
|
72
|
+
segment.style.width = pct + '%';
|
|
73
|
+
segment.style.backgroundColor = colors[state];
|
|
74
|
+
segment.title = state + ': ' + count + ' (' + pct + '%)';
|
|
75
|
+
segment.textContent = state;
|
|
76
|
+
bar.appendChild(segment);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!hasSegments) {
|
|
81
|
+
var fallback = document.createElement('div');
|
|
82
|
+
fallback.className = 'progress-bar bg-success';
|
|
83
|
+
fallback.style.width = '100%';
|
|
84
|
+
fallback.textContent = 'All Active';
|
|
85
|
+
bar.appendChild(fallback);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function renderLifecycleAgeStats(data) {
|
|
90
|
+
var container = document.getElementById('lifecycle-age-content');
|
|
91
|
+
if (!container) return;
|
|
92
|
+
var stats = data.age_stats || {};
|
|
93
|
+
if (Object.keys(stats).length === 0) {
|
|
94
|
+
container.textContent = '';
|
|
95
|
+
var empty = document.createElement('span');
|
|
96
|
+
empty.className = 'text-muted';
|
|
97
|
+
empty.textContent = 'No age data available yet.';
|
|
98
|
+
container.appendChild(empty);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
var table = document.createElement('table');
|
|
103
|
+
table.className = 'table table-sm table-hover mb-0';
|
|
104
|
+
var thead = document.createElement('thead');
|
|
105
|
+
var headRow = document.createElement('tr');
|
|
106
|
+
['State', 'Avg Age', 'Newest', 'Oldest'].forEach(function(h) {
|
|
107
|
+
var th = document.createElement('th');
|
|
108
|
+
th.textContent = h;
|
|
109
|
+
headRow.appendChild(th);
|
|
110
|
+
});
|
|
111
|
+
thead.appendChild(headRow);
|
|
112
|
+
table.appendChild(thead);
|
|
113
|
+
|
|
114
|
+
var tbody = document.createElement('tbody');
|
|
115
|
+
var stateOrder = ['active', 'warm', 'cold', 'archived'];
|
|
116
|
+
var badgeColors = { active: 'success', warm: 'warning', cold: 'info', archived: 'secondary' };
|
|
117
|
+
|
|
118
|
+
for (var i = 0; i < stateOrder.length; i++) {
|
|
119
|
+
var s = stateOrder[i];
|
|
120
|
+
if (stats[s]) {
|
|
121
|
+
var row = document.createElement('tr');
|
|
122
|
+
|
|
123
|
+
var stateCell = document.createElement('td');
|
|
124
|
+
var stateBadge = document.createElement('span');
|
|
125
|
+
stateBadge.className = 'badge bg-' + (badgeColors[s] || 'secondary');
|
|
126
|
+
stateBadge.textContent = s;
|
|
127
|
+
stateCell.appendChild(stateBadge);
|
|
128
|
+
row.appendChild(stateCell);
|
|
129
|
+
|
|
130
|
+
var avgCell = document.createElement('td');
|
|
131
|
+
avgCell.textContent = (stats[s].avg_days || 0) + 'd';
|
|
132
|
+
row.appendChild(avgCell);
|
|
133
|
+
|
|
134
|
+
var minCell = document.createElement('td');
|
|
135
|
+
minCell.textContent = (stats[s].min_days || 0) + 'd';
|
|
136
|
+
row.appendChild(minCell);
|
|
137
|
+
|
|
138
|
+
var maxCell = document.createElement('td');
|
|
139
|
+
maxCell.textContent = (stats[s].max_days || 0) + 'd';
|
|
140
|
+
row.appendChild(maxCell);
|
|
141
|
+
|
|
142
|
+
tbody.appendChild(row);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
table.appendChild(tbody);
|
|
146
|
+
|
|
147
|
+
container.textContent = '';
|
|
148
|
+
container.appendChild(table);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function renderLifecycleTransitions(data) {
|
|
152
|
+
var container = document.getElementById('lifecycle-transitions-content');
|
|
153
|
+
if (!container) return;
|
|
154
|
+
var transitions = data.recent_transitions || [];
|
|
155
|
+
if (transitions.length === 0) {
|
|
156
|
+
container.textContent = '';
|
|
157
|
+
var empty = document.createElement('span');
|
|
158
|
+
empty.className = 'text-muted';
|
|
159
|
+
empty.textContent = 'No transitions yet. Memories start as Active and transition based on usage.';
|
|
160
|
+
container.appendChild(empty);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
var table = document.createElement('table');
|
|
165
|
+
table.className = 'table table-sm table-hover mb-0';
|
|
166
|
+
var thead = document.createElement('thead');
|
|
167
|
+
var headRow = document.createElement('tr');
|
|
168
|
+
['Memory', 'State', 'Last Transition'].forEach(function(h) {
|
|
169
|
+
var th = document.createElement('th');
|
|
170
|
+
th.textContent = h;
|
|
171
|
+
headRow.appendChild(th);
|
|
172
|
+
});
|
|
173
|
+
thead.appendChild(headRow);
|
|
174
|
+
table.appendChild(thead);
|
|
175
|
+
|
|
176
|
+
var tbody = document.createElement('tbody');
|
|
177
|
+
for (var i = 0; i < transitions.length; i++) {
|
|
178
|
+
var t = transitions[i];
|
|
179
|
+
var row = document.createElement('tr');
|
|
180
|
+
|
|
181
|
+
var memCell = document.createElement('td');
|
|
182
|
+
var preview = (t.content_preview || '').substring(0, 40);
|
|
183
|
+
memCell.textContent = '#' + t.memory_id + ' ' + preview + (preview.length >= 40 ? '...' : '');
|
|
184
|
+
memCell.title = t.content_preview || '';
|
|
185
|
+
row.appendChild(memCell);
|
|
186
|
+
|
|
187
|
+
var stateCell = document.createElement('td');
|
|
188
|
+
var badge = document.createElement('span');
|
|
189
|
+
badge.className = 'badge bg-secondary';
|
|
190
|
+
badge.textContent = t.current_state || '';
|
|
191
|
+
stateCell.appendChild(badge);
|
|
192
|
+
row.appendChild(stateCell);
|
|
193
|
+
|
|
194
|
+
var transCell = document.createElement('td');
|
|
195
|
+
transCell.className = 'small text-muted';
|
|
196
|
+
transCell.textContent = JSON.stringify(t.last_transition || {});
|
|
197
|
+
row.appendChild(transCell);
|
|
198
|
+
|
|
199
|
+
tbody.appendChild(row);
|
|
200
|
+
}
|
|
201
|
+
table.appendChild(tbody);
|
|
202
|
+
|
|
203
|
+
container.textContent = '';
|
|
204
|
+
container.appendChild(table);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function compactDryRun() {
|
|
208
|
+
try {
|
|
209
|
+
var response = await fetch('/api/lifecycle/compact', {
|
|
210
|
+
method: 'POST',
|
|
211
|
+
headers: { 'Content-Type': 'application/json' },
|
|
212
|
+
body: JSON.stringify({ dry_run: true })
|
|
213
|
+
});
|
|
214
|
+
var data = await response.json();
|
|
215
|
+
var resultsDiv = document.getElementById('compaction-results');
|
|
216
|
+
var titleEl = document.getElementById('compaction-results-title');
|
|
217
|
+
var contentEl = document.getElementById('compaction-results-content');
|
|
218
|
+
resultsDiv.classList.remove('d-none');
|
|
219
|
+
titleEl.textContent = 'Compaction Preview (Dry Run)';
|
|
220
|
+
contentEl.textContent = '';
|
|
221
|
+
|
|
222
|
+
if (data.recommendations === 0) {
|
|
223
|
+
var ok = document.createElement('span');
|
|
224
|
+
ok.className = 'text-success';
|
|
225
|
+
var icon = document.createElement('i');
|
|
226
|
+
icon.className = 'bi bi-check-circle';
|
|
227
|
+
ok.appendChild(icon);
|
|
228
|
+
ok.appendChild(document.createTextNode(' No compaction needed. All memories are in optimal states.'));
|
|
229
|
+
contentEl.appendChild(ok);
|
|
230
|
+
} else {
|
|
231
|
+
var p = document.createElement('p');
|
|
232
|
+
p.className = 'mb-2';
|
|
233
|
+
p.textContent = data.recommendations + ' memories would be transitioned:';
|
|
234
|
+
contentEl.appendChild(p);
|
|
235
|
+
|
|
236
|
+
var table = document.createElement('table');
|
|
237
|
+
table.className = 'table table-sm mb-0';
|
|
238
|
+
var thead = document.createElement('thead');
|
|
239
|
+
var headRow = document.createElement('tr');
|
|
240
|
+
['Memory ID', 'From', 'To'].forEach(function(h) {
|
|
241
|
+
var th = document.createElement('th');
|
|
242
|
+
th.textContent = h;
|
|
243
|
+
headRow.appendChild(th);
|
|
244
|
+
});
|
|
245
|
+
thead.appendChild(headRow);
|
|
246
|
+
table.appendChild(thead);
|
|
247
|
+
|
|
248
|
+
var tbody = document.createElement('tbody');
|
|
249
|
+
var details = data.details || [];
|
|
250
|
+
for (var i = 0; i < details.length; i++) {
|
|
251
|
+
var row = document.createElement('tr');
|
|
252
|
+
var idCell = document.createElement('td');
|
|
253
|
+
idCell.textContent = '#' + details[i].memory_id;
|
|
254
|
+
row.appendChild(idCell);
|
|
255
|
+
var fromCell = document.createElement('td');
|
|
256
|
+
fromCell.textContent = details[i].from || '';
|
|
257
|
+
row.appendChild(fromCell);
|
|
258
|
+
var toCell = document.createElement('td');
|
|
259
|
+
toCell.textContent = details[i].to || '';
|
|
260
|
+
row.appendChild(toCell);
|
|
261
|
+
tbody.appendChild(row);
|
|
262
|
+
}
|
|
263
|
+
table.appendChild(tbody);
|
|
264
|
+
contentEl.appendChild(table);
|
|
265
|
+
}
|
|
266
|
+
} catch (e) {
|
|
267
|
+
console.error('Compaction preview error:', e);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function compactExecute() {
|
|
272
|
+
if (!confirm('This will transition memories to lower lifecycle states. Continue?')) return;
|
|
273
|
+
try {
|
|
274
|
+
var response = await fetch('/api/lifecycle/compact', {
|
|
275
|
+
method: 'POST',
|
|
276
|
+
headers: { 'Content-Type': 'application/json' },
|
|
277
|
+
body: JSON.stringify({ dry_run: false })
|
|
278
|
+
});
|
|
279
|
+
var data = await response.json();
|
|
280
|
+
var resultsDiv = document.getElementById('compaction-results');
|
|
281
|
+
var contentEl = document.getElementById('compaction-results-content');
|
|
282
|
+
resultsDiv.classList.remove('d-none');
|
|
283
|
+
document.getElementById('compaction-results-title').textContent = 'Compaction Results';
|
|
284
|
+
|
|
285
|
+
contentEl.textContent = '';
|
|
286
|
+
var ok = document.createElement('span');
|
|
287
|
+
ok.className = 'text-success';
|
|
288
|
+
var icon = document.createElement('i');
|
|
289
|
+
icon.className = 'bi bi-check-circle';
|
|
290
|
+
ok.appendChild(icon);
|
|
291
|
+
ok.appendChild(document.createTextNode(' ' + (data.transitioned || 0) + ' memories transitioned successfully.'));
|
|
292
|
+
contentEl.appendChild(ok);
|
|
293
|
+
|
|
294
|
+
loadLifecycle(); // Refresh
|
|
295
|
+
} catch (e) {
|
|
296
|
+
console.error('Compaction error:', e);
|
|
297
|
+
}
|
|
298
|
+
}
|
package/ui/js/profiles.js
CHANGED
|
@@ -220,6 +220,10 @@ async function switchProfile(profileName) {
|
|
|
220
220
|
if (typeof loadMemories === 'function') loadMemories();
|
|
221
221
|
if (typeof loadTimeline === 'function') loadTimeline();
|
|
222
222
|
if (typeof loadEvents === 'function') loadEvents();
|
|
223
|
+
// v2.8 tabs
|
|
224
|
+
if (typeof loadLifecycle === 'function') loadLifecycle();
|
|
225
|
+
if (typeof loadBehavioral === 'function') loadBehavioral();
|
|
226
|
+
if (typeof loadCompliance === 'function') loadCompliance();
|
|
223
227
|
var activeTab = document.querySelector('#mainTabs .nav-link.active');
|
|
224
228
|
if (activeTab) activeTab.click();
|
|
225
229
|
} else {
|
package/ui_server.py
CHANGED
|
@@ -148,6 +148,25 @@ app.include_router(ws_router)
|
|
|
148
148
|
if LEARNING_ROUTES:
|
|
149
149
|
app.include_router(learning_router)
|
|
150
150
|
|
|
151
|
+
# v2.8 routes (graceful — don't fail if engines unavailable)
|
|
152
|
+
try:
|
|
153
|
+
from routes.lifecycle import router as lifecycle_router
|
|
154
|
+
app.include_router(lifecycle_router)
|
|
155
|
+
except ImportError:
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
from routes.behavioral import router as behavioral_router
|
|
160
|
+
app.include_router(behavioral_router)
|
|
161
|
+
except ImportError:
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
from routes.compliance import router as compliance_router
|
|
166
|
+
app.include_router(compliance_router)
|
|
167
|
+
except ImportError:
|
|
168
|
+
pass
|
|
169
|
+
|
|
151
170
|
# Wire WebSocket manager into routes that need broadcast capability
|
|
152
171
|
import routes.profiles as _profiles_mod
|
|
153
172
|
import routes.data_io as _data_io_mod
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|