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
package/pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "superlocalmemory"
|
|
3
|
-
version = "3.8.
|
|
3
|
+
version = "3.8.2"
|
|
4
4
|
description = "Local-first agent memory with auditable hybrid retrieval"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "AGPL-3.0-or-later"
|
|
@@ -171,6 +171,7 @@ superlocalmemory = ["ui/**/*", "optimize/NOTICE"]
|
|
|
171
171
|
"share/superlocalmemory/codex/skills/slm-remember" = ["plugin-src/skills/slm-remember/SKILL.md"]
|
|
172
172
|
"share/superlocalmemory/codex/skills/slm-session" = ["plugin-src/skills/slm-session/SKILL.md"]
|
|
173
173
|
"share/superlocalmemory/codex/skills/slm-status" = ["plugin-src/skills/slm-status/SKILL.md"]
|
|
174
|
+
"share/superlocalmemory/portable-kit/rules" = ["plugin-src/rules/AGENTS.md"]
|
|
174
175
|
|
|
175
176
|
[tool.pytest.ini_options]
|
|
176
177
|
testpaths = ["tests"]
|
package/scripts/postinstall.js
CHANGED
|
@@ -200,7 +200,13 @@ function main(argv = process.argv.slice(2)) {
|
|
|
200
200
|
|
|
201
201
|
console.log(`SuperLocalMemory ${packageVersion}: isolated runtime verified.`);
|
|
202
202
|
console.log('No memory database, IDE hooks, daemon, configuration, or models were changed.');
|
|
203
|
-
console.log('
|
|
203
|
+
console.log('');
|
|
204
|
+
console.log(' Next step — run the guided setup (picks your mode, downloads');
|
|
205
|
+
console.log(' models, connects your IDEs). Takes about a minute:');
|
|
206
|
+
console.log('');
|
|
207
|
+
console.log(' slm setup');
|
|
208
|
+
console.log('');
|
|
209
|
+
console.log(' Prefer to tune performance profiles? Use: slm reconfigure');
|
|
204
210
|
console.log('');
|
|
205
211
|
return 0;
|
|
206
212
|
}
|
|
@@ -32,7 +32,7 @@ if "OMP_NUM_THREADS" not in os.environ:
|
|
|
32
32
|
os.environ["OMP_NUM_THREADS"] = "2"
|
|
33
33
|
# ---------------------------------------------------------------------------
|
|
34
34
|
|
|
35
|
-
__version__ = "3.8.
|
|
35
|
+
__version__ = "3.8.2"
|
|
36
36
|
|
|
37
37
|
_REQUIRED_VERSIONS = {
|
|
38
38
|
"sentence_transformers": "5.3.0",
|
|
@@ -36,10 +36,138 @@ def _cmd_db_dispatch(args: Namespace) -> None:
|
|
|
36
36
|
if rc:
|
|
37
37
|
sys.exit(rc)
|
|
38
38
|
return
|
|
39
|
-
|
|
39
|
+
if sub == "reembed":
|
|
40
|
+
_cmd_db_reembed(args)
|
|
41
|
+
return
|
|
42
|
+
print(
|
|
43
|
+
"Usage: slm db migrate [--status] [--dry-run] "
|
|
44
|
+
"| slm db scale <action> "
|
|
45
|
+
"| slm db reembed [--missing-only] [--all-profiles] [--limit N]"
|
|
46
|
+
)
|
|
40
47
|
sys.exit(2)
|
|
41
48
|
|
|
42
49
|
|
|
50
|
+
def _cmd_db_reembed(args: Namespace) -> None:
|
|
51
|
+
"""Backfill NULL embeddings on atomic_facts.
|
|
52
|
+
|
|
53
|
+
Scans for atomic_facts rows with no embedding (facts stored while the
|
|
54
|
+
embedder was unavailable) and embeds them now. Safe to re-run — only
|
|
55
|
+
NULL rows are processed.
|
|
56
|
+
|
|
57
|
+
Usage:
|
|
58
|
+
slm db reembed --missing-only # backfill NULLs (default)
|
|
59
|
+
slm db reembed --missing-only --limit 500 # cap per run
|
|
60
|
+
slm db reembed --missing-only --all-profiles # cover every profile
|
|
61
|
+
"""
|
|
62
|
+
import time
|
|
63
|
+
|
|
64
|
+
from superlocalmemory.core.config import SLMConfig
|
|
65
|
+
from superlocalmemory.core.engine import MemoryEngine
|
|
66
|
+
from superlocalmemory.storage.embedding_migrator import backfill_missing_embeddings
|
|
67
|
+
|
|
68
|
+
use_json = getattr(args, "json", False)
|
|
69
|
+
missing_only = getattr(args, "missing_only", True)
|
|
70
|
+
all_profiles = getattr(args, "all_profiles", False)
|
|
71
|
+
limit = getattr(args, "limit", None)
|
|
72
|
+
|
|
73
|
+
if not missing_only:
|
|
74
|
+
if use_json:
|
|
75
|
+
from superlocalmemory.cli.json_output import json_print
|
|
76
|
+
json_print(
|
|
77
|
+
"db-reembed",
|
|
78
|
+
error={
|
|
79
|
+
"code": "NOT_IMPLEMENTED",
|
|
80
|
+
"message": "Only --missing-only is supported. "
|
|
81
|
+
"For full re-embed use 'slm db migrate'.",
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
print("Only --missing-only mode is supported. "
|
|
86
|
+
"For full re-embed use 'slm db migrate'.")
|
|
87
|
+
sys.exit(2)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
config = SLMConfig.load()
|
|
91
|
+
engine = MemoryEngine(config)
|
|
92
|
+
engine.initialize()
|
|
93
|
+
except Exception as exc:
|
|
94
|
+
if use_json:
|
|
95
|
+
from superlocalmemory.cli.json_output import json_print
|
|
96
|
+
json_print(
|
|
97
|
+
"db-reembed",
|
|
98
|
+
error={"code": "ENGINE_INIT_ERROR", "message": str(exc)},
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
print(f"Error initializing engine: {exc}", file=sys.stderr)
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
|
|
104
|
+
embedder = getattr(engine, "_embedder", None)
|
|
105
|
+
if embedder is None:
|
|
106
|
+
if use_json:
|
|
107
|
+
from superlocalmemory.cli.json_output import json_print
|
|
108
|
+
json_print(
|
|
109
|
+
"db-reembed",
|
|
110
|
+
error={
|
|
111
|
+
"code": "NO_EMBEDDER",
|
|
112
|
+
"message": "Embedder is not available. "
|
|
113
|
+
"Start the daemon first or check embedding configuration.",
|
|
114
|
+
},
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
print(
|
|
118
|
+
"Embedder is not available. "
|
|
119
|
+
"Start the daemon ('slm serve') or check embedding config.",
|
|
120
|
+
file=sys.stderr,
|
|
121
|
+
)
|
|
122
|
+
engine.close()
|
|
123
|
+
sys.exit(1)
|
|
124
|
+
|
|
125
|
+
t0 = time.monotonic()
|
|
126
|
+
try:
|
|
127
|
+
result = backfill_missing_embeddings(
|
|
128
|
+
config,
|
|
129
|
+
engine._db,
|
|
130
|
+
embedder,
|
|
131
|
+
batch_size=50,
|
|
132
|
+
limit=limit,
|
|
133
|
+
all_profiles=all_profiles,
|
|
134
|
+
)
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
if use_json:
|
|
137
|
+
from superlocalmemory.cli.json_output import json_print
|
|
138
|
+
json_print(
|
|
139
|
+
"db-reembed",
|
|
140
|
+
error={"code": "BACKFILL_ERROR", "message": str(exc)},
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
print(f"Backfill error: {exc}", file=sys.stderr)
|
|
144
|
+
engine.close()
|
|
145
|
+
sys.exit(1)
|
|
146
|
+
|
|
147
|
+
elapsed = time.monotonic() - t0
|
|
148
|
+
engine.close()
|
|
149
|
+
|
|
150
|
+
if use_json:
|
|
151
|
+
from superlocalmemory.cli.json_output import json_print
|
|
152
|
+
json_print(
|
|
153
|
+
"db-reembed",
|
|
154
|
+
data={
|
|
155
|
+
"scanned": result["scanned"],
|
|
156
|
+
"embedded": result["embedded"],
|
|
157
|
+
"remaining_null": result["remaining_null"],
|
|
158
|
+
"elapsed_s": round(elapsed, 2),
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
print(
|
|
163
|
+
f"Backfill complete — "
|
|
164
|
+
f"scanned={result['scanned']} "
|
|
165
|
+
f"embedded={result['embedded']} "
|
|
166
|
+
f"remaining_null={result['remaining_null']} "
|
|
167
|
+
f"elapsed={elapsed:.1f}s"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
43
171
|
def _cmd_mesh_dispatch(args: Namespace) -> None:
|
|
44
172
|
"""Route ``slm mesh ...`` inspection subcommands (M-03)."""
|
|
45
173
|
from superlocalmemory.cli.mesh_cmd import cmd_mesh
|
|
@@ -159,7 +287,10 @@ def cmd_session(args: Namespace) -> None:
|
|
|
159
287
|
def dispatch(args: Namespace) -> None:
|
|
160
288
|
"""Route CLI command to the appropriate handler."""
|
|
161
289
|
# Auto-install/upgrade hooks on version change (single file read, ~0.1ms)
|
|
162
|
-
if
|
|
290
|
+
if (
|
|
291
|
+
args.command not in ("hooks", "codex", "init", "mcp")
|
|
292
|
+
and not getattr(args, "dry_run", False)
|
|
293
|
+
):
|
|
163
294
|
try:
|
|
164
295
|
from superlocalmemory.hooks.claude_code_hooks import auto_install_if_needed
|
|
165
296
|
auto_install_if_needed()
|
|
@@ -235,6 +366,8 @@ def dispatch(args: Namespace) -> None:
|
|
|
235
366
|
"help-optimize": _cmd_help_optimize,
|
|
236
367
|
# V3.8.0 bounded loops (gate-verified agent loops, SLM-backed ledger)
|
|
237
368
|
"loop": _cmd_loop,
|
|
369
|
+
# V3.8.2 super-help — grouped overview of every command + topics
|
|
370
|
+
"help": cmd_help,
|
|
238
371
|
}
|
|
239
372
|
handler = handlers.get(args.command)
|
|
240
373
|
if handler:
|
|
@@ -958,16 +1091,22 @@ def _agents_md_source_factory():
|
|
|
958
1091
|
Gracefully skips if absent — never fails the MCP write.
|
|
959
1092
|
"""
|
|
960
1093
|
from pathlib import Path
|
|
1094
|
+
import sysconfig
|
|
961
1095
|
|
|
962
1096
|
# Resolve relative to the package root (src/superlocalmemory/../../)
|
|
963
1097
|
_pkg_root = Path(__file__).resolve().parents[3]
|
|
964
|
-
|
|
1098
|
+
_candidates = (
|
|
1099
|
+
_pkg_root / "plugin-src" / "rules" / "AGENTS.md",
|
|
1100
|
+
Path(sysconfig.get_path("data")) / "share" / "superlocalmemory"
|
|
1101
|
+
/ "portable-kit" / "rules" / "AGENTS.md",
|
|
1102
|
+
)
|
|
965
1103
|
|
|
966
1104
|
def _read() -> str | None:
|
|
967
|
-
|
|
968
|
-
|
|
1105
|
+
for candidate in _candidates:
|
|
1106
|
+
if candidate.exists():
|
|
1107
|
+
return candidate.read_text(encoding="utf-8")
|
|
969
1108
|
logger.warning(
|
|
970
|
-
"WP-05 AGENTS.md not
|
|
1109
|
+
"WP-05 AGENTS.md is not bundled — skipping AGENTS.md write"
|
|
971
1110
|
)
|
|
972
1111
|
return None
|
|
973
1112
|
|
|
@@ -1008,9 +1147,10 @@ def cmd_connect(args: Namespace) -> None:
|
|
|
1008
1147
|
here=here,
|
|
1009
1148
|
profile=profile,
|
|
1010
1149
|
agents_md_source=_agents_md_source_factory(),
|
|
1150
|
+
dry_run=getattr(args, "dry_run", False),
|
|
1011
1151
|
)
|
|
1012
1152
|
|
|
1013
|
-
if not result.get("error"):
|
|
1153
|
+
if not result.get("error") and not getattr(args, "dry_run", False):
|
|
1014
1154
|
from superlocalmemory.infra.local_diagnostics import record_operation
|
|
1015
1155
|
|
|
1016
1156
|
record_operation("activation", client=ide_arg)
|
|
@@ -1029,6 +1169,7 @@ def cmd_connect(args: Namespace) -> None:
|
|
|
1029
1169
|
sys.exit(1)
|
|
1030
1170
|
|
|
1031
1171
|
status_sym = {"wrote": "[+]", "merged": "[~]", "unchanged": "[=]",
|
|
1172
|
+
"would_write": "[~]",
|
|
1032
1173
|
"skipped": "[s]", "error": "[!]"}.get(
|
|
1033
1174
|
result["mcp_config"], "[?]"
|
|
1034
1175
|
)
|
|
@@ -1057,6 +1198,7 @@ def cmd_connect(args: Namespace) -> None:
|
|
|
1057
1198
|
from superlocalmemory.hooks.ide_connector import IDEConnector
|
|
1058
1199
|
|
|
1059
1200
|
connector = IDEConnector()
|
|
1201
|
+
dry_run = bool(getattr(args, "dry_run", False))
|
|
1060
1202
|
|
|
1061
1203
|
if getattr(args, 'json', False):
|
|
1062
1204
|
from superlocalmemory.cli.json_output import json_print
|
|
@@ -1068,6 +1210,14 @@ def cmd_connect(args: Namespace) -> None:
|
|
|
1068
1210
|
elif getattr(args, "ide", None):
|
|
1069
1211
|
success = connector.connect(args.ide)
|
|
1070
1212
|
json_print("connect", data={"ide": args.ide, "connected": success})
|
|
1213
|
+
elif dry_run:
|
|
1214
|
+
json_print(
|
|
1215
|
+
"connect",
|
|
1216
|
+
data={
|
|
1217
|
+
"dry_run": True,
|
|
1218
|
+
"would_configure": connector.get_status(),
|
|
1219
|
+
},
|
|
1220
|
+
)
|
|
1071
1221
|
else:
|
|
1072
1222
|
json_print("connect", data={"results": connector.connect_all()},
|
|
1073
1223
|
next_actions=[
|
|
@@ -1081,6 +1231,12 @@ def cmd_connect(args: Namespace) -> None:
|
|
|
1081
1231
|
mark = "[+]" if s["installed"] else "[-]"
|
|
1082
1232
|
print(f" {mark} {s['name']:20s} {s['config_path']}")
|
|
1083
1233
|
return
|
|
1234
|
+
if dry_run:
|
|
1235
|
+
status = connector.get_status()
|
|
1236
|
+
print("Dry run — no IDE, hook, config, or SLM data files were written.")
|
|
1237
|
+
for item in status:
|
|
1238
|
+
print(f" would inspect/configure: {item['name']} ({item['config_path']})")
|
|
1239
|
+
return
|
|
1084
1240
|
if getattr(args, "ide", None):
|
|
1085
1241
|
success = connector.connect(args.ide)
|
|
1086
1242
|
print(f"{'Connected' if success else 'Failed'}: {args.ide}")
|
|
@@ -1372,7 +1528,10 @@ def cmd_recall(args: Namespace) -> None:
|
|
|
1372
1528
|
|
|
1373
1529
|
response = engine.recall(
|
|
1374
1530
|
args.query, limit=args.limit,
|
|
1375
|
-
|
|
1531
|
+
# v3.8.2: --fast → True; unset → None so engine resolves the
|
|
1532
|
+
# client-driven-agentic default (parity with the daemon path, which
|
|
1533
|
+
# omits the fast query param when --fast is absent).
|
|
1534
|
+
fast=(True if getattr(args, "fast", False) else None),
|
|
1376
1535
|
include_global=include_global,
|
|
1377
1536
|
include_shared=include_shared,
|
|
1378
1537
|
window=getattr(args, "window", "") or None,
|
|
@@ -1563,16 +1722,86 @@ def cmd_forget(args: Namespace) -> None:
|
|
|
1563
1722
|
|
|
1564
1723
|
def cmd_delete(args: Namespace) -> None:
|
|
1565
1724
|
"""Delete a specific memory by exact fact ID."""
|
|
1725
|
+
import urllib.parse
|
|
1726
|
+
|
|
1727
|
+
from superlocalmemory.cli.daemon import daemon_request, is_daemon_running
|
|
1566
1728
|
from superlocalmemory.core.config import SLMConfig
|
|
1567
1729
|
from superlocalmemory.core.engine import MemoryEngine
|
|
1568
1730
|
|
|
1569
1731
|
use_json = getattr(args, 'json', False)
|
|
1732
|
+
fact_id = args.fact_id.strip()
|
|
1733
|
+
if is_daemon_running():
|
|
1734
|
+
path = "/api/memories/" + urllib.parse.quote(fact_id, safe="")
|
|
1735
|
+
confirmed = getattr(args, "yes", False)
|
|
1736
|
+
content = ""
|
|
1737
|
+
if not confirmed:
|
|
1738
|
+
detail = daemon_request(
|
|
1739
|
+
"GET",
|
|
1740
|
+
"/api/facts/" + urllib.parse.quote(fact_id, safe=""),
|
|
1741
|
+
)
|
|
1742
|
+
if not isinstance(detail, dict):
|
|
1743
|
+
if use_json:
|
|
1744
|
+
from superlocalmemory.cli.json_output import json_print
|
|
1745
|
+
json_print("delete", error={
|
|
1746
|
+
"code": "DAEMON_MUTATION_FAILED",
|
|
1747
|
+
"message": "Resident daemon could not resolve the memory.",
|
|
1748
|
+
})
|
|
1749
|
+
sys.exit(1)
|
|
1750
|
+
raise RuntimeError("Resident daemon could not resolve the memory.")
|
|
1751
|
+
content = str(detail.get("content") or "")
|
|
1752
|
+
if use_json:
|
|
1753
|
+
from superlocalmemory.cli.json_output import json_print
|
|
1754
|
+
json_print("delete", data={
|
|
1755
|
+
"fact_id": fact_id,
|
|
1756
|
+
"content": content[:120],
|
|
1757
|
+
"deleted": False,
|
|
1758
|
+
"hint": "Add --yes to confirm deletion",
|
|
1759
|
+
}, next_actions=[
|
|
1760
|
+
{
|
|
1761
|
+
"command": f"slm delete {fact_id} --json --yes",
|
|
1762
|
+
"description": "Confirm deletion",
|
|
1763
|
+
},
|
|
1764
|
+
])
|
|
1765
|
+
return
|
|
1766
|
+
print(f"Memory: {content[:120]}")
|
|
1767
|
+
confirmed = input("Delete this memory? [y/N] ").strip().lower() in (
|
|
1768
|
+
"y", "yes",
|
|
1769
|
+
)
|
|
1770
|
+
if not confirmed:
|
|
1771
|
+
print("Cancelled.")
|
|
1772
|
+
return
|
|
1773
|
+
|
|
1774
|
+
result = daemon_request("DELETE", path)
|
|
1775
|
+
if not isinstance(result, dict) or not result.get("success"):
|
|
1776
|
+
if use_json:
|
|
1777
|
+
from superlocalmemory.cli.json_output import json_print
|
|
1778
|
+
json_print("delete", error={
|
|
1779
|
+
"code": "DAEMON_MUTATION_FAILED",
|
|
1780
|
+
"message": "Resident daemon rejected the delete operation.",
|
|
1781
|
+
})
|
|
1782
|
+
sys.exit(1)
|
|
1783
|
+
raise RuntimeError("Resident daemon rejected the delete operation.")
|
|
1784
|
+
if use_json:
|
|
1785
|
+
from superlocalmemory.cli.json_output import json_print
|
|
1786
|
+
json_print(
|
|
1787
|
+
"delete",
|
|
1788
|
+
data={"deleted": fact_id, "content": content[:120]},
|
|
1789
|
+
next_actions=[
|
|
1790
|
+
{
|
|
1791
|
+
"command": "slm list --json",
|
|
1792
|
+
"description": "Verify remaining memories",
|
|
1793
|
+
},
|
|
1794
|
+
],
|
|
1795
|
+
)
|
|
1796
|
+
else:
|
|
1797
|
+
print(f"Deleted: {fact_id}")
|
|
1798
|
+
return
|
|
1799
|
+
|
|
1570
1800
|
try:
|
|
1571
1801
|
config = SLMConfig.load()
|
|
1572
1802
|
engine = MemoryEngine(config)
|
|
1573
1803
|
engine.initialize()
|
|
1574
1804
|
|
|
1575
|
-
fact_id = args.fact_id.strip()
|
|
1576
1805
|
rows = engine._db.execute(
|
|
1577
1806
|
"SELECT content FROM atomic_facts WHERE fact_id = ? AND profile_id = ?",
|
|
1578
1807
|
(fact_id, engine.profile_id),
|
|
@@ -1642,6 +1871,9 @@ def cmd_delete(args: Namespace) -> None:
|
|
|
1642
1871
|
|
|
1643
1872
|
def cmd_update(args: Namespace) -> None:
|
|
1644
1873
|
"""Update the content of a specific memory by exact fact ID."""
|
|
1874
|
+
import urllib.parse
|
|
1875
|
+
|
|
1876
|
+
from superlocalmemory.cli.daemon import daemon_request, is_daemon_running
|
|
1645
1877
|
from superlocalmemory.core.config import SLMConfig
|
|
1646
1878
|
from superlocalmemory.core.engine import MemoryEngine
|
|
1647
1879
|
|
|
@@ -1657,6 +1889,34 @@ def cmd_update(args: Namespace) -> None:
|
|
|
1657
1889
|
print("Error: content cannot be empty")
|
|
1658
1890
|
return
|
|
1659
1891
|
|
|
1892
|
+
if is_daemon_running():
|
|
1893
|
+
path = "/api/memories/" + urllib.parse.quote(fact_id, safe="")
|
|
1894
|
+
result = daemon_request("PATCH", path, {"content": new_content})
|
|
1895
|
+
if not isinstance(result, dict) or not result.get("success"):
|
|
1896
|
+
if use_json:
|
|
1897
|
+
from superlocalmemory.cli.json_output import json_print
|
|
1898
|
+
json_print("update", error={
|
|
1899
|
+
"code": "DAEMON_MUTATION_FAILED",
|
|
1900
|
+
"message": "Resident daemon rejected the update operation.",
|
|
1901
|
+
})
|
|
1902
|
+
sys.exit(1)
|
|
1903
|
+
raise RuntimeError("Resident daemon rejected the update operation.")
|
|
1904
|
+
if use_json:
|
|
1905
|
+
from superlocalmemory.cli.json_output import json_print
|
|
1906
|
+
json_print("update", data={
|
|
1907
|
+
"fact_id": fact_id,
|
|
1908
|
+
"new_content": new_content[:120],
|
|
1909
|
+
}, next_actions=[
|
|
1910
|
+
{
|
|
1911
|
+
"command": "slm list --json",
|
|
1912
|
+
"description": "List recent memories",
|
|
1913
|
+
},
|
|
1914
|
+
])
|
|
1915
|
+
else:
|
|
1916
|
+
print(f"New: {new_content[:100]}")
|
|
1917
|
+
print(f"Updated: {fact_id}")
|
|
1918
|
+
return
|
|
1919
|
+
|
|
1660
1920
|
try:
|
|
1661
1921
|
config = SLMConfig.load()
|
|
1662
1922
|
engine = MemoryEngine(config)
|
|
@@ -2013,6 +2273,175 @@ def _readline_with_timeout(
|
|
|
2013
2273
|
)
|
|
2014
2274
|
|
|
2015
2275
|
|
|
2276
|
+
# ---------------------------------------------------------------------------
|
|
2277
|
+
# Super-help (v3.8.2) — one place a non-technical user sees EVERYTHING.
|
|
2278
|
+
# Grouped so `slm help` is scannable; a drift test asserts every top-level
|
|
2279
|
+
# command in the parser appears here (see test_super_help).
|
|
2280
|
+
# ---------------------------------------------------------------------------
|
|
2281
|
+
|
|
2282
|
+
_COMMAND_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
|
|
2283
|
+
("Getting started", [
|
|
2284
|
+
("setup", "Guided first-time setup (models, mode, IDEs) — start here"),
|
|
2285
|
+
("init", "Set up + wire Claude Code hooks + connect IDEs"),
|
|
2286
|
+
("reconfigure", "Re-run setup / pick a performance profile"),
|
|
2287
|
+
("mode", "Switch memory mode: a (local) / b (Ollama) / c (cloud)"),
|
|
2288
|
+
("provider", "Configure the cloud LLM provider + API key (Mode C)"),
|
|
2289
|
+
("connect", "Auto-configure detected IDEs (Cursor, VS Code, …)"),
|
|
2290
|
+
("hooks", "Install/inspect Claude Code hooks"),
|
|
2291
|
+
("codex", "Configure the Codex / OpenAI integration"),
|
|
2292
|
+
]),
|
|
2293
|
+
("Store & recall memories", [
|
|
2294
|
+
("remember", "Store a memory (--tags to label)"),
|
|
2295
|
+
("recall", "Semantic + keyword search across memories"),
|
|
2296
|
+
("search", "Exact keyword / full-text search"),
|
|
2297
|
+
("list", "Show recent memories (-n N)"),
|
|
2298
|
+
("update", "Correct an existing memory by id"),
|
|
2299
|
+
("delete", "Delete a memory by id"),
|
|
2300
|
+
("forget", "Run the decay cycle (preview first)"),
|
|
2301
|
+
("trace", "Recall with a per-channel score breakdown"),
|
|
2302
|
+
("ingest", "Ingest external observations / documents"),
|
|
2303
|
+
]),
|
|
2304
|
+
("Run SLM (daemon & dashboard)", [
|
|
2305
|
+
("serve", "Start/stop the background daemon"),
|
|
2306
|
+
("restart", "Restart the daemon (applies restart-only settings)"),
|
|
2307
|
+
("dashboard", "Open the web dashboard (localhost:8765)"),
|
|
2308
|
+
("mcp", "Start the MCP server (used by IDEs)"),
|
|
2309
|
+
("warmup", "Pre-load models so the first recall is fast"),
|
|
2310
|
+
("status", "Show daemon status + memory counts"),
|
|
2311
|
+
]),
|
|
2312
|
+
("Health & self-healing", [
|
|
2313
|
+
("doctor", "Full pre-flight check (add --fix to auto-repair)"),
|
|
2314
|
+
("health", "Quick math/retrieval layer status"),
|
|
2315
|
+
("diagnostics", "Export a local diagnostics bundle"),
|
|
2316
|
+
("evidence", "Build/inspect evidence bundles"),
|
|
2317
|
+
("rotate-token", "Rotate the local dashboard install token"),
|
|
2318
|
+
]),
|
|
2319
|
+
("Configuration", [
|
|
2320
|
+
("config", "View/set configuration (see: slm help config)"),
|
|
2321
|
+
("enable", "Enable a feature/escape hatch"),
|
|
2322
|
+
("disable", "Disable a feature/escape hatch"),
|
|
2323
|
+
("clear-cache", "Clear caches"),
|
|
2324
|
+
]),
|
|
2325
|
+
("Profiles & sessions", [
|
|
2326
|
+
("profile", "Manage workspaces/profiles (add, switch, list)"),
|
|
2327
|
+
("session", "Open/close a work session"),
|
|
2328
|
+
("session-context", "Pre-stage session context for hooks"),
|
|
2329
|
+
]),
|
|
2330
|
+
("Multi-device mesh", [
|
|
2331
|
+
("mesh", "Inspect/manage the cross-device memory mesh"),
|
|
2332
|
+
]),
|
|
2333
|
+
("Token optimization", [
|
|
2334
|
+
("optimize", "Configure the Optimize surface (cache/compress/proxy)"),
|
|
2335
|
+
("cache", "Manage the LLM response cache"),
|
|
2336
|
+
("compress", "Manage prose compression"),
|
|
2337
|
+
("proxy", "Manage the optimizing LLM proxy"),
|
|
2338
|
+
("wrap", "Run an agent (claude, codex, …) through the proxy"),
|
|
2339
|
+
("help-optimize", "Detailed help for the Optimize surface"),
|
|
2340
|
+
]),
|
|
2341
|
+
("Learning & maintenance", [
|
|
2342
|
+
("evolve", "Skill-evolution controls"),
|
|
2343
|
+
("observe", "External observation / telemetry ingestion"),
|
|
2344
|
+
("decay", "Run a forgetting/decay pass"),
|
|
2345
|
+
("consolidate", "Merge/consolidate related memories"),
|
|
2346
|
+
("quantize", "Quantize embeddings to save space"),
|
|
2347
|
+
("soft-prompts", "Manage learned soft prompts"),
|
|
2348
|
+
("reap", "Clean up stale/dead records"),
|
|
2349
|
+
("adapters", "Manage ingestion adapters"),
|
|
2350
|
+
("benchmark", "Run local benchmarks"),
|
|
2351
|
+
]),
|
|
2352
|
+
("Data & migration", [
|
|
2353
|
+
("db", "Low-level database operations"),
|
|
2354
|
+
("migrate", "Migrate the data store to the current version"),
|
|
2355
|
+
]),
|
|
2356
|
+
("Automation", [
|
|
2357
|
+
("loop", "Run gate-verified bounded agent loops"),
|
|
2358
|
+
]),
|
|
2359
|
+
("Help", [
|
|
2360
|
+
("help", "This overview. Try: slm help config | modes | self-heal"),
|
|
2361
|
+
]),
|
|
2362
|
+
]
|
|
2363
|
+
|
|
2364
|
+
_HELP_TOPICS: dict[str, str] = {
|
|
2365
|
+
"modes": """\
|
|
2366
|
+
Operating modes
|
|
2367
|
+
a Local Guardian — no model-provider call in the core memory path.
|
|
2368
|
+
b Smart Local — uses a local Ollama LLM (auto-detected at setup).
|
|
2369
|
+
c Full Power — uses a cloud LLM (OpenAI/Anthropic/…), needs a key.
|
|
2370
|
+
|
|
2371
|
+
Switch any time: slm mode a (or b / c)
|
|
2372
|
+
""",
|
|
2373
|
+
"config": """\
|
|
2374
|
+
Configuration
|
|
2375
|
+
slm config Show current configuration
|
|
2376
|
+
slm config get <key> Read one setting
|
|
2377
|
+
slm config set <key> <val> Change a setting
|
|
2378
|
+
|
|
2379
|
+
Most day-to-day settings are editable live in the dashboard
|
|
2380
|
+
(Settings tab) and apply without a restart. Settings that need a
|
|
2381
|
+
restart show a one-click "apply & restart" banner.
|
|
2382
|
+
|
|
2383
|
+
Common runtime settings: recall depth (top_k), memory injection on/off,
|
|
2384
|
+
stale-memory suppression, idle consolidation, PII redaction, reranker.
|
|
2385
|
+
""",
|
|
2386
|
+
"self-heal": """\
|
|
2387
|
+
Self-healing (v3.8.2)
|
|
2388
|
+
SLM repairs itself. On every start the daemon checks that all models
|
|
2389
|
+
and dependencies are present and re-downloads/installs the fixable ones
|
|
2390
|
+
in the background — you don't run anything.
|
|
2391
|
+
|
|
2392
|
+
See what's present/missing:
|
|
2393
|
+
slm doctor Full report with fix commands
|
|
2394
|
+
slm doctor --fix Auto-repair everything fixable, then report
|
|
2395
|
+
Dashboard → Help → System Health Live "what's missing" report
|
|
2396
|
+
|
|
2397
|
+
Big or opt-in pieces (the local LLM 'Ollama', the large compression
|
|
2398
|
+
model, PyTorch) are never installed without you — SLM shows the exact
|
|
2399
|
+
command instead.
|
|
2400
|
+
""",
|
|
2401
|
+
}
|
|
2402
|
+
# Aliases so common phrasings resolve to a topic.
|
|
2403
|
+
_HELP_TOPICS["health"] = _HELP_TOPICS["self-heal"]
|
|
2404
|
+
_HELP_TOPICS["selfheal"] = _HELP_TOPICS["self-heal"]
|
|
2405
|
+
_HELP_TOPICS["mode"] = _HELP_TOPICS["modes"]
|
|
2406
|
+
|
|
2407
|
+
|
|
2408
|
+
def all_help_commands() -> set[str]:
|
|
2409
|
+
"""Every command name listed in the super-help (drift-test hook)."""
|
|
2410
|
+
return {cmd for _title, rows in _COMMAND_GROUPS for cmd, _desc in rows}
|
|
2411
|
+
|
|
2412
|
+
|
|
2413
|
+
def cmd_help(args: Namespace) -> None:
|
|
2414
|
+
"""Super-help: a grouped overview of every command, plus focused topics.
|
|
2415
|
+
|
|
2416
|
+
`slm help` → grouped command overview
|
|
2417
|
+
`slm help <topic>` → modes | config | self-heal
|
|
2418
|
+
"""
|
|
2419
|
+
topic = getattr(args, "topic", None)
|
|
2420
|
+
if topic:
|
|
2421
|
+
key = topic.strip().lower()
|
|
2422
|
+
text = _HELP_TOPICS.get(key)
|
|
2423
|
+
if text:
|
|
2424
|
+
print(text)
|
|
2425
|
+
else:
|
|
2426
|
+
print(f"No help topic '{topic}'. Available topics: "
|
|
2427
|
+
+ ", ".join(sorted(set(_HELP_TOPICS))))
|
|
2428
|
+
return
|
|
2429
|
+
|
|
2430
|
+
from superlocalmemory.cli.json_output import _get_version
|
|
2431
|
+
print(f"SuperLocalMemory V3 ({_get_version()}) — command overview")
|
|
2432
|
+
print("=" * 58)
|
|
2433
|
+
print("Run any command with -h for its options, e.g. slm recall -h\n")
|
|
2434
|
+
for title, rows in _COMMAND_GROUPS:
|
|
2435
|
+
print(f"{title}:")
|
|
2436
|
+
for cmd, desc in rows:
|
|
2437
|
+
print(f" {cmd:<16} {desc}")
|
|
2438
|
+
print()
|
|
2439
|
+
print("Health at a glance: slm doctor (auto-repair: slm doctor --fix)")
|
|
2440
|
+
print("Topics: slm help modes | slm help config | "
|
|
2441
|
+
"slm help self-heal")
|
|
2442
|
+
print("Docs: https://superlocalmemory.com")
|
|
2443
|
+
|
|
2444
|
+
|
|
2016
2445
|
def cmd_doctor(args: Namespace) -> None:
|
|
2017
2446
|
"""Comprehensive pre-flight check — verify everything works.
|
|
2018
2447
|
|
|
@@ -2050,6 +2479,34 @@ def cmd_doctor(args: Namespace) -> None:
|
|
|
2050
2479
|
print("=" * 50)
|
|
2051
2480
|
print()
|
|
2052
2481
|
|
|
2482
|
+
# v3.8.2 `--fix`: run the component healer (the SAME repair the daemon
|
|
2483
|
+
# self-heal performs) BEFORE the checks, so the report below reflects the
|
|
2484
|
+
# healed state. Re-downloads missing models / installs sqlite-vec; big or
|
|
2485
|
+
# opt-in items are left as manual fixes. Fail-open — never blocks doctor.
|
|
2486
|
+
if getattr(args, "fix", False):
|
|
2487
|
+
try:
|
|
2488
|
+
from superlocalmemory.core import component_healer
|
|
2489
|
+
from superlocalmemory.core.config import SLMConfig as _FixCfg
|
|
2490
|
+
try:
|
|
2491
|
+
_fcfg = _FixCfg.load()
|
|
2492
|
+
except Exception:
|
|
2493
|
+
_fcfg = None
|
|
2494
|
+
if not use_json:
|
|
2495
|
+
print("Auto-repair (--fix): repairing fixable components…")
|
|
2496
|
+
_fix_res = component_healer.heal_missing(
|
|
2497
|
+
_fcfg,
|
|
2498
|
+
on_progress=(None if use_json
|
|
2499
|
+
else (lambda k, m: print(f" [{k}] {m}"))),
|
|
2500
|
+
)
|
|
2501
|
+
if not use_json:
|
|
2502
|
+
print(
|
|
2503
|
+
f" repaired {_fix_res['healed']}/{_fix_res['attempted']} "
|
|
2504
|
+
f"(failed {_fix_res['failed']})\n"
|
|
2505
|
+
)
|
|
2506
|
+
except Exception as _fix_exc:
|
|
2507
|
+
if not use_json:
|
|
2508
|
+
print(f" auto-repair error: {_fix_exc}\n")
|
|
2509
|
+
|
|
2053
2510
|
# 1. Python version
|
|
2054
2511
|
v = sys.version_info
|
|
2055
2512
|
if v >= (3, 11):
|
|
@@ -2315,6 +2772,34 @@ def cmd_doctor(args: Namespace) -> None:
|
|
|
2315
2772
|
except Exception:
|
|
2316
2773
|
pass # advisory only — never fail doctor on this check
|
|
2317
2774
|
|
|
2775
|
+
# 12a. Component registry (v3.8.2) — models + optional vector/graph/
|
|
2776
|
+
# compression backends the older checks above did NOT cover
|
|
2777
|
+
# (embedder/reranker/compressor cache presence, sqlite-vec, lancedb,
|
|
2778
|
+
# cozo, llmlingua). Single source of truth shared with the daemon
|
|
2779
|
+
# self-heal and the dashboard "what's missing" report. Missing items
|
|
2780
|
+
# are advisory (WARN), never FAIL — the self-heal thread and
|
|
2781
|
+
# `slm doctor --fix` repair the auto-fixable ones. Runs BEFORE the
|
|
2782
|
+
# Optimize check so that check stays last (its ordering is pinned by
|
|
2783
|
+
# test_doctor_optimize).
|
|
2784
|
+
try:
|
|
2785
|
+
from superlocalmemory.core import component_registry as _cr
|
|
2786
|
+
try:
|
|
2787
|
+
from superlocalmemory.core.config import SLMConfig as _Cfg
|
|
2788
|
+
_dcfg = _Cfg.load()
|
|
2789
|
+
except Exception:
|
|
2790
|
+
_dcfg = None
|
|
2791
|
+
_already = {"python", "search_deps", "ollama"}
|
|
2792
|
+
for _c in _cr.probe_all(_dcfg):
|
|
2793
|
+
if _c.key in _already:
|
|
2794
|
+
continue # covered by checks 1/3/8 above — avoid duplicates
|
|
2795
|
+
_status = "PASS" if _c.status == _cr.STATUS_OK else "WARN"
|
|
2796
|
+
_detail = _c.detail or _c.status
|
|
2797
|
+
if _c.auto_fixable:
|
|
2798
|
+
_detail += " (auto-heals on daemon start)"
|
|
2799
|
+
_check(f"Component: {_c.label}", _status, _detail, _c.fix_cmd)
|
|
2800
|
+
except Exception as _cr_exc:
|
|
2801
|
+
_check("Component registry", "WARN", f"probe failed: {_cr_exc}")
|
|
2802
|
+
|
|
2318
2803
|
# 12. Optimize (Surface B) — reads daemon-persisted metrics (≤60s stale).
|
|
2319
2804
|
info = _gather_optimize_surface_b()
|
|
2320
2805
|
_enabled = info["enabled"]
|