nexo-brain 7.17.7 → 7.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -1
- package/package.json +1 -1
- package/src/cli.py +275 -0
- package/src/crons/manifest.json +13 -0
- package/src/db/_schema.py +208 -0
- package/src/local_context/__init__.py +50 -0
- package/src/local_context/api.py +928 -0
- package/src/local_context/embeddings.py +34 -0
- package/src/local_context/extractors.py +202 -0
- package/src/local_context/logging.py +40 -0
- package/src/local_context/privacy.py +104 -0
- package/src/local_context/util.py +77 -0
- package/src/runtime_power.py +16 -1
- package/src/scripts/nexo-local-index.py +95 -0
- package/src/server.py +123 -0
- package/src/tools_hot_context.py +34 -1
- package/tool-enforcement-map.json +165 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.18.0",
|
|
4
4
|
"description": "Local cognitive runtime for Claude Code \u2014 persistent memory, overnight learning, doctor diagnostics, personal scripts, recovery-aware jobs, startup preflight, and optional dashboard/power helper.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "NEXO Brain",
|
package/README.md
CHANGED
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
|
|
19
19
|
[Watch the overview video](https://nexo-brain.com/watch/) · [Watch on YouTube](https://www.youtube.com/watch?v=i2lkGhKyVqI) · [Open the infographic](https://nexo-brain.com/assets/nexo-brain-infographic-v5.png)
|
|
20
20
|
|
|
21
|
-
Version `7.
|
|
21
|
+
Version `7.18.0` is the current packaged-runtime line. Minor release over v7.17.8 - Brain adds the Local Context Layer: a local-only background memory index with checkpoints, extraction, graph links, embeddings, MCP/CLI controls, and pre-action evidence for NEXO agents.
|
|
22
|
+
|
|
23
|
+
Previously in `7.17.8`: patch release over v7.17.7 - standalone `nexo chat` now surfaces macOS Full Disk Access guidance, and Brain clears stale permission state after a live access probe succeeds.
|
|
24
|
+
|
|
25
|
+
Previously in `7.17.7`: patch release over v7.17.6 - macOS TCC privacy denials now become a guided Full Disk Access permission state instead of an unexplained cron failure, with Desktop-ready status written for user action.
|
|
22
26
|
|
|
23
27
|
Previously in `7.17.6`: patch release over v7.17.5 - cron health diagnostics are clearer for macOS TCC approval, and catch-up fallback executions now stay visible in `cron_runs` even on legacy or partially migrated runtimes.
|
|
24
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.18.0",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
5
|
"description": "NEXO Brain — Shared brain for AI agents. Persistent memory, semantic RAG, natural forgetting, metacognitive guard, trust scoring, 150+ MCP tools. Works with Claude Code, Codex, Claude Desktop & any MCP client. 100% local, free.",
|
|
6
6
|
"homepage": "https://nexo-brain.com",
|
package/src/cli.py
CHANGED
|
@@ -20,6 +20,7 @@ Entry points:
|
|
|
20
20
|
nexo scripts run NAME_OR_PATH [-- args...]
|
|
21
21
|
nexo scripts doctor [NAME_OR_PATH] [--json]
|
|
22
22
|
nexo scripts call TOOL --input JSON [--json-output]
|
|
23
|
+
nexo local-context status|run-once|pause|resume|roots|exclusions|query|diagnostics|models [--json]
|
|
23
24
|
nexo automations reactivate NAME [--test-run] [--json]
|
|
24
25
|
nexo skills list [--level ...] [--source-kind ...] [--json]
|
|
25
26
|
nexo skills get ID [--json]
|
|
@@ -1269,6 +1270,135 @@ def _scripts_call(args):
|
|
|
1269
1270
|
return 1
|
|
1270
1271
|
|
|
1271
1272
|
|
|
1273
|
+
def _local_context_emit(payload: dict, args) -> int:
|
|
1274
|
+
return _print_json_or_text(payload, as_json=bool(getattr(args, "json", False)))
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
def _local_context_status(args) -> int:
|
|
1278
|
+
import local_context
|
|
1279
|
+
return _local_context_emit(local_context.status(), args)
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
def _local_context_run_once(args) -> int:
|
|
1283
|
+
import local_context
|
|
1284
|
+
return _local_context_emit(
|
|
1285
|
+
local_context.run_once(
|
|
1286
|
+
root=getattr(args, "root", "") or None,
|
|
1287
|
+
limit=getattr(args, "limit", None),
|
|
1288
|
+
process_limit=int(getattr(args, "process_limit", 100) or 100),
|
|
1289
|
+
),
|
|
1290
|
+
args,
|
|
1291
|
+
)
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
def _local_context_control(args) -> int:
|
|
1295
|
+
import local_context
|
|
1296
|
+
command = str(getattr(args, "local_context_command", "") or "")
|
|
1297
|
+
if command == "pause":
|
|
1298
|
+
return _local_context_emit(local_context.pause(), args)
|
|
1299
|
+
if command == "resume":
|
|
1300
|
+
return _local_context_emit(local_context.resume(), args)
|
|
1301
|
+
if command == "clear-index":
|
|
1302
|
+
return _local_context_emit(local_context.clear_index(), args)
|
|
1303
|
+
return _local_context_emit({"ok": False, "error": f"unsupported control command: {command}"}, args)
|
|
1304
|
+
|
|
1305
|
+
|
|
1306
|
+
def _local_context_roots(args) -> int:
|
|
1307
|
+
import local_context
|
|
1308
|
+
command = str(getattr(args, "local_context_roots_command", "") or "")
|
|
1309
|
+
if command == "list":
|
|
1310
|
+
return _local_context_emit({"ok": True, "roots": local_context.list_roots()}, args)
|
|
1311
|
+
if command == "add":
|
|
1312
|
+
return _local_context_emit(
|
|
1313
|
+
local_context.add_root(
|
|
1314
|
+
getattr(args, "path", ""),
|
|
1315
|
+
mode=getattr(args, "mode", "normal") or "normal",
|
|
1316
|
+
depth=getattr(args, "depth", None),
|
|
1317
|
+
),
|
|
1318
|
+
args,
|
|
1319
|
+
)
|
|
1320
|
+
if command == "remove":
|
|
1321
|
+
return _local_context_emit(local_context.remove_root(getattr(args, "path", "")), args)
|
|
1322
|
+
return _local_context_emit({"ok": False, "error": f"unsupported roots command: {command}"}, args)
|
|
1323
|
+
|
|
1324
|
+
|
|
1325
|
+
def _local_context_exclusions(args) -> int:
|
|
1326
|
+
import local_context
|
|
1327
|
+
command = str(getattr(args, "local_context_exclusions_command", "") or "")
|
|
1328
|
+
if command == "list":
|
|
1329
|
+
return _local_context_emit({"ok": True, "exclusions": local_context.list_exclusions()}, args)
|
|
1330
|
+
if command == "add":
|
|
1331
|
+
return _local_context_emit(
|
|
1332
|
+
local_context.add_exclusion(
|
|
1333
|
+
getattr(args, "path", ""),
|
|
1334
|
+
reason=getattr(args, "reason", "user") or "user",
|
|
1335
|
+
),
|
|
1336
|
+
args,
|
|
1337
|
+
)
|
|
1338
|
+
if command == "remove":
|
|
1339
|
+
return _local_context_emit(local_context.remove_exclusion(getattr(args, "path", "")), args)
|
|
1340
|
+
return _local_context_emit({"ok": False, "error": f"unsupported exclusions command: {command}"}, args)
|
|
1341
|
+
|
|
1342
|
+
|
|
1343
|
+
def _local_context_query(args) -> int:
|
|
1344
|
+
import local_context
|
|
1345
|
+
return _local_context_emit(
|
|
1346
|
+
local_context.context_query(
|
|
1347
|
+
getattr(args, "query", "") or "",
|
|
1348
|
+
intent=getattr(args, "intent", "answer") or "answer",
|
|
1349
|
+
limit=int(getattr(args, "limit", 12) or 12),
|
|
1350
|
+
evidence_required=not bool(getattr(args, "no_evidence_required", False)),
|
|
1351
|
+
current_context=getattr(args, "current_context", "") or "",
|
|
1352
|
+
),
|
|
1353
|
+
args,
|
|
1354
|
+
)
|
|
1355
|
+
|
|
1356
|
+
|
|
1357
|
+
def _local_context_diagnostics(args) -> int:
|
|
1358
|
+
import local_context
|
|
1359
|
+
return _local_context_emit(
|
|
1360
|
+
local_context.diagnostics_tail(limit=int(getattr(args, "limit", 100) or 100)),
|
|
1361
|
+
args,
|
|
1362
|
+
)
|
|
1363
|
+
|
|
1364
|
+
|
|
1365
|
+
def _local_context_models(args) -> int:
|
|
1366
|
+
import local_context
|
|
1367
|
+
command = str(getattr(args, "local_context_models_command", "") or "status")
|
|
1368
|
+
if command == "warmup":
|
|
1369
|
+
return _local_context_emit(
|
|
1370
|
+
local_context.warmup_models(local_files_only=not bool(getattr(args, "allow_download", False))),
|
|
1371
|
+
args,
|
|
1372
|
+
)
|
|
1373
|
+
return _local_context_emit(local_context.model_status(), args)
|
|
1374
|
+
|
|
1375
|
+
|
|
1376
|
+
def _local_context_service_config(args) -> int:
|
|
1377
|
+
from local_context import api as local_context_api
|
|
1378
|
+
return _local_context_emit(
|
|
1379
|
+
local_context_api.render_service_config(getattr(args, "platform", "") or None),
|
|
1380
|
+
args,
|
|
1381
|
+
)
|
|
1382
|
+
|
|
1383
|
+
|
|
1384
|
+
def _local_context_asset(args) -> int:
|
|
1385
|
+
import local_context
|
|
1386
|
+
command = str(getattr(args, "local_context_asset_command", "") or "")
|
|
1387
|
+
if command == "get":
|
|
1388
|
+
return _local_context_emit(local_context.get_asset(getattr(args, "asset_id", "")), args)
|
|
1389
|
+
if command == "neighbors":
|
|
1390
|
+
return _local_context_emit(
|
|
1391
|
+
local_context.get_neighbors(
|
|
1392
|
+
getattr(args, "asset_id", ""),
|
|
1393
|
+
limit=int(getattr(args, "limit", 30) or 30),
|
|
1394
|
+
),
|
|
1395
|
+
args,
|
|
1396
|
+
)
|
|
1397
|
+
if command == "purge":
|
|
1398
|
+
return _local_context_emit(local_context.purge_asset(getattr(args, "asset_id", "")), args)
|
|
1399
|
+
return _local_context_emit({"ok": False, "error": f"unsupported asset command: {command}"}, args)
|
|
1400
|
+
|
|
1401
|
+
|
|
1272
1402
|
def _recover(args):
|
|
1273
1403
|
"""Delegate to plugins.recover.cli_main so the logic lives in one place."""
|
|
1274
1404
|
from plugins.recover import cli_main as _recover_cli_main
|
|
@@ -2187,6 +2317,29 @@ def _prompt_for_terminal_client(
|
|
|
2187
2317
|
print("Invalid choice. Try again.", file=sys.stderr)
|
|
2188
2318
|
|
|
2189
2319
|
|
|
2320
|
+
def _ensure_chat_full_disk_access_notice(*, interactive: bool | None = None) -> dict:
|
|
2321
|
+
runtime_power = _load_runtime_power_support()
|
|
2322
|
+
if interactive is None:
|
|
2323
|
+
interactive = bool(getattr(sys.stdin, "isatty", lambda: False)())
|
|
2324
|
+
try:
|
|
2325
|
+
choice = runtime_power["ensure_full_disk_access_choice"](
|
|
2326
|
+
interactive=interactive,
|
|
2327
|
+
reason="chat",
|
|
2328
|
+
output_fn=lambda message: print(message, file=sys.stderr),
|
|
2329
|
+
)
|
|
2330
|
+
except EOFError:
|
|
2331
|
+
return {
|
|
2332
|
+
"status": "later",
|
|
2333
|
+
"prompted": False,
|
|
2334
|
+
"reasons": [],
|
|
2335
|
+
"message": "Full Disk Access setup skipped because stdin closed.",
|
|
2336
|
+
}
|
|
2337
|
+
message = str(choice.get("message") or "").strip()
|
|
2338
|
+
if message and (choice.get("prompted") or choice.get("relevant")):
|
|
2339
|
+
print(f"[NEXO] Full Disk Access: {message}", file=sys.stderr)
|
|
2340
|
+
return choice
|
|
2341
|
+
|
|
2342
|
+
|
|
2190
2343
|
def _chat(args):
|
|
2191
2344
|
target = args.path or "."
|
|
2192
2345
|
selected_client = getattr(args, "client", None)
|
|
@@ -2211,6 +2364,11 @@ def _chat(args):
|
|
|
2211
2364
|
except Exception:
|
|
2212
2365
|
pass
|
|
2213
2366
|
|
|
2367
|
+
try:
|
|
2368
|
+
_ensure_chat_full_disk_access_notice()
|
|
2369
|
+
except Exception as exc:
|
|
2370
|
+
print(f"[NEXO] Full Disk Access check warning: {exc}", file=sys.stderr)
|
|
2371
|
+
|
|
2214
2372
|
try:
|
|
2215
2373
|
from client_preferences import (
|
|
2216
2374
|
detect_installed_clients,
|
|
@@ -2984,6 +3142,89 @@ def main():
|
|
|
2984
3142
|
call_p.add_argument("--input", default="{}", help="JSON input payload")
|
|
2985
3143
|
call_p.add_argument("--json-output", action="store_true", help="Force JSON output")
|
|
2986
3144
|
|
|
3145
|
+
local_context_parser = sub.add_parser("local-context", help="Manage the local memory index")
|
|
3146
|
+
local_context_sub = local_context_parser.add_subparsers(dest="local_context_command")
|
|
3147
|
+
|
|
3148
|
+
local_context_status_p = local_context_sub.add_parser("status", help="Show local memory index status")
|
|
3149
|
+
local_context_status_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3150
|
+
|
|
3151
|
+
local_context_run_p = local_context_sub.add_parser("run-once", help="Run one incremental scan/extract cycle")
|
|
3152
|
+
local_context_run_p.add_argument("--root", default="", help="Optional folder to add before running")
|
|
3153
|
+
local_context_run_p.add_argument("--limit", type=int, default=None, help="Maximum files to scan in this cycle")
|
|
3154
|
+
local_context_run_p.add_argument("--process-limit", type=int, default=100, help="Maximum extraction/graph jobs to process")
|
|
3155
|
+
local_context_run_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3156
|
+
|
|
3157
|
+
local_context_pause_p = local_context_sub.add_parser("pause", help="Pause local memory indexing")
|
|
3158
|
+
local_context_pause_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3159
|
+
|
|
3160
|
+
local_context_resume_p = local_context_sub.add_parser("resume", help="Resume local memory indexing")
|
|
3161
|
+
local_context_resume_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3162
|
+
|
|
3163
|
+
local_context_clear_p = local_context_sub.add_parser("clear-index", help="Clear the local memory index")
|
|
3164
|
+
local_context_clear_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3165
|
+
|
|
3166
|
+
local_context_roots_p = local_context_sub.add_parser("roots", help="Manage included folders")
|
|
3167
|
+
local_context_roots_sub = local_context_roots_p.add_subparsers(dest="local_context_roots_command")
|
|
3168
|
+
local_context_roots_list_p = local_context_roots_sub.add_parser("list", help="List included folders")
|
|
3169
|
+
local_context_roots_list_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3170
|
+
local_context_roots_add_p = local_context_roots_sub.add_parser("add", help="Add an included folder")
|
|
3171
|
+
local_context_roots_add_p.add_argument("path", help="Folder path")
|
|
3172
|
+
local_context_roots_add_p.add_argument("--mode", choices=["normal", "developer", "complete"], default="normal", help="Indexing mode")
|
|
3173
|
+
local_context_roots_add_p.add_argument("--depth", type=int, default=None, help="Privacy depth override")
|
|
3174
|
+
local_context_roots_add_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3175
|
+
local_context_roots_remove_p = local_context_roots_sub.add_parser("remove", help="Remove an included folder")
|
|
3176
|
+
local_context_roots_remove_p.add_argument("path", help="Folder path")
|
|
3177
|
+
local_context_roots_remove_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3178
|
+
|
|
3179
|
+
local_context_exclusions_p = local_context_sub.add_parser("exclusions", help="Manage excluded folders")
|
|
3180
|
+
local_context_exclusions_sub = local_context_exclusions_p.add_subparsers(dest="local_context_exclusions_command")
|
|
3181
|
+
local_context_exclusions_list_p = local_context_exclusions_sub.add_parser("list", help="List excluded folders")
|
|
3182
|
+
local_context_exclusions_list_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3183
|
+
local_context_exclusions_add_p = local_context_exclusions_sub.add_parser("add", help="Add an excluded folder")
|
|
3184
|
+
local_context_exclusions_add_p.add_argument("path", help="Folder path")
|
|
3185
|
+
local_context_exclusions_add_p.add_argument("--reason", default="user", help="Reason label")
|
|
3186
|
+
local_context_exclusions_add_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3187
|
+
local_context_exclusions_remove_p = local_context_exclusions_sub.add_parser("remove", help="Remove an excluded folder")
|
|
3188
|
+
local_context_exclusions_remove_p.add_argument("path", help="Folder path")
|
|
3189
|
+
local_context_exclusions_remove_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3190
|
+
|
|
3191
|
+
local_context_query_p = local_context_sub.add_parser("query", help="Query local memory evidence")
|
|
3192
|
+
local_context_query_p.add_argument("query", help="Question or search phrase")
|
|
3193
|
+
local_context_query_p.add_argument("--intent", default="answer", help="Intent label stored with the query audit row")
|
|
3194
|
+
local_context_query_p.add_argument("--limit", type=int, default=12, help="Maximum evidence rows")
|
|
3195
|
+
local_context_query_p.add_argument("--current-context", default="", help="Optional current conversation/task context")
|
|
3196
|
+
local_context_query_p.add_argument("--no-evidence-required", action="store_true", help="Allow empty evidence results")
|
|
3197
|
+
local_context_query_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3198
|
+
|
|
3199
|
+
local_context_diagnostics_p = local_context_sub.add_parser("diagnostics", help="Tail local memory diagnostic events")
|
|
3200
|
+
local_context_diagnostics_p.add_argument("--limit", type=int, default=100, help="Maximum log rows")
|
|
3201
|
+
local_context_diagnostics_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3202
|
+
|
|
3203
|
+
local_context_service_p = local_context_sub.add_parser("service-config", help="Render macOS/Windows service config metadata")
|
|
3204
|
+
local_context_service_p.add_argument("--platform", choices=["", "macos", "windows", "linux"], default="", help="Override platform")
|
|
3205
|
+
local_context_service_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3206
|
+
|
|
3207
|
+
local_context_models_p = local_context_sub.add_parser("models", help="Show or warm local model profiles")
|
|
3208
|
+
local_context_models_sub = local_context_models_p.add_subparsers(dest="local_context_models_command")
|
|
3209
|
+
local_context_models_status_p = local_context_models_sub.add_parser("status", help="Show local model status")
|
|
3210
|
+
local_context_models_status_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3211
|
+
local_context_models_warmup_p = local_context_models_sub.add_parser("warmup", help="Warm local model profiles without network by default")
|
|
3212
|
+
local_context_models_warmup_p.add_argument("--allow-download", action="store_true", help="Allow model downloads")
|
|
3213
|
+
local_context_models_warmup_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3214
|
+
|
|
3215
|
+
local_context_asset_p = local_context_sub.add_parser("asset", help="Inspect or purge one indexed asset")
|
|
3216
|
+
local_context_asset_sub = local_context_asset_p.add_subparsers(dest="local_context_asset_command")
|
|
3217
|
+
local_context_asset_get_p = local_context_asset_sub.add_parser("get", help="Get indexed asset details")
|
|
3218
|
+
local_context_asset_get_p.add_argument("asset_id", help="Asset id")
|
|
3219
|
+
local_context_asset_get_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3220
|
+
local_context_asset_neighbors_p = local_context_asset_sub.add_parser("neighbors", help="Get graph neighbors")
|
|
3221
|
+
local_context_asset_neighbors_p.add_argument("asset_id", help="Asset id")
|
|
3222
|
+
local_context_asset_neighbors_p.add_argument("--limit", type=int, default=30, help="Maximum relation rows")
|
|
3223
|
+
local_context_asset_neighbors_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3224
|
+
local_context_asset_purge_p = local_context_asset_sub.add_parser("purge", help="Purge one indexed asset")
|
|
3225
|
+
local_context_asset_purge_p.add_argument("asset_id", help="Asset id")
|
|
3226
|
+
local_context_asset_purge_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3227
|
+
|
|
2987
3228
|
automations_parser = sub.add_parser("automations", help="Manage Desktop-facing automations")
|
|
2988
3229
|
automations_sub = automations_parser.add_subparsers(dest="automations_command")
|
|
2989
3230
|
|
|
@@ -3488,6 +3729,40 @@ def main():
|
|
|
3488
3729
|
else:
|
|
3489
3730
|
scripts_parser.print_help()
|
|
3490
3731
|
return 0
|
|
3732
|
+
elif args.command == "local-context":
|
|
3733
|
+
if args.local_context_command == "status":
|
|
3734
|
+
return _local_context_status(args)
|
|
3735
|
+
if args.local_context_command == "run-once":
|
|
3736
|
+
return _local_context_run_once(args)
|
|
3737
|
+
if args.local_context_command in {"pause", "resume", "clear-index"}:
|
|
3738
|
+
return _local_context_control(args)
|
|
3739
|
+
if args.local_context_command == "roots":
|
|
3740
|
+
if not args.local_context_roots_command:
|
|
3741
|
+
local_context_roots_p.print_help()
|
|
3742
|
+
return 0
|
|
3743
|
+
return _local_context_roots(args)
|
|
3744
|
+
if args.local_context_command == "exclusions":
|
|
3745
|
+
if not args.local_context_exclusions_command:
|
|
3746
|
+
local_context_exclusions_p.print_help()
|
|
3747
|
+
return 0
|
|
3748
|
+
return _local_context_exclusions(args)
|
|
3749
|
+
if args.local_context_command == "query":
|
|
3750
|
+
return _local_context_query(args)
|
|
3751
|
+
if args.local_context_command == "diagnostics":
|
|
3752
|
+
return _local_context_diagnostics(args)
|
|
3753
|
+
if args.local_context_command == "service-config":
|
|
3754
|
+
return _local_context_service_config(args)
|
|
3755
|
+
if args.local_context_command == "models":
|
|
3756
|
+
if not args.local_context_models_command:
|
|
3757
|
+
args.local_context_models_command = "status"
|
|
3758
|
+
return _local_context_models(args)
|
|
3759
|
+
if args.local_context_command == "asset":
|
|
3760
|
+
if not args.local_context_asset_command:
|
|
3761
|
+
local_context_asset_p.print_help()
|
|
3762
|
+
return 0
|
|
3763
|
+
return _local_context_asset(args)
|
|
3764
|
+
local_context_parser.print_help()
|
|
3765
|
+
return 0
|
|
3491
3766
|
elif args.command == "automations":
|
|
3492
3767
|
if args.automations_command == "list":
|
|
3493
3768
|
return _automations_list(args)
|
package/src/crons/manifest.json
CHANGED
|
@@ -302,6 +302,19 @@
|
|
|
302
302
|
"run_on_boot": true,
|
|
303
303
|
"run_on_wake": true
|
|
304
304
|
},
|
|
305
|
+
{
|
|
306
|
+
"id": "local-index",
|
|
307
|
+
"script": "scripts/nexo-local-index.py",
|
|
308
|
+
"interval_seconds": 60,
|
|
309
|
+
"description": "Local Context Layer indexing cycle for on-device memory",
|
|
310
|
+
"core": true,
|
|
311
|
+
"recovery_policy": "restart",
|
|
312
|
+
"idempotent": true,
|
|
313
|
+
"max_catchup_age": 600,
|
|
314
|
+
"stuck_after_seconds": 900,
|
|
315
|
+
"run_on_boot": true,
|
|
316
|
+
"run_on_wake": true
|
|
317
|
+
},
|
|
305
318
|
{
|
|
306
319
|
"id": "email-monitor",
|
|
307
320
|
"script": "scripts/nexo-email-monitor.py",
|
package/src/db/_schema.py
CHANGED
|
@@ -1765,6 +1765,213 @@ def _m62_memory_observations_fts_trigger_fix(conn):
|
|
|
1765
1765
|
)
|
|
1766
1766
|
|
|
1767
1767
|
|
|
1768
|
+
def _m63_local_context_layer(conn):
|
|
1769
|
+
"""Local Context Layer storage for on-device memory indexing."""
|
|
1770
|
+
conn.executescript(
|
|
1771
|
+
"""
|
|
1772
|
+
CREATE TABLE IF NOT EXISTS local_index_roots (
|
|
1773
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1774
|
+
root_path TEXT NOT NULL UNIQUE,
|
|
1775
|
+
display_path TEXT NOT NULL,
|
|
1776
|
+
mode TEXT NOT NULL DEFAULT 'normal',
|
|
1777
|
+
depth INTEGER NOT NULL DEFAULT 2,
|
|
1778
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
1779
|
+
last_scan_at REAL,
|
|
1780
|
+
created_at REAL NOT NULL,
|
|
1781
|
+
updated_at REAL NOT NULL
|
|
1782
|
+
);
|
|
1783
|
+
|
|
1784
|
+
CREATE TABLE IF NOT EXISTS local_index_exclusions (
|
|
1785
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1786
|
+
path TEXT NOT NULL UNIQUE,
|
|
1787
|
+
display_path TEXT NOT NULL,
|
|
1788
|
+
reason TEXT NOT NULL DEFAULT 'user',
|
|
1789
|
+
created_at REAL NOT NULL
|
|
1790
|
+
);
|
|
1791
|
+
|
|
1792
|
+
CREATE TABLE IF NOT EXISTS local_index_jobs (
|
|
1793
|
+
job_id TEXT PRIMARY KEY,
|
|
1794
|
+
asset_id TEXT NOT NULL,
|
|
1795
|
+
job_type TEXT NOT NULL,
|
|
1796
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
1797
|
+
priority INTEGER NOT NULL DEFAULT 50,
|
|
1798
|
+
claimed_by TEXT NOT NULL DEFAULT '',
|
|
1799
|
+
lease_expires_at REAL,
|
|
1800
|
+
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
1801
|
+
next_attempt_at REAL,
|
|
1802
|
+
last_error_code TEXT NOT NULL DEFAULT '',
|
|
1803
|
+
created_at REAL NOT NULL,
|
|
1804
|
+
updated_at REAL NOT NULL
|
|
1805
|
+
);
|
|
1806
|
+
|
|
1807
|
+
CREATE TABLE IF NOT EXISTS local_index_checkpoints (
|
|
1808
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1809
|
+
root_id INTEGER,
|
|
1810
|
+
phase TEXT NOT NULL DEFAULT 'quick_index',
|
|
1811
|
+
current_path TEXT NOT NULL DEFAULT '',
|
|
1812
|
+
total_seen INTEGER NOT NULL DEFAULT 0,
|
|
1813
|
+
total_changed INTEGER NOT NULL DEFAULT 0,
|
|
1814
|
+
total_errors INTEGER NOT NULL DEFAULT 0,
|
|
1815
|
+
eta_seconds REAL,
|
|
1816
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
1817
|
+
created_at REAL NOT NULL,
|
|
1818
|
+
updated_at REAL NOT NULL
|
|
1819
|
+
);
|
|
1820
|
+
|
|
1821
|
+
CREATE TABLE IF NOT EXISTS local_index_state (
|
|
1822
|
+
key TEXT PRIMARY KEY,
|
|
1823
|
+
value TEXT NOT NULL,
|
|
1824
|
+
updated_at REAL NOT NULL
|
|
1825
|
+
);
|
|
1826
|
+
|
|
1827
|
+
CREATE TABLE IF NOT EXISTS local_index_errors (
|
|
1828
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1829
|
+
asset_id TEXT NOT NULL DEFAULT '',
|
|
1830
|
+
path TEXT NOT NULL DEFAULT '',
|
|
1831
|
+
phase TEXT NOT NULL,
|
|
1832
|
+
error_code TEXT NOT NULL,
|
|
1833
|
+
user_message TEXT NOT NULL DEFAULT '',
|
|
1834
|
+
technical_detail TEXT NOT NULL DEFAULT '',
|
|
1835
|
+
retryable INTEGER NOT NULL DEFAULT 1,
|
|
1836
|
+
created_at REAL NOT NULL
|
|
1837
|
+
);
|
|
1838
|
+
|
|
1839
|
+
CREATE TABLE IF NOT EXISTS local_index_logs (
|
|
1840
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1841
|
+
created_at REAL NOT NULL,
|
|
1842
|
+
level TEXT NOT NULL,
|
|
1843
|
+
event TEXT NOT NULL,
|
|
1844
|
+
message TEXT NOT NULL,
|
|
1845
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
1846
|
+
);
|
|
1847
|
+
|
|
1848
|
+
CREATE TABLE IF NOT EXISTS local_assets (
|
|
1849
|
+
asset_id TEXT PRIMARY KEY,
|
|
1850
|
+
root_id INTEGER,
|
|
1851
|
+
path TEXT NOT NULL UNIQUE,
|
|
1852
|
+
display_path TEXT NOT NULL,
|
|
1853
|
+
parent_path TEXT NOT NULL DEFAULT '',
|
|
1854
|
+
volume_id TEXT NOT NULL DEFAULT '',
|
|
1855
|
+
file_type TEXT NOT NULL DEFAULT 'file',
|
|
1856
|
+
extension TEXT NOT NULL DEFAULT '',
|
|
1857
|
+
size_bytes INTEGER NOT NULL DEFAULT 0,
|
|
1858
|
+
created_at_fs REAL,
|
|
1859
|
+
modified_at_fs REAL,
|
|
1860
|
+
quick_fingerprint TEXT NOT NULL DEFAULT '',
|
|
1861
|
+
depth INTEGER NOT NULL DEFAULT 2,
|
|
1862
|
+
depth_reason TEXT NOT NULL DEFAULT 'default',
|
|
1863
|
+
phase TEXT NOT NULL DEFAULT 'quick_index',
|
|
1864
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
1865
|
+
privacy_class TEXT NOT NULL DEFAULT 'normal',
|
|
1866
|
+
permission_state TEXT NOT NULL DEFAULT 'unknown',
|
|
1867
|
+
first_seen_at REAL NOT NULL,
|
|
1868
|
+
last_seen_at REAL NOT NULL,
|
|
1869
|
+
updated_at REAL NOT NULL,
|
|
1870
|
+
deleted_at REAL
|
|
1871
|
+
);
|
|
1872
|
+
|
|
1873
|
+
CREATE TABLE IF NOT EXISTS local_asset_versions (
|
|
1874
|
+
version_id TEXT PRIMARY KEY,
|
|
1875
|
+
asset_id TEXT NOT NULL,
|
|
1876
|
+
quick_fingerprint TEXT NOT NULL DEFAULT '',
|
|
1877
|
+
content_hash TEXT NOT NULL DEFAULT '',
|
|
1878
|
+
size_bytes INTEGER NOT NULL DEFAULT 0,
|
|
1879
|
+
modified_at_fs REAL,
|
|
1880
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
1881
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
1882
|
+
created_at REAL NOT NULL
|
|
1883
|
+
);
|
|
1884
|
+
|
|
1885
|
+
CREATE TABLE IF NOT EXISTS local_chunks (
|
|
1886
|
+
chunk_id TEXT PRIMARY KEY,
|
|
1887
|
+
asset_id TEXT NOT NULL,
|
|
1888
|
+
version_id TEXT NOT NULL,
|
|
1889
|
+
chunk_index INTEGER NOT NULL DEFAULT 0,
|
|
1890
|
+
text TEXT NOT NULL DEFAULT '',
|
|
1891
|
+
token_count INTEGER NOT NULL DEFAULT 0,
|
|
1892
|
+
created_at REAL NOT NULL
|
|
1893
|
+
);
|
|
1894
|
+
|
|
1895
|
+
CREATE TABLE IF NOT EXISTS local_entities (
|
|
1896
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1897
|
+
entity_id TEXT NOT NULL,
|
|
1898
|
+
asset_id TEXT NOT NULL,
|
|
1899
|
+
version_id TEXT NOT NULL,
|
|
1900
|
+
name TEXT NOT NULL,
|
|
1901
|
+
entity_type TEXT NOT NULL DEFAULT 'entity',
|
|
1902
|
+
confidence REAL NOT NULL DEFAULT 0.5,
|
|
1903
|
+
evidence TEXT NOT NULL DEFAULT '',
|
|
1904
|
+
created_at REAL NOT NULL,
|
|
1905
|
+
UNIQUE(entity_id, asset_id, version_id)
|
|
1906
|
+
);
|
|
1907
|
+
|
|
1908
|
+
CREATE TABLE IF NOT EXISTS local_relations (
|
|
1909
|
+
relation_id TEXT PRIMARY KEY,
|
|
1910
|
+
source_asset_id TEXT NOT NULL,
|
|
1911
|
+
target_asset_id TEXT NOT NULL DEFAULT '',
|
|
1912
|
+
target_ref TEXT NOT NULL DEFAULT '',
|
|
1913
|
+
relation_type TEXT NOT NULL,
|
|
1914
|
+
confidence REAL NOT NULL DEFAULT 0.5,
|
|
1915
|
+
evidence TEXT NOT NULL DEFAULT '',
|
|
1916
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1917
|
+
created_at REAL NOT NULL
|
|
1918
|
+
);
|
|
1919
|
+
|
|
1920
|
+
CREATE TABLE IF NOT EXISTS local_embeddings (
|
|
1921
|
+
embedding_id TEXT PRIMARY KEY,
|
|
1922
|
+
asset_id TEXT NOT NULL,
|
|
1923
|
+
chunk_id TEXT NOT NULL,
|
|
1924
|
+
model_id TEXT NOT NULL,
|
|
1925
|
+
model_revision TEXT NOT NULL DEFAULT '',
|
|
1926
|
+
dimension INTEGER NOT NULL,
|
|
1927
|
+
vector_json TEXT NOT NULL,
|
|
1928
|
+
created_at REAL NOT NULL
|
|
1929
|
+
);
|
|
1930
|
+
|
|
1931
|
+
CREATE TABLE IF NOT EXISTS local_context_queries (
|
|
1932
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1933
|
+
query_hash TEXT NOT NULL,
|
|
1934
|
+
intent TEXT NOT NULL DEFAULT 'answer',
|
|
1935
|
+
result_count INTEGER NOT NULL DEFAULT 0,
|
|
1936
|
+
confidence REAL NOT NULL DEFAULT 0.0,
|
|
1937
|
+
warnings_json TEXT NOT NULL DEFAULT '[]',
|
|
1938
|
+
created_at REAL NOT NULL
|
|
1939
|
+
);
|
|
1940
|
+
|
|
1941
|
+
CREATE INDEX IF NOT EXISTS idx_local_index_roots_status
|
|
1942
|
+
ON local_index_roots(status);
|
|
1943
|
+
CREATE INDEX IF NOT EXISTS idx_local_index_jobs_status_priority
|
|
1944
|
+
ON local_index_jobs(status, priority, created_at);
|
|
1945
|
+
CREATE INDEX IF NOT EXISTS idx_local_index_jobs_asset
|
|
1946
|
+
ON local_index_jobs(asset_id);
|
|
1947
|
+
CREATE INDEX IF NOT EXISTS idx_local_index_errors_created
|
|
1948
|
+
ON local_index_errors(created_at);
|
|
1949
|
+
CREATE INDEX IF NOT EXISTS idx_local_index_logs_created
|
|
1950
|
+
ON local_index_logs(created_at);
|
|
1951
|
+
CREATE INDEX IF NOT EXISTS idx_local_assets_root_status
|
|
1952
|
+
ON local_assets(root_id, status);
|
|
1953
|
+
CREATE INDEX IF NOT EXISTS idx_local_assets_path
|
|
1954
|
+
ON local_assets(path);
|
|
1955
|
+
CREATE INDEX IF NOT EXISTS idx_local_assets_parent
|
|
1956
|
+
ON local_assets(parent_path);
|
|
1957
|
+
CREATE INDEX IF NOT EXISTS idx_local_assets_volume
|
|
1958
|
+
ON local_assets(volume_id);
|
|
1959
|
+
CREATE INDEX IF NOT EXISTS idx_local_versions_asset
|
|
1960
|
+
ON local_asset_versions(asset_id, created_at);
|
|
1961
|
+
CREATE INDEX IF NOT EXISTS idx_local_chunks_asset
|
|
1962
|
+
ON local_chunks(asset_id);
|
|
1963
|
+
CREATE INDEX IF NOT EXISTS idx_local_entities_name
|
|
1964
|
+
ON local_entities(name);
|
|
1965
|
+
CREATE INDEX IF NOT EXISTS idx_local_entities_asset
|
|
1966
|
+
ON local_entities(asset_id);
|
|
1967
|
+
CREATE INDEX IF NOT EXISTS idx_local_relations_source
|
|
1968
|
+
ON local_relations(source_asset_id, relation_type);
|
|
1969
|
+
CREATE INDEX IF NOT EXISTS idx_local_embeddings_chunk
|
|
1970
|
+
ON local_embeddings(chunk_id);
|
|
1971
|
+
"""
|
|
1972
|
+
)
|
|
1973
|
+
|
|
1974
|
+
|
|
1768
1975
|
MIGRATIONS = [
|
|
1769
1976
|
(1, "learnings_columns", _m1_learnings_columns),
|
|
1770
1977
|
(2, "followups_reasoning", _m2_followups_reasoning),
|
|
@@ -1828,6 +2035,7 @@ MIGRATIONS = [
|
|
|
1828
2035
|
(60, "memory_observations", _m60_memory_observations),
|
|
1829
2036
|
(61, "memory_observations_fts", _m61_memory_observations_fts),
|
|
1830
2037
|
(62, "memory_observations_fts_trigger_fix", _m62_memory_observations_fts_trigger_fix),
|
|
2038
|
+
(63, "local_context_layer", _m63_local_context_layer),
|
|
1831
2039
|
]
|
|
1832
2040
|
|
|
1833
2041
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Local Context Layer runtime.
|
|
2
|
+
|
|
3
|
+
This package owns the local index used by Brain before Nero answers or acts.
|
|
4
|
+
It is intentionally local-only: no scanner, extractor, embedding or resolver
|
|
5
|
+
path calls an external API.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .api import (
|
|
9
|
+
add_exclusion,
|
|
10
|
+
add_root,
|
|
11
|
+
clear_index,
|
|
12
|
+
context_query,
|
|
13
|
+
diagnostics_tail,
|
|
14
|
+
ensure_default_roots,
|
|
15
|
+
get_asset,
|
|
16
|
+
get_neighbors,
|
|
17
|
+
list_exclusions,
|
|
18
|
+
list_roots,
|
|
19
|
+
model_status,
|
|
20
|
+
pause,
|
|
21
|
+
purge_asset,
|
|
22
|
+
remove_exclusion,
|
|
23
|
+
remove_root,
|
|
24
|
+
resume,
|
|
25
|
+
run_once,
|
|
26
|
+
status,
|
|
27
|
+
warmup_models,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"add_exclusion",
|
|
32
|
+
"add_root",
|
|
33
|
+
"clear_index",
|
|
34
|
+
"context_query",
|
|
35
|
+
"diagnostics_tail",
|
|
36
|
+
"ensure_default_roots",
|
|
37
|
+
"get_asset",
|
|
38
|
+
"get_neighbors",
|
|
39
|
+
"list_exclusions",
|
|
40
|
+
"list_roots",
|
|
41
|
+
"model_status",
|
|
42
|
+
"pause",
|
|
43
|
+
"purge_asset",
|
|
44
|
+
"remove_exclusion",
|
|
45
|
+
"remove_root",
|
|
46
|
+
"resume",
|
|
47
|
+
"run_once",
|
|
48
|
+
"status",
|
|
49
|
+
"warmup_models",
|
|
50
|
+
]
|