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.
Files changed (212) hide show
  1. package/ATTRIBUTION.md +4 -4
  2. package/CHANGELOG.md +113 -121
  3. package/README.md +65 -63
  4. package/docs/pi-dev-integration.md +1 -1
  5. package/package.json +6 -1
  6. package/plugin/.claude-plugin/plugin.json +1 -1
  7. package/plugin/CLAUDE.md +3 -3
  8. package/plugin/agents/slm-governance-advisor.md +1 -1
  9. package/plugin/agents/slm-loop-runner.md +1 -1
  10. package/plugin/agents/slm-memory-advisor.md +1 -1
  11. package/plugin/agents/slm-optimize-advisor.md +1 -1
  12. package/plugin/requirements.txt +1 -1
  13. package/plugin/skills/slm-cache/SKILL.md +1 -1
  14. package/plugin/skills/slm-compress/SKILL.md +1 -1
  15. package/plugin/skills/slm-governance/SKILL.md +1 -1
  16. package/plugin/skills/slm-graph/SKILL.md +1 -1
  17. package/plugin/skills/slm-loop/SKILL.md +1 -1
  18. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  19. package/plugin/skills/slm-profile/SKILL.md +1 -1
  20. package/plugin/skills/slm-recall/SKILL.md +1 -1
  21. package/plugin/skills/slm-remember/SKILL.md +1 -1
  22. package/plugin/skills/slm-scope/SKILL.md +1 -1
  23. package/plugin/skills/slm-session/SKILL.md +1 -1
  24. package/plugin/skills/slm-status/SKILL.md +1 -1
  25. package/plugin-src/rules/AGENTS.md +1 -1
  26. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  27. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  28. package/plugin-src/skills/slm-governance/SKILL.md +248 -0
  29. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  30. package/plugin-src/skills/slm-loop/SKILL.md +99 -0
  31. package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
  32. package/plugin-src/skills/slm-profile/SKILL.md +148 -0
  33. package/plugin-src/skills/slm-recall/SKILL.md +1 -1
  34. package/plugin-src/skills/slm-remember/SKILL.md +1 -1
  35. package/plugin-src/skills/slm-scope/SKILL.md +176 -0
  36. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  37. package/plugin-src/skills/slm-status/SKILL.md +1 -1
  38. package/pyproject.toml +11 -4
  39. package/src/superlocalmemory/__init__.py +1 -1
  40. package/src/superlocalmemory/cli/commands.py +125 -11
  41. package/src/superlocalmemory/cli/daemon.py +5 -1
  42. package/src/superlocalmemory/cli/main.py +35 -2
  43. package/src/superlocalmemory/cli/ops_cmd.py +281 -0
  44. package/src/superlocalmemory/cli/setup_wizard.py +1 -1
  45. package/src/superlocalmemory/compliance/audit.py +65 -0
  46. package/src/superlocalmemory/compliance/eu_ai_act.py +27 -57
  47. package/src/superlocalmemory/compliance/gdpr.py +416 -20
  48. package/src/superlocalmemory/compliance/retention.py +74 -22
  49. package/src/superlocalmemory/compliance/scheduler.py +78 -9
  50. package/src/superlocalmemory/core/actor_context.py +166 -0
  51. package/src/superlocalmemory/core/admission.py +549 -0
  52. package/src/superlocalmemory/core/backend_orchestrator.py +23 -10
  53. package/src/superlocalmemory/core/config.py +202 -24
  54. package/src/superlocalmemory/core/consolidation_engine.py +13 -13
  55. package/src/superlocalmemory/core/context_cache.py +28 -0
  56. package/src/superlocalmemory/core/embeddings.py +64 -2
  57. package/src/superlocalmemory/core/engine.py +7 -2
  58. package/src/superlocalmemory/core/engine_ingestion.py +65 -3
  59. package/src/superlocalmemory/core/engine_wiring.py +36 -9
  60. package/src/superlocalmemory/core/ingest_policy.py +38 -0
  61. package/src/superlocalmemory/core/maintenance.py +255 -0
  62. package/src/superlocalmemory/core/modes.py +40 -13
  63. package/src/superlocalmemory/core/mutations.py +437 -44
  64. package/src/superlocalmemory/core/operation_policy.py +92 -0
  65. package/src/superlocalmemory/core/operation_policy_registry.py +542 -0
  66. package/src/superlocalmemory/core/operation_request.py +127 -0
  67. package/src/superlocalmemory/core/ops_remediation.py +542 -0
  68. package/src/superlocalmemory/core/recall_pipeline.py +7 -0
  69. package/src/superlocalmemory/core/remember_runtime.py +202 -4
  70. package/src/superlocalmemory/core/remote_mode.py +20 -5
  71. package/src/superlocalmemory/core/store_pipeline.py +150 -0
  72. package/src/superlocalmemory/core/topic_signature.py +19 -4
  73. package/src/superlocalmemory/core/transactions/__init__.py +78 -0
  74. package/src/superlocalmemory/core/transactions/concrete_owners.py +597 -0
  75. package/src/superlocalmemory/core/transactions/erasure.py +825 -0
  76. package/src/superlocalmemory/core/transactions/manifest.py +255 -0
  77. package/src/superlocalmemory/core/transactions/manifest_key.py +155 -0
  78. package/src/superlocalmemory/core/transactions/obligations.py +272 -0
  79. package/src/superlocalmemory/core/transactions/owners.py +114 -0
  80. package/src/superlocalmemory/core/transactions/reconciler.py +285 -0
  81. package/src/superlocalmemory/core/transactions/service.py +330 -0
  82. package/src/superlocalmemory/core/worker_pool.py +33 -5
  83. package/src/superlocalmemory/encoding/cognitive_consolidator.py +70 -28
  84. package/src/superlocalmemory/encoding/emotional.py +75 -14
  85. package/src/superlocalmemory/encoding/scene_builder.py +115 -13
  86. package/src/superlocalmemory/encoding/temporal_parser.py +4 -0
  87. package/src/superlocalmemory/evolution/blind_verifier.py +11 -4
  88. package/src/superlocalmemory/evolution/evolution_store.py +244 -4
  89. package/src/superlocalmemory/evolution/llm_dispatch.py +40 -0
  90. package/src/superlocalmemory/evolution/model_selection.py +18 -3
  91. package/src/superlocalmemory/evolution/mutation_generator.py +3 -0
  92. package/src/superlocalmemory/evolution/skill_activator.py +270 -0
  93. package/src/superlocalmemory/evolution/skill_evolver.py +281 -59
  94. package/src/superlocalmemory/evolution/types.py +30 -8
  95. package/src/superlocalmemory/graph/cozo_backend.py +17 -9
  96. package/src/superlocalmemory/hooks/auto_invoker.py +2 -1
  97. package/src/superlocalmemory/hooks/auto_recall.py +64 -30
  98. package/src/superlocalmemory/hooks/codex_assets.py +14 -1
  99. package/src/superlocalmemory/infra/backup.py +434 -7
  100. package/src/superlocalmemory/infra/process_reaper.py +18 -0
  101. package/src/superlocalmemory/infra/self_heal.py +401 -0
  102. package/src/superlocalmemory/learning/feedback.py +52 -9
  103. package/src/superlocalmemory/loops/engine.py +10 -0
  104. package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
  105. package/src/superlocalmemory/mcp/http_transport.py +30 -331
  106. package/src/superlocalmemory/mcp/profiles.py +5 -0
  107. package/src/superlocalmemory/mcp/resources.py +8 -0
  108. package/src/superlocalmemory/mcp/server.py +51 -4
  109. package/src/superlocalmemory/mcp/shared.py +19 -0
  110. package/src/superlocalmemory/mcp/tools_active.py +25 -4
  111. package/src/superlocalmemory/mcp/tools_code_graph.py +26 -18
  112. package/src/superlocalmemory/mcp/tools_context.py +50 -8
  113. package/src/superlocalmemory/mcp/tools_core.py +69 -21
  114. package/src/superlocalmemory/mcp/tools_evolution.py +9 -2
  115. package/src/superlocalmemory/mcp/tools_learning.py +21 -10
  116. package/src/superlocalmemory/mcp/tools_loops.py +29 -18
  117. package/src/superlocalmemory/mcp/tools_mesh.py +8 -0
  118. package/src/superlocalmemory/mcp/tools_ops.py +115 -0
  119. package/src/superlocalmemory/mcp/tools_optimize.py +4 -0
  120. package/src/superlocalmemory/mcp/tools_v28.py +10 -3
  121. package/src/superlocalmemory/mcp/tools_v3.py +34 -14
  122. package/src/superlocalmemory/mcp/tools_v33.py +18 -33
  123. package/src/superlocalmemory/mesh/broker.py +124 -46
  124. package/src/superlocalmemory/mesh/broker_security.py +470 -0
  125. package/src/superlocalmemory/mesh/discovery.py +365 -0
  126. package/src/superlocalmemory/mesh/lock_protocol.py +313 -0
  127. package/src/superlocalmemory/mesh/node_identity.py +97 -0
  128. package/src/superlocalmemory/mesh/outbox_remote.py +429 -0
  129. package/src/superlocalmemory/mesh/remote_sync.py +511 -28
  130. package/src/superlocalmemory/mesh/state_sync.py +286 -0
  131. package/src/superlocalmemory/optimize/config/store.py +45 -0
  132. package/src/superlocalmemory/parameterization/cross_project.py +12 -0
  133. package/src/superlocalmemory/parameterization/prompt_injector.py +13 -11
  134. package/src/superlocalmemory/parameterization/prompt_lifecycle.py +8 -2
  135. package/src/superlocalmemory/parameterization/workflow_miner.py +17 -0
  136. package/src/superlocalmemory/retrieval/ann_index.py +5 -0
  137. package/src/superlocalmemory/retrieval/bm25_channel.py +49 -2
  138. package/src/superlocalmemory/retrieval/engine.py +19 -4
  139. package/src/superlocalmemory/retrieval/fusion.py +4 -1
  140. package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -3
  141. package/src/superlocalmemory/retrieval/remote_reranker.py +47 -22
  142. package/src/superlocalmemory/retrieval/reranker.py +32 -1
  143. package/src/superlocalmemory/retrieval/temporal_channel.py +16 -3
  144. package/src/superlocalmemory/retrieval/temporal_utils.py +107 -0
  145. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +155 -42
  146. package/src/superlocalmemory/retrieval/vector_store.py +214 -8
  147. package/src/superlocalmemory/server/api.py +5 -5
  148. package/src/superlocalmemory/server/egress_policy.py +258 -0
  149. package/src/superlocalmemory/server/rbac_enforce.py +32 -0
  150. package/src/superlocalmemory/server/route_mutations.py +20 -0
  151. package/src/superlocalmemory/server/routes/compliance.py +153 -7
  152. package/src/superlocalmemory/server/routes/data_io.py +43 -2
  153. package/src/superlocalmemory/server/routes/events.py +15 -0
  154. package/src/superlocalmemory/server/routes/memories.py +56 -3
  155. package/src/superlocalmemory/server/routes/mesh.py +82 -1
  156. package/src/superlocalmemory/server/routes/mesh_lock.py +54 -0
  157. package/src/superlocalmemory/server/routes/mesh_state.py +63 -0
  158. package/src/superlocalmemory/server/routes/v3_api.py +50 -27
  159. package/src/superlocalmemory/server/routes/ws.py +86 -0
  160. package/src/superlocalmemory/server/ui.py +6 -6
  161. package/src/superlocalmemory/server/unified_daemon.py +942 -119
  162. package/src/superlocalmemory/storage/_migration_internals.py +568 -0
  163. package/src/superlocalmemory/storage/_schema_version.py +110 -0
  164. package/src/superlocalmemory/storage/database.py +329 -24
  165. package/src/superlocalmemory/storage/embedding_migrator.py +246 -51
  166. package/src/superlocalmemory/storage/erasure_fence.py +45 -0
  167. package/src/superlocalmemory/storage/generation_fence.py +63 -0
  168. package/src/superlocalmemory/storage/migration_runner.py +140 -417
  169. package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +40 -0
  170. package/src/superlocalmemory/storage/migrations/M033_projection_transactions.py +148 -0
  171. package/src/superlocalmemory/storage/migrations/M034_obligation_integrity.py +58 -0
  172. package/src/superlocalmemory/storage/migrations/M035_erasure_receipts.py +113 -0
  173. package/src/superlocalmemory/storage/migrations/M036_vector_row_map.py +107 -0
  174. package/src/superlocalmemory/storage/migrations/M037_manifest_hmac_version.py +162 -0
  175. package/src/superlocalmemory/storage/migrations/{M033_learning_feedback_channel.py → M038_learning_feedback_channel.py} +3 -3
  176. package/src/superlocalmemory/storage/migrations/M039_scene_fact_members.py +137 -0
  177. package/src/superlocalmemory/storage/migrations/__init__.py +4 -2
  178. package/src/superlocalmemory/storage/schema.py +67 -0
  179. package/src/superlocalmemory/storage/write_coordinator.py +125 -0
  180. package/src/superlocalmemory/trust/scorer.py +28 -4
  181. package/src/superlocalmemory/ui/index.html +14 -3
  182. package/src/superlocalmemory/ui/js/auto-settings.js +12 -1
  183. package/src/superlocalmemory/ui/js/brain.js +6 -4
  184. package/src/superlocalmemory/ui/js/compliance.js +66 -12
  185. package/src/superlocalmemory/ui/js/dashboard.js +13 -3
  186. package/src/superlocalmemory/ui/js/feedback.js +8 -2
  187. package/src/superlocalmemory/ui/js/lifecycle.js +7 -1
  188. package/src/superlocalmemory/ui/js/modal.js +272 -5
  189. package/src/superlocalmemory/ui/js/od-backup.js +9 -2
  190. package/src/superlocalmemory/ui/js/od-compliance-ext.js +301 -0
  191. package/src/superlocalmemory/ui/js/od-operations.js +154 -23
  192. package/src/superlocalmemory/ui/js/od-ops-health.js +417 -0
  193. package/src/superlocalmemory/ui/js/od-optimize.js +35 -21
  194. package/src/superlocalmemory/ui/js/od-team.js +9 -2
  195. package/src/superlocalmemory/ui/js/optimize.js +13 -16
  196. package/src/superlocalmemory/ui/js/profiles.js +7 -3
  197. package/src/superlocalmemory/ui/js/settings.js +7 -1
  198. package/src/superlocalmemory/vector/lancedb_backend.py +19 -9
  199. package/src/superlocalmemory/attribution/mathematical_dna.py +0 -235
  200. package/src/superlocalmemory/cli/post_install.py +0 -114
  201. package/src/superlocalmemory/core/clock_monitor.py +0 -45
  202. package/src/superlocalmemory/core/db_pool.py +0 -80
  203. package/src/superlocalmemory/core/error_catalog.py +0 -113
  204. package/src/superlocalmemory/core/loop_watchdog.py +0 -56
  205. package/src/superlocalmemory/core/priority_queue.py +0 -61
  206. package/src/superlocalmemory/core/pruning_engine.py +0 -216
  207. package/src/superlocalmemory/core/queue_dispatcher.py +0 -73
  208. package/src/superlocalmemory/core/slmignore.py +0 -125
  209. package/src/superlocalmemory/infra/heartbeat_monitor.py +0 -140
  210. package/src/superlocalmemory/infra/webhook_dispatcher.py +0 -247
  211. package/src/superlocalmemory/learning/quantization_scheduler.py +0 -320
  212. package/src/superlocalmemory/storage/access_control.py +0 -182
@@ -0,0 +1,417 @@
1
+ /* od-ops-health.js — Wave-3 Admin Operations & Health panel
2
+ * Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar — AGPL-3.0-or-later
3
+ *
4
+ * Injects an "Admin: Ops Health" tab into the Governance pane tab bar
5
+ * rendered by od-operations.js. Uses a MutationObserver so it fires after
6
+ * od-operations.js's innerHTML replacement completes. Additive only — never
7
+ * modifies or removes od-operations.js tabs or sections.
8
+ *
9
+ * Surfaces:
10
+ * - Dead-letter DLQ entries (ingestion exhausted)
11
+ * - Degraded completion manifests
12
+ * - Exhausted projection obligations
13
+ * - Writer stall warning
14
+ * - Per-entry action buttons: retry / force_reconcile / cancel
15
+ */
16
+
17
+ (function () {
18
+ 'use strict';
19
+
20
+ /* -----------------------------------------------------------------------
21
+ * Constants
22
+ * --------------------------------------------------------------------- */
23
+ var POLL_MS = 30000; // auto-refresh interval when tab is active
24
+ var TAB_KEY = 'admin-ops-health';
25
+ var TAB_LABEL = 'Admin: Ops Health';
26
+ var PANE_ID = 'od-pane-admin-ops-health';
27
+ var _pollTimer = null;
28
+ var _injected = false;
29
+
30
+ /* -----------------------------------------------------------------------
31
+ * DOM helpers
32
+ * --------------------------------------------------------------------- */
33
+ function $id(id) { return document.getElementById(id); }
34
+
35
+ function _esc(str) {
36
+ return String(str)
37
+ .replace(/&/g, '&')
38
+ .replace(/</g, '&lt;')
39
+ .replace(/>/g, '&gt;')
40
+ .replace(/"/g, '&quot;');
41
+ }
42
+
43
+ /* -----------------------------------------------------------------------
44
+ * Fetch helpers — never throws
45
+ * --------------------------------------------------------------------- */
46
+ function _get(path) {
47
+ return fetch(path, { credentials: 'same-origin' })
48
+ .then(function (r) {
49
+ return r.json().then(function (d) { return { ok: r.ok, status: r.status, data: d }; });
50
+ })
51
+ .catch(function (err) { return { ok: false, status: 0, data: null, err: String(err) }; });
52
+ }
53
+
54
+ function _post(path, body) {
55
+ return fetch(path, {
56
+ method: 'POST',
57
+ credentials: 'same-origin',
58
+ headers: { 'Content-Type': 'application/json' },
59
+ body: JSON.stringify(body),
60
+ })
61
+ .then(function (r) {
62
+ return r.json().then(function (d) { return { ok: r.ok, status: r.status, data: d }; });
63
+ })
64
+ .catch(function (err) { return { ok: false, status: 0, data: null, err: String(err) }; });
65
+ }
66
+
67
+ /* -----------------------------------------------------------------------
68
+ * Tab injection — called once od-operations.js has rendered #od-ops-tabs
69
+ * --------------------------------------------------------------------- */
70
+ function _injectTab() {
71
+ var tabBar = $id('od-ops-tabs');
72
+ if (!tabBar) return;
73
+
74
+ /* Don't inject twice (idempotent) */
75
+ if (_injected || tabBar.querySelector('[data-od-tab="' + TAB_KEY + '"]')) {
76
+ _injected = true;
77
+ return;
78
+ }
79
+
80
+ /* Add tab button */
81
+ var tabBtn = document.createElement('button');
82
+ tabBtn.className = 'tab';
83
+ tabBtn.setAttribute('data-od-tab', TAB_KEY);
84
+ tabBtn.style.cssText = 'border-color: var(--bs-warning, #ffc107); color: var(--bs-warning, #ffc107);';
85
+ tabBtn.textContent = TAB_LABEL;
86
+ tabBar.appendChild(tabBtn);
87
+
88
+ /* Add tab pane section */
89
+ var pane = document.createElement('section');
90
+ pane.className = 'tabpane';
91
+ pane.setAttribute('data-od-pane', TAB_KEY);
92
+ pane.id = PANE_ID;
93
+ pane.innerHTML = _buildPaneHtml();
94
+ tabBar.parentNode.appendChild(pane);
95
+
96
+ /* Wire tab click into od-operations.js's existing tab-switching logic
97
+ * by dispatching a click on the same element od-operations uses */
98
+ tabBtn.addEventListener('click', function () { _onTabActivated(); });
99
+
100
+ /* Also patch od-operations.js's tab system if it uses delegated events */
101
+ _patchTabSystem(tabBar);
102
+
103
+ _injected = true;
104
+ _attachActionListeners(pane);
105
+ }
106
+
107
+ function _buildPaneHtml() {
108
+ return (
109
+ '<div style="padding:16px">' +
110
+
111
+ /* Header */
112
+ '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">' +
113
+ '<div>' +
114
+ '<h2 style="font-size:20px;margin-bottom:4px">Operations Health</h2>' +
115
+ '<p style="font-size:13px;opacity:0.7;margin:0">Stuck ingestion, failed projections, writer stalls — visible here first. OWNER or ADMIN access required.</p>' +
116
+ '</div>' +
117
+ '<button id="oh-refresh-btn" style="padding:6px 14px;border-radius:6px;border:1px solid var(--bs-secondary,#6c757d);background:transparent;color:inherit;cursor:pointer">&#x21bb; Refresh</button>' +
118
+ '</div>' +
119
+
120
+ /* Health banner (error state) */
121
+ '<div id="oh-banner-error" style="display:none;padding:10px 14px;border-radius:8px;background:rgba(220,53,69,0.15);border:1px solid rgba(220,53,69,0.4);margin-bottom:14px;font-size:13px">' +
122
+ '&#x26a0; <span id="oh-banner-text">Failures detected.</span>' +
123
+ '<span style="opacity:0.6;margin-left:8px">Resolve below or run <code>slm ops list</code>.</span>' +
124
+ '</div>' +
125
+
126
+ /* Health banner (ok state) */
127
+ '<div id="oh-banner-ok" style="display:none;padding:10px 14px;border-radius:8px;background:rgba(25,135,84,0.15);border:1px solid rgba(25,135,84,0.4);margin-bottom:14px;font-size:13px">' +
128
+ '&#x2713; All operations healthy &mdash; no failures detected.' +
129
+ '</div>' +
130
+
131
+ /* Writer stall warning */
132
+ '<div id="oh-stall-banner" style="display:none;padding:10px 14px;border-radius:8px;background:rgba(255,193,7,0.12);border:1px solid rgba(255,193,7,0.5);margin-bottom:14px;font-size:13px">' +
133
+ '&#x23f3; <strong>Writer stalled</strong> &mdash; operation <code id="oh-stall-op">?</code> inflight for <span id="oh-stall-age">?</span>s. Other team members may experience slow writes.' +
134
+ '</div>' +
135
+
136
+ /* KPI strip */
137
+ '<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:14px">' +
138
+ '<div style="border:1px solid var(--bs-border-color,rgba(255,255,255,0.12));border-radius:8px;padding:12px;text-align:center">' +
139
+ '<div id="oh-kpi-dlq" style="font-size:22px;font-weight:600">-</div>' +
140
+ '<div style="font-size:12px;opacity:0.6">Dead-letter</div>' +
141
+ '</div>' +
142
+ '<div style="border:1px solid var(--bs-border-color,rgba(255,255,255,0.12));border-radius:8px;padding:12px;text-align:center">' +
143
+ '<div id="oh-kpi-degraded" style="font-size:22px;font-weight:600">-</div>' +
144
+ '<div style="font-size:12px;opacity:0.6">Degraded</div>' +
145
+ '</div>' +
146
+ '<div style="border:1px solid var(--bs-border-color,rgba(255,255,255,0.12));border-radius:8px;padding:12px;text-align:center">' +
147
+ '<div id="oh-kpi-exhausted" style="font-size:22px;font-weight:600">-</div>' +
148
+ '<div style="font-size:12px;opacity:0.6">Exhausted</div>' +
149
+ '</div>' +
150
+ '</div>' +
151
+
152
+ /* Table wrap */
153
+ '<div id="oh-table-wrap"><p style="opacity:0.5;font-size:13px">Click Refresh or navigate to this tab to load.</p></div>' +
154
+
155
+ '</div>'
156
+ );
157
+ }
158
+
159
+ /* Patches od-operations.js's internal tab router so our pane hides/shows
160
+ * correctly when other tabs are clicked. Intercepts the tab bar's click
161
+ * events and extends the existing handler. */
162
+ function _patchTabSystem(tabBar) {
163
+ tabBar.addEventListener('click', function (e) {
164
+ var btn = e.target.closest ? e.target.closest('[data-od-tab]') : e.target;
165
+ if (!btn || !btn.getAttribute) return;
166
+ var key = btn.getAttribute('data-od-tab');
167
+ if (!key) return;
168
+
169
+ /* Show our pane only when our tab is clicked */
170
+ var ourPane = $id(PANE_ID);
171
+ if (!ourPane) return;
172
+
173
+ if (key === TAB_KEY) {
174
+ /* Deactivate all od-operations.js panes first */
175
+ var sibs = tabBar.parentNode.querySelectorAll('[data-od-pane]');
176
+ sibs.forEach(function (s) { s.classList.remove('active'); });
177
+ ourPane.classList.add('active');
178
+ _onTabActivated();
179
+ } else {
180
+ ourPane.classList.remove('active');
181
+ }
182
+ }, true /* capture — fires before od-operations.js's handler */);
183
+ }
184
+
185
+ /* -----------------------------------------------------------------------
186
+ * Tab activation — load + auto-refresh
187
+ * --------------------------------------------------------------------- */
188
+ function _onTabActivated() {
189
+ _load();
190
+ _startAutoRefresh();
191
+ }
192
+
193
+ function _startAutoRefresh() {
194
+ _stopAutoRefresh();
195
+ _pollTimer = setInterval(_load, POLL_MS);
196
+ }
197
+
198
+ function _stopAutoRefresh() {
199
+ if (_pollTimer) { clearInterval(_pollTimer); _pollTimer = null; }
200
+ }
201
+
202
+ /* -----------------------------------------------------------------------
203
+ * Data loading
204
+ * --------------------------------------------------------------------- */
205
+ function _load() {
206
+ var wrap = $id('oh-table-wrap');
207
+ if (wrap) wrap.innerHTML = '<p style="opacity:0.5;font-size:13px">Loading…</p>';
208
+
209
+ _get('/operations/failed').then(function (res) {
210
+ if (res.status === 403) { _renderPermissionDenied(); return; }
211
+ if (!res.ok || !res.data) { _renderError(res.err || ('HTTP ' + res.status)); return; }
212
+ _renderData(res.data);
213
+ });
214
+
215
+ _get('/status').then(function (res) {
216
+ if (!res.ok || !res.data) return;
217
+ _renderStall(res.data);
218
+ });
219
+ }
220
+
221
+ /* -----------------------------------------------------------------------
222
+ * Rendering
223
+ * --------------------------------------------------------------------- */
224
+ function _set(id, val) { var el = $id(id); if (el) el.textContent = val; }
225
+ function _show(id) { var el = $id(id); if (el) el.style.display = ''; }
226
+ function _hide(id) { var el = $id(id); if (el) el.style.display = 'none'; }
227
+
228
+ function _renderPermissionDenied() {
229
+ _hide('oh-banner-error'); _hide('oh-banner-ok');
230
+ var w = $id('oh-table-wrap');
231
+ if (w) w.innerHTML = '<p style="color:#ffc107;font-size:13px">&#x1f512; Permission denied. This panel requires <strong>OWNER</strong> or <strong>ADMIN</strong> role.</p>';
232
+ _set('oh-kpi-dlq', '-'); _set('oh-kpi-degraded', '-'); _set('oh-kpi-exhausted', '-');
233
+ }
234
+
235
+ function _renderError(msg) {
236
+ _hide('oh-banner-error'); _hide('oh-banner-ok');
237
+ var w = $id('oh-table-wrap');
238
+ if (w) w.innerHTML = '<p style="opacity:0.5;font-size:13px">Could not load operations: ' + _esc(msg) + '</p>';
239
+ }
240
+
241
+ function _renderData(data) {
242
+ var dlq = data.dead_letter || [];
243
+ var degraded = data.degraded_manifests || [];
244
+ var exhausted = data.exhausted_obligations || [];
245
+ var total = (data.total !== undefined) ? data.total : (dlq.length + degraded.length + exhausted.length);
246
+
247
+ _set('oh-kpi-dlq', dlq.length);
248
+ _set('oh-kpi-degraded', degraded.length);
249
+ _set('oh-kpi-exhausted', exhausted.length);
250
+
251
+ if (total > 0) {
252
+ var parts = [];
253
+ if (dlq.length) parts.push(dlq.length + ' dead-letter');
254
+ if (degraded.length) parts.push(degraded.length + ' degraded');
255
+ if (exhausted.length) parts.push(exhausted.length + ' exhausted');
256
+ _set('oh-banner-text', parts.join(', ') + ' — action required.');
257
+ _show('oh-banner-error'); _hide('oh-banner-ok');
258
+ } else {
259
+ _hide('oh-banner-error'); _show('oh-banner-ok');
260
+ }
261
+
262
+ var all = dlq.concat(degraded).concat(exhausted);
263
+ var w = $id('oh-table-wrap');
264
+ if (w) {
265
+ w.innerHTML = _renderTable(all);
266
+ _attachActionListeners(w);
267
+ }
268
+ }
269
+
270
+ function _renderStall(status) {
271
+ if (status.writer_stalled) {
272
+ _set('oh-stall-op', status.writer_stalled_op_id || '?');
273
+ _set('oh-stall-age', status.writer_stalled_age_s != null ? Math.round(status.writer_stalled_age_s) : '?');
274
+ _show('oh-stall-banner');
275
+ } else {
276
+ _hide('oh-stall-banner');
277
+ }
278
+ }
279
+
280
+ function _catLabel(cat) {
281
+ return { dead_letter: 'Dead-letter', degraded_manifest: 'Degraded', exhausted_obligation: 'Exhausted' }[cat] || cat;
282
+ }
283
+
284
+ function _renderTable(entries) {
285
+ if (!entries || entries.length === 0) {
286
+ return '<p style="opacity:0.5;font-size:13px">No failed operations.</p>';
287
+ }
288
+ var style = 'width:100%;border-collapse:collapse;font-size:13px';
289
+ var thStyle = 'padding:8px 10px;text-align:left;opacity:0.6;border-bottom:1px solid rgba(255,255,255,0.1)';
290
+ var tdStyle = 'padding:8px 10px;border-bottom:1px solid rgba(255,255,255,0.06)';
291
+ var rows = entries.map(function (e) {
292
+ var opId = _esc(e.operation_id || '?');
293
+ var cat = _esc(_catLabel(e.category));
294
+ var profile = _esc(e.profile_id || '-');
295
+ var attempts = e.attempts !== undefined ? e.attempts : '-';
296
+ var err = e.error ? ('<span style="color:#f66;font-size:12px">' + _esc(e.error.slice(0, 60)) + '</span>') : '';
297
+ var btns =
298
+ '<button class="oh-action" data-op="' + opId + '" data-act="retry" style="margin-right:4px;padding:3px 8px;border-radius:4px;border:1px solid;cursor:pointer;font-size:12px">Retry</button>' +
299
+ '<button class="oh-action" data-op="' + opId + '" data-act="force_reconcile" style="margin-right:4px;padding:3px 8px;border-radius:4px;border:1px solid;cursor:pointer;font-size:12px">Reconcile</button>' +
300
+ '<button class="oh-action" data-op="' + opId + '" data-act="cancel" style="padding:3px 8px;border-radius:4px;border:1px solid rgba(220,53,69,0.5);color:#f66;cursor:pointer;font-size:12px">Cancel</button>';
301
+ return '<tr><td style="' + tdStyle + '"><code style="font-size:11px">' + opId + '</code></td>' +
302
+ '<td style="' + tdStyle + '">' + cat + '</td>' +
303
+ '<td style="' + tdStyle + '">' + profile + '</td>' +
304
+ '<td style="' + tdStyle + '">' + attempts + '</td>' +
305
+ '<td style="' + tdStyle + '">' + err + '</td>' +
306
+ '<td style="' + tdStyle + '">' + btns + '</td></tr>';
307
+ }).join('');
308
+
309
+ return '<table style="' + style + '"><thead><tr>' +
310
+ ['Operation ID', 'Category', 'Profile', 'Attempts', 'Error', 'Actions'].map(function (h) {
311
+ return '<th style="' + thStyle + '">' + h + '</th>';
312
+ }).join('') +
313
+ '</tr></thead><tbody>' + rows + '</tbody></table>';
314
+ }
315
+
316
+ /* -----------------------------------------------------------------------
317
+ * Action buttons
318
+ * --------------------------------------------------------------------- */
319
+ function _attachActionListeners(container) {
320
+ var btns = container.querySelectorAll('.oh-action');
321
+ for (var i = 0; i < btns.length; i++) {
322
+ (function (btn) {
323
+ btn.addEventListener('click', function () {
324
+ _resolveOp(btn.getAttribute('data-op'), btn.getAttribute('data-act'));
325
+ });
326
+ })(btns[i]);
327
+ }
328
+
329
+ var refreshBtn = $id('oh-refresh-btn');
330
+ if (refreshBtn && !refreshBtn._wired) {
331
+ refreshBtn._wired = true;
332
+ refreshBtn.addEventListener('click', _load);
333
+ }
334
+ }
335
+
336
+ function _resolveOp(opId, action) {
337
+ if (typeof window.confirmDestructive !== 'function') {
338
+ _toast('error', 'Confirmation control unavailable; operation was not changed.');
339
+ return;
340
+ }
341
+ window.confirmDestructive({
342
+ title: 'Resolve operation',
343
+ target: opId,
344
+ consequence: 'Apply the "' + action + '" resolution to this operation.',
345
+ confirmLabel: 'Resolve'
346
+ }).then(function (confirmed) {
347
+ if (confirmed) _resolveOpConfirmed(opId, action);
348
+ });
349
+ }
350
+
351
+ function _resolveOpConfirmed(opId, action) {
352
+ _post('/operations/' + encodeURIComponent(opId) + '/resolve', { action: action })
353
+ .then(function (res) {
354
+ if (res.status === 403) { _toast('error', 'Permission denied.'); return; }
355
+ var d = res.data || {};
356
+ if (d.success) { _toast('ok', 'Resolved: ' + opId); setTimeout(_load, 800); }
357
+ else { _toast('error', 'Not resolved: ' + (d.reason || d.error || 'unknown')); }
358
+ });
359
+ }
360
+
361
+ function _toast(kind, msg) {
362
+ var w = $id('oh-table-wrap');
363
+ if (!w) return;
364
+ var el = document.createElement('div');
365
+ el.style.cssText = 'padding:8px 12px;border-radius:6px;margin-bottom:8px;font-size:13px;' +
366
+ (kind === 'ok' ? 'background:rgba(25,135,84,0.2);' : 'background:rgba(220,53,69,0.2);');
367
+ el.textContent = msg;
368
+ w.insertBefore(el, w.firstChild);
369
+ setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, 5000);
370
+ }
371
+
372
+ /* -----------------------------------------------------------------------
373
+ * Injection observer — waits for od-operations.js to render #od-ops-tabs
374
+ * --------------------------------------------------------------------- */
375
+ function _waitForTabBar() {
376
+ /* Fast path: already rendered */
377
+ if ($id('od-ops-tabs')) { _injectTab(); return; }
378
+
379
+ if (typeof MutationObserver === 'undefined') {
380
+ /* Fallback: polling (od-operations.js renders lazily on tab click) */
381
+ var t = setInterval(function () {
382
+ if ($id('od-ops-tabs')) { clearInterval(t); _injectTab(); }
383
+ }, 200);
384
+ /* Stop after 5 min — well past any realistic page session */
385
+ setTimeout(function () { clearInterval(t); }, 300000);
386
+ return;
387
+ }
388
+
389
+ /* Observe the whole document body so we catch od-operations.js's
390
+ * deferred render regardless of which element it targets. */
391
+ var obs = new MutationObserver(function () {
392
+ if ($id('od-ops-tabs') && !_injected) {
393
+ _injectTab();
394
+ if (_injected) obs.disconnect();
395
+ }
396
+ });
397
+ obs.observe(document.body, { childList: true, subtree: true });
398
+
399
+ /* Safety: stop observing after 5 min */
400
+ setTimeout(function () { obs.disconnect(); }, 300000);
401
+ }
402
+
403
+ /* -----------------------------------------------------------------------
404
+ * Init
405
+ * --------------------------------------------------------------------- */
406
+ function init() {
407
+ _waitForTabBar();
408
+ }
409
+
410
+ window.OpsHealth = { load: _load, init: init };
411
+
412
+ if (document.readyState === 'loading') {
413
+ document.addEventListener('DOMContentLoaded', init);
414
+ } else {
415
+ init();
416
+ }
417
+ })();
@@ -656,12 +656,19 @@
656
656
  if (!btn) return;
657
657
  var mode = btn.getAttribute('data-mode');
658
658
  if (mode === 'aggressive') {
659
- if (!window.confirm(
660
- 'WARNING: Aggressive mode may reduce output fidelity.\n\n' +
661
- 'Do NOT use for: code generation, legal text, exact-output tasks, math.\n' +
662
- 'Safe for: summarization, brainstorming, open-ended chat.\n\n' +
663
- 'Continue?'
664
- )) return;
659
+ window.confirmDestructive({
660
+ title: 'Enable aggressive compression',
661
+ target: 'Compression mode',
662
+ consequence: 'May reduce output fidelity. Not safe for code, legal text, or math tasks.',
663
+ confirmLabel: 'Enable',
664
+ }).then(function(confirmed) {
665
+ if (!confirmed) return;
666
+ seg.querySelectorAll('button[data-mode]').forEach(function (b) {
667
+ b.classList.toggle('active', b === btn);
668
+ });
669
+ putConfig({ compress_mode: 'aggressive' });
670
+ });
671
+ return;
665
672
  }
666
673
  seg.querySelectorAll('button[data-mode]').forEach(function (b) {
667
674
  b.classList.toggle('active', b === btn);
@@ -674,21 +681,28 @@
674
681
  var clearBtn = container.querySelector('#od-opt-clear-cache');
675
682
  if (clearBtn) {
676
683
  clearBtn.addEventListener('click', function () {
677
- if (!window.confirm('Clear all cache entries from llmcache.db?')) return;
678
- fetch('/api/optimize/cache/clear', { method: 'DELETE' })
679
- .then(function (r) { return r.json(); })
680
- .then(function (d) {
681
- var msgEl = document.getElementById('od-opt-clear-msg');
682
- if (msgEl) {
683
- msgEl.textContent = 'Cleared ' + fmtNum(d.deleted || 0) + ' entries.';
684
- msgEl.style.display = '';
685
- setTimeout(function () {
686
- msgEl.style.display = 'none';
687
- loadSavings();
688
- }, 3000);
689
- }
690
- })
691
- .catch(function () {});
684
+ window.confirmDestructive({
685
+ title: 'Clear LLM cache',
686
+ target: 'All entries in llmcache.db',
687
+ consequence: 'All cached responses will be cleared.',
688
+ confirmLabel: 'Clear',
689
+ }).then(function(confirmed) {
690
+ if (!confirmed) return;
691
+ fetch('/api/optimize/cache/clear', { method: 'DELETE' })
692
+ .then(function (r) { return r.json(); })
693
+ .then(function (d) {
694
+ var msgEl = document.getElementById('od-opt-clear-msg');
695
+ if (msgEl) {
696
+ msgEl.textContent = 'Cleared ' + fmtNum(d.deleted || 0) + ' entries.';
697
+ msgEl.style.display = '';
698
+ setTimeout(function () {
699
+ msgEl.style.display = 'none';
700
+ loadSavings();
701
+ }, 3000);
702
+ }
703
+ })
704
+ .catch(function () {});
705
+ });
692
706
  });
693
707
  }
694
708
 
@@ -232,8 +232,15 @@
232
232
  btn.addEventListener('click', function () {
233
233
  var uid = btn.getAttribute('data-uid');
234
234
  var uname = btn.getAttribute('data-uname');
235
- if (!window.confirm('Remove user "' + uname + '"? This deletes their account and access.')) return;
236
- postJSON('/api/rbac/users/' + encodeURIComponent(uid), {}, 'DELETE').then(reload);
235
+ window.confirmDestructive({
236
+ title: 'Remove user',
237
+ target: '"' + uname + '"',
238
+ consequence: 'This deletes their account and access.',
239
+ confirmLabel: 'Remove',
240
+ }).then(function(confirmed) {
241
+ if (!confirmed) return;
242
+ postJSON('/api/rbac/users/' + encodeURIComponent(uid), {}, 'DELETE').then(reload);
243
+ });
237
244
  });
238
245
  });
239
246
 
@@ -95,22 +95,19 @@
95
95
  if (id === 'opt-compress-mode') {
96
96
  var mode = e.target.value;
97
97
  if (mode === 'aggressive') {
98
- // M-03: Bootstrap modal replaces browser confirm() for aggressive warning
99
- var modalEl = document.getElementById('optimizeAggressiveModal');
100
- if (modalEl && typeof bootstrap !== 'undefined') {
101
- var modal = bootstrap.Modal.getOrCreateInstance(modalEl);
102
- modal.show();
103
- var confirmBtn = document.getElementById('optimize-aggressive-confirm');
104
- var cancelBtn = document.getElementById('optimize-aggressive-cancel');
105
- if (confirmBtn) confirmBtn.onclick = function() { modal.hide(); _putConfig({compress_mode: 'aggressive'}); };
106
- if (cancelBtn) cancelBtn.onclick = function() { modal.hide(); e.target.value = 'safe'; };
107
- return;
108
- }
109
- // Fallback for environments without Bootstrap
110
- if (!confirm('WARNING: Aggressive mode may reduce output fidelity.\n\nDo NOT use for: code generation, legal text, exact-output tasks, math.\nSafe for: summarization, brainstorming, open-ended chat.\n\nContinue?')) {
111
- e.target.value = 'safe';
112
- return;
113
- }
98
+ confirmDestructive({
99
+ title: 'Enable aggressive compression',
100
+ target: 'Compression mode',
101
+ consequence: 'May reduce output fidelity. Not safe for code, legal text, or math tasks.',
102
+ confirmLabel: 'Enable',
103
+ }).then(function(confirmed) {
104
+ if (confirmed) {
105
+ _putConfig({compress_mode: 'aggressive'});
106
+ } else {
107
+ e.target.value = 'safe';
108
+ }
109
+ });
110
+ return;
114
111
  }
115
112
  _putConfig({compress_mode: mode});
116
113
  return;
@@ -211,9 +211,13 @@ async function deleteProfile(name) {
211
211
  showToast('Cannot delete the default profile');
212
212
  return;
213
213
  }
214
- if (!confirm('Delete profile "' + name + '"?\nIts memories will be moved to the default profile.')) {
215
- return;
216
- }
214
+ var confirmed = await confirmDestructive({
215
+ title: 'Delete profile',
216
+ target: '"' + name + '"',
217
+ consequence: 'Its memories will be moved to the default profile.',
218
+ confirmLabel: 'Delete',
219
+ });
220
+ if (!confirmed) return;
217
221
  try {
218
222
  var response = await fetch('/api/profiles/' + encodeURIComponent(name), {
219
223
  method: 'DELETE'
@@ -474,7 +474,13 @@ async function connectGoogleDrive() {
474
474
  }
475
475
 
476
476
  async function disconnectDestination(destId) {
477
- if (!confirm('Disconnect this backup destination? Your backups on the cloud will remain.')) return;
477
+ var confirmed = await confirmDestructive({
478
+ title: 'Disconnect backup destination',
479
+ target: destId,
480
+ consequence: 'Your backups on the cloud will remain.',
481
+ confirmLabel: 'Disconnect',
482
+ });
483
+ if (!confirmed) return;
478
484
 
479
485
  try {
480
486
  var response = await fetch('/api/backup/disconnect/' + destId, { method: 'DELETE' });
@@ -14,6 +14,7 @@ Part of Qualixar | Author: Varun Pratap Bhardwaj
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ import importlib.util
17
18
  import logging
18
19
  import sqlite3
19
20
  import struct
@@ -24,13 +25,18 @@ from superlocalmemory.storage.sqlite_vectors import iter_canonical_vectors
24
25
 
25
26
  logger = logging.getLogger(__name__)
26
27
 
27
- # Optional import
28
- try:
29
- import lancedb
30
- _LANCEDB_AVAILABLE = True
31
- except ImportError:
32
- lancedb = None # type: ignore[assignment]
33
- _LANCEDB_AVAILABLE = False
28
+ # Availability via find_spec only — never import lancedb at module load.
29
+ # `import lancedb` starts LanceDBBackgroundEventLoop; under Python 3.14 that
30
+ # background native runtime races GC and can segfault the full pytest suite.
31
+ # Real import is deferred to LanceDBVectorBackend construction.
32
+ def _lancedb_spec_present() -> bool:
33
+ try:
34
+ return importlib.util.find_spec("lancedb") is not None
35
+ except Exception:
36
+ return False
37
+
38
+
39
+ _LANCEDB_AVAILABLE: bool = _lancedb_spec_present()
34
40
 
35
41
 
36
42
  class LanceDBError(Exception):
@@ -60,10 +66,14 @@ class LanceDBVectorBackend:
60
66
  DEFAULT_DIMENSION: int = 768
61
67
 
62
68
  def __init__(self, db_path: str, dimension: int | None = None) -> None:
63
- if not _LANCEDB_AVAILABLE:
69
+ # Re-check at construct time so a late install still works.
70
+ if not _lancedb_spec_present():
64
71
  raise LanceDBNotAvailable(
65
72
  "LanceDB not installed. Run: pip install superlocalmemory[lancedb]"
66
73
  )
74
+ # Lazy import: only construct-time starts the native runtime.
75
+ import lancedb # noqa: PLC0415
76
+
67
77
  path = Path(db_path)
68
78
  path.mkdir(parents=True, exist_ok=True)
69
79
  self._db_path = str(path)
@@ -73,7 +83,7 @@ class LanceDBVectorBackend:
73
83
  # wins over the requested value to keep already-materialized stores
74
84
  # readable after a config change.
75
85
  self._dimension = int(dimension) if dimension else self.DEFAULT_DIMENSION
76
- self._db = lancedb.connect(self._db_path) # type: ignore[union-attr]
86
+ self._db = lancedb.connect(self._db_path)
77
87
  self._table = self._open_or_create_table()
78
88
 
79
89
  @property