superlocalmemory 3.8.13 → 4.0.0
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 +4 -4
- package/CHANGELOG.md +113 -121
- package/README.md +65 -63
- package/docs/pi-dev-integration.md +1 -1
- package/package.json +6 -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/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-governance/SKILL.md +248 -0
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-loop/SKILL.md +99 -0
- package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
- package/plugin-src/skills/slm-profile/SKILL.md +148 -0
- package/plugin-src/skills/slm-recall/SKILL.md +1 -1
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-scope/SKILL.md +176 -0
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +11 -4
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +125 -11
- package/src/superlocalmemory/cli/daemon.py +5 -1
- package/src/superlocalmemory/cli/main.py +35 -2
- package/src/superlocalmemory/cli/ops_cmd.py +281 -0
- package/src/superlocalmemory/cli/setup_wizard.py +1 -1
- package/src/superlocalmemory/compliance/audit.py +65 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +27 -57
- package/src/superlocalmemory/compliance/gdpr.py +416 -20
- package/src/superlocalmemory/compliance/retention.py +74 -22
- package/src/superlocalmemory/compliance/scheduler.py +78 -9
- package/src/superlocalmemory/core/actor_context.py +166 -0
- package/src/superlocalmemory/core/admission.py +549 -0
- package/src/superlocalmemory/core/backend_orchestrator.py +23 -10
- package/src/superlocalmemory/core/config.py +202 -24
- package/src/superlocalmemory/core/consolidation_engine.py +13 -13
- package/src/superlocalmemory/core/context_cache.py +28 -0
- package/src/superlocalmemory/core/embeddings.py +64 -2
- package/src/superlocalmemory/core/engine.py +7 -2
- package/src/superlocalmemory/core/engine_ingestion.py +65 -3
- package/src/superlocalmemory/core/engine_wiring.py +36 -9
- package/src/superlocalmemory/core/ingest_policy.py +38 -0
- package/src/superlocalmemory/core/maintenance.py +255 -0
- package/src/superlocalmemory/core/modes.py +40 -13
- package/src/superlocalmemory/core/mutations.py +437 -44
- package/src/superlocalmemory/core/operation_policy.py +92 -0
- package/src/superlocalmemory/core/operation_policy_registry.py +542 -0
- package/src/superlocalmemory/core/operation_request.py +127 -0
- package/src/superlocalmemory/core/ops_remediation.py +542 -0
- package/src/superlocalmemory/core/recall_pipeline.py +7 -0
- package/src/superlocalmemory/core/remember_runtime.py +202 -4
- package/src/superlocalmemory/core/remote_mode.py +20 -5
- package/src/superlocalmemory/core/store_pipeline.py +150 -0
- package/src/superlocalmemory/core/topic_signature.py +19 -4
- package/src/superlocalmemory/core/transactions/__init__.py +78 -0
- package/src/superlocalmemory/core/transactions/concrete_owners.py +597 -0
- package/src/superlocalmemory/core/transactions/erasure.py +825 -0
- package/src/superlocalmemory/core/transactions/manifest.py +255 -0
- package/src/superlocalmemory/core/transactions/manifest_key.py +155 -0
- package/src/superlocalmemory/core/transactions/obligations.py +272 -0
- package/src/superlocalmemory/core/transactions/owners.py +114 -0
- package/src/superlocalmemory/core/transactions/reconciler.py +285 -0
- package/src/superlocalmemory/core/transactions/service.py +330 -0
- package/src/superlocalmemory/core/worker_pool.py +33 -5
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +70 -28
- package/src/superlocalmemory/encoding/emotional.py +75 -14
- package/src/superlocalmemory/encoding/scene_builder.py +115 -13
- package/src/superlocalmemory/encoding/temporal_parser.py +4 -0
- package/src/superlocalmemory/evolution/blind_verifier.py +11 -4
- package/src/superlocalmemory/evolution/evolution_store.py +244 -4
- package/src/superlocalmemory/evolution/llm_dispatch.py +40 -0
- package/src/superlocalmemory/evolution/model_selection.py +18 -3
- package/src/superlocalmemory/evolution/mutation_generator.py +3 -0
- package/src/superlocalmemory/evolution/skill_activator.py +270 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +281 -59
- package/src/superlocalmemory/evolution/types.py +30 -8
- package/src/superlocalmemory/graph/cozo_backend.py +17 -9
- package/src/superlocalmemory/hooks/auto_invoker.py +2 -1
- package/src/superlocalmemory/hooks/auto_recall.py +64 -30
- package/src/superlocalmemory/hooks/codex_assets.py +14 -1
- package/src/superlocalmemory/infra/backup.py +434 -7
- package/src/superlocalmemory/infra/process_reaper.py +18 -0
- package/src/superlocalmemory/infra/self_heal.py +401 -0
- package/src/superlocalmemory/learning/feedback.py +52 -9
- package/src/superlocalmemory/loops/engine.py +10 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/http_transport.py +30 -331
- package/src/superlocalmemory/mcp/profiles.py +5 -0
- package/src/superlocalmemory/mcp/resources.py +8 -0
- package/src/superlocalmemory/mcp/server.py +51 -4
- package/src/superlocalmemory/mcp/shared.py +19 -0
- package/src/superlocalmemory/mcp/tools_active.py +25 -4
- package/src/superlocalmemory/mcp/tools_code_graph.py +26 -18
- package/src/superlocalmemory/mcp/tools_context.py +50 -8
- package/src/superlocalmemory/mcp/tools_core.py +69 -21
- package/src/superlocalmemory/mcp/tools_evolution.py +9 -2
- package/src/superlocalmemory/mcp/tools_learning.py +21 -10
- package/src/superlocalmemory/mcp/tools_loops.py +29 -18
- package/src/superlocalmemory/mcp/tools_mesh.py +8 -0
- package/src/superlocalmemory/mcp/tools_ops.py +115 -0
- package/src/superlocalmemory/mcp/tools_optimize.py +4 -0
- package/src/superlocalmemory/mcp/tools_v28.py +10 -3
- package/src/superlocalmemory/mcp/tools_v3.py +34 -14
- package/src/superlocalmemory/mcp/tools_v33.py +18 -33
- package/src/superlocalmemory/mesh/broker.py +124 -46
- package/src/superlocalmemory/mesh/broker_security.py +470 -0
- package/src/superlocalmemory/mesh/discovery.py +365 -0
- package/src/superlocalmemory/mesh/lock_protocol.py +313 -0
- package/src/superlocalmemory/mesh/node_identity.py +97 -0
- package/src/superlocalmemory/mesh/outbox_remote.py +429 -0
- package/src/superlocalmemory/mesh/remote_sync.py +511 -28
- package/src/superlocalmemory/mesh/state_sync.py +286 -0
- package/src/superlocalmemory/optimize/config/store.py +45 -0
- package/src/superlocalmemory/parameterization/cross_project.py +12 -0
- package/src/superlocalmemory/parameterization/prompt_injector.py +13 -11
- package/src/superlocalmemory/parameterization/prompt_lifecycle.py +8 -2
- package/src/superlocalmemory/parameterization/workflow_miner.py +17 -0
- package/src/superlocalmemory/retrieval/ann_index.py +5 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +49 -2
- package/src/superlocalmemory/retrieval/engine.py +19 -4
- package/src/superlocalmemory/retrieval/fusion.py +4 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -3
- package/src/superlocalmemory/retrieval/remote_reranker.py +47 -22
- package/src/superlocalmemory/retrieval/reranker.py +32 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +16 -3
- package/src/superlocalmemory/retrieval/temporal_utils.py +107 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +155 -42
- package/src/superlocalmemory/retrieval/vector_store.py +214 -8
- package/src/superlocalmemory/server/api.py +5 -5
- package/src/superlocalmemory/server/egress_policy.py +258 -0
- package/src/superlocalmemory/server/rbac_enforce.py +32 -0
- package/src/superlocalmemory/server/route_mutations.py +20 -0
- package/src/superlocalmemory/server/routes/compliance.py +153 -7
- package/src/superlocalmemory/server/routes/data_io.py +43 -2
- package/src/superlocalmemory/server/routes/events.py +15 -0
- package/src/superlocalmemory/server/routes/memories.py +56 -3
- package/src/superlocalmemory/server/routes/mesh.py +82 -1
- package/src/superlocalmemory/server/routes/mesh_lock.py +54 -0
- package/src/superlocalmemory/server/routes/mesh_state.py +63 -0
- package/src/superlocalmemory/server/routes/v3_api.py +50 -27
- package/src/superlocalmemory/server/routes/ws.py +86 -0
- package/src/superlocalmemory/server/ui.py +6 -6
- package/src/superlocalmemory/server/unified_daemon.py +942 -119
- package/src/superlocalmemory/storage/_migration_internals.py +568 -0
- package/src/superlocalmemory/storage/_schema_version.py +110 -0
- package/src/superlocalmemory/storage/database.py +329 -24
- package/src/superlocalmemory/storage/embedding_migrator.py +246 -51
- package/src/superlocalmemory/storage/erasure_fence.py +45 -0
- package/src/superlocalmemory/storage/generation_fence.py +63 -0
- package/src/superlocalmemory/storage/migration_runner.py +140 -417
- package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +40 -0
- package/src/superlocalmemory/storage/migrations/M033_projection_transactions.py +148 -0
- package/src/superlocalmemory/storage/migrations/M034_obligation_integrity.py +58 -0
- package/src/superlocalmemory/storage/migrations/M035_erasure_receipts.py +113 -0
- package/src/superlocalmemory/storage/migrations/M036_vector_row_map.py +107 -0
- package/src/superlocalmemory/storage/migrations/M037_manifest_hmac_version.py +162 -0
- package/src/superlocalmemory/storage/migrations/{M033_learning_feedback_channel.py → M038_learning_feedback_channel.py} +3 -3
- package/src/superlocalmemory/storage/migrations/M039_scene_fact_members.py +137 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +4 -2
- package/src/superlocalmemory/storage/schema.py +67 -0
- package/src/superlocalmemory/storage/write_coordinator.py +125 -0
- package/src/superlocalmemory/trust/scorer.py +28 -4
- package/src/superlocalmemory/ui/index.html +14 -3
- package/src/superlocalmemory/ui/js/auto-settings.js +12 -1
- package/src/superlocalmemory/ui/js/brain.js +6 -4
- package/src/superlocalmemory/ui/js/compliance.js +66 -12
- package/src/superlocalmemory/ui/js/dashboard.js +13 -3
- package/src/superlocalmemory/ui/js/feedback.js +8 -2
- package/src/superlocalmemory/ui/js/lifecycle.js +7 -1
- package/src/superlocalmemory/ui/js/modal.js +272 -5
- package/src/superlocalmemory/ui/js/od-backup.js +9 -2
- package/src/superlocalmemory/ui/js/od-compliance-ext.js +301 -0
- package/src/superlocalmemory/ui/js/od-operations.js +154 -23
- package/src/superlocalmemory/ui/js/od-ops-health.js +417 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +35 -21
- package/src/superlocalmemory/ui/js/od-team.js +9 -2
- package/src/superlocalmemory/ui/js/optimize.js +13 -16
- package/src/superlocalmemory/ui/js/profiles.js +7 -3
- package/src/superlocalmemory/ui/js/settings.js +7 -1
- package/src/superlocalmemory/vector/lancedb_backend.py +19 -9
- package/src/superlocalmemory/attribution/mathematical_dna.py +0 -235
- package/src/superlocalmemory/cli/post_install.py +0 -114
- package/src/superlocalmemory/core/clock_monitor.py +0 -45
- package/src/superlocalmemory/core/db_pool.py +0 -80
- package/src/superlocalmemory/core/error_catalog.py +0 -113
- package/src/superlocalmemory/core/loop_watchdog.py +0 -56
- package/src/superlocalmemory/core/priority_queue.py +0 -61
- package/src/superlocalmemory/core/pruning_engine.py +0 -216
- package/src/superlocalmemory/core/queue_dispatcher.py +0 -73
- package/src/superlocalmemory/core/slmignore.py +0 -125
- package/src/superlocalmemory/infra/heartbeat_monitor.py +0 -140
- package/src/superlocalmemory/infra/webhook_dispatcher.py +0 -247
- package/src/superlocalmemory/learning/quantization_scheduler.py +0 -320
- package/src/superlocalmemory/storage/access_control.py +0 -182
|
@@ -10,11 +10,9 @@ async function loadCompliance() {
|
|
|
10
10
|
var filterValue = filterEl ? filterEl.value : '';
|
|
11
11
|
|
|
12
12
|
try {
|
|
13
|
-
var
|
|
14
|
-
if (
|
|
15
|
-
var
|
|
16
|
-
if (!response.ok) throw new Error('HTTP ' + response.status);
|
|
17
|
-
var data = await response.json();
|
|
13
|
+
var statusResp = await fetch('/api/compliance/status');
|
|
14
|
+
if (!statusResp.ok) throw new Error('HTTP ' + statusResp.status);
|
|
15
|
+
var data = await statusResp.json();
|
|
18
16
|
_complianceData = data;
|
|
19
17
|
|
|
20
18
|
if (!data.available) {
|
|
@@ -24,20 +22,37 @@ async function loadCompliance() {
|
|
|
24
22
|
|
|
25
23
|
renderComplianceStats(data);
|
|
26
24
|
renderCompliancePolicies(data);
|
|
27
|
-
|
|
25
|
+
|
|
26
|
+
// Task C: fetch real audit trail from dedicated endpoint with filters
|
|
27
|
+
var auditUrl = '/api/compliance/audit?limit=100';
|
|
28
|
+
if (filterValue) auditUrl += '&event_type=' + encodeURIComponent(filterValue);
|
|
29
|
+
var auditResp = await fetch(auditUrl);
|
|
30
|
+
var auditData = auditResp.ok ? await auditResp.json() : null;
|
|
31
|
+
renderComplianceAudit(auditData || data);
|
|
28
32
|
|
|
29
33
|
var badge = document.getElementById('compliance-profile-badge');
|
|
30
34
|
if (badge) badge.textContent = data.active_profile || 'default';
|
|
35
|
+
|
|
36
|
+
// Task C: show chain-integrity indicator
|
|
37
|
+
var chainEl = document.getElementById('cp-chain-integrity');
|
|
38
|
+
if (chainEl && auditData) {
|
|
39
|
+
var ok = auditData.chain_verified !== false;
|
|
40
|
+
chainEl.innerHTML = ok
|
|
41
|
+
? '<span class="badge bg-success"><i class="bi bi-shield-check"></i> Chain verified</span>'
|
|
42
|
+
: '<span class="badge bg-danger"><i class="bi bi-shield-x"></i> Chain integrity issue</span>';
|
|
43
|
+
}
|
|
31
44
|
} catch (error) {
|
|
32
45
|
console.error('Error loading compliance:', error);
|
|
33
46
|
}
|
|
34
47
|
}
|
|
35
48
|
|
|
49
|
+
// Task D: Fix KPI field reads — /api/compliance/status returns top-level fields,
|
|
50
|
+
// NOT a nested .stats object. audit_events_count, retention_policies (array),
|
|
51
|
+
// abac_policies_count are all top-level.
|
|
36
52
|
function renderComplianceStats(data) {
|
|
37
|
-
|
|
38
|
-
animateCounter('cp-
|
|
39
|
-
animateCounter('cp-
|
|
40
|
-
animateCounter('cp-abac-count', stats.abac_count || 0);
|
|
53
|
+
animateCounter('cp-audit-count', data.audit_events_count || 0);
|
|
54
|
+
animateCounter('cp-retention-count', (data.retention_policies || []).length);
|
|
55
|
+
animateCounter('cp-abac-count', data.abac_policies_count || 0);
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
function renderCompliancePolicies(data) {
|
|
@@ -58,7 +73,7 @@ function renderCompliancePolicies(data) {
|
|
|
58
73
|
table.className = 'table table-sm table-hover mb-0';
|
|
59
74
|
var thead = document.createElement('thead');
|
|
60
75
|
var headRow = document.createElement('tr');
|
|
61
|
-
['Policy Name', 'Retention (days)', 'Category', 'Action', 'Created'].forEach(function(h) {
|
|
76
|
+
['Policy Name', 'Retention (days)', 'Category', 'Action', 'Created', ''].forEach(function(h) {
|
|
62
77
|
var th = document.createElement('th');
|
|
63
78
|
th.textContent = h;
|
|
64
79
|
headRow.appendChild(th);
|
|
@@ -106,16 +121,55 @@ function renderCompliancePolicies(data) {
|
|
|
106
121
|
dateCell.textContent = formatDate(pol.created_at || '');
|
|
107
122
|
row.appendChild(dateCell);
|
|
108
123
|
|
|
124
|
+
// Task E: Delete button per row — calls DELETE /api/compliance/retention-policy?name=
|
|
125
|
+
var actCell = document.createElement('td');
|
|
126
|
+
var delBtn = document.createElement('button');
|
|
127
|
+
delBtn.className = 'btn btn-sm btn-outline-danger';
|
|
128
|
+
delBtn.textContent = 'Delete';
|
|
129
|
+
delBtn.addEventListener('click', (function(policyName) {
|
|
130
|
+
return async function() {
|
|
131
|
+
var confirmed = await confirmDestructive({
|
|
132
|
+
title: 'Delete retention policy',
|
|
133
|
+
target: policyName,
|
|
134
|
+
consequence: 'This policy will stop applying to all memories.',
|
|
135
|
+
confirmLabel: 'Delete',
|
|
136
|
+
});
|
|
137
|
+
if (!confirmed) return;
|
|
138
|
+
delBtn.disabled = true;
|
|
139
|
+
try {
|
|
140
|
+
var r = await fetch(
|
|
141
|
+
'/api/compliance/retention-policy?name=' + encodeURIComponent(policyName),
|
|
142
|
+
{ method: 'DELETE' }
|
|
143
|
+
);
|
|
144
|
+
var d = await r.json().catch(function() { return {}; });
|
|
145
|
+
if (d.success !== false) {
|
|
146
|
+
showToast('Policy deleted.');
|
|
147
|
+
loadCompliance();
|
|
148
|
+
} else {
|
|
149
|
+
showToast((d.error || 'Delete failed.'));
|
|
150
|
+
delBtn.disabled = false;
|
|
151
|
+
}
|
|
152
|
+
} catch (e) {
|
|
153
|
+
showToast('Network error deleting policy.');
|
|
154
|
+
delBtn.disabled = false;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}(pol.name || '')));
|
|
158
|
+
actCell.appendChild(delBtn);
|
|
159
|
+
row.appendChild(actCell);
|
|
160
|
+
|
|
109
161
|
tbody.appendChild(row);
|
|
110
162
|
}
|
|
111
163
|
table.appendChild(tbody);
|
|
112
164
|
container.appendChild(table);
|
|
113
165
|
}
|
|
114
166
|
|
|
167
|
+
// Task C: renderComplianceAudit accepts data from either the status endpoint
|
|
168
|
+
// (recent_audit_events) or the dedicated audit endpoint (events). Tries both.
|
|
115
169
|
function renderComplianceAudit(data) {
|
|
116
170
|
var container = document.getElementById('compliance-audit-content');
|
|
117
171
|
if (!container) return;
|
|
118
|
-
var events = data.audit_events || [];
|
|
172
|
+
var events = data.events || data.recent_audit_events || data.audit_events || [];
|
|
119
173
|
container.textContent = '';
|
|
120
174
|
|
|
121
175
|
if (events.length === 0) {
|
|
@@ -354,11 +354,21 @@ async function loadDashboard() {
|
|
|
354
354
|
if (dashVer) dashVer.textContent = ver;
|
|
355
355
|
if (settVer) settVer.textContent = ver;
|
|
356
356
|
|
|
357
|
-
// OD dashboard subtitle
|
|
357
|
+
// OD dashboard subtitle — locality comes from the mode record via API
|
|
358
|
+
// (data.data_locality_label). Never hardcode a fixed locality claim;
|
|
359
|
+
// Mode C is provider-assisted and must not claim data never leaves.
|
|
358
360
|
var subtitle = document.getElementById('od-dash-subtitle');
|
|
359
361
|
if (subtitle && data.mode_name) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
+
var locality = (data.data_locality_label || '').trim();
|
|
363
|
+
var parts = [
|
|
364
|
+
'Mode ' + data.mode.toUpperCase(),
|
|
365
|
+
data.mode_name,
|
|
366
|
+
];
|
|
367
|
+
if (locality) {
|
|
368
|
+
parts.push(locality);
|
|
369
|
+
}
|
|
370
|
+
parts.push('v' + (ver || '?'));
|
|
371
|
+
subtitle.textContent = parts.join(' · ');
|
|
362
372
|
}
|
|
363
373
|
|
|
364
374
|
// Update mode badge in sidebar (ng-premount hidden element)
|
|
@@ -312,8 +312,14 @@ function showPrivacyDetails() {
|
|
|
312
312
|
/**
|
|
313
313
|
* Reset all learning data.
|
|
314
314
|
*/
|
|
315
|
-
function resetLearningData() {
|
|
316
|
-
|
|
315
|
+
async function resetLearningData() {
|
|
316
|
+
var confirmed = await confirmDestructive({
|
|
317
|
+
title: 'Reset learning data',
|
|
318
|
+
target: 'All learned patterns and ranking signals',
|
|
319
|
+
consequence: 'Your memories will be preserved.',
|
|
320
|
+
confirmLabel: 'Reset',
|
|
321
|
+
});
|
|
322
|
+
if (!confirmed) return;
|
|
317
323
|
|
|
318
324
|
fetch('/api/learning/reset', {method: 'POST'})
|
|
319
325
|
.then(function(r) { return r.json(); })
|
|
@@ -299,7 +299,13 @@ async function compactDryRun() {
|
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
async function compactExecute() {
|
|
302
|
-
|
|
302
|
+
var confirmed = await confirmDestructive({
|
|
303
|
+
title: 'Apply compaction',
|
|
304
|
+
target: 'All eligible memories',
|
|
305
|
+
consequence: 'Transitions memories to lower lifecycle states.',
|
|
306
|
+
confirmLabel: 'Apply',
|
|
307
|
+
});
|
|
308
|
+
if (!confirmed) return;
|
|
303
309
|
try {
|
|
304
310
|
var response = await fetch('/api/lifecycle/compact', {
|
|
305
311
|
method: 'POST',
|
|
@@ -261,9 +261,13 @@ function openMemoryDetail(mem, source) {
|
|
|
261
261
|
forgetBtn.className = 'btn btn-outline-warning btn-sm';
|
|
262
262
|
forgetBtn.innerHTML = '<i class="bi bi-archive"></i> Forget';
|
|
263
263
|
forgetBtn.title = 'Archive this memory — hidden from recall but recoverable';
|
|
264
|
-
forgetBtn.onclick = function() {
|
|
265
|
-
|
|
266
|
-
|
|
264
|
+
forgetBtn.onclick = async function() {
|
|
265
|
+
var confirmed = await confirmDestructive({
|
|
266
|
+
title: 'Forget memory',
|
|
267
|
+
target: mem.content ? mem.content.slice(0, 80) : 'Memory #' + mem.id,
|
|
268
|
+
consequence: 'Archived — hidden from recall but recoverable.',
|
|
269
|
+
});
|
|
270
|
+
if (!confirmed) return;
|
|
267
271
|
forgetBtn.disabled = true;
|
|
268
272
|
fetch('/api/memories/' + encodeURIComponent(mem.id) + '/forget',
|
|
269
273
|
{method: 'POST'})
|
|
@@ -323,13 +327,147 @@ function openMemoryDetail(mem, source) {
|
|
|
323
327
|
};
|
|
324
328
|
actionsDiv.appendChild(mergeBtn);
|
|
325
329
|
|
|
330
|
+
// Task F: Set Scope — PATCH /api/memories/{fact_id}/scope
|
|
331
|
+
// Lets the user change personal → shared → global visibility.
|
|
332
|
+
var scopeBtn = document.createElement('button');
|
|
333
|
+
scopeBtn.className = 'btn btn-outline-secondary btn-sm';
|
|
334
|
+
scopeBtn.innerHTML = '<i class="bi bi-globe"></i> Set Scope…';
|
|
335
|
+
scopeBtn.title = 'Change memory visibility: personal, shared, or global';
|
|
336
|
+
(function() {
|
|
337
|
+
var scopeFormEl = null;
|
|
338
|
+
scopeBtn.addEventListener('click', function() {
|
|
339
|
+
// Toggle the inline scope form
|
|
340
|
+
if (scopeFormEl) {
|
|
341
|
+
scopeFormEl.remove();
|
|
342
|
+
scopeFormEl = null;
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
scopeFormEl = document.createElement('div');
|
|
346
|
+
scopeFormEl.className = 'mt-2 p-2 border rounded';
|
|
347
|
+
scopeFormEl.style.cssText = 'display:flex;gap:8px;flex-wrap:wrap;align-items:center;width:100%';
|
|
348
|
+
var scopeSel = document.createElement('select');
|
|
349
|
+
scopeSel.className = 'form-select form-select-sm';
|
|
350
|
+
scopeSel.style.width = 'auto';
|
|
351
|
+
['personal', 'shared', 'global'].forEach(function(s) {
|
|
352
|
+
var opt = document.createElement('option');
|
|
353
|
+
opt.value = s;
|
|
354
|
+
opt.textContent = s;
|
|
355
|
+
if (s === (mem.scope || 'personal')) opt.selected = true;
|
|
356
|
+
scopeSel.appendChild(opt);
|
|
357
|
+
});
|
|
358
|
+
var sharedInput = document.createElement('input');
|
|
359
|
+
sharedInput.className = 'form-control form-control-sm';
|
|
360
|
+
sharedInput.placeholder = 'shared_with (profile1,profile2)';
|
|
361
|
+
sharedInput.style.flex = '1';
|
|
362
|
+
sharedInput.style.display = scopeSel.value === 'shared' ? '' : 'none';
|
|
363
|
+
if (mem.shared_with) {
|
|
364
|
+
try {
|
|
365
|
+
var sw = typeof mem.shared_with === 'string'
|
|
366
|
+
? JSON.parse(mem.shared_with) : mem.shared_with;
|
|
367
|
+
sharedInput.value = Array.isArray(sw) ? sw.join(',') : String(sw);
|
|
368
|
+
} catch(e) { sharedInput.value = String(mem.shared_with || ''); }
|
|
369
|
+
}
|
|
370
|
+
scopeSel.addEventListener('change', function() {
|
|
371
|
+
sharedInput.style.display = scopeSel.value === 'shared' ? '' : 'none';
|
|
372
|
+
});
|
|
373
|
+
var saveBtn = document.createElement('button');
|
|
374
|
+
saveBtn.className = 'btn btn-sm btn-primary';
|
|
375
|
+
saveBtn.textContent = 'Save';
|
|
376
|
+
saveBtn.addEventListener('click', function() {
|
|
377
|
+
var scope = scopeSel.value;
|
|
378
|
+
var sharedWith = scope === 'shared' ? sharedInput.value : '';
|
|
379
|
+
saveBtn.disabled = true;
|
|
380
|
+
saveBtn.textContent = 'Saving…';
|
|
381
|
+
fetch('/api/memories/' + encodeURIComponent(mem.fact_id || mem.id) + '/scope', {
|
|
382
|
+
method: 'PATCH',
|
|
383
|
+
headers: {'Content-Type': 'application/json'},
|
|
384
|
+
body: JSON.stringify({scope: scope, shared_with: sharedWith}),
|
|
385
|
+
}).then(function(r) { return r.json(); })
|
|
386
|
+
.then(function(d) {
|
|
387
|
+
if (d.success) {
|
|
388
|
+
mem.scope = scope;
|
|
389
|
+
if (typeof showToast === 'function') showToast('Scope set to ' + scope);
|
|
390
|
+
scopeFormEl.remove(); scopeFormEl = null;
|
|
391
|
+
if (typeof loadMemories === 'function') setTimeout(loadMemories, 300);
|
|
392
|
+
} else {
|
|
393
|
+
if (typeof showToast === 'function') showToast('Scope update failed: ' + (d.detail || d.error || 'unknown'));
|
|
394
|
+
saveBtn.disabled = false; saveBtn.textContent = 'Save';
|
|
395
|
+
}
|
|
396
|
+
}).catch(function() {
|
|
397
|
+
if (typeof showToast === 'function') showToast('Network error setting scope.');
|
|
398
|
+
saveBtn.disabled = false; saveBtn.textContent = 'Save';
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
scopeFormEl.appendChild(scopeSel);
|
|
402
|
+
scopeFormEl.appendChild(sharedInput);
|
|
403
|
+
scopeFormEl.appendChild(saveBtn);
|
|
404
|
+
actionsDiv.insertAdjacentElement('afterend', scopeFormEl);
|
|
405
|
+
});
|
|
406
|
+
}());
|
|
407
|
+
actionsDiv.appendChild(scopeBtn);
|
|
408
|
+
|
|
409
|
+
// Task G: Pin — POST /api/tiers/pin — keep this fact in the active tier forever
|
|
410
|
+
var factIdForTier = mem.fact_id || mem.id;
|
|
411
|
+
var pinBtn = document.createElement('button');
|
|
412
|
+
pinBtn.className = 'btn btn-outline-success btn-sm';
|
|
413
|
+
pinBtn.innerHTML = '<i class="bi bi-pin-fill"></i> Pin';
|
|
414
|
+
pinBtn.title = 'Pin to active tier — this fact will not be demoted by lifecycle';
|
|
415
|
+
pinBtn.addEventListener('click', function() {
|
|
416
|
+
pinBtn.disabled = true;
|
|
417
|
+
fetch('/api/tiers/pin', {
|
|
418
|
+
method: 'POST',
|
|
419
|
+
headers: {'Content-Type': 'application/json'},
|
|
420
|
+
body: JSON.stringify({fact_id: factIdForTier, reason: 'pinned from dashboard'}),
|
|
421
|
+
}).then(function(r) { return r.json(); })
|
|
422
|
+
.then(function(d) {
|
|
423
|
+
pinBtn.disabled = false;
|
|
424
|
+
if (typeof showToast === 'function') {
|
|
425
|
+
showToast(d && d.success ? 'Fact pinned to active tier.' : 'Pin failed: ' + (d && (d.detail || d.error) || 'unknown'));
|
|
426
|
+
}
|
|
427
|
+
}).catch(function() {
|
|
428
|
+
pinBtn.disabled = false;
|
|
429
|
+
if (typeof showToast === 'function') showToast('Network error pinning fact.');
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
actionsDiv.appendChild(pinBtn);
|
|
433
|
+
|
|
434
|
+
// Task G: Unpin — POST /api/tiers/unpin — allows normal tier demotion again
|
|
435
|
+
var unpinBtn = document.createElement('button');
|
|
436
|
+
unpinBtn.className = 'btn btn-outline-warning btn-sm';
|
|
437
|
+
unpinBtn.innerHTML = '<i class="bi bi-pin-angle"></i> Unpin';
|
|
438
|
+
unpinBtn.title = 'Unpin — allow normal lifecycle tier demotion';
|
|
439
|
+
unpinBtn.addEventListener('click', function() {
|
|
440
|
+
unpinBtn.disabled = true;
|
|
441
|
+
fetch('/api/tiers/unpin', {
|
|
442
|
+
method: 'POST',
|
|
443
|
+
headers: {'Content-Type': 'application/json'},
|
|
444
|
+
body: JSON.stringify({fact_id: factIdForTier, reason: ''}),
|
|
445
|
+
}).then(function(r) { return r.json(); })
|
|
446
|
+
.then(function(d) {
|
|
447
|
+
unpinBtn.disabled = false;
|
|
448
|
+
if (typeof showToast === 'function') {
|
|
449
|
+
showToast(d && d.success ? 'Fact unpinned — will age normally.' : 'Unpin failed: ' + (d && (d.detail || d.error) || 'unknown'));
|
|
450
|
+
}
|
|
451
|
+
}).catch(function() {
|
|
452
|
+
unpinBtn.disabled = false;
|
|
453
|
+
if (typeof showToast === 'function') showToast('Network error unpinning fact.');
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
actionsDiv.appendChild(unpinBtn);
|
|
457
|
+
|
|
326
458
|
// Delete button — always available (hard delete, irreversible)
|
|
327
459
|
var deleteBtn = document.createElement('button');
|
|
328
460
|
deleteBtn.className = 'btn btn-outline-danger btn-sm';
|
|
329
461
|
deleteBtn.innerHTML = '<i class="bi bi-trash"></i> Delete';
|
|
330
462
|
deleteBtn.title = 'Permanently delete (cannot be undone) — prefer Forget';
|
|
331
|
-
deleteBtn.onclick = function() {
|
|
332
|
-
|
|
463
|
+
deleteBtn.onclick = async function() {
|
|
464
|
+
var confirmed = await confirmDestructive({
|
|
465
|
+
title: 'Delete memory',
|
|
466
|
+
target: mem.content ? mem.content.slice(0, 80) : 'Memory #' + mem.id,
|
|
467
|
+
consequence: 'Permanently deleted — this cannot be undone.',
|
|
468
|
+
confirmLabel: 'Delete',
|
|
469
|
+
});
|
|
470
|
+
if (!confirmed) return;
|
|
333
471
|
fetch('/api/memories/' + encodeURIComponent(mem.id), {method: 'DELETE'})
|
|
334
472
|
.then(function(r) { return r.json(); })
|
|
335
473
|
.then(function(d) {
|
|
@@ -432,6 +570,135 @@ function addDetailTagsRow(parent, label, tags) {
|
|
|
432
570
|
parent.appendChild(dd);
|
|
433
571
|
}
|
|
434
572
|
|
|
573
|
+
/**
|
|
574
|
+
* Show a shared confirmation modal for destructive dashboard actions.
|
|
575
|
+
* Creates the modal element on first call; reuses it on subsequent calls.
|
|
576
|
+
*
|
|
577
|
+
* @param {object} opts
|
|
578
|
+
* @param {string} opts.title Short header, e.g. "Delete profile"
|
|
579
|
+
* @param {string} opts.target Exact item being acted on, e.g. "my-project"
|
|
580
|
+
* @param {string} opts.consequence What happens, e.g. "Memories moved to default profile"
|
|
581
|
+
* @param {string} [opts.confirmLabel] Confirm button text (default: "Confirm")
|
|
582
|
+
* @param {string} [opts.confirmationText] Exact text required to unlock confirmation
|
|
583
|
+
* @returns {Promise<boolean>} Resolves true when confirmed, false when cancelled
|
|
584
|
+
*/
|
|
585
|
+
var activeDestructiveConfirmation = null;
|
|
586
|
+
|
|
587
|
+
function confirmDestructive(opts) {
|
|
588
|
+
return new Promise(function(resolve) {
|
|
589
|
+
if (activeDestructiveConfirmation) {
|
|
590
|
+
activeDestructiveConfirmation.cancel();
|
|
591
|
+
}
|
|
592
|
+
var settled = false;
|
|
593
|
+
var MODAL_ID = 'slm-confirm-destructive-modal';
|
|
594
|
+
var modalEl = document.getElementById(MODAL_ID);
|
|
595
|
+
|
|
596
|
+
if (!modalEl) {
|
|
597
|
+
modalEl = document.createElement('div');
|
|
598
|
+
modalEl.id = MODAL_ID;
|
|
599
|
+
modalEl.className = 'modal fade';
|
|
600
|
+
modalEl.setAttribute('tabindex', '-1');
|
|
601
|
+
modalEl.setAttribute('aria-modal', 'true');
|
|
602
|
+
modalEl.setAttribute('role', 'dialog');
|
|
603
|
+
modalEl.innerHTML =
|
|
604
|
+
'<div class="modal-dialog modal-dialog-centered">' +
|
|
605
|
+
'<div class="modal-content">' +
|
|
606
|
+
'<div class="modal-header border-0 pb-1">' +
|
|
607
|
+
'<h5 class="modal-title slm-cd-title text-danger"></h5>' +
|
|
608
|
+
'<button type="button" class="btn-close"' +
|
|
609
|
+
' data-slm-cd-action="cancel" aria-label="Close"></button>' +
|
|
610
|
+
'</div>' +
|
|
611
|
+
'<div class="modal-body pt-1">' +
|
|
612
|
+
'<p class="slm-cd-target fw-semibold mb-1"></p>' +
|
|
613
|
+
'<p class="slm-cd-consequence text-muted small mb-2"></p>' +
|
|
614
|
+
'<label class="form-label small mb-1" for="slm-cd-challenge">' +
|
|
615
|
+
'Type <code class="slm-cd-confirmation-text"></code> to continue</label>' +
|
|
616
|
+
'<input id="slm-cd-challenge" class="form-control form-control-sm slm-cd-challenge"' +
|
|
617
|
+
' type="text" autocomplete="off" spellcheck="false">' +
|
|
618
|
+
'</div>' +
|
|
619
|
+
'<div class="modal-footer border-0 pt-0">' +
|
|
620
|
+
'<button type="button" class="btn btn-secondary btn-sm"' +
|
|
621
|
+
' data-slm-cd-action="cancel">Cancel</button>' +
|
|
622
|
+
'<button type="button" class="btn btn-danger btn-sm"' +
|
|
623
|
+
' data-slm-cd-action="confirm">Confirm</button>' +
|
|
624
|
+
'</div>' +
|
|
625
|
+
'</div>' +
|
|
626
|
+
'</div>';
|
|
627
|
+
document.body.appendChild(modalEl);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
var titleEl = modalEl.querySelector('.slm-cd-title');
|
|
631
|
+
var targetEl = modalEl.querySelector('.slm-cd-target');
|
|
632
|
+
var consequenceEl = modalEl.querySelector('.slm-cd-consequence');
|
|
633
|
+
var confirmationTextEl = modalEl.querySelector('.slm-cd-confirmation-text');
|
|
634
|
+
var challengeInput = modalEl.querySelector('.slm-cd-challenge');
|
|
635
|
+
var confirmBtn = modalEl.querySelector('[data-slm-cd-action="confirm"]');
|
|
636
|
+
var confirmationText = opts.confirmationText || opts.target || 'CONFIRM';
|
|
637
|
+
|
|
638
|
+
if (titleEl) titleEl.textContent = opts.title || 'Confirm action';
|
|
639
|
+
if (targetEl) targetEl.textContent = opts.target || '';
|
|
640
|
+
if (consequenceEl) consequenceEl.textContent = opts.consequence || '';
|
|
641
|
+
if (confirmationTextEl) confirmationTextEl.textContent = confirmationText;
|
|
642
|
+
if (challengeInput) challengeInput.value = '';
|
|
643
|
+
if (confirmBtn) {
|
|
644
|
+
confirmBtn.textContent = opts.confirmLabel || 'Confirm';
|
|
645
|
+
confirmBtn.disabled = true;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
var bsModal = null;
|
|
649
|
+
if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
|
|
650
|
+
bsModal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function settle(result, hideModal) {
|
|
654
|
+
if (settled) return;
|
|
655
|
+
settled = true;
|
|
656
|
+
if (activeDestructiveConfirmation === confirmationSession) {
|
|
657
|
+
activeDestructiveConfirmation = null;
|
|
658
|
+
}
|
|
659
|
+
modalEl.removeEventListener('click', onAction);
|
|
660
|
+
if (challengeInput) challengeInput.removeEventListener('input', onChallengeInput);
|
|
661
|
+
if (bsModal) {
|
|
662
|
+
modalEl.removeEventListener('hidden.bs.modal', onHide);
|
|
663
|
+
modalEl.removeEventListener('shown.bs.modal', onShown);
|
|
664
|
+
if (hideModal !== false) bsModal.hide();
|
|
665
|
+
}
|
|
666
|
+
resolve(result);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function onAction(e) {
|
|
670
|
+
var actionEl = e.target.closest('[data-slm-cd-action]');
|
|
671
|
+
if (!actionEl) return;
|
|
672
|
+
if (actionEl.getAttribute('data-slm-cd-action') === 'confirm' &&
|
|
673
|
+
(!challengeInput || challengeInput.value !== confirmationText)) return;
|
|
674
|
+
settle(actionEl.getAttribute('data-slm-cd-action') === 'confirm');
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function onChallengeInput() {
|
|
678
|
+
if (confirmBtn) confirmBtn.disabled = challengeInput.value !== confirmationText;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function onShown() {
|
|
682
|
+
if (challengeInput) challengeInput.focus();
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function onHide() { settle(false); }
|
|
686
|
+
|
|
687
|
+
var confirmationSession = {
|
|
688
|
+
cancel: function() { settle(false, false); }
|
|
689
|
+
};
|
|
690
|
+
activeDestructiveConfirmation = confirmationSession;
|
|
691
|
+
|
|
692
|
+
modalEl.addEventListener('click', onAction);
|
|
693
|
+
if (challengeInput) challengeInput.addEventListener('input', onChallengeInput);
|
|
694
|
+
if (bsModal) {
|
|
695
|
+
modalEl.addEventListener('hidden.bs.modal', onHide, { once: true });
|
|
696
|
+
modalEl.addEventListener('shown.bs.modal', onShown, { once: true });
|
|
697
|
+
bsModal.show();
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
435
702
|
function copyMemoryToClipboard() {
|
|
436
703
|
if (!currentMemoryDetail) return;
|
|
437
704
|
var text = currentMemoryDetail.content || currentMemoryDetail.summary || '';
|
|
@@ -642,14 +642,21 @@
|
|
|
642
642
|
}
|
|
643
643
|
|
|
644
644
|
function doDisconnect(root, destId) {
|
|
645
|
-
|
|
646
|
-
|
|
645
|
+
window.confirmDestructive({
|
|
646
|
+
title: 'Disconnect cloud destination',
|
|
647
|
+
target: destId,
|
|
648
|
+
consequence: 'Existing backups are not deleted.',
|
|
649
|
+
confirmLabel: 'Disconnect',
|
|
650
|
+
}).then(function(confirmed) {
|
|
651
|
+
if (!confirmed) return;
|
|
652
|
+
authMutation('/api/backup/disconnect/' + encodeURIComponent(destId), 'DELETE')
|
|
647
653
|
.then(function (r) { return r.json(); })
|
|
648
654
|
.then(function (d) {
|
|
649
655
|
toast(d.success ? 'Disconnected' : 'Disconnect failed: ' + esc(d.error || ''), !d.success);
|
|
650
656
|
loadDestinations(root);
|
|
651
657
|
})
|
|
652
658
|
.catch(function () { toast('Disconnect failed', true); });
|
|
659
|
+
});
|
|
653
660
|
}
|
|
654
661
|
|
|
655
662
|
function openOAuth(root, oauthPath, providerLabel) {
|