superlocalmemory 3.8.0 → 3.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.
Files changed (134) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +32 -120
  3. package/package.json +9 -2
  4. package/plugin/.claude-plugin/plugin.json +1 -2
  5. package/plugin/CLAUDE.md +3 -3
  6. package/plugin/agents/slm-governance-advisor.md +1 -1
  7. package/plugin/agents/slm-loop-runner.md +1 -1
  8. package/plugin/agents/slm-memory-advisor.md +2 -2
  9. package/plugin/agents/slm-optimize-advisor.md +1 -1
  10. package/plugin/requirements.txt +1 -1
  11. package/plugin/skills/slm-cache/SKILL.md +1 -1
  12. package/plugin/skills/slm-compress/SKILL.md +1 -1
  13. package/plugin/skills/slm-governance/SKILL.md +1 -1
  14. package/plugin/skills/slm-graph/SKILL.md +1 -1
  15. package/plugin/skills/slm-loop/SKILL.md +1 -1
  16. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  17. package/plugin/skills/slm-profile/SKILL.md +1 -1
  18. package/plugin/skills/slm-recall/SKILL.md +3 -5
  19. package/plugin/skills/slm-remember/SKILL.md +1 -1
  20. package/plugin/skills/slm-scope/SKILL.md +1 -1
  21. package/plugin/skills/slm-session/SKILL.md +1 -1
  22. package/plugin/skills/slm-status/SKILL.md +1 -1
  23. package/plugin-src/rules/AGENTS.md +2 -1
  24. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  25. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  26. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  27. package/plugin-src/skills/slm-recall/SKILL.md +3 -5
  28. package/plugin-src/skills/slm-remember/SKILL.md +1 -1
  29. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  30. package/plugin-src/skills/slm-status/SKILL.md +1 -1
  31. package/pyproject.toml +2 -1
  32. package/scripts/postinstall.js +7 -1
  33. package/src/superlocalmemory/__init__.py +1 -1
  34. package/src/superlocalmemory/cli/commands.py +494 -9
  35. package/src/superlocalmemory/cli/daemon.py +7 -0
  36. package/src/superlocalmemory/cli/loop_cmd.py +2 -7
  37. package/src/superlocalmemory/cli/main.py +72 -7
  38. package/src/superlocalmemory/cli/setup_wizard.py +142 -16
  39. package/src/superlocalmemory/cli/version_banner.py +17 -3
  40. package/src/superlocalmemory/core/backend_orchestrator.py +18 -16
  41. package/src/superlocalmemory/core/component_healer.py +144 -0
  42. package/src/superlocalmemory/core/component_registry.py +487 -0
  43. package/src/superlocalmemory/core/config.py +21 -0
  44. package/src/superlocalmemory/core/embedding_worker.py +4 -5
  45. package/src/superlocalmemory/core/embeddings.py +132 -45
  46. package/src/superlocalmemory/core/engine.py +29 -22
  47. package/src/superlocalmemory/core/engine_ingestion.py +332 -45
  48. package/src/superlocalmemory/core/ingestion_command.py +154 -25
  49. package/src/superlocalmemory/core/injection.py +12 -7
  50. package/src/superlocalmemory/core/maintenance.py +43 -0
  51. package/src/superlocalmemory/core/maintenance_scheduler.py +44 -6
  52. package/src/superlocalmemory/core/recall_pipeline.py +42 -4
  53. package/src/superlocalmemory/core/store_pipeline.py +195 -20
  54. package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
  55. package/src/superlocalmemory/hooks/portable_kit.py +34 -2
  56. package/src/superlocalmemory/learning/model_rollback.py +3 -0
  57. package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
  58. package/src/superlocalmemory/learning/reward.py +50 -0
  59. package/src/superlocalmemory/learning/source_quality.py +523 -1
  60. package/src/superlocalmemory/loops/ledger.py +25 -5
  61. package/src/superlocalmemory/mcp/_daemon_proxy.py +6 -2
  62. package/src/superlocalmemory/mcp/_pool_adapter.py +4 -1
  63. package/src/superlocalmemory/mcp/server.py +11 -30
  64. package/src/superlocalmemory/mcp/tools_active.py +1 -1
  65. package/src/superlocalmemory/mcp/tools_core.py +21 -5
  66. package/src/superlocalmemory/mcp/tools_learning.py +2 -2
  67. package/src/superlocalmemory/retrieval/bridge_discovery.py +14 -0
  68. package/src/superlocalmemory/retrieval/engine.py +53 -21
  69. package/src/superlocalmemory/retrieval/reranker.py +3 -4
  70. package/src/superlocalmemory/retrieval/spreading_activation.py +68 -38
  71. package/src/superlocalmemory/server/config_file.py +90 -0
  72. package/src/superlocalmemory/server/origin.py +50 -0
  73. package/src/superlocalmemory/server/routes/backup.py +293 -70
  74. package/src/superlocalmemory/server/routes/behavioral.py +342 -61
  75. package/src/superlocalmemory/server/routes/brain.py +57 -16
  76. package/src/superlocalmemory/server/routes/config_api.py +84 -82
  77. package/src/superlocalmemory/server/routes/entity.py +100 -23
  78. package/src/superlocalmemory/server/routes/evolution.py +103 -100
  79. package/src/superlocalmemory/server/routes/learning.py +286 -105
  80. package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
  81. package/src/superlocalmemory/server/routes/memories.py +8 -3
  82. package/src/superlocalmemory/server/routes/mesh.py +121 -32
  83. package/src/superlocalmemory/server/routes/ratelimit.py +33 -25
  84. package/src/superlocalmemory/server/routes/stats.py +93 -155
  85. package/src/superlocalmemory/server/routes/token.py +3 -13
  86. package/src/superlocalmemory/server/routes/v3_api.py +184 -20
  87. package/src/superlocalmemory/server/unified_daemon.py +732 -41
  88. package/src/superlocalmemory/storage/embedding_migrator.py +235 -0
  89. package/src/superlocalmemory/storage/migration_runner.py +79 -1
  90. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
  91. package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
  92. package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
  93. package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
  94. package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
  95. package/src/superlocalmemory/storage/schema.py +49 -1
  96. package/src/superlocalmemory/storage/schema_v32.py +2 -0
  97. package/src/superlocalmemory/storage/schema_v347.py +4 -0
  98. package/src/superlocalmemory/ui/index.html +6 -8
  99. package/src/superlocalmemory/ui/js/core.js +52 -9
  100. package/src/superlocalmemory/ui/js/dashboard.js +169 -82
  101. package/src/superlocalmemory/ui/js/od-backup.js +156 -65
  102. package/src/superlocalmemory/ui/js/od-brain.js +88 -51
  103. package/src/superlocalmemory/ui/js/od-components.js +147 -0
  104. package/src/superlocalmemory/ui/js/od-entities.js +65 -22
  105. package/src/superlocalmemory/ui/js/od-graph.js +46 -4
  106. package/src/superlocalmemory/ui/js/od-health.js +18 -0
  107. package/src/superlocalmemory/ui/js/od-memories.js +84 -5
  108. package/src/superlocalmemory/ui/js/od-mesh.js +23 -9
  109. package/src/superlocalmemory/ui/js/od-operations.js +36 -0
  110. package/src/superlocalmemory/ui/js/od-settings.js +186 -63
  111. package/src/superlocalmemory/ui/js/od-shell.js +249 -33
  112. package/src/superlocalmemory/ui/js/od-skills.js +44 -17
  113. package/src/superlocalmemory/ui/js/settings.js +15 -1
  114. package/plugin-src/.mcp.json +0 -12
  115. package/plugin-src/agents/slm-governance-advisor.md +0 -80
  116. package/plugin-src/agents/slm-loop-runner.md +0 -71
  117. package/plugin-src/agents/slm-memory-advisor.md +0 -49
  118. package/plugin-src/agents/slm-optimize-advisor.md +0 -44
  119. package/plugin-src/commands/slm-loop.md +0 -31
  120. package/plugin-src/hooks/.gitkeep +0 -0
  121. package/plugin-src/hooks/hooks.json +0 -102
  122. package/plugin-src/manifest.json +0 -30
  123. package/plugin-src/requirements.txt +0 -1
  124. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  125. package/plugin-src/scripts/ensure-venv.bat +0 -122
  126. package/plugin-src/scripts/ensure-venv.sh +0 -105
  127. package/plugin-src/scripts/slm-launch +0 -62
  128. package/plugin-src/scripts/slm-launch.bat +0 -23
  129. package/plugin-src/settings.json +0 -25
  130. package/plugin-src/skills/slm-governance/SKILL.md +0 -248
  131. package/plugin-src/skills/slm-loop/SKILL.md +0 -99
  132. package/plugin-src/skills/slm-mesh/SKILL.md +0 -282
  133. package/plugin-src/skills/slm-profile/SKILL.md +0 -148
  134. package/plugin-src/skills/slm-scope/SKILL.md +0 -176
@@ -0,0 +1,137 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later
3
+
4
+ """M029 — profile-scoped indexes for large behavioral histories.
5
+
6
+ Dashboard reads always filter behavioral and outcome history by profile before
7
+ sorting or aggregating. These composite indexes keep those operations bounded
8
+ to one tenant as the shared database grows.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import sqlite3
14
+
15
+ NAME = "M029_behavioral_history_indexes"
16
+ DB_TARGET = "memory"
17
+
18
+ DDL = """
19
+ CREATE INDEX IF NOT EXISTS idx_outcomes_profile_outcome_time
20
+ ON action_outcomes(profile_id, outcome, timestamp DESC);
21
+ CREATE INDEX IF NOT EXISTS idx_outcomes_profile_settled_time
22
+ ON action_outcomes(profile_id, settled, settled_at DESC);
23
+ CREATE INDEX IF NOT EXISTS idx_outcomes_profile_settled_cursor
24
+ ON action_outcomes(
25
+ profile_id, settled, COALESCE(settled_at, ''), outcome_id
26
+ );
27
+ CREATE INDEX IF NOT EXISTS idx_assertions_profile_confidence
28
+ ON behavioral_assertions(profile_id, confidence DESC);
29
+ CREATE INDEX IF NOT EXISTS idx_tool_events_profile_created
30
+ ON tool_events(profile_id, created_at DESC);
31
+ CREATE INDEX IF NOT EXISTS idx_soft_prompts_profile_active_category
32
+ ON soft_prompt_templates(profile_id, active, category, prompt_id);
33
+ """
34
+
35
+ _INDEXES: tuple[tuple[str, str, tuple[str, ...]], ...] = (
36
+ (
37
+ "idx_outcomes_profile_outcome_time",
38
+ "action_outcomes",
39
+ ("profile_id", "outcome", "timestamp"),
40
+ ),
41
+ (
42
+ "idx_outcomes_profile_settled_time",
43
+ "action_outcomes",
44
+ ("profile_id", "settled", "settled_at"),
45
+ ),
46
+ (
47
+ "idx_outcomes_profile_settled_cursor",
48
+ "action_outcomes",
49
+ ("profile_id", "settled", "settled_at", "outcome_id"),
50
+ ),
51
+ (
52
+ "idx_assertions_profile_confidence",
53
+ "behavioral_assertions",
54
+ ("profile_id", "confidence"),
55
+ ),
56
+ (
57
+ "idx_tool_events_profile_created",
58
+ "tool_events",
59
+ ("profile_id", "created_at"),
60
+ ),
61
+ (
62
+ "idx_soft_prompts_profile_active_category",
63
+ "soft_prompt_templates",
64
+ ("profile_id", "active", "category", "prompt_id"),
65
+ ),
66
+ )
67
+
68
+
69
+ def _columns(conn: sqlite3.Connection, table: str) -> set[str]:
70
+ return {
71
+ str(row[1])
72
+ for row in conn.execute(f"PRAGMA table_info({table})").fetchall()
73
+ }
74
+
75
+
76
+ def _index_sql(name: str) -> str:
77
+ statements = {
78
+ "idx_outcomes_profile_outcome_time": (
79
+ "CREATE INDEX IF NOT EXISTS idx_outcomes_profile_outcome_time "
80
+ "ON action_outcomes(profile_id, outcome, timestamp DESC)"
81
+ ),
82
+ "idx_outcomes_profile_settled_time": (
83
+ "CREATE INDEX IF NOT EXISTS idx_outcomes_profile_settled_time "
84
+ "ON action_outcomes(profile_id, settled, settled_at DESC)"
85
+ ),
86
+ "idx_outcomes_profile_settled_cursor": (
87
+ "CREATE INDEX IF NOT EXISTS idx_outcomes_profile_settled_cursor "
88
+ "ON action_outcomes("
89
+ "profile_id, settled, COALESCE(settled_at, ''), outcome_id)"
90
+ ),
91
+ "idx_assertions_profile_confidence": (
92
+ "CREATE INDEX IF NOT EXISTS idx_assertions_profile_confidence "
93
+ "ON behavioral_assertions(profile_id, confidence DESC)"
94
+ ),
95
+ "idx_tool_events_profile_created": (
96
+ "CREATE INDEX IF NOT EXISTS idx_tool_events_profile_created "
97
+ "ON tool_events(profile_id, created_at DESC)"
98
+ ),
99
+ "idx_soft_prompts_profile_active_category": (
100
+ "CREATE INDEX IF NOT EXISTS "
101
+ "idx_soft_prompts_profile_active_category "
102
+ "ON soft_prompt_templates(profile_id, active, category, prompt_id)"
103
+ ),
104
+ }
105
+ return statements[name]
106
+
107
+
108
+ def apply(conn: sqlite3.Connection) -> None:
109
+ """Add each index supported by the installed runtime schema.
110
+
111
+ Optional v3.2/v3.4.7 tables also declare these indexes in their owning
112
+ schema modules, so a partial bootstrap cannot permanently miss an index
113
+ when those tables are created later.
114
+ """
115
+ for name, table, required_columns in _INDEXES:
116
+ if set(required_columns) <= _columns(conn, table):
117
+ conn.execute(_index_sql(name))
118
+
119
+
120
+ def verify(conn: sqlite3.Connection) -> bool:
121
+ """Require each index whose backing table and columns are present."""
122
+ names = {
123
+ str(row[0])
124
+ for row in conn.execute(
125
+ "SELECT name FROM sqlite_master WHERE type='index'"
126
+ ).fetchall()
127
+ }
128
+ return all(
129
+ name in names
130
+ for name, table, required_columns in _INDEXES
131
+ if set(required_columns) <= _columns(conn, table)
132
+ )
133
+
134
+
135
+ def repair(conn: sqlite3.Connection) -> None:
136
+ """Create only indexes supported by the current optional schema."""
137
+ apply(conn)
@@ -0,0 +1,93 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later
3
+
4
+ """M030 — page-first Entity Explorer indexes for large installations."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import sqlite3
9
+
10
+ NAME = "M030_entity_explorer_indexes"
11
+ DB_TARGET = "memory"
12
+
13
+ DDL = """
14
+ CREATE INDEX IF NOT EXISTS idx_entities_profile_fact_count_id
15
+ ON canonical_entities(profile_id, fact_count DESC, entity_id ASC);
16
+ CREATE INDEX IF NOT EXISTS idx_entities_profile_type_fact_count_id
17
+ ON canonical_entities(
18
+ profile_id, entity_type COLLATE NOCASE, fact_count DESC, entity_id ASC
19
+ );
20
+ CREATE INDEX IF NOT EXISTS idx_entity_profiles_profile_entity_rank
21
+ ON entity_profiles(
22
+ profile_id, entity_id, last_compiled_at DESC,
23
+ project_name COLLATE NOCASE
24
+ );
25
+ """
26
+
27
+ _INDEXES: tuple[tuple[str, str, tuple[str, ...], str], ...] = (
28
+ (
29
+ "idx_entities_profile_fact_count_id",
30
+ "canonical_entities",
31
+ ("profile_id", "fact_count", "entity_id"),
32
+ "CREATE INDEX IF NOT EXISTS idx_entities_profile_fact_count_id "
33
+ "ON canonical_entities(profile_id, fact_count DESC, entity_id ASC)",
34
+ ),
35
+ (
36
+ "idx_entities_profile_type_fact_count_id",
37
+ "canonical_entities",
38
+ ("profile_id", "entity_type", "fact_count", "entity_id"),
39
+ "CREATE INDEX IF NOT EXISTS idx_entities_profile_type_fact_count_id "
40
+ "ON canonical_entities("
41
+ "profile_id, entity_type COLLATE NOCASE, fact_count DESC, entity_id ASC"
42
+ ")",
43
+ ),
44
+ (
45
+ "idx_entity_profiles_profile_entity_rank",
46
+ "entity_profiles",
47
+ (
48
+ "profile_id",
49
+ "entity_id",
50
+ "last_compiled_at",
51
+ "project_name",
52
+ ),
53
+ "CREATE INDEX IF NOT EXISTS idx_entity_profiles_profile_entity_rank "
54
+ "ON entity_profiles("
55
+ "profile_id, entity_id, last_compiled_at DESC, "
56
+ "project_name COLLATE NOCASE"
57
+ ")",
58
+ ),
59
+ )
60
+
61
+
62
+ def _columns(conn: sqlite3.Connection, table: str) -> set[str]:
63
+ return {
64
+ str(row[1])
65
+ for row in conn.execute(f"PRAGMA table_info({table})").fetchall()
66
+ }
67
+
68
+
69
+ def apply(conn: sqlite3.Connection) -> None:
70
+ """Create every index supported by the installed optional schema."""
71
+ for _, table, required_columns, sql in _INDEXES:
72
+ if set(required_columns) <= _columns(conn, table):
73
+ conn.execute(sql)
74
+
75
+
76
+ def verify(conn: sqlite3.Connection) -> bool:
77
+ """Require indexes only when their complete backing schema exists."""
78
+ names = {
79
+ str(row[0])
80
+ for row in conn.execute(
81
+ "SELECT name FROM sqlite_master WHERE type='index'"
82
+ ).fetchall()
83
+ }
84
+ return all(
85
+ name in names
86
+ for name, table, required_columns, _ in _INDEXES
87
+ if set(required_columns) <= _columns(conn, table)
88
+ )
89
+
90
+
91
+ def repair(conn: sqlite3.Connection) -> None:
92
+ """Safely restore a dropped index without replaying data migrations."""
93
+ apply(conn)
@@ -26,6 +26,8 @@ from . import (
26
26
  M015_add_pinned_column,
27
27
  M019_derivation_lineage,
28
28
  M020_model_state_integrity,
29
+ M029_behavioral_history_indexes,
30
+ M030_entity_explorer_indexes,
29
31
  )
30
32
 
31
33
  # ---------------------------------------------------------------------------
@@ -77,6 +79,8 @@ __all__ = (
77
79
  "M015_add_pinned_column",
78
80
  "M019_derivation_lineage",
79
81
  "M020_model_state_integrity",
82
+ "M029_behavioral_history_indexes",
83
+ "M030_entity_explorer_indexes",
80
84
  # Legacy re-exports (backward compat):
81
85
  "CURRENT_SCHEMA_VERSION",
82
86
  "get_schema_version",
@@ -38,6 +38,8 @@ _TABLES: Final[tuple[str, ...]] = (
38
38
  "memories",
39
39
  "atomic_facts",
40
40
  "canonical_entities",
41
+ "fact_entity_associations",
42
+ "fact_entity_association_repair_state",
41
43
  "entity_aliases",
42
44
  "entity_profiles",
43
45
  "memory_scenes",
@@ -328,7 +330,52 @@ CREATE INDEX IF NOT EXISTS idx_entities_profile
328
330
  CREATE INDEX IF NOT EXISTS idx_entities_name_lower
329
331
  ON canonical_entities (profile_id, canonical_name COLLATE NOCASE);
330
332
  CREATE INDEX IF NOT EXISTS idx_entities_type
331
- ON canonical_entities (profile_id, entity_type);"""
333
+ ON canonical_entities (profile_id, entity_type);
334
+ CREATE INDEX IF NOT EXISTS idx_entities_profile_fact_count
335
+ ON canonical_entities (profile_id, fact_count DESC);
336
+ CREATE INDEX IF NOT EXISTS idx_entities_profile_type_fact_count
337
+ ON canonical_entities (
338
+ profile_id, entity_type COLLATE NOCASE, fact_count DESC
339
+ );"""
340
+
341
+
342
+ # ---------------------------------------------------------------------------
343
+ # Normalized fact/entity associations (also the ingestion effect ledger)
344
+ # ---------------------------------------------------------------------------
345
+
346
+ _SQL_FACT_ENTITY_ASSOCIATIONS: Final[str] = """
347
+ CREATE TABLE IF NOT EXISTS fact_entity_associations (
348
+ profile_id TEXT NOT NULL,
349
+ fact_id TEXT NOT NULL,
350
+ entity_id TEXT NOT NULL,
351
+ first_operation_id TEXT NOT NULL DEFAULT '',
352
+ count_applied INTEGER NOT NULL DEFAULT 0
353
+ CHECK (count_applied IN (0, 1)),
354
+ created_at TEXT NOT NULL DEFAULT (
355
+ strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
356
+ ),
357
+ PRIMARY KEY (profile_id, fact_id, entity_id),
358
+ FOREIGN KEY (fact_id) REFERENCES atomic_facts(fact_id) ON DELETE CASCADE,
359
+ FOREIGN KEY (entity_id)
360
+ REFERENCES canonical_entities(entity_id) ON DELETE CASCADE
361
+ );
362
+ CREATE INDEX IF NOT EXISTS idx_fact_entity_associations_entity
363
+ ON fact_entity_associations(profile_id, entity_id, fact_id);
364
+ CREATE TABLE IF NOT EXISTS fact_entity_association_repair_state (
365
+ repair_key TEXT PRIMARY KEY,
366
+ state TEXT NOT NULL DEFAULT 'pending'
367
+ CHECK (state IN ('pending', 'running', 'retrying', 'complete')),
368
+ target_fact_rowid INTEGER NOT NULL DEFAULT -1,
369
+ last_fact_rowid INTEGER NOT NULL DEFAULT 0,
370
+ scanned INTEGER NOT NULL DEFAULT 0,
371
+ inserted INTEGER NOT NULL DEFAULT 0,
372
+ last_error TEXT NOT NULL DEFAULT '',
373
+ updated_at TEXT NOT NULL
374
+ );
375
+ INSERT OR IGNORE INTO fact_entity_association_repair_state
376
+ (repair_key, state, target_fact_rowid, updated_at)
377
+ VALUES ('historical-backfill', 'pending', -1, '');
378
+ """
332
379
 
333
380
 
334
381
  # ---------------------------------------------------------------------------
@@ -752,6 +799,7 @@ _DDL_ORDERED: Final[tuple[str, ...]] = (
752
799
  _SQL_MEMORIES,
753
800
  _SQL_ATOMIC_FACTS,
754
801
  _SQL_CANONICAL_ENTITIES,
802
+ _SQL_FACT_ENTITY_ASSOCIATIONS,
755
803
  _SQL_ENTITY_ALIASES,
756
804
  _SQL_ENTITY_PROFILES,
757
805
  _SQL_MEMORY_SCENES,
@@ -359,6 +359,8 @@ V32_DDL: list[str] = [
359
359
  ON soft_prompt_templates(profile_id, active);
360
360
  CREATE INDEX IF NOT EXISTS idx_soft_prompt_category
361
361
  ON soft_prompt_templates(profile_id, category);
362
+ CREATE INDEX IF NOT EXISTS idx_soft_prompts_profile_active_category
363
+ ON soft_prompt_templates(profile_id, active, category, prompt_id);
362
364
  CREATE UNIQUE INDEX IF NOT EXISTS idx_soft_prompt_unique_cat
363
365
  ON soft_prompt_templates(profile_id, category)
364
366
  WHERE active = 1;
@@ -48,6 +48,8 @@ CREATE INDEX IF NOT EXISTS idx_tool_events_session
48
48
  ON tool_events(session_id);
49
49
  CREATE INDEX IF NOT EXISTS idx_tool_events_project
50
50
  ON tool_events(project_path);
51
+ CREATE INDEX IF NOT EXISTS idx_tool_events_profile_created
52
+ ON tool_events(profile_id, created_at DESC);
51
53
  """
52
54
 
53
55
  # ---------------------------------------------------------------------------
@@ -80,6 +82,8 @@ CREATE INDEX IF NOT EXISTS idx_assertions_project
80
82
  ON behavioral_assertions(project_path, profile_id);
81
83
  CREATE INDEX IF NOT EXISTS idx_assertions_category
82
84
  ON behavioral_assertions(category);
85
+ CREATE INDEX IF NOT EXISTS idx_assertions_profile_confidence
86
+ ON behavioral_assertions(profile_id, confidence DESC);
83
87
  """
84
88
 
85
89
  # ---------------------------------------------------------------------------
@@ -182,7 +182,6 @@
182
182
  <div class="value num" id="k-clu">—</div>
183
183
  <div class="delta" id="k-clu-delta">&nbsp;</div>
184
184
  <div class="spark" id="sp-clu"></div>
185
- <!-- TODO: endpoint /api/v3/clusters/count does not exist yet -->
186
185
  </div>
187
186
  </section>
188
187
 
@@ -192,8 +191,8 @@
192
191
  <!-- memory growth + quick actions -->
193
192
  <div class="card">
194
193
  <div class="card-head">
195
- <h3>Memory growth</h3>
196
- <span class="sub">facts stored over time</span>
194
+ <h3>Memory activity</h3>
195
+ <span class="sub">facts added by day · past year</span>
197
196
  <div class="spacer"></div>
198
197
  </div>
199
198
  <div class="card-pad">
@@ -300,9 +299,7 @@
300
299
  <div class="card">
301
300
  <div class="card-head"><h3>Where memories come from</h3></div>
302
301
  <div class="card-pad">
303
- <!-- populated by loadDashboardSources() in dashboard.js -->
304
- <!-- TODO: endpoint /api/v3/ingestion/sources does not exist yet;
305
- showing static breakdown from /api/stats for now -->
302
+ <!-- populated by loadDashboardSources() from profile-scoped provenance -->
306
303
  <div id="src">
307
304
  <div class="muted" style="padding:16px;text-align:center;font-size:13px">Loading...</div>
308
305
  </div>
@@ -1569,7 +1566,8 @@
1569
1566
  <!-- OD screen modules (v3.7.9): approved-design panes wired to live data.
1570
1567
  Load LAST so their window.load<Screen> overrides win over the legacy loaders. -->
1571
1568
  <script src="static/js/od-brain.js?v=379"></script>
1572
- <script src="static/js/od-health.js?v=380"></script>
1569
+ <script src="static/js/od-components.js?v=382"></script>
1570
+ <script src="static/js/od-health.js?v=382"></script>
1573
1571
  <script src="static/js/od-operations.js?v=380"></script>
1574
1572
  <script src="static/js/od-team.js?v=379"></script>
1575
1573
  <script src="static/js/od-graph.js?v=379"></script>
@@ -1580,7 +1578,7 @@
1580
1578
  <script src="static/js/od-skills.js?v=379"></script>
1581
1579
  <script src="static/js/od-mesh.js?v=379"></script>
1582
1580
  <script src="static/js/od-optimize.js?v=379"></script>
1583
- <script src="static/js/od-settings.js?v=380"></script>
1581
+ <script src="static/js/od-settings.js?v=382"></script>
1584
1582
  <script src="static/js/od-backup.js?v=379"></script>
1585
1583
  <!-- MCP & Integrations pane (v3.8.0): shows exposed MCP tool profile + counts -->
1586
1584
  <script src="static/js/od-mcp.js?v=380"></script>
@@ -35,11 +35,11 @@ window.slmInstallToken = (function () {
35
35
  };
36
36
  })();
37
37
 
38
- // Global fetch patch: apply the abort timeout to every relative-URL request
38
+ // Global fetch patch: apply the abort timeout to every same-origin request
39
39
  // automatically. 17 UI modules call bare fetch() — patching here avoids
40
40
  // touching each one and guarantees no future callsite can regress to an
41
- // un-timed fetch that holds the spinner forever. Absolute URLs (external
42
- // resources) are passed through unchanged. Callers that already supply
41
+ // un-timed fetch that holds the spinner forever. External URLs are passed
42
+ // through unchanged. Callers that already supply
43
43
  // `signal` keep their own behavior. `init.timeoutMs` lets callers override
44
44
  // the default per-request.
45
45
  (function patchFetch() {
@@ -49,25 +49,68 @@ window.slmInstallToken = (function () {
49
49
  window.__slmOriginalFetch = _origFetch;
50
50
  window.fetch = function (input, init) {
51
51
  init = Object.assign({}, init || {});
52
- var urlStr = typeof input === 'string' ? input : (input && input.url) || '';
53
- var isRelative = !(/^https?:\/\//i.test(urlStr));
52
+ var urlStr = typeof input === 'string'
53
+ ? input
54
+ : (input && (input.url || input.href)) || '';
55
+ // Resolve every URL before deciding whether it is local. In particular,
56
+ // protocol-relative URLs such as //host/path inherit the current scheme
57
+ // but are not same-origin and must never receive the install token.
58
+ var isSameOrigin = false;
59
+ var requestUrl = null;
60
+ try {
61
+ requestUrl = new URL(urlStr, window.location.href);
62
+ isSameOrigin = requestUrl.origin === window.location.origin;
63
+ } catch (e) {
64
+ // A malformed URL is handled by native fetch. Treat it as external
65
+ // here so it cannot obtain local credentials while failing.
66
+ }
54
67
  var method = String(
55
68
  init.method || (input && input.method) || 'GET'
56
69
  ).toUpperCase();
57
70
  var mutating = ['POST', 'PUT', 'PATCH', 'DELETE'].indexOf(method) !== -1;
71
+ var invalidatesCache = init.slmInvalidatesCache !== false;
72
+ var requiresWriteAuth = init.slmRequiresWriteAuth !== false;
73
+ delete init.slmInvalidatesCache;
74
+ delete init.slmRequiresWriteAuth;
75
+
76
+ function invalidateAfterMutation(request) {
77
+ if (!isSameOrigin || !mutating || !invalidatesCache) return request;
78
+ return request.then(function (response) {
79
+ if (
80
+ response && response.ok &&
81
+ typeof window.slmInvalidatePanes === 'function'
82
+ ) {
83
+ window.slmInvalidatePanes();
84
+ }
85
+ if (
86
+ response && response.ok &&
87
+ typeof window.slmInvalidateDashboardCache === 'function'
88
+ ) {
89
+ window.slmInvalidateDashboardCache();
90
+ }
91
+ return response;
92
+ });
93
+ }
58
94
 
59
95
  function send() {
60
- if (!isRelative || init.signal) {
61
- return _origFetch(input, init);
96
+ if (!isSameOrigin || init.signal) {
97
+ return invalidateAfterMutation(_origFetch(input, init));
62
98
  }
63
99
  var controller = new AbortController();
64
100
  var timeoutMs = init.timeoutMs || window.SLM_FETCH_TIMEOUT_MS;
65
101
  var timer = setTimeout(function () { controller.abort(); }, timeoutMs);
66
102
  init.signal = controller.signal;
67
- return _origFetch(input, init).finally(function () { clearTimeout(timer); });
103
+ return invalidateAfterMutation(
104
+ _origFetch(input, init).finally(function () { clearTimeout(timer); })
105
+ );
68
106
  }
69
107
 
70
- if (isRelative && mutating && urlStr !== '/internal/token') {
108
+ if (
109
+ isSameOrigin &&
110
+ mutating &&
111
+ requiresWriteAuth &&
112
+ (!requestUrl || requestUrl.pathname !== '/internal/token')
113
+ ) {
71
114
  return window.slmInstallToken(false).then(function (token) {
72
115
  if (!token) throw new Error('local write credential unavailable');
73
116
  var headers = new Headers(