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.
- package/CHANGELOG.md +112 -0
- package/README.md +32 -120
- package/package.json +9 -2
- package/plugin/.claude-plugin/plugin.json +1 -2
- 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 +2 -2
- 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 +3 -5
- 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 +2 -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-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-recall/SKILL.md +3 -5
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +2 -1
- package/scripts/postinstall.js +7 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +494 -9
- package/src/superlocalmemory/cli/daemon.py +7 -0
- package/src/superlocalmemory/cli/loop_cmd.py +2 -7
- package/src/superlocalmemory/cli/main.py +72 -7
- package/src/superlocalmemory/cli/setup_wizard.py +142 -16
- package/src/superlocalmemory/cli/version_banner.py +17 -3
- package/src/superlocalmemory/core/backend_orchestrator.py +18 -16
- package/src/superlocalmemory/core/component_healer.py +144 -0
- package/src/superlocalmemory/core/component_registry.py +487 -0
- package/src/superlocalmemory/core/config.py +21 -0
- package/src/superlocalmemory/core/embedding_worker.py +4 -5
- package/src/superlocalmemory/core/embeddings.py +132 -45
- package/src/superlocalmemory/core/engine.py +29 -22
- package/src/superlocalmemory/core/engine_ingestion.py +332 -45
- package/src/superlocalmemory/core/ingestion_command.py +154 -25
- package/src/superlocalmemory/core/injection.py +12 -7
- package/src/superlocalmemory/core/maintenance.py +43 -0
- package/src/superlocalmemory/core/maintenance_scheduler.py +44 -6
- package/src/superlocalmemory/core/recall_pipeline.py +42 -4
- package/src/superlocalmemory/core/store_pipeline.py +195 -20
- package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
- package/src/superlocalmemory/hooks/portable_kit.py +34 -2
- package/src/superlocalmemory/learning/model_rollback.py +3 -0
- package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
- package/src/superlocalmemory/learning/reward.py +50 -0
- package/src/superlocalmemory/learning/source_quality.py +523 -1
- package/src/superlocalmemory/loops/ledger.py +25 -5
- package/src/superlocalmemory/mcp/_daemon_proxy.py +6 -2
- package/src/superlocalmemory/mcp/_pool_adapter.py +4 -1
- package/src/superlocalmemory/mcp/server.py +11 -30
- package/src/superlocalmemory/mcp/tools_active.py +1 -1
- package/src/superlocalmemory/mcp/tools_core.py +21 -5
- package/src/superlocalmemory/mcp/tools_learning.py +2 -2
- package/src/superlocalmemory/retrieval/bridge_discovery.py +14 -0
- package/src/superlocalmemory/retrieval/engine.py +53 -21
- package/src/superlocalmemory/retrieval/reranker.py +3 -4
- package/src/superlocalmemory/retrieval/spreading_activation.py +68 -38
- package/src/superlocalmemory/server/config_file.py +90 -0
- package/src/superlocalmemory/server/origin.py +50 -0
- package/src/superlocalmemory/server/routes/backup.py +293 -70
- package/src/superlocalmemory/server/routes/behavioral.py +342 -61
- package/src/superlocalmemory/server/routes/brain.py +57 -16
- package/src/superlocalmemory/server/routes/config_api.py +84 -82
- package/src/superlocalmemory/server/routes/entity.py +100 -23
- package/src/superlocalmemory/server/routes/evolution.py +103 -100
- package/src/superlocalmemory/server/routes/learning.py +286 -105
- package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
- package/src/superlocalmemory/server/routes/memories.py +8 -3
- package/src/superlocalmemory/server/routes/mesh.py +121 -32
- package/src/superlocalmemory/server/routes/ratelimit.py +33 -25
- package/src/superlocalmemory/server/routes/stats.py +93 -155
- package/src/superlocalmemory/server/routes/token.py +3 -13
- package/src/superlocalmemory/server/routes/v3_api.py +184 -20
- package/src/superlocalmemory/server/unified_daemon.py +732 -41
- package/src/superlocalmemory/storage/embedding_migrator.py +235 -0
- package/src/superlocalmemory/storage/migration_runner.py +79 -1
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
- package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
- package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
- package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
- package/src/superlocalmemory/storage/schema.py +49 -1
- package/src/superlocalmemory/storage/schema_v32.py +2 -0
- package/src/superlocalmemory/storage/schema_v347.py +4 -0
- package/src/superlocalmemory/ui/index.html +6 -8
- package/src/superlocalmemory/ui/js/core.js +52 -9
- package/src/superlocalmemory/ui/js/dashboard.js +169 -82
- package/src/superlocalmemory/ui/js/od-backup.js +156 -65
- package/src/superlocalmemory/ui/js/od-brain.js +88 -51
- package/src/superlocalmemory/ui/js/od-components.js +147 -0
- package/src/superlocalmemory/ui/js/od-entities.js +65 -22
- package/src/superlocalmemory/ui/js/od-graph.js +46 -4
- package/src/superlocalmemory/ui/js/od-health.js +18 -0
- package/src/superlocalmemory/ui/js/od-memories.js +84 -5
- package/src/superlocalmemory/ui/js/od-mesh.js +23 -9
- package/src/superlocalmemory/ui/js/od-operations.js +36 -0
- package/src/superlocalmemory/ui/js/od-settings.js +186 -63
- package/src/superlocalmemory/ui/js/od-shell.js +249 -33
- package/src/superlocalmemory/ui/js/od-skills.js +44 -17
- package/src/superlocalmemory/ui/js/settings.js +15 -1
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-governance-advisor.md +0 -80
- package/plugin-src/agents/slm-loop-runner.md +0 -71
- package/plugin-src/agents/slm-memory-advisor.md +0 -49
- package/plugin-src/agents/slm-optimize-advisor.md +0 -44
- package/plugin-src/commands/slm-loop.md +0 -31
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -102
- package/plugin-src/manifest.json +0 -30
- package/plugin-src/requirements.txt +0 -1
- package/plugin-src/rules/CLAUDE.md.fragment +0 -44
- package/plugin-src/scripts/ensure-venv.bat +0 -122
- package/plugin-src/scripts/ensure-venv.sh +0 -105
- package/plugin-src/scripts/slm-launch +0 -62
- package/plugin-src/scripts/slm-launch.bat +0 -23
- package/plugin-src/settings.json +0 -25
- package/plugin-src/skills/slm-governance/SKILL.md +0 -248
- package/plugin-src/skills/slm-loop/SKILL.md +0 -99
- package/plugin-src/skills/slm-mesh/SKILL.md +0 -282
- package/plugin-src/skills/slm-profile/SKILL.md +0 -148
- 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"> </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
|
|
196
|
-
<span class="sub">facts
|
|
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()
|
|
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-
|
|
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=
|
|
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
|
|
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.
|
|
42
|
-
//
|
|
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'
|
|
53
|
-
|
|
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 (!
|
|
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
|
|
103
|
+
return invalidateAfterMutation(
|
|
104
|
+
_origFetch(input, init).finally(function () { clearTimeout(timer); })
|
|
105
|
+
);
|
|
68
106
|
}
|
|
69
107
|
|
|
70
|
-
if (
|
|
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(
|